Last active
May 13, 2024 16:34
-
-
Save NotoriousPyro/7c3545bbd6f9046f4becd278470fb7e3 to your computer and use it in GitHub Desktop.
Error handling with Anchor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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