Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add custom program errors #130

Merged
merged 1 commit into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions lib/client/rpc/market-clients/ammMarkets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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(
Expand Down
19 changes: 10 additions & 9 deletions lib/client/rpc/market-clients/openbookMarkets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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] }
);
}

Expand Down
10 changes: 8 additions & 2 deletions lib/client/rpc/proposals/finalizeProposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
);
}

Expand All @@ -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);
Expand Down
21 changes: 15 additions & 6 deletions lib/client/rpc/proposals/proposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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) { }
}
100 changes: 56 additions & 44 deletions lib/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ export class TransactionSender {
async send<T extends Transaction | VersionedTransaction>(
txs: SingleOrArray<T>[],
connection: Connection,
// alreadySignedTxs?: T[],
opts?: { sequential?: boolean, commitment?: Commitment, CUs?: SingleOrArray<number> }
opts?: { sequential?: boolean, commitment?: Commitment, CUs?: SingleOrArray<number>, customErrors?: { code: number, name: string, msg: string }[][] }
): SendTransactionResponse {
if (!connection || !this.owner || !this.signAllTransactions) {
throw new Error("Bad wallet connection");
Expand All @@ -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;
Expand All @@ -107,34 +105,48 @@ 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)
}
)
)
));
}
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,
Expand Down
Loading