Skip to content

Commit 7762b2c

Browse files
committed
WIP
1 parent c4302fc commit 7762b2c

File tree

17 files changed

+603
-146
lines changed

17 files changed

+603
-146
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
filippo.io/edwards25519 v1.1.0
77
github.com/aws/aws-sdk-go-v2 v0.17.0
88
github.com/bits-and-blooms/bloom/v3 v3.1.0
9-
github.com/code-payments/code-protobuf-api v1.19.1-0.20250610140050-4cadbcc86f16
9+
github.com/code-payments/code-protobuf-api v1.19.1-0.20250618155621-c66659ab4ff5
1010
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba
1111
github.com/emirpasic/gods v1.12.0
1212
github.com/envoyproxy/protoc-gen-validate v1.2.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
8080
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
8181
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
8282
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
83-
github.com/code-payments/code-protobuf-api v1.19.1-0.20250610140050-4cadbcc86f16 h1:drAMKRdbyObW8E4H6xc1pKIDxoFYgpaTdMlEnIKBIJ0=
84-
github.com/code-payments/code-protobuf-api v1.19.1-0.20250610140050-4cadbcc86f16/go.mod h1:ee6TzKbgMS42ZJgaFEMG3c4R3dGOiffHSu6MrY7WQvs=
83+
github.com/code-payments/code-protobuf-api v1.19.1-0.20250618155621-c66659ab4ff5 h1:fsZBRUCPGSZQngq/1rsJcEwhiYrZokr0wketRRTgGmI=
84+
github.com/code-payments/code-protobuf-api v1.19.1-0.20250618155621-c66659ab4ff5/go.mod h1:ee6TzKbgMS42ZJgaFEMG3c4R3dGOiffHSu6MrY7WQvs=
8585
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba h1:Bkp+gmeb6Y2PWXfkSCTMBGWkb2P1BujRDSjWeI+0j5I=
8686
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba/go.mod h1:jSiifpiBpyBQ8q2R0MGEbkSgWC6sbdRTyDBntmW+j1E=
8787
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=

pkg/code/aml/guard.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ func (g *Guard) AllowMoneyMovement(ctx context.Context, intentRecord *intent.Rec
6363
case intent.ReceivePaymentsPublicly:
6464
// Public receives are always allowed
6565
return true, nil
66+
case intent.PublicDistribution:
67+
// Public distributions are always allowed
68+
return true, nil
6669
default:
6770
err := errors.New("intent record must be a send or receive payment")
6871
tracer.OnError(err)

pkg/code/antispam/guard.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package antispam
33
import (
44
"context"
55

6+
transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"
7+
68
"github.com/code-payments/code-server/pkg/code/common"
79
"github.com/code-payments/code-server/pkg/metrics"
810
)
@@ -15,11 +17,11 @@ func NewGuard(integration Integration) *Guard {
1517
return &Guard{integration: integration}
1618
}
1719

18-
func (g *Guard) AllowOpenAccounts(ctx context.Context, owner *common.Account) (bool, error) {
20+
func (g *Guard) AllowOpenAccounts(ctx context.Context, owner *common.Account, accountSet transactionpb.OpenAccountsMetadata_AccountSet) (bool, error) {
1921
tracer := metrics.TraceMethodCall(ctx, metricsStructName, "AllowOpenAccounts")
2022
defer tracer.End()
2123

22-
allow, reason, err := g.integration.AllowOpenAccounts(ctx, owner)
24+
allow, reason, err := g.integration.AllowOpenAccounts(ctx, owner, accountSet)
2325
if err != nil {
2426
return false, err
2527
}
@@ -70,3 +72,17 @@ func (g *Guard) AllowReceivePayments(ctx context.Context, owner *common.Account,
7072
}
7173
return allow, nil
7274
}
75+
76+
func (g *Guard) AllowDistribution(ctx context.Context, owner *common.Account, isPublic bool) (bool, error) {
77+
tracer := metrics.TraceMethodCall(ctx, metricsStructName, "AllowDistribution")
78+
defer tracer.End()
79+
80+
allow, reason, err := g.integration.AllowDistribution(ctx, owner, isPublic)
81+
if err != nil {
82+
return false, err
83+
}
84+
if !allow {
85+
recordDenialEvent(ctx, actionDistribution, reason)
86+
}
87+
return allow, nil
88+
}

pkg/code/antispam/integration.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@ package antispam
33
import (
44
"context"
55

6+
transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"
7+
68
"github.com/code-payments/code-server/pkg/code/common"
79
)
810

911
// Integration is an antispam guard integration that apps can implement to check
1012
// whether operations of interest are allowed to be performed.
1113
type Integration interface {
12-
AllowOpenAccounts(ctx context.Context, owner *common.Account) (bool, string, error)
14+
AllowOpenAccounts(ctx context.Context, owner *common.Account, accountSet transactionpb.OpenAccountsMetadata_AccountSet) (bool, string, error)
1315

1416
AllowWelcomeBonus(ctx context.Context, owner *common.Account) (bool, string, error)
1517

1618
AllowSendPayment(ctx context.Context, owner, destination *common.Account, isPublic bool) (bool, string, error)
1719

1820
AllowReceivePayments(ctx context.Context, owner *common.Account, isPublic bool) (bool, string, error)
21+
22+
AllowDistribution(ctx context.Context, owner *common.Account, isPublic bool) (bool, string, error)
1923
}
2024

2125
type allowEverythingIntegration struct {
@@ -26,7 +30,7 @@ func NewAllowEverything() Integration {
2630
return &allowEverythingIntegration{}
2731
}
2832

29-
func (i *allowEverythingIntegration) AllowOpenAccounts(ctx context.Context, owner *common.Account) (bool, string, error) {
33+
func (i *allowEverythingIntegration) AllowOpenAccounts(ctx context.Context, owner *common.Account, accountSet transactionpb.OpenAccountsMetadata_AccountSet) (bool, string, error) {
3034
return true, "", nil
3135
}
3236

@@ -41,3 +45,7 @@ func (i *allowEverythingIntegration) AllowSendPayment(ctx context.Context, owner
4145
func (i *allowEverythingIntegration) AllowReceivePayments(ctx context.Context, owner *common.Account, isPublic bool) (bool, string, error) {
4246
return true, "", nil
4347
}
48+
49+
func (i *allowEverythingIntegration) AllowDistribution(ctx context.Context, owner *common.Account, isPublic bool) (bool, string, error) {
50+
return true, "", nil
51+
}

pkg/code/antispam/metrics.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const (
1414
actionOpenAccounts = "OpenAccounts"
1515
actionSendPayment = "SendPayment"
1616
actionReceivePayments = "ReceivePayments"
17+
actionDistribution = "Distribution"
1718

1819
actionWelcomeBonus = "WelcomeBonus"
1920
)

pkg/code/async/sequencer/intent_handler.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,37 @@ func (h *ReceivePaymentsPubliclyIntentHandler) OnActionUpdated(ctx context.Conte
125125
return nil
126126
}
127127

128+
type PublicDistributionIntentHandler struct {
129+
data code_data.Provider
130+
}
131+
132+
func NewPublicDistributionIntentHandler(data code_data.Provider) IntentHandler {
133+
return &PublicDistributionIntentHandler{
134+
data: data,
135+
}
136+
}
137+
138+
func (h *PublicDistributionIntentHandler) OnActionUpdated(ctx context.Context, intentId string) error {
139+
actionRecords, err := h.data.GetAllActionsByIntent(ctx, intentId)
140+
if err != nil {
141+
return err
142+
}
143+
144+
for _, actionRecord := range actionRecords {
145+
// Intent is failed if at least one transfer or withdraw action fails
146+
if actionRecord.State == action.StateFailed {
147+
return markIntentFailed(ctx, h.data, intentId)
148+
}
149+
150+
if actionRecord.State != action.StateConfirmed {
151+
return nil
152+
}
153+
}
154+
155+
// Intent is confirmed when all transfer and withdraw actions are confirmed
156+
return markIntentConfirmed(ctx, h.data, intentId)
157+
}
158+
128159
func validateIntentState(record *intent.Record, states ...intent.State) error {
129160
for _, validState := range states {
130161
if record.State == validState {
@@ -176,5 +207,6 @@ func getIntentHandlers(data code_data.Provider) map[intent.Type]IntentHandler {
176207
handlersByType[intent.OpenAccounts] = NewOpenAccountsIntentHandler(data)
177208
handlersByType[intent.SendPublicPayment] = NewSendPublicPaymentIntentHandler(data)
178209
handlersByType[intent.ReceivePaymentsPublicly] = NewReceivePaymentsPubliclyIntentHandler(data)
210+
handlersByType[intent.PublicDistribution] = NewPublicDistributionIntentHandler(data)
179211
return handlersByType
180212
}

pkg/code/data/account/acccount_info.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ func (r *Record) Validate() error {
180180
if r.OwnerAccount == r.AuthorityAccount {
181181
return errors.New("owner cannot be authority for swap account")
182182
}
183+
case commonpb.AccountType_POOL:
184+
if r.OwnerAccount == r.AuthorityAccount {
185+
return errors.New("owner cannot be authority pool account")
186+
}
183187
default:
184188
return errors.Errorf("unhandled account type: %s", r.AccountType.String())
185189
}

pkg/code/data/intent/intent.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const (
3333
ReceivePaymentsPublicly
3434
EstablishRelationship // Deprecated privacy flow
3535
Login // Deprecated login flow
36+
PublicDistribution
3637
)
3738

3839
type Record struct {
@@ -47,6 +48,7 @@ type Record struct {
4748
ExternalDepositMetadata *ExternalDepositMetadata
4849
SendPublicPaymentMetadata *SendPublicPaymentMetadata
4950
ReceivePaymentsPubliclyMetadata *ReceivePaymentsPubliclyMetadata
51+
PublicDistributionMetadata *PublicDistributionMetadata
5052

5153
ExtendedMetadata []byte
5254

@@ -58,7 +60,7 @@ type Record struct {
5860
}
5961

6062
type OpenAccountsMetadata struct {
61-
// Nothing yet
63+
// todo: What should be stored here given different flows?
6264
}
6365

6466
type ExternalDepositMetadata struct {
@@ -96,6 +98,12 @@ type ReceivePaymentsPubliclyMetadata struct {
9698
UsdMarketValue float64
9799
}
98100

101+
type PublicDistributionMetadata struct {
102+
Source string
103+
Quantity uint64
104+
UsdMarketValue float64
105+
}
106+
99107
func (r *Record) IsCompleted() bool {
100108
return r.State == StateConfirmed
101109
}
@@ -125,6 +133,12 @@ func (r *Record) Clone() Record {
125133
receivePaymentsPubliclyMetadata = &cloned
126134
}
127135

136+
var publicDistributionMetadata *PublicDistributionMetadata
137+
if r.PublicDistributionMetadata != nil {
138+
cloned := r.PublicDistributionMetadata.Clone()
139+
publicDistributionMetadata = &cloned
140+
}
141+
128142
return Record{
129143
Id: r.Id,
130144

@@ -137,6 +151,7 @@ func (r *Record) Clone() Record {
137151
ExternalDepositMetadata: externalDepositMetadata,
138152
SendPublicPaymentMetadata: sendPublicPaymentMetadata,
139153
ReceivePaymentsPubliclyMetadata: receivePaymentsPubliclyMetadata,
154+
PublicDistributionMetadata: publicDistributionMetadata,
140155

141156
ExtendedMetadata: r.ExtendedMetadata,
142157

@@ -160,6 +175,7 @@ func (r *Record) CopyTo(dst *Record) {
160175
dst.ExternalDepositMetadata = r.ExternalDepositMetadata
161176
dst.SendPublicPaymentMetadata = r.SendPublicPaymentMetadata
162177
dst.ReceivePaymentsPubliclyMetadata = r.ReceivePaymentsPubliclyMetadata
178+
dst.PublicDistributionMetadata = r.PublicDistributionMetadata
163179

164180
dst.ExtendedMetadata = r.ExtendedMetadata
165181

@@ -227,6 +243,17 @@ func (r *Record) Validate() error {
227243
}
228244
}
229245

246+
if r.IntentType == PublicDistribution {
247+
if r.PublicDistributionMetadata == nil {
248+
return errors.New("public distribution metadata must be present")
249+
}
250+
251+
err := r.PublicDistributionMetadata.Validate()
252+
if err != nil {
253+
return err
254+
}
255+
}
256+
230257
return nil
231258
}
232259

@@ -388,6 +415,32 @@ func (m *ReceivePaymentsPubliclyMetadata) Validate() error {
388415
return nil
389416
}
390417

418+
func (m *PublicDistributionMetadata) Clone() PublicDistributionMetadata {
419+
return PublicDistributionMetadata{
420+
Source: m.Source,
421+
Quantity: m.Quantity,
422+
UsdMarketValue: m.UsdMarketValue,
423+
}
424+
}
425+
426+
func (m *PublicDistributionMetadata) CopyTo(dst *PublicDistributionMetadata) {
427+
dst.Source = m.Source
428+
dst.Quantity = m.Quantity
429+
dst.UsdMarketValue = m.UsdMarketValue
430+
}
431+
432+
func (m *PublicDistributionMetadata) Validate() error {
433+
if len(m.Source) == 0 {
434+
return errors.New("source is required")
435+
}
436+
437+
if m.Quantity == 0 {
438+
return errors.New("quantity is required")
439+
}
440+
441+
return nil
442+
}
443+
391444
func (s State) IsTerminal() bool {
392445
switch s {
393446
case StateConfirmed:
@@ -439,6 +492,8 @@ func (t Type) String() string {
439492
return "establish_relationship"
440493
case Login:
441494
return "login"
495+
case PublicDistribution:
496+
return "public_distribution"
442497
}
443498

444499
return "unknown"

pkg/code/data/intent/memory/store.go

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -110,22 +110,6 @@ func (s *store) findBySource(source string) []*intent.Record {
110110
return res
111111
}
112112

113-
func (s *store) findByInitiatorAndType(intentType intent.Type, owner string) []*intent.Record {
114-
res := make([]*intent.Record, 0)
115-
for _, item := range s.records {
116-
if item.IntentType != intentType {
117-
continue
118-
}
119-
120-
if item.InitiatorOwnerAccount != owner {
121-
continue
122-
}
123-
124-
res = append(res, item)
125-
}
126-
return res
127-
}
128-
129113
func (s *store) findByOwnerSinceTimestamp(owner string, since time.Time) []*intent.Record {
130114
res := make([]*intent.Record, 0)
131115
for _, item := range s.records {
@@ -318,25 +302,6 @@ func (s *store) GetAllByOwner(ctx context.Context, owner string, cursor query.Cu
318302
return nil, intent.ErrIntentNotFound
319303
}
320304

321-
func (s *store) GetLatestByInitiatorAndType(ctx context.Context, intentType intent.Type, owner string) (*intent.Record, error) {
322-
s.mu.Lock()
323-
defer s.mu.Unlock()
324-
325-
items := s.findByInitiatorAndType(intentType, owner)
326-
if len(items) == 0 {
327-
return nil, intent.ErrIntentNotFound
328-
}
329-
330-
latest := items[0]
331-
for _, item := range items {
332-
if item.CreatedAt.After(latest.CreatedAt) {
333-
latest = item
334-
}
335-
}
336-
337-
return latest, nil
338-
}
339-
340305
func (s *store) GetOriginalGiftCardIssuedIntent(ctx context.Context, giftCardVault string) (*intent.Record, error) {
341306
s.mu.Lock()
342307
defer s.mu.Unlock()

pkg/code/data/intent/postgres/model.go

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ func toIntentModel(obj *intent.Record) (*intentModel, error) {
9797
m.NativeAmount = obj.ReceivePaymentsPubliclyMetadata.OriginalNativeAmount
9898

9999
m.UsdMarketValue = obj.ReceivePaymentsPubliclyMetadata.UsdMarketValue
100+
case intent.PublicDistribution:
101+
m.Source = obj.PublicDistributionMetadata.Source
102+
m.Quantity = obj.PublicDistributionMetadata.Quantity
103+
m.UsdMarketValue = obj.PublicDistributionMetadata.UsdMarketValue
100104
default:
101105
return nil, errors.New("unsupported intent type")
102106
}
@@ -152,6 +156,12 @@ func fromIntentModel(obj *intentModel) *intent.Record {
152156
OriginalExchangeRate: obj.ExchangeRate,
153157
OriginalNativeAmount: obj.NativeAmount,
154158

159+
UsdMarketValue: obj.UsdMarketValue,
160+
}
161+
case intent.PublicDistribution:
162+
record.PublicDistributionMetadata = &intent.PublicDistributionMetadata{
163+
Source: obj.Source,
164+
Quantity: obj.Quantity,
155165
UsdMarketValue: obj.UsdMarketValue,
156166
}
157167
}
@@ -241,22 +251,6 @@ func dbGetAllByOwner(ctx context.Context, db *sqlx.DB, owner string, cursor q.Cu
241251
return res, nil
242252
}
243253

244-
func dbGetLatestByInitiatorAndType(ctx context.Context, db *sqlx.DB, intentType intent.Type, owner string) (*intentModel, error) {
245-
res := &intentModel{}
246-
247-
query := `SELECT id, intent_id, intent_type, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_deposit, is_remote_send, is_returned, is_issuer_voiding_gift_card, is_micro_payment, extended_metadata, state, version, created_at
248-
FROM ` + intentTableName + `
249-
WHERE owner = $1 AND intent_type = $2
250-
ORDER BY created_at DESC
251-
LIMIT 1`
252-
253-
err := db.GetContext(ctx, res, query, owner, intentType)
254-
if err != nil {
255-
return nil, pgutil.CheckNoRows(err, intent.ErrIntentNotFound)
256-
}
257-
return res, nil
258-
}
259-
260254
func dbGetOriginalGiftCardIssuedIntent(ctx context.Context, db *sqlx.DB, giftCardVault string) (*intentModel, error) {
261255
res := []*intentModel{}
262256

0 commit comments

Comments
 (0)