Implement retry of lock transaction #16

Merged
nabarun merged 1 commits from as-retry-tx into main 2025-02-07 06:06:38 +00:00
3 changed files with 75 additions and 80 deletions

View File

@ -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

View File

@ -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<Locker> {
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");
}

View File

@ -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<anchor.web3.PublicKey | undefined> {
export async function createLock(tokenLockerKeypair: anchor.web3.Keypair, recipientPubKey: anchor.web3.PublicKey, duration: BN, amount: BN): Promise<anchor.web3.PublicKey | void> {
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);
}