@@ -21,6 +21,7 @@ import (
2121 "github.com/code-payments/code-server/pkg/code/data/account"
2222 "github.com/code-payments/code-server/pkg/code/data/action"
2323 "github.com/code-payments/code-server/pkg/code/data/intent"
24+ "github.com/code-payments/code-server/pkg/code/data/swap"
2425 "github.com/code-payments/code-server/pkg/code/data/timelock"
2526 currency_lib "github.com/code-payments/code-server/pkg/currency"
2627 "github.com/code-payments/code-server/pkg/solana"
@@ -62,6 +63,11 @@ type CreateIntentHandler interface {
6263
6364 // AllowCreation determines whether the new intent creation should be allowed.
6465 AllowCreation (ctx context.Context , intentRecord * intent.Record , metadata * transactionpb.Metadata , actions []* transactionpb.Action ) error
66+
67+ // OnCommitToDB is a callback when the intent is being committed to the DB
68+ // within the scope of a DB transaction. Additional supporting DB records
69+ // relevant to the intent should be saved here.
70+ OnCommitToDB (ctx context.Context ) error
6571}
6672
6773type OpenAccountsIntentHandler struct {
@@ -221,7 +227,17 @@ func (h *OpenAccountsIntentHandler) AllowCreation(ctx context.Context, intentRec
221227 // Part 4: Validate fee payments
222228 //
223229
224- return validateFeePayments (ctx , h .data , h .conf , intentRecord , simResult )
230+ err = validateFeePayments (ctx , h .data , h .conf , intentRecord , simResult )
231+ if err != nil {
232+ return err
233+ }
234+
235+ //
236+ // Part 5: Validate reserved intent IDs
237+ //
238+
239+ _ , err = validateSwapFunding (ctx , h .data , intentRecord )
240+ return err
225241}
226242
227243func (h * OpenAccountsIntentHandler ) validateActions (
@@ -331,13 +347,18 @@ func (h *OpenAccountsIntentHandler) validateActions(
331347 return nil
332348}
333349
350+ func (h * OpenAccountsIntentHandler ) OnCommitToDB (ctx context.Context ) error {
351+ return nil
352+ }
353+
334354type SendPublicPaymentIntentHandler struct {
335355 conf * conf
336356 data code_data.Provider
337357 antispamGuard * antispam.Guard
338358 amlGuard * aml.Guard
339359
340360 cachedDestinationAccountInfoRecord * account.Record
361+ cachedSwapRecord * swap.Record
341362}
342363
343364func NewSendPublicPaymentIntentHandler (
@@ -574,7 +595,16 @@ func (h *SendPublicPaymentIntentHandler) AllowCreation(ctx context.Context, inte
574595 }
575596
576597 //
577- // Part 7: Validate the individual actions
598+ // Part 7: Validate reserved intent IDs
599+ //
600+
601+ h .cachedSwapRecord , err = validateSwapFunding (ctx , h .data , intentRecord )
602+ if err != nil {
603+ return err
604+ }
605+
606+ //
607+ // Part 8: Validate the individual actions
578608 //
579609
580610 return h .validateActions (
@@ -719,27 +749,12 @@ func (h *SendPublicPaymentIntentHandler) validateActions(
719749 return NewIntentValidationErrorf ("destination is not the ata for %s" , destinationOwner .PublicKey ().ToBase58 ())
720750 }
721751
722- vmSwapPdaTimelockRecord , err : = h .data .GetTimelockBySwapPda (ctx , destinationOwner .PublicKey ().ToBase58 ())
752+ _ , err = h .data .GetTimelockBySwapPda (ctx , destinationOwner .PublicKey ().ToBase58 ())
723753 if err == nil {
724754 isVmSwapPda = true
725755 } else if err != timelock .ErrTimelockNotFound {
726756 return err
727757 }
728-
729- if isVmSwapPda {
730- accountInfoRecord , err := h .data .GetAccountInfoByTokenAddress (ctx , vmSwapPdaTimelockRecord .VaultAddress )
731- if err != nil {
732- return err
733- }
734-
735- if accountInfoRecord .OwnerAccount != initiatorOwnerAccount .PublicKey ().ToBase58 () {
736- return NewIntentDeniedError ("can only send to your own vm swap pda" )
737- }
738-
739- if accountInfoRecord .MintAccount != intentMint .PublicKey ().ToBase58 () {
740- return NewIntentValidationError ("vm swap pda is for a different mint" )
741- }
742- }
743758 }
744759
745760 // Technically we should always enforce a fee payment, but these checks are only
@@ -876,6 +891,15 @@ func (h *SendPublicPaymentIntentHandler) validateActions(
876891 return nil
877892}
878893
894+ func (h * SendPublicPaymentIntentHandler ) OnCommitToDB (ctx context.Context ) error {
895+ if h .cachedSwapRecord != nil {
896+ h .cachedSwapRecord .State = swap .StateFunding
897+ return h .data .SaveSwap (ctx , h .cachedSwapRecord )
898+ }
899+
900+ return nil
901+ }
902+
879903type ReceivePaymentsPubliclyIntentHandler struct {
880904 conf * conf
881905 data code_data.Provider
@@ -1087,7 +1111,16 @@ func (h *ReceivePaymentsPubliclyIntentHandler) AllowCreation(ctx context.Context
10871111 }
10881112
10891113 //
1090- // Part 7: Validate the individual actions
1114+ // Part 7: Validate reserved intent IDs
1115+ //
1116+
1117+ _ , err = validateSwapFunding (ctx , h .data , intentRecord )
1118+ if err != nil {
1119+ return err
1120+ }
1121+
1122+ //
1123+ // Part 8: Validate the individual actions
10911124 //
10921125
10931126 return h .validateActions (
@@ -1194,6 +1227,10 @@ func (h *ReceivePaymentsPubliclyIntentHandler) validateActions(
11941227 return validateMoneyMovementActionUserAccounts (ctx , h .data , intent .ReceivePaymentsPublicly , initiatorAccountsByVault , actions )
11951228}
11961229
1230+ func (h * ReceivePaymentsPubliclyIntentHandler ) OnCommitToDB (ctx context.Context ) error {
1231+ return nil
1232+ }
1233+
11971234type PublicDistributionIntentHandler struct {
11981235 conf * conf
11991236 data code_data.Provider
@@ -1392,7 +1429,16 @@ func (h *PublicDistributionIntentHandler) AllowCreation(ctx context.Context, int
13921429 }
13931430
13941431 //
1395- // Part 5: Validate actions
1432+ // Part 5: Validate reserved intent IDs
1433+ //
1434+
1435+ _ , err = validateSwapFunding (ctx , h .data , intentRecord )
1436+ if err != nil {
1437+ return err
1438+ }
1439+
1440+ //
1441+ // Part 6: Validate actions
13961442 //
13971443
13981444 return h .validateActions (typedMetadata , actions , simResult )
@@ -1525,6 +1571,10 @@ func (h *PublicDistributionIntentHandler) validateActions(
15251571 return nil
15261572}
15271573
1574+ func (h * PublicDistributionIntentHandler ) OnCommitToDB (ctx context.Context ) error {
1575+ return nil
1576+ }
1577+
15281578func validateAllUserAccountsManagedByCode (ctx context.Context , initiatorAccounts []* common.AccountRecords ) error {
15291579 // Try to unlock *ANY* latest account, and you're done
15301580 for _ , accountRecords := range initiatorAccounts {
@@ -1942,6 +1992,89 @@ func validateDistributedPool(ctx context.Context, data code_data.Provider, poolV
19421992 return nil
19431993}
19441994
1995+ func validateSwapFunding (ctx context.Context , data code_data.Provider , intentRecord * intent.Record ) (* swap.Record , error ) {
1996+ swapRecord , err := data .GetSwapByFundingId (ctx , intentRecord .IntentId )
1997+ if err != nil && err != swap .ErrNotFound {
1998+ return nil , err
1999+ }
2000+ isIntentReservedForSwap := swapRecord != nil
2001+
2002+ if isIntentReservedForSwap && swapRecord .State != swap .StateCreated {
2003+ return nil , errors .Errorf ("unexpected swap state: %s" , swapRecord .State )
2004+ }
2005+
2006+ // Intent-specific validation for swaps
2007+ if isIntentReservedForSwap {
2008+ if intentRecord .IntentType != intent .SendPublicPayment {
2009+ return nil , NewIntentDeniedError ("intent id reserved to fund swap" )
2010+ }
2011+
2012+ if intentRecord .InitiatorOwnerAccount != swapRecord .Owner {
2013+ return nil , NewIntentDeniedError ("not the owner of the swap" )
2014+ }
2015+
2016+ if ! intentRecord .SendPublicPaymentMetadata .IsWithdrawal {
2017+ return nil , NewIntentValidationError ("swap funding must be an external withdrawal" )
2018+ }
2019+
2020+ if len (intentRecord .SendPublicPaymentMetadata .DestinationOwnerAccount ) == 0 {
2021+ return nil , NewIntentValidationError ("destination owner must be a vm swap pda" )
2022+ }
2023+
2024+ if intentRecord .MintAccount != swapRecord .FromMint {
2025+ return nil , NewIntentValidationErrorf ("must fund swap with %s mint" , swapRecord .FromMint )
2026+ }
2027+
2028+ if intentRecord .SendPublicPaymentMetadata .Quantity != swapRecord .Amount {
2029+ return nil , NewIntentValidationErrorf ("must fund swap with %d quarks" , swapRecord .Amount )
2030+ }
2031+ }
2032+ if ! isIntentReservedForSwap && intentRecord .IntentType != intent .SendPublicPayment {
2033+ return nil , nil
2034+ }
2035+
2036+ // Account-specific validation for swaps
2037+ if len (intentRecord .SendPublicPaymentMetadata .DestinationOwnerAccount ) > 0 {
2038+ destinationOwner , err := common .NewAccountFromPublicKeyString (intentRecord .SendPublicPaymentMetadata .DestinationOwnerAccount )
2039+ if err != nil {
2040+ return nil , err
2041+ }
2042+
2043+ var isVmSwapPda bool
2044+ vmSwapPdaTimelockRecord , err := data .GetTimelockBySwapPda (ctx , destinationOwner .PublicKey ().ToBase58 ())
2045+ if err == nil {
2046+ isVmSwapPda = true
2047+ } else if err != timelock .ErrTimelockNotFound {
2048+ return nil , err
2049+ }
2050+
2051+ if isVmSwapPda {
2052+ if ! isIntentReservedForSwap {
2053+ return nil , NewIntentDeniedError ("intent id is not reserved for a swap" )
2054+ }
2055+
2056+ accountInfoRecord , err := data .GetAccountInfoByTokenAddress (ctx , vmSwapPdaTimelockRecord .VaultAddress )
2057+ if err != nil {
2058+ return nil , err
2059+ }
2060+
2061+ if accountInfoRecord .OwnerAccount != intentRecord .InitiatorOwnerAccount {
2062+ return nil , NewIntentDeniedError ("can only send to your own vm swap pda" )
2063+ }
2064+
2065+ if accountInfoRecord .MintAccount != intentRecord .MintAccount {
2066+ return nil , NewIntentValidationError ("vm swap pda is for a different mint" )
2067+ }
2068+ }
2069+
2070+ if ! isVmSwapPda && isIntentReservedForSwap {
2071+ return nil , NewIntentValidationError ("destination owner must be a vm swap pda" )
2072+ }
2073+ }
2074+
2075+ return swapRecord , nil
2076+ }
2077+
19452078func validateTimelockUnlockStateDoesntExist (ctx context.Context , data code_data.Provider , openAction * transactionpb.OpenAccountAction ) error {
19462079 mintAccount , err := common .GetBackwardsCompatMint (openAction .Mint )
19472080 if err != nil {
0 commit comments