Skip to content

Commit

Permalink
[PAY-2389] Perform recoveries via payment router to allow indexing (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
schottra committed Jan 24, 2024
1 parent 0ae5f3d commit c570329
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 9 deletions.
50 changes: 50 additions & 0 deletions packages/common/src/services/audius-backend/solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const DEFAULT_RETRY_DELAY = 1000
const DEFAULT_MAX_RETRY_COUNT = 120

const PLACEHOLDER_SIGNATURE = Buffer.from(new Array(64).fill(0))
const RECOVERY_MEMO_STRING = 'recovery'

/**
* Memo program V1
Expand Down Expand Up @@ -416,6 +417,55 @@ export const findAssociatedTokenAddress = async (
).solanaWeb3Manager!.findAssociatedTokenAddress(solanaAddress, mint)
}

export const createRootWalletRecoveryTransaction = async (
audiusBackendInstance: AudiusBackend,
{
userBank,
wallet,
amount,
feePayer
}: {
userBank: PublicKey
wallet: Keypair
amount: bigint
feePayer?: PublicKey
usePaymentRouter?: boolean
}
) => {
const libs = await audiusBackendInstance.getAudiusLibsTyped()
const solanaWeb3Manager = libs.solanaWeb3Manager!

// See: https://github.com/solana-labs/solana-program-library/blob/d6297495ea4dcc1bd48f3efdd6e3bbdaef25a495/memo/js/src/index.ts#L27
const memoInstruction = new TransactionInstruction({
keys: [
{
pubkey: wallet.publicKey,
isSigner: true,
isWritable: true
}
],
programId: MEMO_PROGRAM_ID,
data: Buffer.from(RECOVERY_MEMO_STRING)
})

const [transferInstruction, routeInstruction] =
// All the memo related parameters are ignored
await solanaWeb3Manager.getPurchaseContentWithPaymentRouterInstructions({
id: 0, // ignored
type: 'track', // ignored
blocknumber: 0, // ignored
splits: { [userBank.toString()]: new BN(amount.toString()) },
purchaserUserId: 0, // ignored
senderAccount: wallet.publicKey
})

const recentBlockhash = await getRecentBlockhash(audiusBackendInstance)

const tx = new Transaction({ recentBlockhash, feePayer })
tx.add(memoInstruction, transferInstruction, routeInstruction)
return tx
}

export const createTransferToUserBankTransaction = async (
audiusBackendInstance: AudiusBackend,
{
Expand Down
66 changes: 57 additions & 9 deletions packages/common/src/store/buy-usdc/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PurchaseVendor } from 'models/PurchaseContent'
import { Status } from 'models/Status'
import {
createPaymentRouterRouteTransaction,
createRootWalletRecoveryTransaction,
createTransferToUserBankTransaction,
findAssociatedTokenAddress,
getRecentBlockhash,
Expand Down Expand Up @@ -54,6 +55,9 @@ type PurchaseStepParams = {
maxRetryCount?: number
}

const TRANSACTION_RETRY_COUNT = 3
const TRANSACTION_RETRY_DELAY_MS = 1000

function* purchaseStep({
desiredAmount,
wallet,
Expand Down Expand Up @@ -150,14 +154,17 @@ function* transferStep({
wallet,
userBank,
amount,
maxRetryCount = 3,
retryDelayMs = 1000
maxRetryCount = TRANSACTION_RETRY_COUNT,
retryDelayMs = TRANSACTION_RETRY_DELAY_MS,
memo
}: {
wallet: Keypair
userBank: PublicKey
amount: bigint
maxRetryCount?: number
retryDelayMs?: number
usePaymentRouter?: boolean
memo: string
}) {
const audiusBackendInstance = yield* getContext('audiusBackendInstance')
const feePayer = yield* select(getFeePayer)
Expand All @@ -177,7 +184,7 @@ function* transferStep({
userBank,
mint: 'usdc',
amount,
memo: 'In-App $USDC Purchase: Link by Stripe',
memo,
feePayer: feePayerOverride,
recentBlockhash
}
Expand Down Expand Up @@ -290,6 +297,7 @@ function* doBuyUSDC({
yield* call(transferStep, {
wallet: rootAccount,
userBank,
memo: 'In-App $USDC Purchase: Link by Stripe',
amount: newBalance
})
break
Expand Down Expand Up @@ -393,7 +401,8 @@ function* recoverPurchaseIfNecessary() {
const audiusBackendInstance = yield* getContext('audiusBackendInstance')

try {
yield* call(waitForValue, getFeePayer)
const feePayerString: string = yield* call(waitForValue, getFeePayer)
const feePayerKey = new PublicKey(feePayerString)
const userBank = yield* getUSDCUserBank()
const rootAccount = yield* call(getRootSolanaAccount, audiusBackendInstance)

Expand Down Expand Up @@ -426,11 +435,50 @@ function* recoverPurchaseIfNecessary() {
userBank: userBankAddress
})
)
yield* call(transferStep, {
wallet: rootAccount,
userBank,
amount
})

yield* call(
retry,
async () => {
const transferTransaction = await createRootWalletRecoveryTransaction(
audiusBackendInstance,
{
wallet: rootAccount,
userBank,
amount,
feePayer: feePayerKey
}
)
transferTransaction.partialSign(rootAccount)

console.debug(`Starting root wallet USDC recovery transaction...`)
const { res, error } = await relayTransaction(audiusBackendInstance, {
transaction: transferTransaction
})

if (res) {
console.debug(`Recovery transaction succeeded: ${res}`)
return res
} else {
console.debug(
`Transfer transaction stringified: ${JSON.stringify(
transferTransaction
)}`
)
// Throw to retry
throw new Error(error ?? 'Unknown root wallet USDC recovery error')
}
},
{
minTimeout: TRANSACTION_RETRY_DELAY_MS,
retries: TRANSACTION_RETRY_COUNT,
factor: 1,
onRetry: (e: Error, attempt: number) => {
console.error(
`Got error recovering USDC from root wallet to user bank: ${e}. Attempt ${attempt}. Retrying...`
)
}
}
)

yield* put(recoveryStatusChanged({ status: Status.SUCCESS }))
yield* call(
Expand Down

0 comments on commit c570329

Please sign in to comment.