Skip to content

Instantly share code, notes, and snippets.

@NotoriousPyro
Last active May 13, 2024 16:34
Show Gist options
  • Select an option

  • Save NotoriousPyro/7c3545bbd6f9046f4becd278470fb7e3 to your computer and use it in GitHub Desktop.

Select an option

Save NotoriousPyro/7c3545bbd6f9046f4becd278470fb7e3 to your computer and use it in GitHub Desktop.
Error handling with Anchor
// Here is a portion of the code used in sexbot.sol that handles errors when sending txs
/**
* Anchor can't handle all errors for some reason, so this exists to handle those cases
* @param logs
* @returns
*/
export const extractErrorDetailFromLogs = (
logs: string[]
): {
programId: string,
logs: string[]
} => {
const failedIndex = logs.findLastIndex(log => log.search(/Program (.*) failed/) !== -1)
const startInvokeIndex = logs.findLastIndex(log => log.search(/Program (.*) invoke/) !== -1);
const programId = logs[startInvokeIndex]?.match(/Program (.*) invoke/)?.[1] ||
logs[failedIndex]?.match(/Program (.*) failed/)?.[1];
const startSuccessIndex = logs.findLastIndex(log => log.search(/Program (.*) success/) !== -1);
const logsFiltered = startSuccessIndex !== -1
? logs.slice(startSuccessIndex, failedIndex + 1)
: startInvokeIndex !== -1
? logs.slice(startInvokeIndex, failedIndex + 1)
: logs.slice(-5)
;
return {
programId: programId || "unknown",
logs: logsFiltered
};
}
try {
yourOtherCode();
const tx = new VersionedTransaction(messageV0);
tx.sign(signers);
const signature = await connection.sendTransaction(tx, {
maxRetries: config.maxTxTries,
skipPreflight,
});
await confirmTransaction(signature).then(
async (logs) => {
logger.log("Confirmed", logs.signature);
}
);
} catch (e) {
if (e instanceof ConfirmTransactionFailureError) {
if (debug) {
if (printQuoteData) {
logger.warn("Failed to confirm transaction", e.message, JSON.stringify({ swapInfoA, swapInfoB }, null, 2));
return;
}
logger.warn("Failed to confirm transaction", e.message);
return;
}
return;
}
if (typeof e.message === "string") {
if (e.message.search(/is not a function/) !== -1) {
throw new Error("Failed to create transaction", e);
}
else if (e.message.search(/(VersionedTransaction too large|encoding overruns Uint8Array|Transaction locked too many accounts)/) !== -1) {
const msg = `Transaction too large`;
if (debug) {
if (printQuoteData) {
logger.warn(msg, e, JSON.stringify({ swapInfoA, swapInfoB }, null, 2));
return;
}
logger.warn(msg, e);
return;
}
logger.warn(msg, e.message);
return;
}
else if (e.message.search(/Blockhash not found/) !== -1) {
logger.error("Blockhash was not found! Reloading account info for nonce account", nonce.noncePubkey, e.message);
doSomething();
return;
}
}
if (e.logs) {
try {
// Because anchor can't handle all errors, we have to try anchor first, then use some manual handling
const anchorError = AnchorError.parse(e.logs);
const programName = programIdsToString.get(anchorError?.program?.toString());
if (programName) {
switch (programName) { // Until specific error handling cases arise, like Meteora DLMM, we will group all of the ones we ignore together
case "Jupiter":
switch (anchorError.error.errorCode.code) {
case JupiterErrors.SlippageToleranceExceeded:
if (debug) {
logger.error(`Failed to swap due to SlippageToleranceExceeded`, anchorError?.logs || e.logs);
}
return;
}
break;
case "Whirlpool":
switch (anchorError.error.errorCode.code) {
case WhirlpoolErrors.InvalidTickArraySequence:
case WhirlpoolErrors.TickArraySequenceInvalidIndex:
doSomething();
return;
}
break;
case "Raydium CLMM":
switch (anchorError.error.errorCode.code) {
case RaydiumClmmErrors.LiquidityInsufficient:
case RaydiumClmmErrors.InvalidFirstTickArrayAccount:
case RaydiumClmmErrors.NotEnoughTickArrayAccount:
doSomething();
return;
}
break;
case "Lifinity V2":
switch (anchorError.error.errorCode.code) {
case LifinityV2Errors.InvalidPythPrice:
doSomething();
return;
}
break;
case "Crema":
switch (anchorError.error.errorCode.code) {
case CremaErrors.InvalidTickArrayAccount:
doSomething();
return;
}
break;
case "Meteora DLMM":
switch (anchorError.error.errorCode.code) {
case AccountErrors.AccountNotEnoughKeys:
if (anchorError.error.origin === "bin_array") {
doSomething();
return; // Can be ignored, price change related: https://discord.com/channels/841152225564950528/864859354335412224/1200605336856444949
}
break;
case MeteoraDLMMErrors.BitmapExtensionAccountIsNotProvided:
return; // not sure of cause but not our fault, it seems
default:
break;
}
}
}
else {
const { programId, logs } = extractErrorDetailFromLogs(e.logs);
const programName = programIdsToString.get(programId) || "unknown";
switch (programName) {
case "Invariant": // https://github.com/invariant-labs/protocol/blob/20e0d7e37e37ad57b412eb9879eb99746d0ca60a/docs/docs/solana/invariant_errors.md
if (logs.find(log => log.search(/0x1777/) !== -1)) { // TICK_NOT_FOUND
doSomething();
return;
}
break;
case "Openbook": // Openbook bases their exceptions on line numbers so if they ever change the code, then this won't work
if (logs.find(log => log.search(/0x10000a6/) !== -1)) { // InvalidMarketFlags
doSomething();
return;
}
break;
case "Perps":
if (logs.find(log => log.search(/0x1773/) !== -1)) { // 6003: https://github.com/solana-labs/perpetuals/blob/master/programs/perpetuals/src/error.rs#L14
doSomething();
return;
}
break;
case "Jupiter":
if (logs.find(log => log.search(/range end index 72 out of range for slice of length 0/) !== -1)) {
logger.error(`Failed swap... likely due to missing ATAs!!!`, JSON.stringify({ routeMintsDeduped, newTokenMints: unseenAtaMintPrograms, knownMints: seenAtaMintPrograms, mintIdempotentInstructions }, null, 2));
return;
}
break;
case "Associated Token Account Program":
if (logs.find(log => log.search(/Associated address does not match seed derivation/) !== -1)) {
logger.error(`Failed swap due to Associated address does not match seed derivation. Probably the ATA was attempted to be created with the wrong seed.`, JSON.stringify({ routeMintsDeduped, newTokenMints: unseenAtaMintPrograms, knownMints: seenAtaMintPrograms, mintIdempotentInstructions }, null, 2));
return;
}
if (logs.find(log => log.search(/IncorrectProgramId/) !== -1)) {
logger.error(`Failed swap due to IncorrectProgramId. Probably the ATA was attempted to be created with the wrong program ID.`, JSON.stringify({ routeMintsDeduped, newTokenMints: unseenAtaMintPrograms, knownMints: seenAtaMintPrograms, mintIdempotentInstructions }, null, 2));
return;
}
break;
case "unknown":
if (logs.find(logs => logs.search(/InvalidTickArraySequence/) !== -1)) {
doSomething();
}
}
const msg = `Ledger Error [program: ${programName}]`;
if (debug) {
if (printQuoteData) {
logger.warn(msg, e.logs || e, JSON.stringify({ swapInfoA, swapInfoB }, null, 2));
return;
}
logger.warn(msg, e.logs || e);
return;
}
logger.warn(msg, logs);
return;
}
const msg = `Anchor Error [program: ${programName}]`;
if (debug) {
if (printQuoteData) {
logger.warn(msg, anchorError?.stack || e, JSON.stringify({ swapInfoA, swapInfoB }, null, 2));
return;
}
logger.warn(msg, anchorError?.stack || e);
return;
}
logger.warn(msg, e.logs?.slice(-5) || anchorError?.error || e.message || e);
return;
} catch (e) {
if (debug) {
if (printQuoteData) {
logger.error(`Failed to parse anchor error`, e, JSON.stringify({ swapInfoA, swapInfoB }, null, 2));
return;
}
logger.error(`Failed to parse anchor error`, e);
return;
}
logger.error(`Failed to parse anchor error`, e.message);
return;
}
}
if (debug) {
if (printQuoteData) {
logger.error(`Failed`, e, JSON.stringify({ swapInfoA, swapInfoB }, null, 2));
return;
}
logger.error(`Failed`, e);
return;
}
logger.error(`Failed`, e.message);
return;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment