@@ -16,6 +16,7 @@ import (
1616 chat_util "github.com/code-payments/code-server/pkg/code/chat"
1717 "github.com/code-payments/code-server/pkg/code/common"
1818 code_data "github.com/code-payments/code-server/pkg/code/data"
19+ "github.com/code-payments/code-server/pkg/code/data/balance"
1920 "github.com/code-payments/code-server/pkg/code/data/chat"
2021 "github.com/code-payments/code-server/pkg/code/data/deposit"
2122 "github.com/code-payments/code-server/pkg/code/data/fulfillment"
@@ -199,7 +200,7 @@ func processPotentialExternalDeposit(ctx context.Context, conf *conf, data code_
199200 return nil
200201 }
201202
202- isCodeSwap , usdcQuarksSwapped , err := getCodeSwapMetadata (ctx , conf , tokenBalances )
203+ isCodeSwap , usdcSwapAccount , usdcQuarksSwapped , err := getCodeSwapMetadata (ctx , conf , tokenBalances )
203204 if err != nil {
204205 return errors .Wrap (err , "error getting code swap metadata" )
205206 }
@@ -215,6 +216,12 @@ func processPotentialExternalDeposit(ctx context.Context, conf *conf, data code_
215216 usdMarketValue = usdExchangeRecord .Rate * float64 (deltaQuarks ) / float64 (kin .QuarksPerKin )
216217 }
217218
219+ if isCodeSwap {
220+ // Checkpoint the Code swap account balance, to minimize chances a
221+ // stale RPC node results in a double counting of funds
222+ bestEffortCacheExternalAccountBalance (ctx , data , usdcSwapAccount , tokenBalances )
223+ }
224+
218225 // For a consistent payment history list
219226 //
220227 // Deprecated in favour of chats (for history purposes)
@@ -297,8 +304,7 @@ func processPotentialExternalDeposit(ctx context.Context, conf *conf, data code_
297304 return nil
298305
299306 case commonpb .AccountType_SWAP :
300- // todo: Don't think we need to track an external deposit record. Balances
301- // cannot be tracked using cached values.
307+ bestEffortCacheExternalAccountBalance (ctx , data , tokenAccount , tokenBalances )
302308
303309 // todo: solana client doesn't return block time
304310 chatMessage , err := chat_util .ToUsdcDepositedMessage (signature , uint64 (deltaQuarks ), time .Now ())
@@ -307,26 +313,25 @@ func processPotentialExternalDeposit(ctx context.Context, conf *conf, data code_
307313 }
308314
309315 canPush , err := chat_util .SendCodeTeamMessage (ctx , data , chatMessageReceiver , chatMessage )
310- if err == chat .ErrMessageAlreadyExists {
311- syncedDepositCache .Insert (cacheKey , true , 1 )
312- return nil
313- } else if err != nil {
316+ switch err {
317+ case nil :
318+ if canPush {
319+ push .SendChatMessagePushNotification (
320+ ctx ,
321+ data ,
322+ pusher ,
323+ chat_util .CodeTeamName ,
324+ chatMessageReceiver ,
325+ chatMessage ,
326+ )
327+ }
328+ case chat .ErrMessageAlreadyExists :
329+ default :
314330 return errors .Wrap (err , "error sending chat message" )
315331 }
316332
317333 syncedDepositCache .Insert (cacheKey , true , 1 )
318334
319- if canPush {
320- push .SendChatMessagePushNotification (
321- ctx ,
322- data ,
323- pusher ,
324- chat_util .CodeTeamName ,
325- chatMessageReceiver ,
326- chatMessage ,
327- )
328- }
329-
330335 return nil
331336
332337 default :
@@ -381,7 +386,21 @@ func getDeltaQuarksFromTokenBalances(tokenAccount *common.Account, tokenBalances
381386 return postQuarkBalance - preQuarkBalance , nil
382387}
383388
384- func getCodeSwapMetadata (ctx context.Context , conf * conf , tokenBalances * solana.TransactionTokenBalances ) (bool , uint64 , error ) {
389+ // todo: can be promoted more broadly
390+ func getPostQuarkBalance (tokenAccount * common.Account , tokenBalances * solana.TransactionTokenBalances ) (uint64 , error ) {
391+ for _ , postBalance := range tokenBalances .PostTokenBalances {
392+ if tokenBalances .Accounts [postBalance .AccountIndex ] == tokenAccount .PublicKey ().ToBase58 () {
393+ postQuarkBalance , err := strconv .ParseUint (postBalance .TokenAmount .Amount , 10 , 64 )
394+ if err != nil {
395+ return 0 , errors .Wrap (err , "error parsing post token balance" )
396+ }
397+ return postQuarkBalance , nil
398+ }
399+ }
400+ return 0 , errors .New ("no post balance for account" )
401+ }
402+
403+ func getCodeSwapMetadata (ctx context.Context , conf * conf , tokenBalances * solana.TransactionTokenBalances ) (bool , * common.Account , uint64 , error ) {
385404 // Detect whether this is a Code swap by inspecting whether the swap subsidizer
386405 // is included in the transaction.
387406 var isCodeSwap bool
@@ -393,34 +412,55 @@ func getCodeSwapMetadata(ctx context.Context, conf *conf, tokenBalances *solana.
393412 }
394413
395414 if ! isCodeSwap {
396- return false , 0 , nil
415+ return false , nil , 0 , nil
397416 }
398417
399418 var usdcPaid uint64
419+ var usdcAccount * common.Account
400420 for _ , tokenBalance := range tokenBalances .PreTokenBalances {
401421 tokenAccount , err := common .NewAccountFromPublicKeyString (tokenBalances .Accounts [tokenBalance .AccountIndex ])
402422 if err != nil {
403- return false , 0 , errors .Wrap (err , "invalid token account" )
423+ return false , nil , 0 , errors .Wrap (err , "invalid token account" )
404424 }
405425
406426 if tokenBalance .Mint == common .UsdcMintAccount .PublicKey ().ToBase58 () {
407427 deltaQuarks , err := getDeltaQuarksFromTokenBalances (tokenAccount , tokenBalances )
408428 if err != nil {
409- return false , 0 , errors .Wrap (err , "error getting delta quarks" )
429+ return false , nil , 0 , errors .Wrap (err , "error getting delta quarks" )
410430 }
411431
412- if deltaQuarks > 0 {
432+ if deltaQuarks >= 0 {
413433 continue
414434 }
415435
416436 absDeltaQuarks := uint64 (- 1 * deltaQuarks )
417437 if absDeltaQuarks > usdcPaid {
418438 usdcPaid = absDeltaQuarks
439+ usdcAccount = tokenAccount
419440 }
420441 }
421442 }
422443
423- return true , usdcPaid , nil
444+ if usdcAccount == nil {
445+ return false , nil , 0 , errors .New ("usdc account not found" )
446+ }
447+
448+ return true , usdcAccount , usdcPaid , nil
449+ }
450+
451+ // Optimistically tries to cache a balance for an external account not managed
452+ // Code. It doesn't need to be perfect and will be lazily corrected on the next
453+ // balance fetch with a newer state returned by a RPC node.
454+ func bestEffortCacheExternalAccountBalance (ctx context.Context , data code_data.Provider , tokenAccount * common.Account , tokenBalances * solana.TransactionTokenBalances ) {
455+ postBalance , err := getPostQuarkBalance (tokenAccount , tokenBalances )
456+ if err == nil {
457+ checkpointRecord := & balance.Record {
458+ TokenAccount : tokenAccount .PublicKey ().ToBase58 (),
459+ Quarks : postBalance ,
460+ SlotCheckpoint : tokenBalances .Slot ,
461+ }
462+ data .SaveBalanceCheckpoint (ctx , checkpointRecord )
463+ }
424464}
425465
426466func getSyncedDepositCacheKey (signature string , account * common.Account ) string {
0 commit comments