diff --git a/lib/client/rpc/market-clients/ammMarkets.ts b/lib/client/rpc/market-clients/ammMarkets.ts index 7e9591c..79e7f57 100644 --- a/lib/client/rpc/market-clients/ammMarkets.ts +++ b/lib/client/rpc/market-clients/ammMarkets.ts @@ -224,7 +224,9 @@ export class FutarchyAmmMarketsRPCClient implements FutarchyAmmMarketsClient { this.rpcProvider.publicKey ); const tx = await ix.transaction(); - return this.transactionSender.send([tx], this.rpcProvider.connection); + return this.transactionSender.send([tx], this.rpcProvider.connection, { + customErrors: [this.ammClient.program.idl.errors] + }); } simulateAddLiquidity( @@ -300,7 +302,9 @@ export class FutarchyAmmMarketsRPCClient implements FutarchyAmmMarketsClient { new BN(minQuoteWithSlippage) ); const tx = await ix.transaction(); - return this.transactionSender?.send([tx], this.rpcProvider.connection); + return this.transactionSender?.send([tx], this.rpcProvider.connection, { + customErrors: [this.ammClient.program.idl.errors] + }); } simulateRemoveLiquidity(lpTokensToBurn: number, ammMarket: AmmMarket) { @@ -371,7 +375,9 @@ export class FutarchyAmmMarketsRPCClient implements FutarchyAmmMarketsClient { ) .transaction(); - return this.transactionSender?.send([tx], this.rpcProvider.connection); + return this.transactionSender?.send([tx], this.rpcProvider.connection, { + customErrors: [this.ammClient.program.idl.errors] + }); } async getSwapPreview( diff --git a/lib/client/rpc/market-clients/openbookMarkets.ts b/lib/client/rpc/market-clients/openbookMarkets.ts index 80e27fa..3911152 100644 --- a/lib/client/rpc/market-clients/openbookMarkets.ts +++ b/lib/client/rpc/market-clients/openbookMarkets.ts @@ -96,15 +96,15 @@ export class FutarchyOpenbookMarketsRPCClient const baseTokenWithSymbol = !baseToken.isFallback ? baseToken : { - ...baseToken, - symbol: marketName.split("/")[0] - }; + ...baseToken, + symbol: marketName.split("/")[0] + }; const quoteTokenWithSymbol = !quoteToken.isFallback ? quoteToken : { - ...quoteToken, - symbol: marketName.split("/")[0] - }; + ...quoteToken, + symbol: marketName.split("/")[0] + }; return { baseMint: obMarket.account.baseMint, @@ -295,7 +295,7 @@ export class FutarchyOpenbookMarketsRPCClient .preInstructions(openTx.instructions) .transaction(); - return this.transactionSender.send([placeTx], this.rpcProvider.connection); + return this.transactionSender.send([placeTx], this.rpcProvider.connection, { customErrors: [market.twapProgram.idl.errors] }); } async getOrCreateOpenOrdersIndexer({ @@ -403,7 +403,7 @@ export class FutarchyOpenbookMarketsRPCClient ] }; const tx = await this.cancelAndSettleFundsTransactions(order, market); - return this.transactionSender.send([tx], this.rpcProvider.connection); + return this.transactionSender.send([tx], this.rpcProvider.connection, { customErrors: [this.openbookClient.program.idl.errors] }); } private async cancelAndSettleFundsTransactions( @@ -608,7 +608,8 @@ export class FutarchyOpenbookMarketsRPCClient placeTx.add(placeIx); return this.transactionSender.send( [placeTx], - this.rpcProvider.connection + this.rpcProvider.connection, + { customErrors: [market.twapProgram.idl.errors] } ); } diff --git a/lib/client/rpc/proposals/finalizeProposal.ts b/lib/client/rpc/proposals/finalizeProposal.ts index 8ef050a..be4d8ea 100644 --- a/lib/client/rpc/proposals/finalizeProposal.ts +++ b/lib/client/rpc/proposals/finalizeProposal.ts @@ -115,7 +115,10 @@ export class FinalizeProposalClient implements FinalizeProposal { return await this.transactionSender?.send( [tx], - this.rpcProvider.connection + this.rpcProvider.connection, + { + customErrors: [autocrat.idl.errors] + } ); } @@ -138,7 +141,10 @@ export class FinalizeProposalClient implements FinalizeProposal { return await this.transactionSender?.send( [finalizeProposalTx], - this.rpcProvider.connection + this.rpcProvider.connection, + { + customErrors: [this.autocratClient.autocrat.idl.errors] + } ); } catch (e) { console.log("error", e); diff --git a/lib/client/rpc/proposals/proposals.ts b/lib/client/rpc/proposals/proposals.ts index b4b4c9d..5404937 100644 --- a/lib/client/rpc/proposals/proposals.ts +++ b/lib/client/rpc/proposals/proposals.ts @@ -204,7 +204,9 @@ export class FutarchyRPCProposalsClient implements FutarchyProposalsClient { mintConditionalsIx ]; const tx = new Transaction().add(...ixs); - return this.transactionSender.send([tx], this.rpcProvider.connection); + return this.transactionSender.send([tx], this.rpcProvider.connection, { + customErrors: [vaultAccount.protocol.vault.idl.errors] + }); } public async createProposal( @@ -270,7 +272,7 @@ export class FutarchyRPCProposalsClient implements FutarchyProposalsClient { if (programVersion == "V0.3" || programVersion == "V0.2") { const vaultForVersion = autocratVersionToConditionalVaultMap[ - proposal.protocol.deploymentVersion + proposal.protocol.deploymentVersion ]; const vaultProgram = new Program( vaultForVersion.idl, @@ -302,7 +304,10 @@ export class FutarchyRPCProposalsClient implements FutarchyProposalsClient { const resp = await this.transactionSender?.send( [mergeTx], - this.rpcProvider.connection + this.rpcProvider.connection, + { + customErrors: [vaultProgram.idl.errors] + } ); return resp; } else throw Error("Version not compatible"); @@ -351,12 +356,16 @@ export class FutarchyRPCProposalsClient implements FutarchyProposalsClient { const tx = new Transaction().add(...redeeemBaseIx).add(...redeeemQuoteIx); const resp = await this.transactionSender?.send( [tx], - this.rpcProvider.connection + this.rpcProvider.connection, + { + customErrors: [vaultProgram.idl.errors] + } + ); return resp; } // TO DO INDEXER - public async saveProposalDetails(proposalDetails: ProposalDetails) {} - public async updateProposalAccounts(accounts: ProposalAccounts) {} + public async saveProposalDetails(proposalDetails: ProposalDetails) { } + public async updateProposalAccounts(accounts: ProposalAccounts) { } } diff --git a/lib/transactions.ts b/lib/transactions.ts index 660bdc6..9a3fbd3 100644 --- a/lib/transactions.ts +++ b/lib/transactions.ts @@ -51,8 +51,7 @@ export class TransactionSender { async send( txs: SingleOrArray[], connection: Connection, - // alreadySignedTxs?: T[], - opts?: { sequential?: boolean, commitment?: Commitment, CUs?: SingleOrArray } + opts?: { sequential?: boolean, commitment?: Commitment, CUs?: SingleOrArray, customErrors?: { code: number, name: string, msg: string }[][] } ): SendTransactionResponse { if (!connection || !this.owner || !this.signAllTransactions) { throw new Error("Bad wallet connection"); @@ -68,36 +67,35 @@ export class TransactionSender { } } - const sequence = - txs[0] instanceof Array ? (txs as T[][]) : ([txs] as T[][]); - - const latestBlockhash = await connection.getLatestBlockhash(); - const timedTxs = sequence.map((set) => - set.map((e: T, i: number) => { - const tx = e; - if (!(tx instanceof VersionedTransaction)) { - tx.recentBlockhash = latestBlockhash.blockhash; - tx.feePayer = this.owner!; - // Compute limit ix & priority fee ix - const units = Array.isArray(opts?.CUs) ? opts.CUs[i] as number : opts?.CUs; - tx.instructions = [ - //MAX 1M - ComputeBudgetProgram.setComputeUnitLimit({ units: units ?? 200_000 }), - ComputeBudgetProgram.setComputeUnitPrice({ - microLamports: this.priorityFee, - }), - ...tx.instructions, - ]; - } - return tx; - }) - ); - const errors: TransactionError[] = [] const signatures: string[] = []; try { - const signedTxs = await this.signAllTransactions(timedTxs.flat()).catch(e => { errors.push({ name: e.name, message: e.message }); return }) - if (!signedTxs) return { signatures: signatures, errors: errors } + const sequence = + txs[0] instanceof Array ? (txs as T[][]) : ([txs] as T[][]); + + const latestBlockhash = await connection.getLatestBlockhash(); + const timedTxs = sequence.map((set) => + set.map((e: T, i: number) => { + const tx = e; + if (!(tx instanceof VersionedTransaction)) { + tx.recentBlockhash = latestBlockhash.blockhash; + tx.feePayer = this.owner!; + // Compute limit ix & priority fee ix + const units = Array.isArray(opts?.CUs) ? opts.CUs[i] as number : opts?.CUs; + tx.instructions = [ + //MAX 1M + ComputeBudgetProgram.setComputeUnitLimit({ units: units ?? 200_000 }), + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: this.priorityFee, + }), + ...tx.instructions, + ]; + } + return tx; + }) + ); + + const signedTxs = await this.signAllTransactions(timedTxs.flat()) // Reconstruct signed sequence let i = 0; @@ -107,15 +105,20 @@ export class TransactionSender { if (!opts?.sequential) { await Promise.all(signedSequence.map(async (set) => - await Promise.all( - set.map(async (tx) => { - return connection - .sendRawTransaction(tx.serialize(), { skipPreflight: true }) - .then((txSignature) => - connection - .confirmTransaction(txSignature, opts?.commitment ?? "confirmed") - .then(() => signatures.push(txSignature)) - ) + await Promise.all( + set.map(async (tx, index) => { + const txSignature = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: true }) + const confirmation = await connection.confirmTransaction(txSignature, opts?.commitment ?? "confirmed") + if (confirmation.value.err) { + //@ts-ignore + confirmation.value.err.InstructionError.forEach((error) => { + if (error.Custom) { + const _error: { code: number, msg: string, name: string } | undefined = opts?.customErrors?.[index]?.find(e => e.code == error.Custom) + errors.push({ message: _error?.msg || "Custom program Error, check on explorer.", name: _error?.name || "Anchor Error" }) + } + }) + } + signatures.push(txSignature) } ) ) @@ -123,18 +126,27 @@ export class TransactionSender { } else { for (const set of signedSequence) { - for (const tx of set) { + set.forEach(async (tx, index) => { const txSignature = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: true }) if (txSignature) { - const confirmation = await connection.confirmTransaction(txSignature, opts?.commitment ?? "confirmed"); - signatures.push(txSignature); // Push only on successful confirmation + const confirmation = await connection.confirmTransaction(txSignature, opts?.commitment ?? "confirmed") + if (confirmation.value.err) { + //@ts-ignore + confirmation.value.err.InstructionError.forEach((error) => { + if (error.Custom) { + const _error: { code: number, msg: string, name: string } | undefined = opts?.customErrors?.[index]?.find(e => e.code == error.Custom) + errors.push({ message: _error?.msg || "Custom program Error, check on explorer.", name: _error?.name || "Anchor Error" }) + } + }) + } + signatures.push(txSignature); } - } + }) } } } - catch (e) { - errors.push(e as any) + catch (e: any) { + errors.push({ message: e.message, name: e.name ?? "Transaction Error" }) } return { signatures: signatures,