Skip to content

Commit fcdb240

Browse files
author
jeffyanta
authored
USDC swap account balance is now checkpointed in Geyser flows (#49)
1 parent 32e9d69 commit fcdb240

File tree

1 file changed

+64
-24
lines changed

1 file changed

+64
-24
lines changed

pkg/code/async/geyser/external_deposit.go

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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

426466
func getSyncedDepositCacheKey(signature string, account *common.Account) string {

0 commit comments

Comments
 (0)