Skip to content

Production 2025-08-24_02 #2901

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion services/wallet/controllers_v4.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/brave-intl/bat-go/libs/inputs"
"github.com/brave-intl/bat-go/libs/logging"
"github.com/brave-intl/bat-go/libs/middleware"
"github.com/brave-intl/bat-go/services/wallet/model"
"github.com/go-chi/chi"
)

Expand Down Expand Up @@ -200,7 +201,14 @@ func GetWalletV4(s *Service) func(w http.ResponseWriter, r *http.Request) *handl
return handlers.WrapError(err, "no such wallet", http.StatusNotFound)
}

resp := infoToResponseV4(info, true)
allow, err := s.allowListRepo.GetAllowListEntry(ctx, s.Datastore.RawDB(), paymentID)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return handlers.WrapError(err, "error getting allow list entry from storage", http.StatusInternalServerError)
}

isSelfCustAvail := allow.IsAllowed(paymentID)

resp := infoToResponseV4(info, isSelfCustAvail)

return handlers.RenderContent(ctx, resp, w, http.StatusOK)
}
Expand Down
13 changes: 8 additions & 5 deletions services/wallet/controllers_v4_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
appctx "github.com/brave-intl/bat-go/libs/context"
errorutils "github.com/brave-intl/bat-go/libs/errors"
"github.com/brave-intl/bat-go/libs/middleware"
"github.com/brave-intl/bat-go/services/wallet/storage"

"github.com/brave-intl/bat-go/libs/clients"

Expand Down Expand Up @@ -612,8 +613,9 @@ func (suite *WalletControllersTestSuite) TestGetWalletV4() {
whitelistWallet(suite.T(), pg, w.ID)

service := &Service{
Datastore: pg,
dappConf: DAppConfig{},
Datastore: pg,
allowListRepo: storage.NewAllowList(),
dappConf: DAppConfig{},
}

handler := handlers.AppHandler(GetWalletV4(service))
Expand Down Expand Up @@ -656,8 +658,9 @@ func (suite *WalletControllersTestSuite) TestGetWalletV4_Not_Whitelisted() {
suite.Require().NoError(err)

s := &Service{
Datastore: pg,
dappConf: DAppConfig{},
Datastore: pg,
allowListRepo: storage.NewAllowList(),
dappConf: DAppConfig{},
}

handler := handlers.AppHandler(GetWalletV4(s))
Expand All @@ -679,7 +682,7 @@ func (suite *WalletControllersTestSuite) TestGetWalletV4_Not_Whitelisted() {
err = json.Unmarshal(rr.Body.Bytes(), &resp)
suite.Require().NoError(err)

suite.Assert().Equal(true, resp.SelfCustodyAvailable["solana"])
suite.Assert().Equal(false, resp.SelfCustodyAvailable["solana"])
}

func signUpdateRequest(req *http.Request, paymentID string, privateKey ed25519.PrivateKey) error {
Expand Down
10 changes: 10 additions & 0 deletions services/wallet/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
)

const (
ErrNotFound Error = "model: not found"
ErrChallengeNotFound Error = "model: challenge not found"
ErrChallengeExpired Error = "model: challenge expired"
ErrNoRowsDeleted Error = "model: no rows deleted"
Expand All @@ -22,6 +23,15 @@ const (
ErrSolAddrsHasNoATAForMint Error = "model: solana address has no ata for mint"
)

type AllowListEntry struct {
PaymentID uuid.UUID `db:"payment_id"`
CreatedAt time.Time `db:"created_at"`
}

func (a AllowListEntry) IsAllowed(paymentID uuid.UUID) bool {
return !uuid.Equal(a.PaymentID, uuid.Nil) && uuid.Equal(a.PaymentID, paymentID)
}

type Challenge struct {
PaymentID uuid.UUID `db:"payment_id"`
CreatedAt time.Time `db:"created_at"`
Expand Down
63 changes: 63 additions & 0 deletions services/wallet/model/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,72 @@ import (
"testing"
"time"

uuid "github.com/satori/go.uuid"
"github.com/stretchr/testify/assert"
)

func TestAllowListEntry_IsAllowed(t *testing.T) {
type tcGiven struct {
allow AllowListEntry
paymentID uuid.UUID
}

type exp struct {
isAllowed bool
}

type testCase struct {
name string
given tcGiven
exp exp
}

tests := []testCase{
{
name: "default_allow_list_entry",
given: tcGiven{
paymentID: uuid.FromStringOrNil("dc5a802a-87e9-47a5-9d9c-af4c8f171bf3"),
},
exp: exp{isAllowed: false},
},
{
name: "payment_id_nil",
given: tcGiven{
paymentID: uuid.Nil,
},
exp: exp{isAllowed: false},
},
{
name: "payment_ids_not_equal",
given: tcGiven{
allow: AllowListEntry{
PaymentID: uuid.FromStringOrNil("d1359406-42f1-4364-99b7-77840e8594e8"),
},
paymentID: uuid.FromStringOrNil("dc5a802a-87e9-47a5-9d9c-af4c8f171bf3"),
},
exp: exp{isAllowed: false},
},
{
name: "payment_ids_are_equal",
given: tcGiven{
allow: AllowListEntry{
PaymentID: uuid.FromStringOrNil("356a634a-dbae-4f95-b276-f3f0f0a53509"),
},
paymentID: uuid.FromStringOrNil("356a634a-dbae-4f95-b276-f3f0f0a53509"),
},
exp: exp{isAllowed: true},
},
}
for i := range tests {
tc := tests[i]

t.Run(tc.name, func(t *testing.T) {
actual := tc.given.allow.IsAllowed(tc.given.paymentID)
assert.Equal(t, tc.exp.isAllowed, actual)
})
}
}

func TestChallenge_IsValid(t *testing.T) {
type tcGiven struct {
chl Challenge
Expand Down
12 changes: 10 additions & 2 deletions services/wallet/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ type challengeRepo interface {
Delete(ctx context.Context, dbi sqlx.ExecerContext, paymentID uuid.UUID) error
}

type allowListRepo interface {
GetAllowListEntry(ctx context.Context, dbi sqlx.QueryerContext, paymentID uuid.UUID) (model.AllowListEntry, error)
}

type solanaWaitlistRepo interface {
Insert(ctx context.Context, dbi sqlx.ExecerContext, paymentID uuid.UUID, joinedAt time.Time) error
Delete(ctx context.Context, dbi sqlx.ExecerContext, paymentID uuid.UUID) error
Expand Down Expand Up @@ -173,7 +177,8 @@ type Service struct {
Datastore Datastore
RoDatastore ReadOnlyDatastore

chlRepo challengeRepo
chlRepo challengeRepo
allowListRepo allowListRepo

solWaitlistRepo solanaWaitlistRepo
solAddrsChecker solanaAddrsChecker
Expand Down Expand Up @@ -207,6 +212,7 @@ func InitService(
datastore Datastore,
roDatastore ReadOnlyDatastore,
chlRepo challengeRepo,
alRepo allowListRepo,
solWaitlistRepo solanaWaitlistRepo,
solAddrsChecker solanaAddrsChecker,
solCl solanaClient,
Expand All @@ -223,6 +229,7 @@ func InitService(
Datastore: datastore,
RoDatastore: roDatastore,
chlRepo: chlRepo,
allowListRepo: alRepo,
solWaitlistRepo: solWaitlistRepo,
solAddrsChecker: solAddrsChecker,
solCl: solCl,
Expand Down Expand Up @@ -258,6 +265,7 @@ func SetupService(ctx context.Context) (context.Context, *Service) {
l := logging.Logger(ctx, "wallet.SetupService")

chlRepo := storage.NewChallenge()
alRepo := storage.NewAllowList()
solWaitlistRepo := storage.NewSolanaWaitlist()

db, err := NewWritablePostgres(viper.GetString("datastore"), false, "wallet_db")
Expand Down Expand Up @@ -364,7 +372,7 @@ func SetupService(ctx context.Context) (context.Context, *Service) {
AllowedOrigins: dappAO,
}

s, err := InitService(db, roDB, chlRepo, solWaitlistRepo, sac, solCl, solConf, repClient, geminiClient, geoCountryValidator, backoff.Retry, mtc, gemx, dappConf)
s, err := InitService(db, roDB, chlRepo, alRepo, solWaitlistRepo, sac, solCl, solConf, repClient, geminiClient, geoCountryValidator, backoff.Retry, mtc, gemx, dappConf)
if err != nil {
l.Panic().Err(err).Msg("failed to initialize wallet service")
}
Expand Down
19 changes: 19 additions & 0 deletions services/wallet/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,25 @@ func (c *Challenge) DeleteAfter(ctx context.Context, dbi sqlx.ExecerContext, int
return nil
}

type AllowList struct{}

func NewAllowList() *AllowList { return &AllowList{} }

// GetAllowListEntry retrieves a model.AllowListEntry from the database for the given paymentID.
func (a *AllowList) GetAllowListEntry(ctx context.Context, dbi sqlx.QueryerContext, paymentID uuid.UUID) (model.AllowListEntry, error) {
const q = `select * from allow_list where payment_id = $1`

var result model.AllowListEntry
if err := sqlx.GetContext(ctx, dbi, &result, q, paymentID); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return result, model.ErrNotFound
}
return result, err
}

return result, nil
}

type SolanaWaitlist struct{}

func NewSolanaWaitlist() *SolanaWaitlist { return &SolanaWaitlist{} }
Expand Down
72 changes: 72 additions & 0 deletions services/wallet/storage/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,78 @@ import (
"github.com/brave-intl/bat-go/services/wallet/model"
)

func TestAllowList_GetAllowListEntry(t *testing.T) {
dbi, err := setupDBI()
must.NoError(t, err)

defer func() {
_, _ = dbi.Exec("TRUNCATE TABLE allow_list;")
}()

type tcGiven struct {
paymentID uuid.UUID
allow model.AllowListEntry
}

type exp struct {
allow model.AllowListEntry
err error
}

type testCase struct {
name string
given tcGiven
exp exp
}

tests := []testCase{
{
name: "success",
given: tcGiven{
paymentID: uuid.FromStringOrNil("6d85a314-0fa8-4594-9cb9-c9141b61a887"),
allow: model.AllowListEntry{
PaymentID: uuid.FromStringOrNil("6d85a314-0fa8-4594-9cb9-c9141b61a887"),
CreatedAt: time.Date(2024, 1, 1, 1, 1, 1, 0, time.UTC),
},
},
exp: exp{
allow: model.AllowListEntry{
PaymentID: uuid.FromStringOrNil("6d85a314-0fa8-4594-9cb9-c9141b61a887"),
CreatedAt: time.Date(2024, 1, 1, 1, 1, 1, 0, time.UTC),
},
err: nil,
},
},
{
name: "not_found",
given: tcGiven{
paymentID: uuid.NewV4(),
},
exp: exp{
err: model.ErrNotFound,
},
},
}

const q = `insert into allow_list (payment_id, created_at) values($1, $2)`

for i := range tests {
tc := tests[i]

t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()

_, err1 := dbi.ExecContext(ctx, q, tc.given.allow.PaymentID, tc.given.allow.CreatedAt)
must.NoError(t, err1)

a := &AllowList{}
actual, err2 := a.GetAllowListEntry(ctx, dbi, tc.given.paymentID)
should.Equal(t, tc.exp.err, err2)
should.Equal(t, tc.exp.allow, actual)
})
}
}

func TestChallenge_Get(t *testing.T) {
dbi, err := setupDBI()
must.NoError(t, err)
Expand Down
Loading