From b8e3dc8b04c2ba99c2c90c1f47a6df26847c39ce Mon Sep 17 00:00:00 2001 From: Dharit Tantiviramanond Date: Wed, 11 Dec 2024 11:41:09 +0700 Subject: [PATCH 1/5] createVersionedTransaction --- .../src/services/audius-backend/solana.ts | 18 +++++++----------- packages/common/src/store/buy-crypto/sagas.ts | 5 ++--- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/common/src/services/audius-backend/solana.ts b/packages/common/src/services/audius-backend/solana.ts index a53cb23eb3f..4fd3453ff39 100644 --- a/packages/common/src/services/audius-backend/solana.ts +++ b/packages/common/src/services/audius-backend/solana.ts @@ -644,11 +644,10 @@ export const relayVersionedTransaction = async ( * not the accounts _in_ the lookup table) from their addresses. */ export const getLookupTableAccounts = async ( - audiusBackendInstance: AudiusBackend, + sdk: AudiusSdk, { lookupTableAddresses }: { lookupTableAddresses: string[] } ) => { - const libs = await audiusBackendInstance.getAudiusLibsTyped() - const connection = libs.solanaWeb3Manager!.getConnection() + const connection = sdk.services.solanaClient.connection return await Promise.all( lookupTableAddresses.map(async (address) => { const account = await connection.getAddressLookupTable( @@ -666,23 +665,20 @@ export const getLookupTableAccounts = async ( * Helper to create a versioned transaction with lookup tables */ export const createVersionedTransaction = async ( - audiusBackendInstance: AudiusBackend, + sdk: AudiusSdk, { instructions, lookupTableAddresses, - feePayer, - sdk + feePayer }: { instructions: TransactionInstruction[] lookupTableAddresses: string[] feePayer: PublicKey - sdk: AudiusSdk } ) => { - const addressLookupTableAccounts = await getLookupTableAccounts( - audiusBackendInstance, - { lookupTableAddresses } - ) + const addressLookupTableAccounts = await getLookupTableAccounts(sdk, { + lookupTableAddresses + }) const recentBlockhash = await getRecentBlockhash({ sdk }) const message = new TransactionMessage({ diff --git a/packages/common/src/store/buy-crypto/sagas.ts b/packages/common/src/store/buy-crypto/sagas.ts index 0603657f434..c35a22c4e93 100644 --- a/packages/common/src/store/buy-crypto/sagas.ts +++ b/packages/common/src/store/buy-crypto/sagas.ts @@ -215,12 +215,11 @@ function* swapSolForCrypto({ const sdk = yield* getSDK() const { transaction, addressLookupTableAccounts } = yield* call( createVersionedTransaction, - audiusBackendInstance, + sdk, { instructions, lookupTableAddresses: addressLookupTableAddresses, - feePayer, - sdk + feePayer } ) transaction.sign([wallet]) From d22927d59d967a1ce2bf3038f00c7432518a8230 Mon Sep 17 00:00:00 2001 From: Dharit Tantiviramanond Date: Wed, 11 Dec 2024 12:24:55 +0700 Subject: [PATCH 2/5] payment router, buy-usdc --- .../common/src/hooks/useCoinflowAdapter.ts | 29 ++--- .../src/services/audius-backend/solana.ts | 106 +++++++----------- packages/common/src/store/buy-crypto/sagas.ts | 30 +++-- packages/common/src/store/buy-usdc/sagas.ts | 24 ++-- 4 files changed, 73 insertions(+), 116 deletions(-) diff --git a/packages/common/src/hooks/useCoinflowAdapter.ts b/packages/common/src/hooks/useCoinflowAdapter.ts index 10e0531d225..951c329006b 100644 --- a/packages/common/src/hooks/useCoinflowAdapter.ts +++ b/packages/common/src/hooks/useCoinflowAdapter.ts @@ -13,7 +13,6 @@ import { useAppContext } from '~/context' import { Name } from '~/models/Analytics' import { decorateCoinflowWithdrawalTransaction, - relayTransaction, getRootSolanaAccount } from '~/services/audius-backend' import { @@ -76,40 +75,30 @@ export const useCoinflowWithdrawalAdapter = () => { feePayer }) finalTransaction.partialSign(wallet) - const { res, error, errorCode } = await relayTransaction( - audiusBackend, - { - transaction: finalTransaction, + const { signature } = await sdk.services.solanaRelay.relay({ + transaction: finalTransaction, + sendOptions: { skipPreflight: true } - ) - if (!res) { + }) + if (!signature) { console.error('Relaying Coinflow transaction failed.', { - error, - errorCode, finalTransaction }) track( make({ - eventName: - Name.WITHDRAW_USDC_COINFLOW_SEND_TRANSACTION_FAILED, - error: error ?? undefined, - errorCode: errorCode ?? undefined + eventName: Name.WITHDRAW_USDC_COINFLOW_SEND_TRANSACTION_FAILED }) ) - throw new Error( - `Relaying Coinflow transaction failed: ${ - error ?? 'Unknown error' - }` - ) + throw new Error('Relaying Coinflow transaction failed') } track( make({ eventName: Name.WITHDRAW_USDC_COINFLOW_SEND_TRANSACTION, - signature: res + signature }) ) - return res + return signature } } }) diff --git a/packages/common/src/services/audius-backend/solana.ts b/packages/common/src/services/audius-backend/solana.ts index 4fd3453ff39..881c9d9b084 100644 --- a/packages/common/src/services/audius-backend/solana.ts +++ b/packages/common/src/services/audius-backend/solana.ts @@ -430,15 +430,24 @@ export const decorateCoinflowWithdrawalTransaction = async ( data.decimals ) - const transferFromUserBankInstructions = - await solanaWeb3Manager.createTransferInstructionsFromCurrentUser({ - amount: new BN(data.amount.toString()), - mint: 'usdc', - senderSolanaAddress: userBank, - recipientSolanaAddress: keys.destination.pubkey.toBase58(), - instructionIndex: transferInstructionIndex + 1, - feePayerKey: feePayer + const transferFromUserBankSecpInstruction = + await sdk.services.claimableTokensClient.createTransferSecpInstruction({ + amount: data.amount, + mint: 'USDC', + destination: keys.destination.pubkey, + ethWallet: ethAddress, + instructionIndex: transferInstructionIndex + 1 }) + const transferFromUserBankInstruction = + await sdk.services.claimableTokensClient.createTransferInstruction({ + feePayer, + mint: 'USDC', + destination: keys.destination.pubkey, + ethWallet: ethAddress + }) + const priorityFeeInstruction = ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: 100000 + }) const withdrawalMemoInstruction = new TransactionInstruction({ keys: [ @@ -457,7 +466,9 @@ export const decorateCoinflowWithdrawalTransaction = async ( transferInstructionIndex, 1, transferToUserBankInstruction, - ...transferFromUserBankInstructions, + transferFromUserBankSecpInstruction, + transferFromUserBankInstruction, + priorityFeeInstruction, withdrawalMemoInstruction ) @@ -475,6 +486,7 @@ export const decorateCoinflowWithdrawalTransaction = async ( } export const createTransferToUserBankTransaction = async ( + sdk: AudiusSdk, audiusBackendInstance: AudiusBackend, { userBank, @@ -494,8 +506,6 @@ export const createTransferToUserBankTransaction = async ( recentBlockhash?: string } ) => { - const libs = await audiusBackendInstance.getAudiusLibsTyped() - const mintPublicKey = libs.solanaWeb3Manager!.mints[mint] const associatedTokenAccount = await findAssociatedTokenAddress( audiusBackendInstance, { @@ -534,31 +544,30 @@ export const createTransferToUserBankTransaction = async ( * that doesn't add the purchase memo. */ export const createPaymentRouterRouteTransaction = async ( - audiusBackendInstance: AudiusBackend, + sdk: AudiusSdk, { sender, - splits + splits, + total }: { sender: PublicKey - splits: Record + splits: { amount: bigint; wallet: PublicKey }[] + total: bigint } ) => { - const solanaWeb3Manager = (await audiusBackendInstance.getAudiusLibsTyped()) - .solanaWeb3Manager! - const { blockhash } = await solanaWeb3Manager - .getConnection() - .getLatestBlockhash() - const [transfer, route] = - // All the memo related parameters are ignored - await solanaWeb3Manager.getPurchaseContentWithPaymentRouterInstructions({ - id: 0, // ignored - type: 'track', // ignored - blocknumber: 0, // ignored - splits, - purchaserUserId: 0, // ignored - senderAccount: sender, - purchaseAccess: PurchaseAccess.STREAM // ignored + const connection = sdk.services.solanaClient.connection + const { blockhash } = await connection.getLatestBlockhash() + const transfer = + await sdk.services.paymentRouterClient.createTransferInstruction({ + total, + sourceWallet: sender, + mint: 'USDC' }) + const route = await sdk.services.paymentRouterClient.createRouteInstruction({ + total, + splits, + mint: 'USDC' + }) return new Transaction({ recentBlockhash: blockhash, feePayer: sender @@ -600,45 +609,6 @@ export const relayTransaction = async ( }) } -/** - * Relays the given versioned transaction using the libs transaction handler - */ -export const relayVersionedTransaction = async ( - audiusBackendInstance: AudiusBackend, - { - transaction, - addressLookupTableAccounts, - skipPreflight - }: { - transaction: VersionedTransaction - addressLookupTableAccounts: AddressLookupTableAccount[] - skipPreflight?: boolean - } -) => { - const placeholderSignature = Buffer.from(PLACEHOLDER_SIGNATURE) - const libs = await audiusBackendInstance.getAudiusLibsTyped() - const decompiledMessage = TransactionMessage.decompile(transaction.message, { - addressLookupTableAccounts - }) - const signatures = transaction.message.staticAccountKeys - .slice(0, transaction.message.header.numRequiredSignatures) - .map((publicKey, index) => ({ - publicKey: publicKey.toBase58(), - signature: Buffer.from(transaction.signatures[index]) - })) - .filter((meta) => !meta.signature.equals(placeholderSignature)) - return await libs.solanaWeb3Manager!.transactionHandler.handleTransaction({ - instructions: decompiledMessage.instructions, - recentBlockhash: decompiledMessage.recentBlockhash, - signatures, - feePayerOverride: decompiledMessage.payerKey, - lookupTableAddresses: addressLookupTableAccounts.map((lut) => - lut.key.toBase58() - ), - skipPreflight - }) -} - /** * Helper that gets the lookup table accounts (that is, the account holding the lookup table, * not the accounts _in_ the lookup table) from their addresses. diff --git a/packages/common/src/store/buy-crypto/sagas.ts b/packages/common/src/store/buy-crypto/sagas.ts index c35a22c4e93..630915f2d8b 100644 --- a/packages/common/src/store/buy-crypto/sagas.ts +++ b/packages/common/src/store/buy-crypto/sagas.ts @@ -38,8 +38,7 @@ import { getRootSolanaAccount, getTokenAccountInfo, pollForBalanceChange, - pollForTokenBalanceChange, - relayVersionedTransaction + pollForTokenBalanceChange } from '~/services/audius-backend/solana' import { FeatureFlags } from '~/services/index' import { IntKeys } from '~/services/remote-config/types' @@ -143,7 +142,6 @@ function* swapSolForCrypto({ userbank: PublicKey quoteResponse: Awaited> }) { - const audiusBackendInstance = yield* getContext('audiusBackendInstance') // Create a memo // See: https://github.com/solana-labs/solana-program-library/blob/d6297495ea4dcc1bd48f3efdd6e3bbdaef25a495/memo/js/src/index.ts#L27 const memoInstruction = new TransactionInstruction({ @@ -213,22 +211,20 @@ function* swapSolForCrypto({ closeWSOLInstruction ] const sdk = yield* getSDK() - const { transaction, addressLookupTableAccounts } = yield* call( - createVersionedTransaction, - sdk, - { - instructions, - lookupTableAddresses: addressLookupTableAddresses, - feePayer - } - ) + const { transaction } = yield* call(createVersionedTransaction, sdk, { + instructions, + lookupTableAddresses: addressLookupTableAddresses, + feePayer + }) transaction.sign([wallet]) - return yield* call(relayVersionedTransaction, audiusBackendInstance, { + const { signature } = yield* call(sdk.services.solanaRelay.relay, { transaction, - addressLookupTableAccounts, - skipPreflight: true + sendOptions: { + skipPreflight: true + } }) + return signature } function* assertRecoverySuccess({ @@ -528,7 +524,7 @@ function* doBuyCryptoViaSol({ const maxRetryCount = 3 const retryDelayMs = 3000 do { - const { res, error } = yield* call(swapSolForCrypto, { + const { signature } = yield* call(swapSolForCrypto, { feePayer, mint, wallet, @@ -805,7 +801,7 @@ function* recoverBuyCryptoViaSolIfNecessary() { // Do the swap. Just do it once, slippage shouldn't be a // concern since the quote is fresh and the tolerance is high. - const { res, error: recoveryError } = yield* call(swapSolForCrypto, { + const signature = yield* call(swapSolForCrypto, { quoteResponse: exactInQuote, mint, wallet, diff --git a/packages/common/src/store/buy-usdc/sagas.ts b/packages/common/src/store/buy-usdc/sagas.ts index be7269d5bc7..41d0cea60c1 100644 --- a/packages/common/src/store/buy-usdc/sagas.ts +++ b/packages/common/src/store/buy-usdc/sagas.ts @@ -19,8 +19,7 @@ import { getTokenAccountInfo, getUserbankAccountInfo, pollForTokenBalanceChange, - recoverUsdcFromRootWallet, - relayTransaction + recoverUsdcFromRootWallet } from '~/services/audius-backend/solana' import { getAccountUser, @@ -184,6 +183,7 @@ function* transferStep({ retry, async () => { const transferTransaction = await createTransferToUserBankTransaction( + sdk, audiusBackendInstance, { wallet, @@ -198,12 +198,12 @@ function* transferStep({ transferTransaction.partialSign(wallet) console.debug(`Starting transfer transaction...`) - const { res, error } = await relayTransaction(audiusBackendInstance, { + const { signature } = await sdk.services.solanaRelay.relay({ transaction: transferTransaction }) - if (res) { - console.debug(`Transfer transaction succeeded: ${res}`) + if (signature) { + console.debug(`Transfer transaction succeeded: ${signature}`) return } @@ -212,8 +212,6 @@ function* transferStep({ transferTransaction )}` ) - // Throw to retry - throw new Error(error ?? 'Unknown USDC user bank transfer error') }, { minTimeout: retryDelayMs, @@ -325,12 +323,16 @@ function* doBuyUSDC({ // Required as only the payment router program is allowed via coinflow. const coinflowTransaction = yield* call( createPaymentRouterRouteTransaction, - audiusBackendInstance, + sdk, { sender: rootAccount.publicKey, - splits: { - [userBank.toBase58()]: new BN(USDC(amount).value.toString()) - } + splits: [ + { + wallet: userBank, + amount: USDC(amount).value + } + ], + total: USDC(amount).value } ) const serializedTransaction = coinflowTransaction From 3b7bb5ac26926bc5d8f762ca5f3d898eb1aab928 Mon Sep 17 00:00:00 2001 From: Dharit Tantiviramanond Date: Wed, 11 Dec 2024 16:34:08 +0700 Subject: [PATCH 3/5] usdc user bank --- packages/common/src/store/buy-usdc/sagas.ts | 10 +++++----- packages/common/src/store/buy-usdc/utils.ts | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/common/src/store/buy-usdc/sagas.ts b/packages/common/src/store/buy-usdc/sagas.ts index 41d0cea60c1..b78f035d31d 100644 --- a/packages/common/src/store/buy-usdc/sagas.ts +++ b/packages/common/src/store/buy-usdc/sagas.ts @@ -238,7 +238,11 @@ function* doBuyUSDC({ const config = yield* call(getBuyUSDCRemoteConfig) const sdk = yield* getSDK() - const userBank = yield* getOrCreateUSDCUserBank() + const { currentUser: ethAddress } = yield* select(getWalletAddresses) + if (!ethAddress) { + throw new Error('User is not signed in') + } + const userBank = yield* getOrCreateUSDCUserBank(ethAddress) const rootAccount = yield* call(getRootSolanaAccount, audiusBackendInstance) try { @@ -370,10 +374,6 @@ function* doBuyUSDC({ yield* put(buyUSDCFlowSucceeded()) // Update USDC balance in store - const { currentUser: ethAddress } = yield* select(getWalletAddresses) - if (!ethAddress) { - throw new Error('User is not signed in') - } const account = yield* call(getUserbankAccountInfo, sdk, { ethAddress, mint: 'USDC' diff --git a/packages/common/src/store/buy-usdc/utils.ts b/packages/common/src/store/buy-usdc/utils.ts index 717e11075ab..86d44735615 100644 --- a/packages/common/src/store/buy-usdc/utils.ts +++ b/packages/common/src/store/buy-usdc/utils.ts @@ -11,7 +11,7 @@ import { BUY_TOKEN_VIA_SOL_SLIPPAGE_BPS } from '~/services/remote-config/defaults' -import { getAccountUser } from '../account/selectors' +import { getWalletAddresses } from '../account/selectors' import { getContext } from '../effects' import { getSDK } from '../sdkUtils' @@ -27,11 +27,11 @@ export function* getOrCreateUSDCUserBank(ethAddress?: string) { const sdk = yield* call(audiusSdk) let ethWallet = ethAddress if (!ethWallet) { - const user = yield* select(getAccountUser) - if (!user?.wallet) { + const { currentUser } = yield* select(getWalletAddresses) + if (!currentUser) { throw new Error('Failed to create USDC user bank: No user wallet found.') } - ethWallet = user.wallet + ethWallet = currentUser } const { userBank } = yield* call( [ From d239e9a9b201e258e31af567a36bdcdf26058f4d Mon Sep 17 00:00:00 2001 From: Dharit Tantiviramanond Date: Wed, 11 Dec 2024 16:44:51 +0700 Subject: [PATCH 4/5] remove unused fn --- packages/web/src/services/solana/solana.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/web/src/services/solana/solana.ts b/packages/web/src/services/solana/solana.ts index 3eed484ee23..941eb15e2ec 100644 --- a/packages/web/src/services/solana/solana.ts +++ b/packages/web/src/services/solana/solana.ts @@ -129,19 +129,6 @@ export const getAssociatedTokenAccountRent = async () => { return await getMinimumBalanceForRentExemptAccount(connection) } -/** - * Returns the associated USDC token account for the given solana account. - */ -export const getUSDCAssociatedTokenAccount = async ( - solanaRootAccountPubkey: PublicKey -) => { - const libs = await getLibs() - return getAssociatedTokenAddressSync( - libs.solanaWeb3Manager!.mints.usdc, - solanaRootAccountPubkey - ) -} - /** * Returns the owner of the token acccount, if the provided account * is a token account. Otherwise, just returns the account From d07caca403f09aff1e43d4f3bffcf273c99450d0 Mon Sep 17 00:00:00 2001 From: Dharit Tantiviramanond Date: Wed, 11 Dec 2024 16:49:31 +0700 Subject: [PATCH 5/5] mobile getUSDCUserBank --- packages/mobile/src/services/buyCrypto.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/mobile/src/services/buyCrypto.ts b/packages/mobile/src/services/buyCrypto.ts index 0ef64c9d645..8180229ec11 100644 --- a/packages/mobile/src/services/buyCrypto.ts +++ b/packages/mobile/src/services/buyCrypto.ts @@ -1,10 +1,11 @@ import { audiusLibs, waitForLibsInit } from './libs' +import { audiusSdk } from './sdk/audius-sdk' export const getUSDCUserBank = async (ethWallet?: string) => { - await waitForLibsInit() - return await audiusLibs?.solanaWeb3Manager?.deriveUserBank({ - ethAddress: ethWallet, - mint: 'usdc' + const sdk = await audiusSdk() + return await sdk.services.claimableTokensClient.deriveUserBank({ + ethWallet, + mint: 'USDC' }) }