Skip to content
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

Fix USDC user bank creation #10445

Merged
merged 6 commits into from
Nov 15, 2024
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
5 changes: 5 additions & 0 deletions .changeset/twelve-cycles-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@audius/sdk': patch
---

Fix getOrCreateUserBank failures due to low priority fees
8 changes: 1 addition & 7 deletions packages/common/src/services/audius-backend/solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,21 +110,15 @@ export const getTokenAccountInfo = async (
audiusBackendInstance: AudiusBackend,
{
tokenAccount,
mint = DEFAULT_MINT,
commitment = 'processed'
}: {
tokenAccount: PublicKey
rickyrombo marked this conversation as resolved.
Show resolved Hide resolved
mint?: MintName
commitment?: Commitment
}
): Promise<Account | null> => {
return (
await audiusBackendInstance.getAudiusLibs()
).solanaWeb3Manager!.getTokenAccountInfo(
tokenAccount.toString(),
mint,
commitment
)
).solanaWeb3Manager!.getTokenAccountInfo(tokenAccount.toString(), commitment)
}

export const deriveUserBankPubkey = async (
Expand Down
79 changes: 23 additions & 56 deletions packages/common/src/store/buy-usdc/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { PublicKey } from '@solana/web3.js'
import { channel, Channel } from 'redux-saga'
import { call, delay, select, take } from 'typed-redux-saga'
import { call, delay, select } from 'typed-redux-saga'

import {
createUserBankIfNeeded,
getTokenAccountInfo,
MintName
} from '~/services/audius-backend/solana'
import { getTokenAccountInfo, MintName } from '~/services/audius-backend/solana'
import { IntKeys } from '~/services/remote-config'
import {
MAX_CONTENT_PRICE_CENTS,
Expand All @@ -16,66 +11,38 @@ import {
BUY_TOKEN_VIA_SOL_SLIPPAGE_BPS
} from '~/services/remote-config/defaults'

import { getAccountUser } from '../account/selectors'
import { getContext } from '../effects'
import { getFeePayer } from '../solana/selectors'

const POLL_ACCOUNT_INFO_DELAY_MS = 1000
const POLL_ACCOUNT_INFO_RETRIES = 30

// Create a channel to manage concurrent requests
let pendingUserBankCreation: Channel<{
result?: Awaited<ReturnType<typeof createUserBankIfNeeded>>
error?: Error
}> | null = null

/**
* Derives a USDC user bank for a given eth address, creating it if necessary.
* Defaults to the wallet of the current user. Uses a channel to manage concurrent
* requests so there is only one creation attempt in flight at a time.
* Defaults to the wallet of the current user.
*/
export function* getOrCreateUSDCUserBank(ethAddress?: string) {
// If there's already a pending operation, wait for its result
if (pendingUserBankCreation) {
const { result, error } = yield* take(pendingUserBankCreation)
if (error) throw error
if (!result)
throw new Error('No user bank returned from createUserBankIfNeeded')
return result
}

// Create a new channel for this bank creation
pendingUserBankCreation = channel()
try {
const audiusBackendInstance = yield* getContext('audiusBackendInstance')
const { track } = yield* getContext('analytics')
const feePayerOverride = yield* select(getFeePayer)

if (!feePayerOverride) {
throw new Error(
'getOrCreateUSDCUserBank: unexpectedly no fee payer override'
)
const audiusSdk = yield* getContext('audiusSdk')
const sdk = yield* call(audiusSdk)
let ethWallet = ethAddress
if (!ethWallet) {
const user = yield* select(getAccountUser)
if (!user?.wallet) {
throw new Error('Failed to create USDC user bank: No user wallet found.')
}

// Perform the bank creation
const result = yield* call(createUserBankIfNeeded, audiusBackendInstance, {
ethAddress,
feePayerOverride,
mint: 'usdc',
recordAnalytics: track
})

// Put the successful result on the channel
pendingUserBankCreation.put({ result })
return result
} catch (error) {
// Put the error on the channel
pendingUserBankCreation.put({ error: error as Error })
throw error
} finally {
// Close and cleanup the channel
pendingUserBankCreation.close()
pendingUserBankCreation = null
ethWallet = user.wallet
}
const { userBank } = yield* call(
[
sdk.services.claimableTokensClient,
sdk.services.claimableTokensClient.getOrCreateUserBank
],
{
ethWallet,
mint: 'USDC'
}
)
return userBank
}

/** Polls for the given token account info up to a maximum retry count. Useful
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {
} from '@audius/spl'
import { SendTransactionOptions } from '@solana/wallet-adapter-base'
import {
TransactionMessage,
VersionedTransaction,
Secp256k1Program,
PublicKey,
Transaction,
SendTransactionError
SendTransactionError,
ComputeBudgetProgram
} from '@solana/web3.js'

import { productionConfig } from '../../../../config/production'
Expand Down Expand Up @@ -152,18 +152,22 @@ export class ClaimableTokensClient {
userBank,
programId: this.programId
})
const confirmationStrategyArgs =
const computeBudgetLimitInstruction =
ComputeBudgetProgram.setComputeUnitLimit({
units: 50000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What criteria determines this value? And I thought we used constants that were settable by remote config for things like this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at what the simulation estimated the compute limit to be, and then doubled it. It's a bit of a magic number, can probably revisit later. Want to get this in though because the smaller we can make this number (it defaults to 200k) the more likely validators will let us in their block.

})
const { blockhash, lastValidBlockHeight } =
await this.client.connection.getLatestBlockhash()
const message = new TransactionMessage({
payerKey: feePayer,
recentBlockhash: confirmationStrategyArgs.blockhash,
instructions: [createUserBankInstruction]
}).compileToLegacyMessage()
const transaction = new VersionedTransaction(message)
const transaction = await this.client.buildTransaction({
instructions: [
createUserBankInstruction,
computeBudgetLimitInstruction
],
recentBlockhash: blockhash
})
const signature = await this.sendTransaction(transaction)
const confirmationStrategy = { ...confirmationStrategyArgs, signature }
await this.client.connection.confirmTransaction(
confirmationStrategy,
{ blockhash, lastValidBlockHeight, signature },
'finalized'
)
return { userBank, didExist: false }
Expand Down