From 2af41abee91a0c8143d9dad66f14b79e046e6893 Mon Sep 17 00:00:00 2001 From: AdityaSalunkhe21 Date: Thu, 6 Feb 2025 20:45:37 +0530 Subject: [PATCH] Implement retry transaction pattern --- .env.example | 1 + src/locker-utils/index.ts | 113 +++++++++++++++++++++----------------- src/utils/create-lock.ts | 41 ++++---------- 3 files changed, 75 insertions(+), 80 deletions(-) diff --git a/.env.example b/.env.example index 1546e0a..ea255af 100644 --- a/.env.example +++ b/.env.example @@ -25,3 +25,4 @@ WSOL_MINT_ADDRESS=So11111111111111111111111111111111111111112 # Duration in seconds that WSOL will be locked for WSOL_LOCK_DURATION_IN_SECONDS=172800 # 48 hours REWARD_MULTIPLIER=4 +MAX_RETRIES_FOR_LOCK_TX=5 diff --git a/src/locker-utils/index.ts b/src/locker-utils/index.ts index 40628c5..dd2a9cb 100644 --- a/src/locker-utils/index.ts +++ b/src/locker-utils/index.ts @@ -35,8 +35,10 @@ import { } from './token-2022/remaining-accounts'; assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL); +assert(process.env.MAX_RETRIES_FOR_LOCK_TX); const connection = new Connection(process.env.NEXT_PUBLIC_SOLANA_RPC_URL); +const MAX_RETRIES = Number(process.env.MAX_RETRIES_FOR_LOCK_TX); export function createLockerProgram(wallet: Wallet): Program { const provider = new AnchorProvider(connection, wallet, { @@ -133,64 +135,73 @@ export async function createVestingPlanV2(params: CreateVestingPlanParams) { assert(tokenProgram); - try { - await program.methods - .createVestingEscrowV2( - { - vestingStartTime, - cliffTime, - frequency, - cliffUnlockAmount, - amountPerPeriod, - numberOfPeriod, - updateRecipientMode, - cancelMode, - }, - remainingAccountsInfo - ) - .accounts({ - base: baseKP.publicKey, - senderToken, - escrowToken, - recipient, - tokenMint, - sender: ownerKeypair.publicKey, - tokenProgram, - systemProgram: web3.SystemProgram.programId, - escrow, - // TODO: Fix type error for escrowToken - } as any) - .remainingAccounts(remainingAccounts ? remainingAccounts : []) - .preInstructions([ - createAssociatedTokenAccountInstruction( - ownerKeypair.publicKey, + let attempt = 0; + + while (attempt < MAX_RETRIES) { + try { + await program.methods + .createVestingEscrowV2( + { + vestingStartTime, + cliffTime, + frequency, + cliffUnlockAmount, + amountPerPeriod, + numberOfPeriod, + updateRecipientMode, + cancelMode, + }, + remainingAccountsInfo + ) + .accounts({ + base: baseKP.publicKey, + senderToken, escrowToken, - escrow, + recipient, tokenMint, + sender: ownerKeypair.publicKey, tokenProgram, - ASSOCIATED_TOKEN_PROGRAM_ID - ), - ]) - .signers([baseKP, ownerKeypair]) - .rpc(); + systemProgram: web3.SystemProgram.programId, + escrow, + } as any) + .remainingAccounts(remainingAccounts ? remainingAccounts : []) + .preInstructions([ + createAssociatedTokenAccountInstruction( + ownerKeypair.publicKey, + escrowToken, + escrow, + tokenMint, + tokenProgram, + ASSOCIATED_TOKEN_PROGRAM_ID + ), + ]) + .signers([baseKP, ownerKeypair]) + .rpc(); - return escrow; - } catch (error) { - if (error instanceof TransactionExpiredTimeoutError) { - console.error('Transaction confirmation delayed for', error.signature); - console.log('Confirming the transaction again...'); - const confirmedTransaction = await connection.getTransaction(error.signature, { - commitment: 'confirmed', - maxSupportedTransactionVersion: 0 - }); + return escrow; // Success, return escrow - if(confirmedTransaction === null) { - console.error('Transaction failed for', error.signature); - throw error; + } catch (error) { + console.error(`Attempt ${attempt + 1} Transaction confirmation delayed`); + + // If transaction expired, check confirmation + if (error instanceof TransactionExpiredTimeoutError) { + console.log('Checking if transaction was confirmed...'); + const confirmedTransaction = await program.provider.connection.getTransaction(error.signature, { + commitment: 'confirmed', + maxSupportedTransactionVersion: 0 + }); + + if (confirmedTransaction !== null) { + return escrow; + } + } else { + console.error(error); } - return escrow; + attempt++; + console.log(`Attempt ${attempt + 1} Transaction failed, retrying...`) } - throw error; } + + throw new Error("Transaction failed after maximum retries"); } diff --git a/src/utils/create-lock.ts b/src/utils/create-lock.ts index 727fa53..1e53bd1 100644 --- a/src/utils/create-lock.ts +++ b/src/utils/create-lock.ts @@ -34,10 +34,10 @@ const provider = new anchor.AnchorProvider( anchor.setProvider(provider); -export async function createLock(tokenLockerKeypair: anchor.web3.Keypair, recipientPubKey: anchor.web3.PublicKey, duration: BN, balance: BN): Promise { +export async function createLock(tokenLockerKeypair: anchor.web3.Keypair, recipientPubKey: anchor.web3.PublicKey, duration: BN, amount: BN): Promise { - if (balance.eq(new BN(0))) { - console.log('No balance available to create lock, skipping...'); + if (amount.eq(new BN(0))) { + console.log('Invalid Amount'); return; } @@ -50,7 +50,7 @@ export async function createLock(tokenLockerKeypair: anchor.web3.Keypair, recipi isAssertion: true, cliffTime: duration, frequency: new BN(1), // Not needed since full unlock happens at cliff - cliffUnlockAmount: balance, // The entire amount should be released at cliff + cliffUnlockAmount: amount, // The entire amount should be released at cliff amountPerPeriod: new BN(0), // No tokens should be released before cliff numberOfPeriod: new BN(1), // Only release tokens once recipient: recipientPubKey, @@ -60,47 +60,30 @@ export async function createLock(tokenLockerKeypair: anchor.web3.Keypair, recipi }); if (escrow) { - console.log('Lock created successfully:',escrow.toString()); + console.log('Lock created successfully:', escrow.toString()); } return escrow; } -export async function extractInfo(transactionSignature: string) { - const transaction = await connection.getParsedTransaction(transactionSignature, 'confirmed'); - if (!transaction) { - throw new Error('Transaction not found'); - } - - const transferInstruction = transaction.transaction.message.instructions.find( - (instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID) - ); - - if (!transferInstruction || !('parsed' in transferInstruction)) { - throw new Error('Transfer instruction not found'); - } - - const { info: { amount, authority } } = transferInstruction.parsed; - return { authority, amount }; -} - -export async function createRewardLock(authority: string, amount: string) { +export async function createRewardLock(recipient: string, txMtmAmount: string) { const { WSOL_LOCKER_ACCOUNT_PK, WSOL_LOCK_DURATION_IN_SECONDS, WSOL_MINT_ADDRESS, NEXT_PUBLIC_MTM_MINT_ADDRESS, REWARD_MULTIPLIER } = process.env; + if (!WSOL_LOCKER_ACCOUNT_PK || !WSOL_LOCK_DURATION_IN_SECONDS || !WSOL_MINT_ADDRESS || !NEXT_PUBLIC_MTM_MINT_ADDRESS || !REWARD_MULTIPLIER) { - throw new Error('Missing required environment variables'); + throw new Error('Missing required environment variables for creating reward wSOL lock'); } - const duration = new BN(WSOL_LOCK_DURATION_IN_SECONDS).add(new BN(Math.floor(Date.now() / 1000))); + const lockDuration = new BN(WSOL_LOCK_DURATION_IN_SECONDS).add(new BN(Math.floor(Date.now() / 1000))); const tokenLockerKeypair = Keypair.fromSecretKey(bs58.decode(WSOL_LOCKER_ACCOUNT_PK)); - const recipientPublicKey = new PublicKey(authority); + const recipientPublicKey = new PublicKey(recipient); const url = `https://api.jup.ag/price/v2?ids=${NEXT_PUBLIC_MTM_MINT_ADDRESS}&vsToken=${WSOL_MINT_ADDRESS}`; const response = await fetch(url); const { data } = await response.json(); const priceWSOLFor1MTM = new Big(data[NEXT_PUBLIC_MTM_MINT_ADDRESS].price).toFixed(9); - const mtmAmount = new Big(amount).div(new Big(10).pow(6)); + const mtmAmount = new Big(txMtmAmount).div(new Big(10).pow(6)); const wsolAmount = new BN(new Big(mtmAmount).times(priceWSOLFor1MTM).times(new Big(10).pow(9)).times(REWARD_MULTIPLIER).toFixed(0)); - return createLock(tokenLockerKeypair, recipientPublicKey, duration, wsolAmount); + return createLock(tokenLockerKeypair, recipientPublicKey, lockDuration, wsolAmount); } -- 2.45.2