├── .eslintrc ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── Debridge_solana_tx_parser_Whitebox_Pentest_Report_Halborn_Final.pdf ├── LICENSE ├── README.md ├── cli └── convert.idl.ts ├── docs ├── .nojekyll ├── README.md ├── classes │ └── SolanaParser.md └── interfaces │ ├── ParsedAccount.md │ ├── ParsedCustomInstruction.md │ ├── ParsedIdlInstruction.md │ └── ProgramInfoType.md ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── camelcase.ts ├── decoders │ ├── associated.ts │ ├── compute.budget.ts │ ├── index.ts │ ├── system.ts │ ├── token.ts │ └── token22.ts ├── helpers.ts ├── index.ts ├── interfaces.ts ├── legacy.idl.converter.ts ├── parsers.ts └── programs │ ├── ata.ts │ ├── compute.budget.ts │ ├── index.ts │ ├── spl-token-22.program.ts │ └── token-extensions │ ├── get-account-data-size-eztension.ts │ ├── index.ts │ ├── token-metadata-extension.ts │ └── transfer-fee-extension.ts ├── tests ├── customParser.test.ts ├── idl │ ├── dst.ts │ ├── jupiter_v2.ts │ ├── jupiter_v6.ts │ └── src.ts ├── parseComputeBudget.test.ts ├── parseDlnDstTransaction.test.ts ├── parseDlnSrcTransaction.test.ts ├── parseIx.test.ts ├── parseLogs.test.ts ├── parseNativeTransaction.test.ts ├── parseSystemTransaction.test.ts └── parseTransaction.test.ts ├── tsconfig.base.json ├── tsconfig.cjs.json ├── tsconfig.esm.json └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true, 6 | "es2020": true 7 | }, 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "ecmaVersion": 2020, 11 | "sourceType": "module", 12 | "project": "./tsconfig.json" 13 | }, 14 | "settings": { 15 | }, 16 | "globals": { 17 | "Atomics": "readonly", 18 | "SharedArrayBuffer": "readonly" 19 | }, 20 | "plugins": ["@typescript-eslint", "import"], 21 | "extends": [ 22 | "airbnb-typescript/base", 23 | "eslint:recommended", 24 | "plugin:@typescript-eslint/eslint-recommended", 25 | "plugin:@typescript-eslint/recommended", 26 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 27 | "plugin:import/typescript", 28 | "plugin:prettier/recommended", 29 | "prettier", 30 | "eslint-config-prettier" 31 | ], 32 | "rules": { 33 | "prettier/prettier": "error", 34 | "max-len": "off", 35 | "spaced-comment": "off", 36 | "no-console": "warn", 37 | "indent": "off", 38 | "@typescript-eslint/explicit-module-boundary-types": "off", 39 | "@typescript-eslint/no-use-before-define": "off", 40 | "@typescript-eslint/no-empty-interface": "off", 41 | "@typescript-eslint/no-var-requires": 0, 42 | "newline-before-return": 1, 43 | "import/prefer-default-export": "off", 44 | "import/no-extraneous-dependencies": "off", 45 | "import/order": [ 46 | 2, 47 | { 48 | "groups": ["builtin", "external", "internal", "parent", ["sibling", "index"]], 49 | "newlines-between": "always" 50 | } 51 | ], 52 | "no-unused-vars": "off", 53 | "prefer-const": [ 54 | "error", 55 | { 56 | "destructuring": "all" 57 | } 58 | ], 59 | "linebreak-style": 0, 60 | "@typescript-eslint/explicit-function-return-type": [ 61 | "off", 62 | { 63 | "allowExpressions": true, 64 | "allowTypedFunctionExpressions": true, 65 | "allowHigherOrderFunctions": true, 66 | "allowConciseArrowFunctionExpressionsStartingWithVoid": true 67 | } 68 | ], 69 | "@typescript-eslint/typedef": [ 70 | "error", 71 | { 72 | "arrayDestructuring": false, 73 | "objectDestructuring": false, 74 | "arrowParameter": false, 75 | "memberVariableDeclaration": true, 76 | "parameter": true, 77 | "propertyDeclaration": true, 78 | "variableDeclaration": false, 79 | "variableDeclarationIgnoreFunction": false 80 | } 81 | ], 82 | "@typescript-eslint/no-inferrable-types": "off", 83 | "@typescript-eslint/space-before-function-paren": "off", 84 | "@typescript-eslint/semi": "off", 85 | "@typescript-eslint/no-extra-semi": "off", 86 | "@typescript-eslint/keyword-spacing": "off", 87 | "@typescript-eslint/indent": "off", 88 | "@typescript-eslint/func-call-spacing": "off", 89 | "@typescript-eslint/comma-spacing": "off", 90 | "@typescript-eslint/brace-style": "off", 91 | "@typescript-eslint/quotes": "off", 92 | "arrow-parens": "off", 93 | "function-paren-newline": "off", 94 | "implicit-arrow-linebreak": "off", 95 | "object-curly-newline": "off", 96 | "object-property-newline": "off", 97 | "rest-spread-spacing": "off", 98 | "semi": "off", 99 | "space-before-blocks": "off", 100 | "space-before-function-paren": "off", 101 | "arrow-body-style": ["error", "as-needed"], 102 | "@typescript-eslint/no-unused-vars": [ 103 | "warn", 104 | { 105 | "args": "none", 106 | "ignoreRestSiblings": true 107 | } 108 | ], 109 | "@typescript-eslint/no-misused-promises": [ 110 | "error", 111 | { 112 | "checksVoidReturn": false 113 | } 114 | ] 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | 1. Tx you're trying to parse in base64 15 | 2. IDL (optional) 16 | 3. code (if custom parser is used) 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Logs** 22 | If applicable, add logs to help explain your problem. 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .rollup.cache 2 | .rollup.cache/**/* 3 | node_modules/ 4 | package-lock.json 5 | yarn.lock 6 | .eslintcache 7 | dist 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 160, 3 | "tabWidth": 4, 4 | "useTabs": true, 5 | "semi": true, 6 | "singleQuote": false, 7 | "arrowParens": "always", 8 | "trailingComma": "all", 9 | "endOfLine": "auto", 10 | "bracketSpacing": true 11 | } 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v3.3.0 2 | - Add idl parsers cache 3 | - Add anchor event ixs parsing 4 | 5 | ## v3.2.3 6 | - Fix token22 authority type map 7 | 8 | ## v3.2.2 9 | - Fix ComputeBudget.setLoadedAccountsDataSizeLimit parsing 10 | 11 | ## v3.2.0 12 | - Export synthetic idls from idl namespace (e.g. `idl.idl.SplTokenIdl`, ixs can be decoded as `ParsedIdlInstruction`) 13 | - Split decoders into separate files 14 | - Add computeBudget parser 15 | 16 | ## v3.0.0 17 | - Add support of token22 out of the box 18 | - Rework logs parsing, parse known error types for token & system programs 19 | - ParsedIdlInstruction -> accounts is strictly typed now. Idl accounts are being converted into flat accounts list 20 | - parseTransactionDump supports versioned transactions 21 | 22 | ### Breaking changes 23 | - Parser requires IDL generated by anchor 0.30.1 (converter from legacy IDLs included) 24 | - parseTransactionDump is async now 25 | 26 | ## v2.0.0 27 | 28 | - Update packages 29 | - Migrate from @project-serum/anchor -> @coral-xyz/anchor 30 | 31 | ### Breaking changes 32 | - Token instruction `setAuthority`, arg `authorityType` changed from `@solana/spl-token AuthorityType` to IDL enum 33 | - Token instruction `initializeAccount3`, arg `authority` renamed to `owner` 34 | - Token instruction `initializeAccount2`, arg `authority` renamed to `owner` -------------------------------------------------------------------------------- /Debridge_solana_tx_parser_Whitebox_Pentest_Report_Halborn_Final.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debridge-finance/solana-tx-parser-public/0957513f49147fb237533cd8be44e0cddd9f0d5c/Debridge_solana_tx_parser_Whitebox_Pentest_Report_Halborn_Final.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [](https://github.com/debridge-finance/debridge-security/blob/master/Debridge_solana_tx_parser_Whitebox_Pentest_Report_Halborn_Final.pdf) 3 | 4 | ## Generated docs 5 | [Generated module docs](/docs/README.md) 6 | 7 | 8 | ## Table of contents 9 | * [Installation](#installation) 10 | * [What this tool can be used for](#what-this-tool-can-be-used-for) 11 | * [How To Use](#how-to-use) 12 | + [Parse by IDL](#parse-by-idl) 13 | + [Parse with custom parser](#parse-with-custom-parser) 14 | - [More details](#more-details) 15 | * [Instruction deserialization](#instruction-deserialization) 16 | * [CPI flattening](#cpi-flattening) 17 | * [Parsing transaction logs](#parsing-transaction-logs) 18 | * [Using everything together](#using-everything-together) 19 | 20 | ### Installation 21 | - Via NPM: 22 | `npm i @debridge-finance/solana-transaction-parser` 23 | 24 | - Via github: 25 | `npm i git+https://github.com/debridge-finance/solana-tx-parser-public`. 26 | 27 | - Manual package.json edit: 28 | dependencies/devDependencies section -> 29 | `"@debridge-finance/solana-transaction-parser": "github:debridge-finance/solana-tx-parser-public"` 30 | Then run `npm i` 31 | 32 | ### What this tool can be used for 33 | - Parse solana instructions using anchor IDL/custom parsers. 34 | - Parse `SystemProgram`, `TokenProgram`, `AssociatedTokenProgram` instructions out of the box. 35 | - Parse transaction logs. 36 | - Convert ParsedTransaction/CompiledTransaction/base64-encoded transaction into a list of TransactionInstructions and parse it. 37 | - Unfold transaction with CPI into artificial transaction with CPI calls included as usual TransactionInstruction. 38 | 39 | ### How To Use 40 | #### Parse by IDL 41 | First step: init parser 42 | ```ts 43 | import { Idl } from "@project-serum/anchor"; 44 | import { PublicKey, Connection } from "@solana/web3.js"; 45 | import { SolanaParser } from "@debridge-finance/solana-transaction-parser"; 46 | import { IDL as JupiterIdl, Jupiter } from "./idl/jupiter"; // idl and types file generated by Anchor 47 | 48 | const rpcConnection = new Connection("https://jupiter.genesysgo.net"); 49 | const txParser = new SolanaParser([{ idl: JupiterIdl as unknown as Idl, programId: "JUP2jxvXaqu7NQY1GmNF4m1vodw12LVXYxbFL2uJvfo" }]); 50 | ``` 51 | Second step: parse transaction by tx hash: 52 | ```ts 53 | const parsed = await txParser.parseTransaction( 54 | rpcConnection, 55 | "5zgvxQjV6BisU8SfahqasBZGfXy5HJ3YxYseMBG7VbR4iypDdtdymvE1jmEMG7G39bdVBaHhLYUHUejSTtuZEpEj", 56 | false, 57 | ); 58 | ``` 59 | Voila! We have a list of TransactionInstruction with instruction names, args and accounts. 60 | ```ts 61 | // we can find instruction by name 62 | const tokenSwapIx = parsed?.find((pix) => pix.name === "tokenSwap"); 63 | // or just use index 64 | const setTokenLedgerIx = parsed[0] as ParsedIdlInstruction; 65 | ``` 66 | #### Parse with custom parser 67 | What if the instructions we want to parse do not belong to Anchor program? 68 | We could provide custom instruction parser to SolanaParser! 69 | Custom parser need to have following interface: `(instruction: TransactionInstruction): ParsedCustomInstruction`. 70 | We've implemented a small program for testing purposes which can perform two actions: sum up two numbers (passed as u64 numbers) OR 71 | print provided data to log (passed as ASCII codes and as a second account in accounts list). 72 | 73 | | First byte | Action | 74 | | ---------- | -------------------------------------------------------------------------------- | 75 | | 0 | Print remaining data as text + string "From: " + second account pubkey as base58 | 76 | | 1 | Sum up two numbers and print the result to log | 77 | 78 | Parser will be really simple in such case: 79 | ```ts 80 | function customParser(instruction: TransactionInstruction): ParsedCustomInstruction { 81 | let args: unknown; 82 | let keys: ParsedAccount[]; 83 | let name: string; 84 | switch (instruction.data[0]) { 85 | case 0: 86 | args = { message: instruction.data.slice(1).toString("utf8") }; 87 | keys = [instruction.keys[0], { name: "messageFrom", ...instruction.keys[1] }]; 88 | name = "echo"; 89 | break; 90 | case 1: 91 | args = { a: instruction.data.readBigInt64LE(1), b: instruction.data.readBigInt64LE(9) }; 92 | keys = instruction.keys; 93 | name = "sum"; 94 | break; 95 | default: 96 | throw new Error("unknown instruction!"); 97 | } 98 | 99 | return { 100 | programId: instruction.programId, 101 | accounts: keys, 102 | args, 103 | name, 104 | }; 105 | } 106 | ``` 107 | Now we need to init SolanaParser object (which contains different parsers that can parse instructions from specified programs) 108 | ```ts 109 | const parser = new SolanaParser([]); 110 | parser.addParser(new PublicKey("5wZA8owNKtmfWGBc7rocEXBvTBxMtbpVpkivXNKXNuCV"), customParser); 111 | ``` 112 | 113 | *UPD: Since solana devnet transactions become unavailable after some time, here's code that can be used to emit test transactions* 114 | ```ts 115 | import { Connection, Keypair, PublicKey, Transaction, TransactionInstruction, LAMPORTS_PER_SOL } from "@solana/web3.js"; 116 | import { Wallet } from "@project-serum/anchor"; 117 | 118 | function echoIx(msg: string, from: PublicKey) { 119 | return new TransactionInstruction({ 120 | programId: new PublicKey("5wZA8owNKtmfWGBc7rocEXBvTBxMtbpVpkivXNKXNuCV"), 121 | data: Buffer.concat([Buffer.from([0]), Buffer.from(msg)]), 122 | keys: [ 123 | { isSigner: true, isWritable: false, pubkey: from }, 124 | { isSigner: false, isWritable: false, pubkey: new Keypair().publicKey }, 125 | ], 126 | }); 127 | } 128 | 129 | function addIx(a: bigint, b: bigint, signer: PublicKey) { 130 | const data: Buffer = Buffer.alloc(2 * 8 + 1); 131 | data[0] = 1; 132 | data.writeBigInt64LE(a, 1); 133 | data.writeBigInt64LE(b, 9); 134 | 135 | return new TransactionInstruction({ 136 | programId: new PublicKey("5wZA8owNKtmfWGBc7rocEXBvTBxMtbpVpkivXNKXNuCV"), 137 | data, 138 | keys: [ 139 | { isSigner: true, isWritable: false, pubkey: signer }, 140 | { isSigner: false, isWritable: false, pubkey: new Keypair().publicKey }, 141 | ], 142 | }); 143 | } 144 | 145 | const tx = new Transaction().add(echoIx("some test msg", kp.publicKey), addIx(555n, 1234n, kp.publicKey)); 146 | // send tx using devnet connection and your wallet 147 | ``` 148 | 149 | To parse transaction which contains this instructions we only need to call `parseTransaction` method on parser object 150 | ```ts 151 | const connection = new Connection(clusterApiUrl("devnet")); 152 | const parsed = await parser.parseTransaction(connection, "5xEeZMdrWVG7i8Fbcbu718FtbgSbXsK9c4GPBv21W2e35vP4DdkVghqH4p8dPKdmroUzNe2mBkctm4RAxaVbo78G"); 153 | // check if no errors was produced during parsing 154 | if (!parsed) throw new Error("failed to get tx/parse!"); 155 | console.log(parsed[0].name); // will print "echo" 156 | ``` 157 | 158 | ## More details 159 | 160 | Main functions of this project are: 161 | - instruction deserialization 162 | - *flattening* transactions with CPI calls 163 | - parsing transaction logs 164 | 165 | ### Instruction deserialization 166 | This part is pretty simple: we have some `const mapping = Map` where keys are program ids and values are parsers for corresponding programId - function that takes TransactionInstruction as input and returns ParsedInstruction (deserialized data, instruction name and accounts meta [with names]). 167 | We can deserialize **TransactionInstruction** using Anchor's IDL or custom parser, hence we can deserialize everything which consists of (or can be converted into) **TransactionInstructions**: Transaction, ParsedTransaction, TransactionResponse, ParsedConfirmedTransaction, Message, CompiledMessage, CompiledInstruction or wire transaction, etc. 168 | Steps of parsing are following: 169 | 1. Convert input into TransactionInstruction, lets name it **ix** (different functions need to be called for different input formats) 170 | 2. Find `ix.programId` in the mapping. If parser exists pass **ix** to it 171 | 3. Check parsed instruction name, set correct data types using generics 172 | 173 | ### CPI flattening 174 | Function: [flattenTransactionResponse](./src/helpers.ts#L87) 175 | Can be only done with TransactionResponse/ParsedTransactionWithMeta objects because we need `transaction.meta.innerInstructions` field. 176 | `transaction.meta.innerInstructions` is a list of objects of following structure: 177 | ```ts 178 | export type CompiledInnerInstruction = { 179 | index: number, // index of instruction in Transaction object which produced CPI, 180 | instructions: CompiledInstruction[] // ordered list of instructions which were called after instruction with index *index* 181 | } 182 | ``` 183 | We create artificial `const result: Transaction = new Transaction();` object which contains all the instructions from TransactionResponse + CPI calls: 184 | Add first TransactionInstruction to result, check if CPI calls with index = 0 exist, add them to result, move to the next instruction, repeat. 185 | Finally, we check that `result.instructions.length === input.instructions.length + total number of CPI instructions`. 186 | We can call index of result.instructions **callId** - index of call in the whole transaction. Same **callId** will be used in the logs part 187 | 188 | ### Parsing transaction logs 189 | Function: [parseLogs](./src/helpers.ts#L143) 190 | Working with Solana's logs is not a trivial task - to determine which program emitted current log line we have to restore call stack, check call depth and set correct **callId** for each log line. parseLogs function implements all that stuff (with call depth and call id checks): 191 | 1. Iterate over logs 192 | 2. Check log type (invoke/return/error/program log/program data) using regex 193 | 3. According to the log type perform action: 194 | - Invoke: init and save new context object which contains program id, call depth of instruction, **callId**, index of instruction in Transaction which produced log (depth == 0) or current CPI call (depth != 0), save call stack (push current callId into stack) 195 | - Return success/fail: pop caller id from call stack, current callId = popped callId 196 | - program log/program data/program consumed: save log into context object with **callId** == current callId 197 | 4. Return list of context objects 198 | 199 | ### Using everything together 200 | ```ts 201 | import { ourIdl } from "programIdl"; 202 | 203 | const parser = new SolanaParser([{programId: "someBase58Address", idl: ourIdl}]); 204 | const flattened = flattenTransactionResponse(response); 205 | const logs = parseLogs(response.meta.logs || []); 206 | const parsed = flattened.map((ix) => parser.parse(ix)); 207 | 208 | const callId = parsed.findIndex( (parsedIx) => parsedIx.name === "someInstruction" ); 209 | if (callId === -1) return Promise.reject("instruction not found"); 210 | 211 | const someInstruction = parsed[callId]; 212 | const someInstructionLogs = logs.find((context) => context.id === callId); 213 | 214 | const onlyNeededProgramLogs = logs.filter((context) => context.programId === "someBase58Address"); 215 | ``` 216 | -------------------------------------------------------------------------------- /cli/convert.idl.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from "fs"; 2 | import { convertLegacyIdlToV30 } from "../src"; 3 | import { IDL } from "../tests/idl/dst"; 4 | 5 | async function main() { 6 | const typeName = "SplToken22"; 7 | const res = convertLegacyIdlToV30(IDL, "JUP2jxvXaqu7NQY1GmNF4m1vodw12LVXYxbFL2uJvfo"); 8 | const stringified = JSON.stringify(res); 9 | writeFileSync( 10 | "./src/programs/spl-token-22.program.ts", 11 | `export declare type ${typeName} = ${stringified};\nexport const IDL: ${typeName} = ${stringified};\n`, 12 | ); 13 | } 14 | 15 | main().catch(console.error); 16 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | @debridge-finance/solana-transaction-parser 2 | 3 | # @debridge-finance/solana-transaction-parser 4 | 5 | ## Table of contents 6 | 7 | ### Classes 8 | 9 | - [SolanaParser](classes/SolanaParser.md) 10 | 11 | ### Interfaces 12 | 13 | - [ParsedAccount](interfaces/ParsedAccount.md) 14 | - [ParsedCustomInstruction](interfaces/ParsedCustomInstruction.md) 15 | - [ParsedIdlInstruction](interfaces/ParsedIdlInstruction.md) 16 | - [ProgramInfoType](interfaces/ProgramInfoType.md) 17 | 18 | ### Type Aliases 19 | 20 | - [IdlAccount](README.md#idlaccount) 21 | - [IdlAccounts](README.md#idlaccounts) 22 | - [InstructionNames](README.md#instructionnames) 23 | - [InstructionParserInfo](README.md#instructionparserinfo) 24 | - [InstructionParsers](README.md#instructionparsers) 25 | - [IxByName](README.md#ixbyname) 26 | - [LogContext](README.md#logcontext) 27 | - [ParsedArgs](README.md#parsedargs) 28 | - [ParsedIdlArgs](README.md#parsedidlargs) 29 | - [ParsedIdlArgsByInstructionName](README.md#parsedidlargsbyinstructionname) 30 | - [ParsedInstruction](README.md#parsedinstruction) 31 | - [ParserFunction](README.md#parserfunction) 32 | - [TransactionWithLogs](README.md#transactionwithlogs) 33 | - [UnknownInstruction](README.md#unknowninstruction) 34 | 35 | ### Functions 36 | 37 | - [compiledInstructionToInstruction](README.md#compiledinstructiontoinstruction) 38 | - [flattenTransactionResponse](README.md#flattentransactionresponse) 39 | - [hexToBuffer](README.md#hextobuffer) 40 | - [parseLogs](README.md#parselogs) 41 | - [parseTransactionAccounts](README.md#parsetransactionaccounts) 42 | - [parsedInstructionToInstruction](README.md#parsedinstructiontoinstruction) 43 | 44 | ## Type Aliases 45 | 46 | ### IdlAccount 47 | 48 | Ƭ **IdlAccount**: `Object` 49 | 50 | #### Type declaration 51 | 52 | | Name | Type | 53 | | :------ | :------ | 54 | | `isMut` | `boolean` | 55 | | `isSigner` | `boolean` | 56 | | `name` | `string` | 57 | 58 | #### Defined in 59 | 60 | [interfaces.ts:133](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L133) 61 | 62 | ___ 63 | 64 | ### IdlAccounts 65 | 66 | Ƭ **IdlAccounts**: `Object` 67 | 68 | #### Type declaration 69 | 70 | | Name | Type | 71 | | :------ | :------ | 72 | | `accounts` | [`IdlAccount`](README.md#idlaccount)[] | 73 | | `name` | `string` | 74 | 75 | #### Defined in 76 | 77 | [interfaces.ts:139](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L139) 78 | 79 | ___ 80 | 81 | ### InstructionNames 82 | 83 | Ƭ **InstructionNames**<`I`\>: `I`[``"instructions"``][`number`][``"name"``] 84 | 85 | #### Type parameters 86 | 87 | | Name | Type | 88 | | :------ | :------ | 89 | | `I` | extends `Idl` | 90 | 91 | #### Defined in 92 | 93 | [interfaces.ts:49](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L49) 94 | 95 | ___ 96 | 97 | ### InstructionParserInfo 98 | 99 | Ƭ **InstructionParserInfo**: [`string`, [`ParserFunction`](README.md#parserfunction)<`Idl`, `string`\>] 100 | 101 | public key as base58 string, parser 102 | 103 | #### Defined in 104 | 105 | [interfaces.ts:35](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L35) 106 | 107 | ___ 108 | 109 | ### InstructionParsers 110 | 111 | Ƭ **InstructionParsers**: `Map`<`string`, [`ParserFunction`](README.md#parserfunction)<`Idl`, `string`\>\> 112 | 113 | Map which keys are programIds (base58-encoded) and values are ix parsers 114 | 115 | #### Defined in 116 | 117 | [interfaces.ts:26](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L26) 118 | 119 | ___ 120 | 121 | ### IxByName 122 | 123 | Ƭ **IxByName**<`I`, `IxName`\>: `I`[``"instructions"``][`number`] & { `name`: `IxName` } 124 | 125 | Interface to get instruction by name from IDL 126 | 127 | #### Type parameters 128 | 129 | | Name | Type | 130 | | :------ | :------ | 131 | | `I` | extends `Idl` | 132 | | `IxName` | extends `I`[``"instructions"``][`number`][``"name"``] | 133 | 134 | #### Defined in 135 | 136 | [interfaces.ts:131](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L131) 137 | 138 | ___ 139 | 140 | ### LogContext 141 | 142 | Ƭ **LogContext**: `Object` 143 | 144 | Context of logs for specific instruction 145 | 146 | #### Type declaration 147 | 148 | | Name | Type | 149 | | :------ | :------ | 150 | | `dataLogs` | `string`[] | 151 | | `depth` | `number` | 152 | | `errors` | `string`[] | 153 | | `id` | `number` | 154 | | `instructionIndex` | `number` | 155 | | `logMessages` | `string`[] | 156 | | `programId` | `string` | 157 | | `rawLogs` | `string`[] | 158 | 159 | #### Defined in 160 | 161 | [interfaces.ts:7](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L7) 162 | 163 | ___ 164 | 165 | ### ParsedArgs 166 | 167 | Ƭ **ParsedArgs**: `Object` 168 | 169 | #### Index signature 170 | 171 | ▪ [key: `string`]: `unknown` 172 | 173 | #### Defined in 174 | 175 | [interfaces.ts:53](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L53) 176 | 177 | ___ 178 | 179 | ### ParsedIdlArgs 180 | 181 | Ƭ **ParsedIdlArgs**<`I`, `IxName`\>: [`ParsedIdlArgsByInstructionName`](README.md#parsedidlargsbyinstructionname)<`I`, [`IxByName`](README.md#ixbyname)<`I`, `IxName`\>\> 182 | 183 | #### Type parameters 184 | 185 | | Name | Type | 186 | | :------ | :------ | 187 | | `I` | extends `Idl` | 188 | | `IxName` | extends [`InstructionNames`](README.md#instructionnames)<`I`\> = [`InstructionNames`](README.md#instructionnames)<`I`\> | 189 | 190 | #### Defined in 191 | 192 | [interfaces.ts:51](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L51) 193 | 194 | ___ 195 | 196 | ### ParsedIdlArgsByInstructionName 197 | 198 | Ƭ **ParsedIdlArgsByInstructionName**<`I`, `Ix`\>: { [ArgName in Ix["args"][number]["name"]]: DecodeType<(Ix["args"][number] & Object)["type"], IdlTypes\> } 199 | 200 | Instructions args with correct types for specific instruction by instruction name 201 | 202 | #### Type parameters 203 | 204 | | Name | Type | 205 | | :------ | :------ | 206 | | `I` | extends `Idl` | 207 | | `Ix` | extends `I`[``"instructions"``][`number`] | 208 | 209 | #### Defined in 210 | 211 | [interfaces.ts:45](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L45) 212 | 213 | ___ 214 | 215 | ### ParsedInstruction 216 | 217 | Ƭ **ParsedInstruction**<`I`, `IxName`\>: [`UnknownInstruction`](README.md#unknowninstruction) \| [`ParsedIdlInstruction`](interfaces/ParsedIdlInstruction.md)<`I`, `IxName`\> \| [`ParsedCustomInstruction`](interfaces/ParsedCustomInstruction.md) 218 | 219 | #### Type parameters 220 | 221 | | Name | Type | 222 | | :------ | :------ | 223 | | `I` | extends `Idl` | 224 | | `IxName` | extends [`InstructionNames`](README.md#instructionnames)<`I`\> = [`InstructionNames`](README.md#instructionnames)<`I`\> | 225 | 226 | #### Defined in 227 | 228 | [interfaces.ts:64](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L64) 229 | 230 | ___ 231 | 232 | ### ParserFunction 233 | 234 | Ƭ **ParserFunction**<`I`, `IxName`\>: (`arg`: `TransactionInstruction`) => [`ParsedInstruction`](README.md#parsedinstruction)<`I`, `IxName`\> 235 | 236 | #### Type parameters 237 | 238 | | Name | Type | 239 | | :------ | :------ | 240 | | `I` | extends `Idl` | 241 | | `IxName` | extends [`InstructionNames`](README.md#instructionnames)<`I`\> | 242 | 243 | #### Type declaration 244 | 245 | ▸ (`arg`): [`ParsedInstruction`](README.md#parsedinstruction)<`I`, `IxName`\> 246 | 247 | Function that takes transaction ix and returns parsed variant 248 | 249 | ##### Parameters 250 | 251 | | Name | Type | 252 | | :------ | :------ | 253 | | `arg` | `TransactionInstruction` | 254 | 255 | ##### Returns 256 | 257 | [`ParsedInstruction`](README.md#parsedinstruction)<`I`, `IxName`\> 258 | 259 | #### Defined in 260 | 261 | [interfaces.ts:30](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L30) 262 | 263 | ___ 264 | 265 | ### TransactionWithLogs 266 | 267 | Ƭ **TransactionWithLogs**: `Object` 268 | 269 | #### Type declaration 270 | 271 | | Name | Type | 272 | | :------ | :------ | 273 | | `logs?` | `string`[] | 274 | | `transaction` | `Transaction` | 275 | 276 | #### Defined in 277 | 278 | [interfaces.ts:18](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L18) 279 | 280 | ___ 281 | 282 | ### UnknownInstruction 283 | 284 | Ƭ **UnknownInstruction**: `Object` 285 | 286 | #### Type declaration 287 | 288 | | Name | Type | 289 | | :------ | :------ | 290 | | `accounts` | [`ParsedAccount`](interfaces/ParsedAccount.md)[] | 291 | | `args` | { `unknown`: `unknown` } | 292 | | `args.unknown` | `unknown` | 293 | | `name` | ``"unknown"`` \| `string` | 294 | | `programId` | `PublicKey` | 295 | 296 | #### Defined in 297 | 298 | [interfaces.ts:57](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L57) 299 | 300 | ## Functions 301 | 302 | ### compiledInstructionToInstruction 303 | 304 | ▸ **compiledInstructionToInstruction**(`compiledInstruction`, `parsedAccounts`): `TransactionInstruction` 305 | 306 | Converts compiled instruction into common TransactionInstruction 307 | 308 | #### Parameters 309 | 310 | | Name | Type | Description | 311 | | :------ | :------ | :------ | 312 | | `compiledInstruction` | `CompiledInstruction` | | 313 | | `parsedAccounts` | `AccountMeta`[] | account meta, result of [parseTransactionAccounts](README.md#parsetransactionaccounts) | 314 | 315 | #### Returns 316 | 317 | `TransactionInstruction` 318 | 319 | TransactionInstruction 320 | 321 | ___ 322 | 323 | ### flattenTransactionResponse 324 | 325 | ▸ **flattenTransactionResponse**(`transaction`): `Transaction` 326 | 327 | Converts transaction response with CPI into artifical transaction that contains all instructions from tx and CPI 328 | 329 | #### Parameters 330 | 331 | | Name | Type | Description | 332 | | :------ | :------ | :------ | 333 | | `transaction` | `TransactionResponse` | transactionResponse to convert from | 334 | 335 | #### Returns 336 | 337 | `Transaction` 338 | 339 | Transaction object 340 | 341 | ___ 342 | 343 | ### hexToBuffer 344 | 345 | ▸ **hexToBuffer**(`data`): `Buffer` 346 | 347 | #### Parameters 348 | 349 | | Name | Type | 350 | | :------ | :------ | 351 | | `data` | `string` | 352 | 353 | #### Returns 354 | 355 | `Buffer` 356 | 357 | ___ 358 | 359 | ### parseLogs 360 | 361 | ▸ **parseLogs**(`logs`): [`LogContext`](README.md#logcontext)[] 362 | 363 | Parses transaction logs and provides additional context such as 364 | - programId that generated the message 365 | - call id of instruction, that generated the message 366 | - call depth of instruction 367 | - data messages, log messages and error messages 368 | 369 | #### Parameters 370 | 371 | | Name | Type | Description | 372 | | :------ | :------ | :------ | 373 | | `logs` | `string`[] | logs from TransactionResponse.meta.logs | 374 | 375 | #### Returns 376 | 377 | [`LogContext`](README.md#logcontext)[] 378 | 379 | parsed logs with call depth and additional context 380 | 381 | ___ 382 | 383 | ### parseTransactionAccounts 384 | 385 | ▸ **parseTransactionAccounts**(`message`): `AccountMeta`[] 386 | 387 | Parse transaction message and extract account metas 388 | 389 | #### Parameters 390 | 391 | | Name | Type | Description | 392 | | :------ | :------ | :------ | 393 | | `message` | `Message` | transaction message | 394 | 395 | #### Returns 396 | 397 | `AccountMeta`[] 398 | 399 | parsed accounts metas 400 | 401 | ___ 402 | 403 | ### parsedInstructionToInstruction 404 | 405 | ▸ **parsedInstructionToInstruction**(`parsedInstruction`, `accountMeta`): `TransactionInstruction` 406 | 407 | #### Parameters 408 | 409 | | Name | Type | 410 | | :------ | :------ | 411 | | `parsedInstruction` | `PartiallyDecodedInstruction` | 412 | | `accountMeta` | `AccountMeta`[] | 413 | 414 | #### Returns 415 | 416 | `TransactionInstruction` 417 | -------------------------------------------------------------------------------- /docs/classes/SolanaParser.md: -------------------------------------------------------------------------------- 1 | [@debridge-finance/solana-transaction-parser](../README.md) / SolanaParser 2 | 3 | # Class: SolanaParser 4 | 5 | Class for parsing arbitrary solana transactions in various formats 6 | - by txHash 7 | - from raw transaction data (base64 encoded or buffer) 8 | - @solana/web3.js getTransaction().message object 9 | - @solana/web3.js getParsedTransaction().message or Transaction.compileMessage() object 10 | - @solana/web3.js TransactionInstruction object 11 | 12 | ## Table of contents 13 | 14 | ### Constructors 15 | 16 | - [constructor](SolanaParser.md#constructor) 17 | 18 | ### Methods 19 | 20 | - [addParser](SolanaParser.md#addparser) 21 | - [addParserFromIdl](SolanaParser.md#addparserfromidl) 22 | - [parseInstruction](SolanaParser.md#parseinstruction) 23 | - [parseTransaction](SolanaParser.md#parsetransaction) 24 | - [parseTransactionData](SolanaParser.md#parsetransactiondata) 25 | - [parseTransactionDump](SolanaParser.md#parsetransactiondump) 26 | - [parseTransactionParsedData](SolanaParser.md#parsetransactionparseddata) 27 | - [removeParser](SolanaParser.md#removeparser) 28 | 29 | ## Constructors 30 | 31 | ### constructor 32 | 33 | • **new SolanaParser**(`programInfos`, `parsers?`) 34 | 35 | Initializes parser object 36 | `SystemProgram`, `TokenProgram` and `AssociatedTokenProgram` are supported by default 37 | but may be overriden by providing custom idl/custom parser 38 | 39 | #### Parameters 40 | 41 | | Name | Type | Description | 42 | | :------ | :------ | :------ | 43 | | `programInfos` | [`ProgramInfoType`](../interfaces/ProgramInfoType.md)[] | list of objects which contains programId and corresponding idl | 44 | | `parsers?` | [`InstructionParserInfo`](../README.md#instructionparserinfo)[] | list of pairs (programId, custom parser) | 45 | 46 | ## Methods 47 | 48 | ### addParser 49 | 50 | ▸ **addParser**(`programId`, `parser`): `void` 51 | 52 | Adds (or updates) parser for provided programId 53 | 54 | #### Parameters 55 | 56 | | Name | Type | Description | 57 | | :------ | :------ | :------ | 58 | | `programId` | `PublicKey` | program id to add parser for | 59 | | `parser` | [`ParserFunction`](../README.md#parserfunction)<`Idl`, `string`\> | parser to parse programId instructions | 60 | 61 | #### Returns 62 | 63 | `void` 64 | 65 | ___ 66 | 67 | ### addParserFromIdl 68 | 69 | ▸ **addParserFromIdl**(`programId`, `idl`): `void` 70 | 71 | Adds (or updates) parser for provided programId 72 | 73 | #### Parameters 74 | 75 | | Name | Type | Description | 76 | | :------ | :------ | :------ | 77 | | `programId` | `string` \| `PublicKey` | program id to add parser for | 78 | | `idl` | `Idl` | IDL that describes anchor program | 79 | 80 | #### Returns 81 | 82 | `void` 83 | 84 | ___ 85 | 86 | ### parseInstruction 87 | 88 | ▸ **parseInstruction**<`I`, `IxName`\>(`instruction`): [`ParsedInstruction`](../README.md#parsedinstruction)<`I`, `IxName`\> 89 | 90 | Parses instruction 91 | 92 | #### Type parameters 93 | 94 | | Name | Type | 95 | | :------ | :------ | 96 | | `I` | extends `Idl` | 97 | | `IxName` | extends `string` | 98 | 99 | #### Parameters 100 | 101 | | Name | Type | Description | 102 | | :------ | :------ | :------ | 103 | | `instruction` | `TransactionInstruction` | transaction instruction to parse | 104 | 105 | #### Returns 106 | 107 | [`ParsedInstruction`](../README.md#parsedinstruction)<`I`, `IxName`\> 108 | 109 | parsed transaction instruction or UnknownInstruction 110 | 111 | ___ 112 | 113 | ### parseTransaction 114 | 115 | ▸ **parseTransaction**(`connection`, `txId`, `flatten?`): `Promise`<``null`` \| [`ParsedInstruction`](../README.md#parsedinstruction)<`Idl`, `string`\>[]\> 116 | 117 | Fetches tx from blockchain and parses it 118 | 119 | #### Parameters 120 | 121 | | Name | Type | Default value | Description | 122 | | :------ | :------ | :------ | :------ | 123 | | `connection` | `Connection` | `undefined` | web3 Connection | 124 | | `txId` | `string` | `undefined` | transaction id | 125 | | `flatten` | `boolean` | `false` | true if CPI calls need to be parsed too | 126 | 127 | #### Returns 128 | 129 | `Promise`<``null`` \| [`ParsedInstruction`](../README.md#parsedinstruction)<`Idl`, `string`\>[]\> 130 | 131 | list of parsed instructions 132 | 133 | ___ 134 | 135 | ### parseTransactionData 136 | 137 | ▸ **parseTransactionData**(`txMessage`): [`ParsedInstruction`](../README.md#parsedinstruction)<`Idl`, `string`\>[] 138 | 139 | Parses transaction data 140 | 141 | #### Parameters 142 | 143 | | Name | Type | Description | 144 | | :------ | :------ | :------ | 145 | | `txMessage` | `Message` | message to parse | 146 | 147 | #### Returns 148 | 149 | [`ParsedInstruction`](../README.md#parsedinstruction)<`Idl`, `string`\>[] 150 | 151 | list of parsed instructions 152 | 153 | ___ 154 | 155 | ### parseTransactionDump 156 | 157 | ▸ **parseTransactionDump**(`txDump`): [`ParsedInstruction`](../README.md#parsedinstruction)<`Idl`, `string`\>[] 158 | 159 | Parses transaction dump 160 | 161 | #### Parameters 162 | 163 | | Name | Type | Description | 164 | | :------ | :------ | :------ | 165 | | `txDump` | `string` \| `Buffer` | base64-encoded string or raw Buffer which contains tx dump | 166 | 167 | #### Returns 168 | 169 | [`ParsedInstruction`](../README.md#parsedinstruction)<`Idl`, `string`\>[] 170 | 171 | list of parsed instructions 172 | 173 | ___ 174 | 175 | ### parseTransactionParsedData 176 | 177 | ▸ **parseTransactionParsedData**(`txParsedMessage`): [`ParsedInstruction`](../README.md#parsedinstruction)<`Idl`, `string`\>[] 178 | 179 | Parses transaction data retrieved from Connection.getParsedTransaction 180 | 181 | #### Parameters 182 | 183 | | Name | Type | Description | 184 | | :------ | :------ | :------ | 185 | | `txParsedMessage` | `ParsedMessage` | message to parse | 186 | 187 | #### Returns 188 | 189 | [`ParsedInstruction`](../README.md#parsedinstruction)<`Idl`, `string`\>[] 190 | 191 | list of parsed instructions 192 | 193 | ___ 194 | 195 | ### removeParser 196 | 197 | ▸ **removeParser**(`programId`): `void` 198 | 199 | Removes parser for provided program id 200 | 201 | #### Parameters 202 | 203 | | Name | Type | Description | 204 | | :------ | :------ | :------ | 205 | | `programId` | `PublicKey` | program id to remove parser for | 206 | 207 | #### Returns 208 | 209 | `void` 210 | -------------------------------------------------------------------------------- /docs/interfaces/ParsedAccount.md: -------------------------------------------------------------------------------- 1 | [@debridge-finance/solana-transaction-parser](../README.md) / ParsedAccount 2 | 3 | # Interface: ParsedAccount 4 | 5 | ## Hierarchy 6 | 7 | - `AccountMeta` 8 | 9 | ↳ **`ParsedAccount`** 10 | 11 | ## Table of contents 12 | 13 | ### Properties 14 | 15 | - [name](ParsedAccount.md#name) 16 | 17 | ## Properties 18 | 19 | ### name 20 | 21 | • `Optional` **name**: `string` 22 | 23 | Account name, same as in Idl, nested accounts look like `account > nestedAccount` 24 | 25 | #### Defined in 26 | 27 | [interfaces.ts:39](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L39) 28 | -------------------------------------------------------------------------------- /docs/interfaces/ParsedCustomInstruction.md: -------------------------------------------------------------------------------- 1 | [@debridge-finance/solana-transaction-parser](../README.md) / ParsedCustomInstruction 2 | 3 | # Interface: ParsedCustomInstruction 4 | 5 | ## Table of contents 6 | 7 | ### Properties 8 | 9 | - [accounts](ParsedCustomInstruction.md#accounts) 10 | - [args](ParsedCustomInstruction.md#args) 11 | - [name](ParsedCustomInstruction.md#name) 12 | - [programId](ParsedCustomInstruction.md#programid) 13 | 14 | ## Properties 15 | 16 | ### accounts 17 | 18 | • **accounts**: [`ParsedAccount`](ParsedAccount.md)[] 19 | 20 | Parsed accounts 21 | 22 | #### Defined in 23 | 24 | [interfaces.ts:76](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L76) 25 | 26 | ___ 27 | 28 | ### args 29 | 30 | • **args**: `unknown` 31 | 32 | Parsed arguments 33 | 34 | #### Defined in 35 | 36 | [interfaces.ts:74](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L74) 37 | 38 | ___ 39 | 40 | ### name 41 | 42 | • **name**: `string` 43 | 44 | Instruction name 45 | 46 | #### Defined in 47 | 48 | [interfaces.ts:71](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L71) 49 | 50 | ___ 51 | 52 | ### programId 53 | 54 | • **programId**: `PublicKey` 55 | 56 | #### Defined in 57 | 58 | [interfaces.ts:72](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L72) 59 | -------------------------------------------------------------------------------- /docs/interfaces/ParsedIdlInstruction.md: -------------------------------------------------------------------------------- 1 | [@debridge-finance/solana-transaction-parser](../README.md) / ParsedIdlInstruction 2 | 3 | # Interface: ParsedIdlInstruction 4 | 5 | ## Type parameters 6 | 7 | | Name | Type | 8 | | :------ | :------ | 9 | | `I` | extends `Idl` | 10 | | `IxName` | extends [`InstructionNames`](../README.md#instructionnames)<`I`\> = [`InstructionNames`](../README.md#instructionnames)<`I`\> | 11 | 12 | ## Table of contents 13 | 14 | ### Properties 15 | 16 | - [accounts](ParsedIdlInstruction.md#accounts) 17 | - [args](ParsedIdlInstruction.md#args) 18 | - [name](ParsedIdlInstruction.md#name) 19 | - [programId](ParsedIdlInstruction.md#programid) 20 | 21 | ## Properties 22 | 23 | ### accounts 24 | 25 | • **accounts**: [`ParsedAccount`](ParsedAccount.md)[] 26 | 27 | Parsed accounts 28 | 29 | #### Defined in 30 | 31 | [interfaces.ts:86](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L86) 32 | 33 | ___ 34 | 35 | ### args 36 | 37 | • **args**: [`ParsedIdlArgs`](../README.md#parsedidlargs)<`I`, `IxName`\> 38 | 39 | Parsed arguments 40 | 41 | #### Defined in 42 | 43 | [interfaces.ts:84](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L84) 44 | 45 | ___ 46 | 47 | ### name 48 | 49 | • **name**: `IxName` 50 | 51 | Instruction name 52 | 53 | #### Defined in 54 | 55 | [interfaces.ts:81](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L81) 56 | 57 | ___ 58 | 59 | ### programId 60 | 61 | • **programId**: `PublicKey` 62 | 63 | #### Defined in 64 | 65 | [interfaces.ts:82](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L82) 66 | -------------------------------------------------------------------------------- /docs/interfaces/ProgramInfoType.md: -------------------------------------------------------------------------------- 1 | [@debridge-finance/solana-transaction-parser](../README.md) / ProgramInfoType 2 | 3 | # Interface: ProgramInfoType 4 | 5 | ## Table of contents 6 | 7 | ### Properties 8 | 9 | - [idl](ProgramInfoType.md#idl) 10 | - [programId](ProgramInfoType.md#programid) 11 | 12 | ## Properties 13 | 14 | ### idl 15 | 16 | • **idl**: `Idl` 17 | 18 | #### Defined in 19 | 20 | [interfaces.ts:90](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L90) 21 | 22 | ___ 23 | 24 | ### programId 25 | 26 | • **programId**: `string` \| `PublicKey` 27 | 28 | #### Defined in 29 | 30 | [interfaces.ts:91](https://github.com/debridge-finance/solana-tx-parser-public/blob/b05f439/src/interfaces.ts#L91) 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@debridge-finance/solana-transaction-parser", 3 | "description": "Tool for parsing arbitrary Solana transactions with IDL/custom parsers", 4 | "version": "3.3.0", 5 | "author": "deBridge", 6 | "license": "LGPL-2.1", 7 | "homepage": "https://debridge.finance", 8 | "repository": { 9 | "type": "git", 10 | "url": "github:debridge-finance/solana-tx-parser-public" 11 | }, 12 | "keywords": [ 13 | "solana", 14 | "deBridge", 15 | "blockchain" 16 | ], 17 | "files": [ 18 | "cli/**", 19 | "dist/**", 20 | "docs/**", 21 | "LICENSE", 22 | "README.md" 23 | ], 24 | "module": "./dist/esm/index.js", 25 | "main": "./dist/cjs/index.js", 26 | "types": "./dist/cjs/index.d.ts", 27 | "scripts": { 28 | "build:esm": "tsc -p tsconfig.esm.json", 29 | "build:node": "tsc -p tsconfig.cjs.json", 30 | "build": "npm run build:esm && npm run build:node", 31 | "prepublishOnly": "npm run build", 32 | "test:ix": "mocha -r ts-node/register -b -t 200000 ./tests/parseIx.test.ts", 33 | "test:tx": "mocha -r ts-node/register -t 200000 ./tests/parseTransaction.test.ts", 34 | "test:tx:dlnsrc": "mocha -r ts-node/register -t 200000 tests/parseDlnSrcTransaction.test.ts", 35 | "test:tx:dlndst": "mocha -r ts-node/register -t 200000 tests/parseDlnDstTransaction.test.ts", 36 | "test:tx:native": "mocha -r ts-node/register -t 200000 tests/parseNativeTransaction.test.ts", 37 | "test:err": "mocha -r ts-node/register -t 200000 ./tests/error.parsing.test.ts", 38 | "test:cb": "mocha -r ts-node/register -t 200000 ./tests/parseComputeBudget.test.ts", 39 | "test:sysTx": "mocha -r ts-node/register -b -t 200000 ./tests/parseSystemTransaction.test.ts", 40 | "test:custom": "mocha -r ts-node/register -b -t 200000 ./tests/customParser.test.ts", 41 | "test:parseLogs": "mocha -r ts-node/register -b -t 200000 ./tests/parseLogs.test.ts", 42 | "prettify": "prettier --write ./**/*.ts", 43 | "lint": "eslint --config .eslintrc \"./{src,tests}/**/*.{js,ts}\"", 44 | "lint:fix": "eslint --config .eslintrc \"./{src,tests}/**/*.{js,ts}\" --fix", 45 | "lint:dump": "eslint --print-config ./.eslintrc.json" 46 | }, 47 | "devDependencies": { 48 | "@rollup/plugin-commonjs": "^22.0.2", 49 | "@rollup/plugin-node-resolve": "^13.3.0", 50 | "@rollup/plugin-typescript": "^8.3.4", 51 | "@types/bn.js": "^5.1.0", 52 | "@types/chai": "^4.3.0", 53 | "@types/mocha": "^9.0.0", 54 | "@types/node": "^17.0.2", 55 | "chai": "^4.3.4", 56 | "eslint": "^8.5.0", 57 | "eslint-config-airbnb-base": "^15.0.0", 58 | "eslint-config-airbnb-typescript": "^16.1.0", 59 | "eslint-config-prettier": "^8.3.0", 60 | "eslint-plugin-eslint-comments": "^3.2.0", 61 | "eslint-plugin-import": "^2.25.3", 62 | "eslint-plugin-prettier": "^5.0.0", 63 | "mocha": "^10.0.0", 64 | "rollup": "^2.77.2", 65 | "rollup-plugin-terser": "^7.0.2", 66 | "snake-case": "^3.0.4", 67 | "ts-node": "^10.9.1", 68 | "tslib": "^2.6.1", 69 | "typedoc": "^0.24.8", 70 | "typedoc-plugin-markdown": "^3.15.4" 71 | }, 72 | "dependencies": { 73 | "@coral-xyz/anchor": "^0.30.1", 74 | "@coral-xyz/spl-token": "^0.30.1", 75 | "@noble/hashes": "^1.6.1", 76 | "@solana/codecs": "2.0.0", 77 | "@solana/spl-token": "^0.4.9", 78 | "@solana/spl-type-length-value": "0.2.0", 79 | "@solana/web3.js": "^1.98.0", 80 | "buffer": "6.0.3" 81 | }, 82 | "peerDependencies": { 83 | "@solana/buffer-layout": "^4.0.0", 84 | "@solana/buffer-layout-utils": "^0.2.0" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from "@rollup/plugin-node-resolve"; 2 | import typescript from "@rollup/plugin-typescript"; 3 | import commonjs from "@rollup/plugin-commonjs"; 4 | import { terser } from "rollup-plugin-terser"; 5 | 6 | export default { 7 | input: "src/index.ts", 8 | plugins: [ 9 | commonjs(), 10 | nodeResolve({ 11 | browser: true, 12 | preferBuiltins: false, 13 | dedupe: ["buffer"], 14 | extensions: [".js", ".ts"], 15 | mainFields: ["browser", "module", "main"], 16 | }), 17 | typescript({ 18 | tsconfig: "./tsconfig.esm.json", 19 | moduleResolution: "node", 20 | target: "es2019", 21 | outputToFilesystem: false, 22 | }), 23 | terser(), 24 | ], 25 | external: ["@solana/web3.js", "@coral-xyz/anchor"], 26 | output: { 27 | file: "dist/browser/index.js", 28 | format: "es", 29 | sourcemap: true, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/camelcase.ts: -------------------------------------------------------------------------------- 1 | const UPPERCASE = /[\p{Lu}]/u; 2 | const LOWERCASE = /[\p{Ll}]/u; 3 | const LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu; 4 | const IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u; 5 | const SEPARATORS = /[_.\- ]+/; 6 | 7 | const LEADING_SEPARATORS = new RegExp("^" + SEPARATORS.source); 8 | const SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, "gu"); 9 | const NUMBERS_AND_IDENTIFIER = new RegExp("\\d+" + IDENTIFIER.source, "gu"); 10 | 11 | type StrFn = (arg: string) => string; 12 | 13 | type Options = { 14 | /** 15 | Uppercase the first character: `foo-bar` → `FooBar`. 16 | 17 | @default false 18 | */ 19 | readonly pascalCase?: boolean; 20 | 21 | /** 22 | Preserve consecutive uppercase characters: `foo-BAR` → `FooBAR`. 23 | 24 | @default false 25 | */ 26 | readonly preserveConsecutiveUppercase?: boolean; 27 | 28 | /** 29 | The locale parameter indicates the locale to be used to convert to upper/lower case according to any locale-specific case mappings. If multiple locales are given in an array, the best available locale is used. 30 | 31 | Setting `locale: false` ignores the platform locale and uses the [Unicode Default Case Conversion](https://unicode-org.github.io/icu/userguide/transforms/casemappings.html#simple-single-character-case-mapping) algorithm. 32 | 33 | Default: The host environment’s current locale. 34 | 35 | @example 36 | ``` 37 | import camelCase from 'camelcase'; 38 | 39 | camelCase('lorem-ipsum', {locale: 'en-US'}); 40 | //=> 'loremIpsum' 41 | 42 | camelCase('lorem-ipsum', {locale: 'tr-TR'}); 43 | //=> 'loremİpsum' 44 | 45 | camelCase('lorem-ipsum', {locale: ['en-US', 'en-GB']}); 46 | //=> 'loremIpsum' 47 | 48 | camelCase('lorem-ipsum', {locale: ['tr', 'TR', 'tr-TR']}); 49 | //=> 'loremİpsum' 50 | ``` 51 | */ 52 | readonly locale?: false | string | readonly string[]; 53 | }; 54 | 55 | const preserveCamelCase = (string: string, toLowerCase: StrFn, toUpperCase: StrFn, preserveConsecutiveUppercase: boolean) => { 56 | let isLastCharLower = false; 57 | let isLastCharUpper = false; 58 | let isLastLastCharUpper = false; 59 | let isLastLastCharPreserved = false; 60 | 61 | for (let index = 0; index < string.length; index++) { 62 | const character = string[index]; 63 | isLastLastCharPreserved = index > 2 ? string[index - 3] === "-" : true; 64 | 65 | if (isLastCharLower && UPPERCASE.test(character)) { 66 | string = string.slice(0, index) + "-" + string.slice(index); 67 | isLastCharLower = false; 68 | isLastLastCharUpper = isLastCharUpper; 69 | isLastCharUpper = true; 70 | index++; 71 | } else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character) && (!isLastLastCharPreserved || preserveConsecutiveUppercase)) { 72 | string = string.slice(0, index - 1) + "-" + string.slice(index - 1); 73 | isLastLastCharUpper = isLastCharUpper; 74 | isLastCharUpper = false; 75 | isLastCharLower = true; 76 | } else { 77 | isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character; 78 | isLastLastCharUpper = isLastCharUpper; 79 | isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character; 80 | } 81 | } 82 | 83 | return string; 84 | }; 85 | 86 | const preserveConsecutiveUppercase = (input: string, toLowerCase: StrFn) => { 87 | LEADING_CAPITAL.lastIndex = 0; 88 | 89 | return input.replaceAll(LEADING_CAPITAL, (match) => toLowerCase(match)); 90 | }; 91 | 92 | const postProcess = (input: string, toUpperCase: StrFn) => { 93 | SEPARATORS_AND_IDENTIFIER.lastIndex = 0; 94 | NUMBERS_AND_IDENTIFIER.lastIndex = 0; 95 | 96 | return input 97 | .replaceAll(NUMBERS_AND_IDENTIFIER, (match, pattern, offset) => (["_", "-"].includes(input.charAt(offset + match.length)) ? match : toUpperCase(match))) 98 | .replaceAll(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier)); 99 | }; 100 | 101 | export function camelCase(input: string, options?: Options) { 102 | options = { 103 | pascalCase: false, 104 | preserveConsecutiveUppercase: false, 105 | ...options, 106 | }; 107 | 108 | input = input.trim(); 109 | 110 | if (input.length === 0) { 111 | return ""; 112 | } 113 | 114 | const toLowerCase = 115 | options.locale === false 116 | ? (string: string) => string.toLowerCase() 117 | : (string: string) => string.toLocaleLowerCase(options?.locale as string | string[] | undefined); 118 | 119 | const toUpperCase = 120 | options.locale === false 121 | ? (string: string) => string.toUpperCase() 122 | : (string: string) => string.toLocaleUpperCase(options?.locale as string | string[] | undefined); 123 | 124 | if (input.length === 1) { 125 | if (SEPARATORS.test(input)) { 126 | return ""; 127 | } 128 | 129 | return options.pascalCase ? toUpperCase(input) : toLowerCase(input); 130 | } 131 | 132 | const hasUpperCase = input !== toLowerCase(input); 133 | 134 | if (hasUpperCase) { 135 | input = preserveCamelCase(input, toLowerCase, toUpperCase, options.preserveConsecutiveUppercase!); 136 | } 137 | 138 | input = input.replace(LEADING_SEPARATORS, ""); 139 | input = options.preserveConsecutiveUppercase ? preserveConsecutiveUppercase(input, toLowerCase) : toLowerCase(input); 140 | 141 | if (options.pascalCase) { 142 | input = toUpperCase(input.charAt(0)) + input.slice(1); 143 | } 144 | 145 | return postProcess(input, toUpperCase); 146 | } 147 | -------------------------------------------------------------------------------- /src/decoders/associated.ts: -------------------------------------------------------------------------------- 1 | import { TransactionInstruction } from "@solana/web3.js"; 2 | import { ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token"; 3 | 4 | import { ParsedInstruction } from "../interfaces"; 5 | import { AssociatedTokenProgramIdl } from "../programs"; 6 | 7 | function decodeAssociatedTokenInstruction(instruction: TransactionInstruction): ParsedInstruction { 8 | return { 9 | name: instruction.data[0] == 0 ? "createAssociatedTokenAccountIdempotent" : "createAssociatedTokenAccount", 10 | accounts: [ 11 | { name: "fundingAccount", ...instruction.keys[0] }, 12 | { name: "newAccount", ...instruction.keys[1] }, 13 | { name: "wallet", ...instruction.keys[2] }, 14 | { name: "tokenMint", ...instruction.keys[3] }, 15 | { name: "systemProgram", ...instruction.keys[4] }, 16 | { name: "tokenProgram", ...instruction.keys[5] }, 17 | ...[instruction.keys.length > 6 ? { name: "rent", ...instruction.keys[6] } : undefined], 18 | ], 19 | args: {}, 20 | programId: ASSOCIATED_TOKEN_PROGRAM_ID, 21 | } as ParsedInstruction; 22 | } 23 | 24 | export { decodeAssociatedTokenInstruction }; 25 | -------------------------------------------------------------------------------- /src/decoders/compute.budget.ts: -------------------------------------------------------------------------------- 1 | import { ComputeBudgetInstruction, TransactionInstruction } from "@solana/web3.js"; 2 | import { BN } from "bn.js"; 3 | 4 | import { ParsedIdlInstruction, ParsedInstruction } from "../interfaces"; 5 | import { ComputeBudget } from "../programs/compute.budget"; 6 | 7 | export function decodeComputeBudgetInstruction(instruction: TransactionInstruction): ParsedInstruction { 8 | const type = instruction.data[0]; // ComputeBudgetInstruction.decodeInstructionType(instruction); 9 | let parsed: ParsedIdlInstruction | null; 10 | switch (type) { 11 | case 0: { 12 | const decoded = ComputeBudgetInstruction.decodeRequestUnits(instruction); 13 | parsed = { 14 | name: "requestUnits", 15 | accounts: [], 16 | args: { 17 | units: decoded.units, 18 | additionalFee: decoded.additionalFee, 19 | }, 20 | programId: instruction.programId, 21 | } as ParsedIdlInstruction; 22 | break; 23 | } 24 | case 1: { 25 | const decoded = ComputeBudgetInstruction.decodeRequestHeapFrame(instruction); 26 | parsed = { 27 | name: "requestHeapFrame", 28 | accounts: [], 29 | args: { 30 | bytes: decoded.bytes, 31 | }, 32 | programId: instruction.programId, 33 | } as ParsedIdlInstruction; 34 | break; 35 | } 36 | case 2: { 37 | const decoded = ComputeBudgetInstruction.decodeSetComputeUnitLimit(instruction); 38 | parsed = { 39 | name: "setComputeUnitLimit", 40 | accounts: [], 41 | args: { 42 | units: decoded.units, 43 | }, 44 | programId: instruction.programId, 45 | } as ParsedIdlInstruction; 46 | break; 47 | } 48 | case 3: { 49 | const decoded = ComputeBudgetInstruction.decodeSetComputeUnitPrice(instruction); 50 | parsed = { 51 | name: "setComputeUnitPrice", 52 | accounts: [], 53 | args: { 54 | microLamports: new BN(decoded.microLamports.toString()), 55 | }, 56 | programId: instruction.programId, 57 | } as ParsedIdlInstruction; 58 | break; 59 | } 60 | case 4: { 61 | parsed = { 62 | name: "setLoadedAccountsDataSizeLimit", 63 | accounts: [], 64 | args: { 65 | bytes: instruction.data.readUInt32LE(1), 66 | }, 67 | programId: instruction.programId, 68 | } as ParsedIdlInstruction; 69 | break; 70 | } 71 | default: { 72 | parsed = null; 73 | } 74 | } 75 | 76 | return parsed 77 | ? parsed 78 | : { 79 | programId: instruction.programId, 80 | name: "unknown", 81 | accounts: instruction.keys, 82 | args: { unknown: instruction.data }, 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /src/decoders/index.ts: -------------------------------------------------------------------------------- 1 | import { decodeSystemInstruction } from "./system"; 2 | import { decodeTokenInstruction } from "./token"; 3 | import { decodeToken2022Instruction } from "./token22"; 4 | import { decodeAssociatedTokenInstruction } from "./associated"; 5 | import { decodeComputeBudgetInstruction } from "./compute.budget"; 6 | 7 | export { decodeSystemInstruction, decodeTokenInstruction, decodeToken2022Instruction, decodeAssociatedTokenInstruction, decodeComputeBudgetInstruction }; 8 | -------------------------------------------------------------------------------- /src/decoders/system.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { TransactionInstruction, SystemInstruction, SystemProgram } from "@solana/web3.js"; 3 | 4 | import { ParsedIdlInstruction, ParsedInstruction } from "../interfaces"; 5 | import { SystemProgramIdl } from "../programs"; 6 | 7 | function decodeSystemInstruction(instruction: TransactionInstruction): ParsedInstruction { 8 | const ixType = SystemInstruction.decodeInstructionType(instruction); 9 | let parsed: ParsedIdlInstruction | null; 10 | switch (ixType) { 11 | case "AdvanceNonceAccount": { 12 | const decoded = SystemInstruction.decodeNonceAdvance(instruction); 13 | 14 | parsed = { 15 | name: "advanceNonceAccount", 16 | args: { authorized: decoded.authorizedPubkey }, 17 | accounts: [ 18 | { name: "nonce", isSigner: false, isWritable: true, pubkey: instruction.keys[0].pubkey }, 19 | { name: "recentBlockhashes", isSigner: false, isWritable: false, pubkey: instruction.keys[1].pubkey }, 20 | { name: "authorized", isSigner: true, isWritable: false, pubkey: instruction.keys[2].pubkey }, 21 | ], 22 | } as ParsedIdlInstruction; 23 | break; 24 | } 25 | case "Allocate": { 26 | const decoded = SystemInstruction.decodeAllocate(instruction); 27 | parsed = { 28 | name: "allocate", 29 | accounts: [{ name: "pubkey", pubkey: decoded.accountPubkey, isSigner: true, isWritable: true }], 30 | args: { space: new BN(decoded.space) }, 31 | } as ParsedIdlInstruction; 32 | break; 33 | } 34 | case "AllocateWithSeed": { 35 | const decoded = SystemInstruction.decodeAllocateWithSeed(instruction); 36 | parsed = { 37 | name: "allocateWithSeed", 38 | accounts: [ 39 | { name: "account", pubkey: decoded.accountPubkey, isSigner: false, isWritable: true }, 40 | { name: "base", pubkey: decoded.basePubkey, isSigner: true, isWritable: false }, 41 | ], 42 | args: { 43 | seed: decoded.seed, 44 | space: new BN(decoded.space), 45 | owner: decoded.programId, 46 | base: decoded.basePubkey, 47 | }, 48 | } as ParsedIdlInstruction; 49 | break; 50 | } 51 | case "Assign": { 52 | const decoded = SystemInstruction.decodeAssign(instruction); 53 | parsed = { 54 | name: "assign", 55 | accounts: [{ name: "pubkey", pubkey: decoded.accountPubkey, isSigner: true, isWritable: true }], 56 | args: { owner: decoded.programId }, 57 | } as ParsedIdlInstruction; 58 | break; 59 | } 60 | case "AssignWithSeed": { 61 | const decoded = SystemInstruction.decodeAssignWithSeed(instruction); 62 | parsed = { 63 | name: "assignWithSeed", 64 | accounts: [ 65 | { name: "account", pubkey: decoded.accountPubkey, isSigner: false, isWritable: true }, 66 | { name: "base", pubkey: decoded.basePubkey, isSigner: true, isWritable: false }, 67 | ], 68 | args: { 69 | seed: decoded.seed, // string 70 | owner: decoded.programId, 71 | base: decoded.basePubkey, 72 | }, 73 | } as ParsedIdlInstruction; 74 | break; 75 | } 76 | case "AuthorizeNonceAccount": { 77 | const decoded = SystemInstruction.decodeNonceAuthorize(instruction); 78 | parsed = { 79 | name: "authorizeNonceAccount", 80 | accounts: [ 81 | { name: "nonce", isSigner: false, isWritable: true, pubkey: decoded.noncePubkey }, 82 | { name: "authorized", isSigner: true, isWritable: false, pubkey: decoded.authorizedPubkey }, 83 | ], 84 | args: { authorized: decoded.newAuthorizedPubkey }, 85 | } as ParsedIdlInstruction; 86 | break; 87 | } 88 | case "Create": { 89 | const decoded = SystemInstruction.decodeCreateAccount(instruction); 90 | parsed = { 91 | name: "createAccount", 92 | accounts: [ 93 | { name: "from", pubkey: decoded.fromPubkey, isSigner: true, isWritable: true }, 94 | { name: "to", pubkey: decoded.newAccountPubkey, isSigner: true, isWritable: true }, 95 | ], 96 | args: { lamports: new BN(decoded.lamports), owner: decoded.programId, space: new BN(decoded.space) }, 97 | } as ParsedIdlInstruction; 98 | break; 99 | } 100 | case "CreateWithSeed": { 101 | const decoded = SystemInstruction.decodeCreateWithSeed(instruction); 102 | parsed = { 103 | name: "createAccountWithSeed", 104 | accounts: [ 105 | { name: "from", pubkey: decoded.fromPubkey, isSigner: true, isWritable: true }, 106 | { name: "to", pubkey: decoded.newAccountPubkey, isSigner: false, isWritable: true }, 107 | { name: "base", pubkey: decoded.basePubkey, isSigner: true, isWritable: false }, 108 | ], 109 | args: { 110 | lamports: new BN(decoded.lamports), 111 | owner: decoded.programId, 112 | space: new BN(decoded.space), 113 | seed: decoded.seed, 114 | base: decoded.basePubkey, 115 | }, 116 | } as ParsedIdlInstruction; 117 | break; 118 | } 119 | case "InitializeNonceAccount": { 120 | const decoded = SystemInstruction.decodeNonceInitialize(instruction); 121 | parsed = { 122 | name: "initializeNonceAccount", 123 | accounts: [ 124 | { name: "nonce", pubkey: decoded.noncePubkey, isSigner: true, isWritable: true }, 125 | { name: "recentBlockhashes", isSigner: false, isWritable: false, pubkey: instruction.keys[1].pubkey }, 126 | { name: "rent", isSigner: false, isWritable: false, pubkey: instruction.keys[2].pubkey }, 127 | ], 128 | args: { authorized: decoded.authorizedPubkey }, 129 | } as ParsedIdlInstruction; 130 | break; 131 | } 132 | case "Transfer": { 133 | const decoded = SystemInstruction.decodeTransfer(instruction); 134 | parsed = { 135 | name: "transfer", 136 | accounts: [ 137 | { name: "from", pubkey: decoded.fromPubkey, isSigner: true, isWritable: true }, 138 | { name: "to", pubkey: decoded.toPubkey, isWritable: true, isSigner: false }, 139 | ], 140 | args: { lamports: new BN(decoded.lamports.toString()) }, 141 | } as ParsedIdlInstruction; 142 | break; 143 | } 144 | case "TransferWithSeed": { 145 | const decoded = SystemInstruction.decodeTransferWithSeed(instruction); 146 | parsed = { 147 | name: "transferWithSeed", 148 | accounts: [ 149 | { name: "from", pubkey: decoded.fromPubkey, isSigner: false, isWritable: true }, 150 | { name: "base", pubkey: decoded.basePubkey, isSigner: true, isWritable: false }, 151 | { name: "to", pubkey: decoded.toPubkey, isSigner: false, isWritable: true }, 152 | ], 153 | args: { owner: decoded.programId, lamports: new BN(decoded.lamports.toString()), seed: decoded.seed }, 154 | } as ParsedIdlInstruction; 155 | break; 156 | } 157 | case "WithdrawNonceAccount": { 158 | const decoded = SystemInstruction.decodeNonceWithdraw(instruction); 159 | parsed = { 160 | name: "withdrawNonceAccount", 161 | accounts: [ 162 | { name: "nonce", pubkey: decoded.noncePubkey, isSigner: false, isWritable: true }, 163 | { name: "to", pubkey: decoded.toPubkey, isSigner: false, isWritable: true }, 164 | { name: "recentBlockhashes", isSigner: false, isWritable: false, pubkey: instruction.keys[2].pubkey }, 165 | { name: "rent", isSigner: false, isWritable: false, pubkey: instruction.keys[3].pubkey }, 166 | { name: "authorized", pubkey: decoded.noncePubkey, isSigner: true, isWritable: false }, 167 | ], 168 | args: { lamports: new BN(decoded.lamports) }, 169 | } as ParsedIdlInstruction; 170 | break; 171 | } 172 | default: { 173 | parsed = null; 174 | } 175 | } 176 | 177 | return parsed 178 | ? { 179 | ...parsed, 180 | programId: SystemProgram.programId, 181 | } 182 | : { 183 | programId: SystemProgram.programId, 184 | name: "unknown", 185 | accounts: instruction.keys, 186 | args: { unknown: instruction.data }, 187 | }; 188 | } 189 | 190 | export { decodeSystemInstruction }; 191 | -------------------------------------------------------------------------------- /src/decoders/token.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthorityType, 3 | TOKEN_PROGRAM_ID, 4 | TokenInstruction, 5 | decodeApproveCheckedInstruction, 6 | decodeApproveInstruction, 7 | decodeBurnCheckedInstruction, 8 | decodeBurnInstruction, 9 | decodeCloseAccountInstruction, 10 | decodeFreezeAccountInstruction, 11 | decodeInitializeAccountInstruction, 12 | decodeInitializeMintInstructionUnchecked, 13 | decodeInitializeMultisigInstruction, 14 | decodeMintToCheckedInstruction, 15 | decodeMintToInstruction, 16 | decodeRevokeInstruction, 17 | decodeSetAuthorityInstruction, 18 | decodeThawAccountInstruction, 19 | decodeTransferCheckedInstruction, 20 | decodeTransferInstruction, 21 | decodeAmountToUiAmountInstruction, 22 | decodeInitializeAccount2Instruction, 23 | decodeInitializeAccount3Instruction, 24 | decodeInitializeMint2Instruction, 25 | decodeInitializeImmutableOwnerInstruction, 26 | decodeSyncNativeInstruction, 27 | decodeUiAmountToAmountInstruction, 28 | } from "@solana/spl-token"; 29 | import { BN } from "@coral-xyz/anchor"; 30 | import { PublicKey, TransactionInstruction } from "@solana/web3.js"; 31 | 32 | import { ParsedInstruction, ParsedIdlInstruction } from "../interfaces"; 33 | import { SplTokenIdl } from "../programs"; 34 | 35 | function decodeTokenInstruction(instruction: TransactionInstruction): ParsedInstruction { 36 | let parsed: ParsedIdlInstruction | null = null; 37 | const decoded = instruction.data[0]; 38 | switch (decoded) { 39 | case TokenInstruction.InitializeMint: { 40 | const decodedIx = decodeInitializeMintInstructionUnchecked(instruction); 41 | parsed = { 42 | name: "initializeMint", 43 | accounts: [ 44 | { name: "mint", isSigner: false, isWritable: true, pubkey: instruction.keys[0].pubkey }, 45 | { name: "rent", isSigner: false, isWritable: false, pubkey: instruction.keys[1].pubkey }, 46 | ], 47 | args: { decimals: decodedIx.data.decimals, mintAuthority: decodedIx.data.mintAuthority, freezeAuthority: decodedIx.data.freezeAuthority }, 48 | } as ParsedIdlInstruction; 49 | break; 50 | } 51 | case TokenInstruction.InitializeAccount: { 52 | const decodedIx = decodeInitializeAccountInstruction(instruction); 53 | parsed = { 54 | name: "initializeAccount", 55 | accounts: [ 56 | { name: "account", ...decodedIx.keys.account }, 57 | { name: "mint", ...decodedIx.keys.mint }, 58 | { name: "owner", ...decodedIx.keys.owner }, 59 | { name: "rent", ...decodedIx.keys.rent }, 60 | ], 61 | args: {}, 62 | } as ParsedIdlInstruction; 63 | break; 64 | } 65 | case TokenInstruction.InitializeMultisig: { 66 | const decodedIx = decodeInitializeMultisigInstruction(instruction); 67 | const multisig = decodedIx.keys.signers.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 68 | parsed = { 69 | name: "initializeMultisig", 70 | accounts: [{ name: "multisig", ...decodedIx.keys.account }, { name: "rent", ...decodedIx.keys.rent }, ...multisig], 71 | args: { m: decodedIx.data.m }, 72 | } as ParsedIdlInstruction; 73 | break; 74 | } 75 | case TokenInstruction.Transfer: { 76 | const decodedIx = decodeTransferInstruction(instruction); 77 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 78 | parsed = { 79 | name: "transfer", 80 | accounts: [ 81 | { name: "source", ...decodedIx.keys.source }, 82 | { name: "destination", ...decodedIx.keys.destination }, 83 | { name: "authority", ...decodedIx.keys.owner }, 84 | ...multisig, 85 | ], 86 | args: { amount: new BN(decodedIx.data.amount.toString()) }, 87 | } as ParsedIdlInstruction; 88 | break; 89 | } 90 | case TokenInstruction.Approve: { 91 | const decodedIx = decodeApproveInstruction(instruction); 92 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 93 | parsed = { 94 | name: "approve", 95 | accounts: [ 96 | { name: "source", ...decodedIx.keys.account }, 97 | { name: "delegate", ...decodedIx.keys.delegate }, 98 | { name: "owner", ...decodedIx.keys.owner }, 99 | ...multisig, 100 | ], 101 | args: { amount: new BN(decodedIx.data.amount.toString()) }, 102 | } as ParsedIdlInstruction; 103 | break; 104 | } 105 | case TokenInstruction.Revoke: { 106 | const decodedIx = decodeRevokeInstruction(instruction); 107 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 108 | parsed = { 109 | name: "revoke", 110 | accounts: [{ name: "source", ...decodedIx.keys.account }, { name: "owner", ...decodedIx.keys.owner }, ...multisig], 111 | args: {}, 112 | } as ParsedIdlInstruction; 113 | break; 114 | } 115 | case TokenInstruction.SetAuthority: { 116 | const decodedIx = decodeSetAuthorityInstruction(instruction); 117 | const authrorityTypeMap = { 118 | [AuthorityType.AccountOwner]: { accountOwner: {} }, 119 | [AuthorityType.CloseAccount]: { closeAccount: {} }, 120 | [AuthorityType.FreezeAccount]: { freezeAccount: {} }, 121 | [AuthorityType.MintTokens]: { mintTokens: {} }, 122 | }; 123 | if ( 124 | ![AuthorityType.AccountOwner, AuthorityType.CloseAccount, AuthorityType.FreezeAccount, AuthorityType.MintTokens].includes( 125 | decodedIx.data.authorityType, 126 | ) 127 | ) { 128 | throw new Error("Unexpected authority type for token program"); 129 | } 130 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 131 | parsed = { 132 | name: "setAuthority", 133 | accounts: [{ name: "owned", ...decodedIx.keys.account }, { name: "owner", ...decodedIx.keys.currentAuthority }, ...multisig], 134 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 135 | // @ts-ignore 136 | args: { authorityType: authrorityTypeMap[decodedIx.data.authorityType], newAuthority: decodedIx.data.newAuthority }, 137 | } as ParsedIdlInstruction; 138 | break; 139 | } 140 | case TokenInstruction.MintTo: { 141 | const decodedIx = decodeMintToInstruction(instruction); 142 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 143 | parsed = { 144 | name: "mintTo", 145 | accounts: [ 146 | { name: "mint", ...decodedIx.keys.mint }, 147 | { name: "account", ...decodedIx.keys.destination }, 148 | { name: "owner", ...decodedIx.keys.authority }, 149 | ...multisig, 150 | ], 151 | args: { amount: new BN(decodedIx.data.amount.toString()) }, 152 | } as ParsedIdlInstruction; 153 | break; 154 | } 155 | case TokenInstruction.Burn: { 156 | const decodedIx = decodeBurnInstruction(instruction); 157 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 158 | parsed = { 159 | name: "burn", 160 | accounts: [ 161 | { name: "account", ...decodedIx.keys.account }, 162 | { name: "mint", ...decodedIx.keys.mint }, 163 | { name: "authority", ...decodedIx.keys.owner }, 164 | ...multisig, 165 | ], 166 | args: { amount: new BN(decodedIx.data.amount.toString()) }, 167 | } as ParsedIdlInstruction; 168 | break; 169 | } 170 | case TokenInstruction.CloseAccount: { 171 | const decodedIx = decodeCloseAccountInstruction(instruction); 172 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 173 | parsed = { 174 | name: "closeAccount", 175 | accounts: [ 176 | { name: "account", ...decodedIx.keys.account }, 177 | { name: "destination", ...decodedIx.keys.destination }, 178 | { name: "owner", ...decodedIx.keys.authority }, 179 | ...multisig, 180 | ], 181 | args: {}, 182 | } as ParsedIdlInstruction; 183 | break; 184 | } 185 | case TokenInstruction.FreezeAccount: { 186 | const decodedIx = decodeFreezeAccountInstruction(instruction); 187 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 188 | parsed = { 189 | name: "freezeAccount", 190 | accounts: [ 191 | { name: "account", ...decodedIx.keys.account }, 192 | { name: "mint", ...decodedIx.keys.mint }, 193 | { name: "owner", ...decodedIx.keys.authority }, 194 | ...multisig, 195 | ], 196 | args: {}, 197 | } as ParsedIdlInstruction; 198 | break; 199 | } 200 | case TokenInstruction.ThawAccount: { 201 | const decodedIx = decodeThawAccountInstruction(instruction); 202 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 203 | parsed = { 204 | name: "thawAccount", 205 | accounts: [ 206 | { name: "account", ...decodedIx.keys.account }, 207 | { name: "mint", ...decodedIx.keys.mint }, 208 | { name: "owner", ...decodedIx.keys.authority }, 209 | ...multisig, 210 | ], 211 | args: {}, 212 | } as ParsedIdlInstruction; 213 | break; 214 | } 215 | case TokenInstruction.TransferChecked: { 216 | const decodedIx = decodeTransferCheckedInstruction(instruction); 217 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 218 | parsed = { 219 | name: "transferChecked", 220 | accounts: [ 221 | { name: "source", ...decodedIx.keys.source }, 222 | { name: "mint", ...decodedIx.keys.mint }, 223 | { name: "destination", ...decodedIx.keys.destination }, 224 | { name: "authority", ...decodedIx.keys.owner }, 225 | ...multisig, 226 | ], 227 | args: { amount: new BN(decodedIx.data.amount.toString()), decimals: decodedIx.data.decimals }, 228 | } as ParsedIdlInstruction; 229 | break; 230 | } 231 | case TokenInstruction.ApproveChecked: { 232 | const decodedIx = decodeApproveCheckedInstruction(instruction); 233 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 234 | parsed = { 235 | name: "approveChecked", 236 | accounts: [ 237 | { name: "source", ...decodedIx.keys.account }, 238 | { name: "mint", ...decodedIx.keys.mint }, 239 | { name: "delegate", ...decodedIx.keys.delegate }, 240 | { name: "owner", ...decodedIx.keys.owner }, 241 | ...multisig, 242 | ], 243 | args: { amount: new BN(decodedIx.data.amount.toString()), decimals: decodedIx.data.decimals }, 244 | } as ParsedIdlInstruction; 245 | break; 246 | } 247 | case TokenInstruction.MintToChecked: { 248 | const decodedIx = decodeMintToCheckedInstruction(instruction); 249 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 250 | parsed = { 251 | name: "mintToChecked", 252 | accounts: [ 253 | { name: "mint", ...decodedIx.keys.mint }, 254 | { name: "account", ...decodedIx.keys.destination }, 255 | { name: "owner", ...decodedIx.keys.authority }, 256 | ...multisig, 257 | ], 258 | args: { amount: new BN(decodedIx.data.amount.toString()), decimals: decodedIx.data.decimals }, 259 | } as ParsedIdlInstruction; 260 | break; 261 | } 262 | case TokenInstruction.BurnChecked: { 263 | const decodedIx = decodeBurnCheckedInstruction(instruction); 264 | const multisig = decodedIx.keys.multiSigners.map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 265 | parsed = { 266 | name: "burnChecked", 267 | accounts: [ 268 | { name: "account", ...decodedIx.keys.account }, 269 | { name: "mint", ...decodedIx.keys.mint }, 270 | { name: "authority", ...decodedIx.keys.owner }, 271 | ...multisig, 272 | ], 273 | args: { amount: new BN(decodedIx.data.amount.toString()), decimals: decodedIx.data.decimals }, 274 | } as ParsedIdlInstruction; 275 | break; 276 | } 277 | case TokenInstruction.InitializeAccount2: { 278 | const decodedIx = decodeInitializeAccount2Instruction(instruction); 279 | parsed = { 280 | name: "initializeAccount2", 281 | accounts: [ 282 | { name: "account", ...decodedIx.keys.account }, 283 | { name: "mint", ...decodedIx.keys.mint }, 284 | { name: "rent", ...decodedIx.keys.rent }, 285 | ], 286 | args: { owner: new PublicKey(decodedIx.data.owner) }, 287 | } as ParsedIdlInstruction; 288 | break; 289 | } 290 | case TokenInstruction.SyncNative: { 291 | const decodedIx = decodeSyncNativeInstruction(instruction); 292 | parsed = { 293 | name: "syncNative", 294 | accounts: [{ name: "account", ...decodedIx.keys.account }], 295 | args: {}, 296 | } as ParsedIdlInstruction; 297 | break; 298 | } 299 | case TokenInstruction.InitializeAccount3: { 300 | const decodedIx = decodeInitializeAccount3Instruction(instruction); 301 | parsed = { 302 | name: "initializeAccount3", 303 | accounts: [ 304 | { name: "account", ...decodedIx.keys.account }, 305 | { name: "mint", ...decodedIx.keys.mint }, 306 | ], 307 | args: { owner: new PublicKey(decodedIx.data.owner) }, 308 | } as ParsedIdlInstruction; 309 | break; 310 | } 311 | case TokenInstruction.InitializeMultisig2: { 312 | const multisig = instruction.keys.slice(1).map((meta, idx) => ({ name: `signer_${idx}`, ...meta })); 313 | parsed = { 314 | name: "initializeMultisig2", 315 | accounts: [{ name: "multisig", ...instruction.keys[0] }, ...multisig], 316 | args: { m: instruction.data[1] }, 317 | } as ParsedIdlInstruction; 318 | break; 319 | } 320 | case TokenInstruction.InitializeMint2: { 321 | const decodedIx = decodeInitializeMint2Instruction(instruction); 322 | const tokenMint = decodedIx.keys.mint; 323 | if (!tokenMint) throw new Error(`Failed to parse InitializeMint2 instruction`); 324 | parsed = { 325 | name: "initializeMint2", 326 | accounts: [{ name: "mint", ...tokenMint }], 327 | args: { decimals: decodedIx.data.decimals, mintAuthority: decodedIx.data.mintAuthority, freezeAuthority: decodedIx.data.freezeAuthority }, 328 | } as ParsedIdlInstruction; 329 | break; 330 | } 331 | case TokenInstruction.InitializeImmutableOwner: { 332 | const decodedIx = decodeInitializeImmutableOwnerInstruction(instruction, instruction.programId); 333 | parsed = { 334 | name: "initializeImmutableOwner", 335 | accounts: [{ name: "account", ...decodedIx.keys.account }], 336 | args: {}, 337 | } as ParsedIdlInstruction; 338 | break; 339 | } 340 | case TokenInstruction.AmountToUiAmount: { 341 | const decodedIx = decodeAmountToUiAmountInstruction(instruction); 342 | parsed = { 343 | name: "amountToUiAmount", 344 | accounts: [{ name: "mint", ...decodedIx.keys.mint }], 345 | args: { amount: new BN(decodedIx.data.amount.toString()) }, 346 | } as ParsedIdlInstruction; 347 | break; 348 | } 349 | case TokenInstruction.UiAmountToAmount: { 350 | const decodedIx = decodeUiAmountToAmountInstruction(instruction); 351 | parsed = { 352 | name: "uiAmountToAmount", 353 | accounts: [{ name: "mint", ...decodedIx.keys.mint }], 354 | args: { uiAmount: new BN(decodedIx.data.amount).toString() }, 355 | } as ParsedIdlInstruction; 356 | break; 357 | } 358 | default: { 359 | parsed = null; 360 | } 361 | } 362 | 363 | return parsed 364 | ? { 365 | ...parsed, 366 | programId: TOKEN_PROGRAM_ID, 367 | } 368 | : { 369 | programId: TOKEN_PROGRAM_ID, 370 | name: "unknown", 371 | accounts: instruction.keys, 372 | args: { unknown: instruction.data }, 373 | }; 374 | } 375 | 376 | export { decodeTokenInstruction }; 377 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { utils } from "@coral-xyz/anchor"; 2 | import { 3 | AccountMeta, 4 | CompiledInstruction, 5 | LoadedAddresses, 6 | Message, 7 | MessageCompiledInstruction, 8 | PartiallyDecodedInstruction, 9 | PublicKey, 10 | TransactionInstruction, 11 | VersionedMessage, 12 | VersionedTransactionResponse, 13 | } from "@solana/web3.js"; 14 | 15 | import { ProgramLogContext } from "./interfaces"; 16 | 17 | export function hexToBuffer(data: string) { 18 | const rawHex = data.startsWith("0x") ? data.slice(2) : data; 19 | 20 | return Buffer.from(rawHex); 21 | } 22 | 23 | /** 24 | * Parse transaction message and extract account metas 25 | * @param message transaction message 26 | * @returns parsed accounts metas 27 | */ 28 | export function parseTransactionAccounts( 29 | message: T, 30 | loadedAddresses: T extends VersionedMessage ? LoadedAddresses | undefined : undefined = undefined, 31 | ): AccountMeta[] { 32 | const accounts: PublicKey[] = message.version === "legacy" ? message.accountKeys : message.staticAccountKeys; 33 | const readonlySignedAccountsCount = message.header.numReadonlySignedAccounts; 34 | const readonlyUnsignedAccountsCount = message.header.numReadonlyUnsignedAccounts; 35 | const requiredSignaturesAccountsCount = message.header.numRequiredSignatures; 36 | const totalAccounts = accounts.length; 37 | let parsedAccounts: AccountMeta[] = accounts.map((account, idx) => { 38 | const isWritable = 39 | idx < requiredSignaturesAccountsCount - readonlySignedAccountsCount || 40 | (idx >= requiredSignaturesAccountsCount && idx < totalAccounts - readonlyUnsignedAccountsCount); 41 | 42 | return { 43 | isSigner: idx < requiredSignaturesAccountsCount, 44 | isWritable, 45 | pubkey: new PublicKey(account), 46 | } as AccountMeta; 47 | }); 48 | const [ALTWritable, ALTReadOnly] = 49 | message.version === "legacy" ? [[], []] : loadedAddresses ? [loadedAddresses.writable, loadedAddresses.readonly] : [[], []]; // message.getAccountKeys({ accountKeysFromLookups: loadedAddresses }).keySegments().slice(1); // omit static keys 50 | if (ALTWritable) parsedAccounts = [...parsedAccounts, ...ALTWritable.map((pubkey) => ({ isSigner: false, isWritable: true, pubkey }))]; 51 | if (ALTReadOnly) parsedAccounts = [...parsedAccounts, ...ALTReadOnly.map((pubkey) => ({ isSigner: false, isWritable: false, pubkey }))]; 52 | 53 | return parsedAccounts; 54 | } 55 | 56 | /** 57 | * Converts compiled instruction into common TransactionInstruction 58 | * @param compiledInstruction 59 | * @param parsedAccounts account meta, result of {@link parseTransactionAccounts} 60 | * @returns TransactionInstruction 61 | */ 62 | export function compiledInstructionToInstruction( 63 | compiledInstruction: Ix, 64 | parsedAccounts: AccountMeta[], 65 | ): TransactionInstruction { 66 | if (typeof compiledInstruction.data === "string") { 67 | const ci = compiledInstruction as CompiledInstruction; 68 | 69 | return new TransactionInstruction({ 70 | data: utils.bytes.bs58.decode(ci.data), 71 | programId: parsedAccounts[ci.programIdIndex].pubkey, 72 | keys: ci.accounts.map((accountIdx) => parsedAccounts[accountIdx]), 73 | }); 74 | } else { 75 | const ci = compiledInstruction as MessageCompiledInstruction; 76 | 77 | return new TransactionInstruction({ 78 | data: Buffer.from(ci.data), 79 | programId: parsedAccounts[ci.programIdIndex].pubkey, 80 | keys: ci.accountKeyIndexes.map((accountIndex) => { 81 | if (accountIndex >= parsedAccounts.length) 82 | throw new Error( 83 | `Trying to resolve account at index ${accountIndex} while parsedAccounts is only ${parsedAccounts.length}. \ 84 | Looks like you're trying to parse versioned transaction, make sure that LoadedAddresses are passed to the \ 85 | parseTransactionAccounts function`, 86 | ); 87 | 88 | return parsedAccounts[accountIndex]; 89 | }), 90 | }); 91 | } 92 | } 93 | 94 | function parsedAccountsToMeta(accounts: PublicKey[], accountMeta: AccountMeta[]): AccountMeta[] { 95 | const meta = accountMeta.map((m) => ({ pk: m.pubkey.toString(), ...m })); 96 | 97 | return accounts.map((account) => { 98 | const encoded = account.toString(); 99 | const found = meta.find((item) => item.pk === encoded); 100 | if (!found) throw new Error(`Account ${encoded} not present in account meta!`); 101 | 102 | return found; 103 | }); 104 | } 105 | 106 | export function parsedInstructionToInstruction(parsedInstruction: PartiallyDecodedInstruction, accountMeta: AccountMeta[]): TransactionInstruction { 107 | return new TransactionInstruction({ 108 | data: utils.bytes.bs58.decode(parsedInstruction.data), 109 | programId: parsedInstruction.programId, 110 | keys: parsedAccountsToMeta(parsedInstruction.accounts, accountMeta), 111 | }); 112 | } 113 | 114 | /** 115 | * Converts transaction response with CPI into artifical transaction that contains all instructions from tx and CPI 116 | * @param transaction transactionResponse to convert from 117 | * @returns Transaction object 118 | */ 119 | export function flattenTransactionResponse(transaction: VersionedTransactionResponse): TransactionInstruction[] { 120 | const result: TransactionInstruction[] = []; 121 | if (transaction === null || transaction === undefined) return []; 122 | const txInstructions = transaction.transaction.message.compiledInstructions; 123 | const accountsMeta = parseTransactionAccounts(transaction.transaction.message, transaction.meta?.loadedAddresses); 124 | const orderedCII = (transaction?.meta?.innerInstructions || []).sort((a, b) => a.index - b.index); 125 | const totalCalls = 126 | (transaction.meta?.innerInstructions || []).reduce((accumulator, cii) => accumulator + cii.instructions.length, 0) + txInstructions.length; 127 | let lastPushedIx = -1; 128 | let callIndex = -1; 129 | for (const CII of orderedCII) { 130 | // push original instructions until we meet CPI 131 | while (lastPushedIx !== CII.index) { 132 | lastPushedIx += 1; 133 | callIndex += 1; 134 | result.push(compiledInstructionToInstruction(txInstructions[lastPushedIx], accountsMeta)); 135 | } 136 | for (const CIIEntry of CII.instructions) { 137 | result.push(compiledInstructionToInstruction(CIIEntry, accountsMeta)); 138 | callIndex += 1; 139 | } 140 | } 141 | while (callIndex < totalCalls - 1) { 142 | lastPushedIx += 1; 143 | callIndex += 1; 144 | result.push(compiledInstructionToInstruction(txInstructions[lastPushedIx], accountsMeta)); 145 | } 146 | 147 | return result; 148 | } 149 | 150 | /** 151 | * @private 152 | */ 153 | function newLogContext(programId: string, depth: number, id: number, instructionIndex: number): ProgramLogContext { 154 | return { 155 | logMessages: [], 156 | dataLogs: [], 157 | rawLogs: [], 158 | errors: [], 159 | programId, 160 | depth, 161 | id, 162 | instructionIndex, 163 | }; 164 | } 165 | 166 | function generateLogsParsingRegex() { 167 | const knownMsgs = [ 168 | "{}'s writable privilege escalated", 169 | "{}'s signer privilege escalated", 170 | "Unknown program {}", 171 | "Account {} is not executable", 172 | "Unable to deserialize config account: {}", 173 | "account {} is not in account list", 174 | "account {} signer_key().is_none()", 175 | "account[{}].signer_key() does not match Config data)", 176 | "account {} is not in stored signer list", 177 | "account[0].signer_key().is_none()", 178 | "new config contains duplicate keys", 179 | "too few signers: {}; expected: {}", 180 | "instruction data too large", 181 | "Checking if destination stake is mergeable", 182 | "Checking if source stake is mergeable", 183 | "Merging stake accounts", 184 | "expected uninitialized stake account owner to be {}, not {}", 185 | "expected uninitialized stake account data len to be {}, not {}", 186 | "expected uninitialized stake account to be uninitialized", 187 | "expected vote account owner to be {}, not {}", 188 | "stake is not active", 189 | "redelegating to the same vote account not permitted", 190 | "invalid stake account data", 191 | "Unable to merge due to metadata mismatch", 192 | "Unable to merge due to voter mismatch", 193 | "Unable to merge due to stake deactivation", 194 | "invalid proof data", 195 | "proof verification failed: {}", 196 | "proof_verification failed: {}", 197 | "CloseContextState", 198 | "VerifyZeroBalance", 199 | "VerifyWithdraw", 200 | "VerifyCiphertextCiphertextEquality", 201 | "VerifyTransfer", 202 | "VerifyTransferWithFee", 203 | "VerifyPubkeyValidity", 204 | "VerifyRangeProof", 205 | "VerifyBatchedRangeProof64", 206 | "VerifyBatchedRangeProof128", 207 | "VerifyBatchedRangeProof256", 208 | "VerifyCiphertextCommitmentEquality", 209 | "VerifyGroupedCiphertext2HandlesValidity", 210 | "VerifyBatchedGroupedCiphertext2HandlesValidity", 211 | "VerifyFeeSigma", 212 | "VerifyGroupedCiphertext3HandlesValidity", 213 | "VerifyBatchedGroupedCiphertext3HandlesValidity", 214 | "Create: address {} does not match derived address {}", 215 | "Allocate: 'to' account {} must sign", 216 | "Allocate: account {} already in use", 217 | "Allocate: requested {}, max allowed {}", 218 | "Assign: account {} must sign", 219 | "Create Account: account {} already in use", 220 | "Transfer: `from` must not carry data", 221 | "Transfer: insufficient lamports {}, need {}", 222 | "Transfer: `from` account {} must sign", 223 | "Transfer: 'from' account {} must sign", 224 | "Transfer: 'from' address {} does not match derived address {}", 225 | "Advance nonce account: recent blockhash list is empty", 226 | "Initialize nonce account: recent blockhash list is empty", 227 | "Advance nonce account: Account {} must be writeable", 228 | "Advance nonce account: Account {} must be a signer", 229 | "Advance nonce account: nonce can only advance once per slot", 230 | "Advance nonce account: Account {} state is invalid", 231 | "Withdraw nonce account: Account {} must be writeable", 232 | "Withdraw nonce account: insufficient lamports {}, need {}", 233 | "Withdraw nonce account: nonce can only advance once per slot", 234 | "Withdraw nonce account: Account {} must sign", 235 | "Initialize nonce account: Account {} must be writeable", 236 | "Initialize nonce account: insufficient lamports {}, need {}", 237 | "Initialize nonce account: Account {} state is invalid", 238 | "Authorize nonce account: Account {} must be writeable", 239 | "Authorize nonce account: Account {} state is invalid", 240 | "Authorize nonce account: Account {} must sign", 241 | "Failed to get runtime environment: {}", 242 | "Failed to register syscalls: {}", 243 | "Write overflow: {} < {}", 244 | "Poseidon hashing {} sequences is not supported", 245 | "Overflow while calculating the compute cost", 246 | "{} Hashing {} sequences in one syscall is over the limit {}", 247 | "Invalid account info pointer `{}': {} != {}", 248 | "Internal error: index mismatch for account {}", 249 | "Account data size realloc limited to {} in inner instructions", 250 | "Table account must not be allocated", 251 | "Authority account must be a signer", 252 | "Payer account must be a signer", 253 | "{} is not a recent slot", 254 | "Table address must match derived address: {}", 255 | "Lookup table is already frozen", 256 | "Deactivated tables cannot be frozen", 257 | "Empty lookup tables cannot be frozen", 258 | "Deactivated tables cannot be extended", 259 | "Lookup table is full and cannot contain more addresses", 260 | "Must extend with at least one address", 261 | "Extended lookup table length {} would exceed max capacity of {}", 262 | "Lookup table is frozen", 263 | "Lookup table is already deactivated", 264 | "Lookup table cannot be the recipient of reclaimed lamports", 265 | "Lookup table is not deactivated", 266 | "Table cannot be closed until it's fully deactivated in {} blocks", 267 | ]; 268 | 269 | const prepareLineForRegex = (l: string) => 270 | l.replaceAll("{}", ".*").replaceAll("(", "\\(").replaceAll(")", "\\)").replaceAll("]", "\\]").replaceAll("[", "\\["); 271 | 272 | const regexTemplate = `\ 273 | (?^Log truncated$)|\ 274 | (?^Program (?[1-9A-HJ-NP-Za-km-z]{32,}) invoke \\[(?\\d+)\\]$)|\ 275 | (?^Program (?[1-9A-HJ-NP-Za-km-z]{32,}) success$)|\ 276 | (?^Program (?[1-9A-HJ-NP-Za-km-z]{32,}) failed: (?.*)$)|\ 277 | (?^Program failed to complete: (?.*)$)|\ 278 | (?^Program log: (?.*)$)|\ 279 | (?^Program data: (?.*)$)|\ 280 | (?^Program (?[1-9A-HJ-NP-Za-km-z]{32,}) consumed (?\\d*) of (?\\d*) compute units$)|\ 281 | (?^Program return: (?[1-9A-HJ-NP-Za-km-z]{32,}) (?.*)$)|\ 282 | (?^(${knownMsgs.map(prepareLineForRegex).join("|")})$)`; 283 | 284 | return new RegExp(regexTemplate, "s"); 285 | } 286 | 287 | type ImmediateLogContext = { 288 | id: number; 289 | currentInstruction: number; 290 | currentDepth: number; 291 | }; 292 | 293 | type FullLogContext = { 294 | immediate: ImmediateLogContext; 295 | callStack: string[]; 296 | callIds: number[]; 297 | }; 298 | 299 | function programEnter(context: FullLogContext, invokedProgram: string, invokeLevel: number): ProgramLogContext { 300 | if (invokeLevel != context.immediate.currentDepth + 1) 301 | throw new Error(`invoke depth mismatch, log: ${invokeLevel}, expected: ${context.immediate.currentDepth}`); 302 | context.immediate.id += 1; 303 | context.immediate.currentDepth += 1; 304 | 305 | context.callStack.push(invokedProgram); 306 | context.callIds.push(context.immediate.id); 307 | 308 | return newLogContext(invokedProgram, invokeLevel, context.immediate.id, context.immediate.currentInstruction); 309 | } 310 | 311 | function programExit(context: FullLogContext, exitedProgram: string): number { 312 | const lastProgram = context.callStack.pop(); 313 | const lastCallIndex = context.callIds.pop(); 314 | if (lastCallIndex === undefined) throw new Error("callIds malformed"); 315 | if (lastProgram != exitedProgram) throw new Error("[ProramExit] callstack mismatch"); 316 | 317 | context.immediate.currentDepth -= 1; 318 | if (context.immediate.currentDepth === 0) { 319 | context.immediate.currentInstruction += 1; 320 | } 321 | 322 | return lastCallIndex; 323 | } 324 | 325 | /** 326 | * Parses transaction logs and provides additional context such as 327 | * - programId that generated the message 328 | * - call id of instruction, that generated the message 329 | * - call depth of instruction 330 | * - data messages, log messages and error messages 331 | * @param logs logs from TransactionResponse.meta.logs 332 | * @returns parsed logs with call depth and additional context 333 | */ 334 | export function parseLogs(logs: string[]): ProgramLogContext[] { 335 | const parserRe = generateLogsParsingRegex(); 336 | const programLogs: ProgramLogContext[] = []; 337 | 338 | const immediate: ImmediateLogContext = { 339 | id: -1, 340 | currentInstruction: 0, 341 | currentDepth: 0, 342 | }; 343 | const context: FullLogContext = { 344 | immediate, 345 | callStack: [], 346 | callIds: [], 347 | }; 348 | 349 | const getCurrentCallId = (c: FullLogContext) => c.callIds[c.callIds.length - 1]; 350 | const getCurrentProgram = (c: FullLogContext) => c.callStack[c.callStack.length - 1]; 351 | 352 | for (const log of logs) { 353 | const match = parserRe.exec(log); 354 | if (!match || !match.groups) { 355 | throw new Error(`Failed to parse log line: ${log}`); 356 | } 357 | 358 | if (match.groups.programInvoke) { 359 | const program = match.groups.invokeProgramId; 360 | const level = Number(match.groups.level); 361 | const newProgramContext = programEnter(context, program, level); 362 | 363 | newProgramContext.rawLogs.push(log); 364 | programLogs.push(newProgramContext); 365 | } else if (match.groups.programSuccessResult) { 366 | const lastCallIndex = programExit(context, match.groups.successResultProgramId); 367 | programLogs[lastCallIndex].rawLogs.push(log); 368 | } else if (match.groups.programFailedResult) { 369 | const lastCallIndex = programExit(context, match.groups.failedResultProgramId); 370 | 371 | programLogs[lastCallIndex].rawLogs.push(log); 372 | programLogs[lastCallIndex].errors.push(log); 373 | } else if (match.groups.programCompleteFailedResult) { 374 | const currentCall = getCurrentCallId(context); 375 | 376 | programLogs[currentCall].rawLogs.push(log); 377 | programLogs[currentCall].errors.push(match.groups.failedCompleteError); 378 | } else { 379 | const currentCall = getCurrentCallId(context); 380 | programLogs[currentCall].rawLogs.push(log); 381 | 382 | if (match.groups.logTruncated) { 383 | programLogs[currentCall].invokeResult = "Log truncated"; 384 | } else if (match.groups.programLog) { 385 | programLogs[currentCall].logMessages.push(match.groups.logMessage); 386 | } else if (match.groups.programData) { 387 | programLogs[currentCall].dataLogs.push(match.groups.data); 388 | } else if (match.groups.programConsumed) { 389 | programLogs[currentCall].unitsConsumed = Number(match.groups.consumedComputeUnits); 390 | } else if (match.groups.programReturn) { 391 | const returnProgram = match.groups.returnProgramId; 392 | if (getCurrentProgram(context) != returnProgram) throw new Error("[InvokeReturn]: callstack mismatch"); 393 | programLogs[currentCall].invokeResult = match.groups.returnMessage; 394 | } else if (match.groups.errorMessage) { 395 | programLogs[currentCall].errors.push(log); 396 | } 397 | } 398 | } 399 | 400 | return programLogs; 401 | } 402 | 403 | /** Python script to extract native solana logs 404 | * 405 | # coding=utf8 406 | # the above tag defines encoding for this document and is for Python 2.x compatibility 407 | 408 | import re 409 | import os 410 | 411 | regex = r"ic_msg!\((\s|.)*?,\s*?\"(?P.*?)\"" 412 | 413 | def print_logs(data): 414 | matches = re.finditer(regex, data) 415 | for m in matches: 416 | print(m.group('log')) 417 | 418 | def open_files_in_directory(directory): 419 | for root, dirs, files in os.walk(directory): 420 | for filename in files: 421 | if '.rs' not in filename: 422 | continue 423 | file_path = os.path.join(root, filename) 424 | try: 425 | with open(file_path, 'r', encoding='utf-8') as file: 426 | print(f'Opened file: {file_path}') 427 | content = file.read() 428 | print_logs(content) 429 | except Exception as e: 430 | print(f'Could not open file {file_path}: {e}') 431 | for d in dirs: 432 | if d == '.' or d == '..': 433 | continue 434 | open_files_in_directory(os.path.join(root, d)) 435 | 436 | if __name__ == "__main__": 437 | open_files_in_directory('INPUT DIR HERE') 438 | 439 | */ 440 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./parsers"; 2 | export * from "./helpers"; 3 | export * from "./interfaces"; 4 | export { convertLegacyIdlToV30 } from "./legacy.idl.converter"; 5 | export * as idl from "./programs"; 6 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { BN, Idl, IdlTypes, DecodeType, BorshInstructionCoder, BorshEventCoder } from "@coral-xyz/anchor"; 2 | import { AccountMeta, PublicKey, Transaction, TransactionInstruction } from "@solana/web3.js"; 3 | 4 | /** 5 | * Context of logs for specific instruction 6 | */ 7 | export type ProgramLogContext = { 8 | rawLogs: string[]; 9 | errors: string[]; 10 | logMessages: string[]; 11 | dataLogs: string[]; 12 | programId: string; 13 | depth: number; 14 | id: number; 15 | instructionIndex: number; 16 | invokeResult?: string; 17 | unitsConsumed?: number; 18 | }; 19 | 20 | export type TransactionWithLogs = { 21 | logs?: string[]; 22 | transaction: Transaction; 23 | }; 24 | 25 | /** 26 | * Map which keys are programIds (base58-encoded) and values are ix parsers 27 | */ 28 | export type InstructionParsers = Map>; 29 | 30 | /** 31 | * Map which keys are programIds (base58-encoded) and values are ix parsers 32 | */ 33 | export type IdlParser = Map; 34 | 35 | /** 36 | * Function that takes transaction ix and returns parsed variant 37 | */ 38 | export type ParserFunction> = (arg: TransactionInstruction) => ParsedInstruction; 39 | 40 | /** 41 | * public key as base58 string, parser 42 | */ 43 | export type InstructionParserInfo = [string, ParserFunction]; 44 | 45 | export interface ParsedAccount extends AccountMeta { 46 | /** Account name, same as in Idl, nested accounts look like `account.nestedAccount` */ 47 | name?: string; 48 | } 49 | 50 | /** 51 | * Instructions args with correct types for specific instruction by instruction name 52 | */ 53 | export type ParsedIdlArgsByInstructionName = { 54 | [ArgName in Ix["args"][number]["name"]]: DecodeType<(Ix["args"][number] & { name: ArgName })["type"], IdlTypes>; 55 | }; 56 | 57 | export type InstructionNames = I["instructions"][number]["name"]; 58 | export type EventNames = I extends { events: Array<{ name: infer N }> } ? (N extends string ? N : never) : never; 59 | 60 | export type ParsedIdlArgs = InstructionNames> = ParsedIdlArgsByInstructionName>; 61 | export type ParsedIdlType> = IdlTypes[EventName]; 62 | 63 | export type UnknownInstruction = { 64 | name: "unknown" | string; 65 | args: { unknown: unknown }; 66 | accounts: ParsedAccount[]; 67 | programId: PublicKey; 68 | }; 69 | 70 | export type ParsedInstruction = InstructionNames> = 71 | | UnknownInstruction 72 | | ParsedIdlInstruction 73 | | ParsedCustomInstruction; 74 | 75 | export interface ParsedCustomInstruction { 76 | /** Instruction name */ 77 | name: string; 78 | programId: PublicKey; 79 | /** Parsed arguments */ 80 | args: unknown; 81 | /** Parsed accounts */ 82 | accounts: ParsedAccount[]; 83 | } 84 | 85 | export interface ParsedIdlInstruction = InstructionNames> { 86 | /** Instruction name */ 87 | name: IxName; 88 | programId: PublicKey; 89 | /** Parsed arguments */ 90 | args: ParsedIdlArgs; 91 | /** Parsed accounts */ 92 | accounts: IdlAccountsToFlatMeta["accounts"]>; 93 | } 94 | 95 | export interface ParsedIdlEvent = EventNames> { 96 | /** Instruction name */ 97 | name: EventName; 98 | programId: PublicKey; 99 | /** Parsed arguments */ 100 | args: ParsedIdlType; 101 | /** Parsed accounts */ 102 | accounts: Array<{ name: string; [key: string]: any }>; 103 | } 104 | 105 | export type IdlInstructionAccountItem2 = IdlInstructionAccount | IdlInstructionAccounts; 106 | 107 | type IdlInstructionAccount = { 108 | name: string; 109 | docs?: string[]; 110 | writable?: boolean; 111 | signer?: boolean; 112 | optional?: boolean; 113 | address?: string; 114 | relations?: string[]; 115 | }; 116 | 117 | type IdlInstructionAccounts = { 118 | name: string; 119 | accounts: IdlInstructionAccountItem2[]; 120 | }; 121 | 122 | export type IdlAccountsToFlatMeta = T extends [infer First, ...infer Rest] 123 | ? First extends IdlInstructionAccounts 124 | ? [ 125 | ...IdlAccountsToFlatMeta, 126 | ...IdlAccountsToFlatMeta, 127 | ] 128 | : First extends IdlInstructionAccount 129 | ? [ 130 | { 131 | name: `${Prefix}${First["name"]}`; 132 | isSigner: First["signer"] extends boolean ? First["signer"] : false; 133 | isWritable: First["writable"] extends boolean ? First["writable"] : false; 134 | pubkey: PublicKey; 135 | }, 136 | ...IdlAccountsToFlatMeta, 137 | ] 138 | : never 139 | : []; 140 | 141 | export interface ProgramInfoType { 142 | idl: Idl; 143 | programId: PublicKey | string; 144 | } 145 | 146 | /** 147 | * @private 148 | */ 149 | type TypeMap = { 150 | publicKey: PublicKey; 151 | pubkey: PublicKey; 152 | bool: boolean; 153 | string: string; 154 | } & { 155 | [K in "u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "f32" | "f64"]: number; 156 | } & { 157 | [K in "u64" | "i64" | "u128" | "i128"]: BN; 158 | }; 159 | 160 | type IdlType = Idl["instructions"][number]["args"][number]["type"]; 161 | 162 | /** 163 | * @deprecated 164 | * @private 165 | */ 166 | export type LegacyDecodeType = T extends keyof TypeMap 167 | ? TypeMap[T] 168 | : T extends { defined: keyof Defined } 169 | ? Defined[T["defined"]] 170 | : T extends { option: { defined: keyof Defined } } 171 | ? Defined[T["option"]["defined"]] 172 | : T extends { option: keyof TypeMap } 173 | ? TypeMap[T["option"]] 174 | : T extends { vec: keyof TypeMap } 175 | ? TypeMap[T["vec"]][] 176 | : T extends { vec: keyof Defined } 177 | ? Defined[T["vec"]][] 178 | : T extends { array: [defined: keyof TypeMap, size: number] } 179 | ? TypeMap[T["array"][0]][] 180 | : unknown; 181 | 182 | /** 183 | * Interface to get instruction by name from IDL 184 | */ 185 | export type IxByName = I["instructions"][number] & { name: IxName }; 186 | 187 | export type IdlAccount = { 188 | name: string; 189 | signer?: boolean; 190 | writable?: boolean; 191 | }; 192 | -------------------------------------------------------------------------------- /src/legacy.idl.converter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a port of the anchor command `anchor idl convert` to TypeScript. 3 | */ 4 | import { Idl } from "@coral-xyz/anchor"; 5 | import { 6 | IdlAccount, 7 | IdlConst, 8 | IdlDefinedFields, 9 | IdlEnumVariant, 10 | IdlErrorCode, 11 | IdlEvent, 12 | IdlField, 13 | IdlInstruction, 14 | IdlInstructionAccountItem, 15 | IdlInstructionAccounts, 16 | IdlMetadata, 17 | IdlType, 18 | IdlTypeDef, 19 | IdlTypeDefined, 20 | } from "@coral-xyz/anchor/dist/cjs/idl"; 21 | import { sha256 } from "@noble/hashes/sha256"; 22 | 23 | import { camelCase } from "./camelcase"; 24 | 25 | function camelToUnderscore(key: string) { 26 | const result = key.replace(/([A-Z])/g, " $1"); 27 | 28 | return result.split(" ").join("_").toLowerCase(); 29 | } 30 | 31 | const snakeCase = camelToUnderscore; 32 | 33 | // Legacy types based on the Rust structs 34 | // Should be included in next minor release of anchor 35 | type LegacyIdl = { 36 | version: string; 37 | name: string; 38 | docs?: string[]; 39 | constants: LegacyIdlConst[]; 40 | instructions: LegacyIdlInstruction[]; 41 | accounts: LegacyIdlTypeDefinition[]; 42 | types: LegacyIdlTypeDefinition[]; 43 | events?: LegacyIdlEvent[]; 44 | errors?: LegacyIdlErrorCode[]; 45 | metadata?: { address: string }; 46 | }; 47 | 48 | type LegacyIdlConst = { 49 | name: string; 50 | type: LegacyIdlType; 51 | value: string; 52 | }; 53 | 54 | type LegacyIdlInstruction = { 55 | name: string; 56 | docs?: string[]; 57 | accounts: LegacyIdlAccountItem[]; 58 | args: LegacyIdlField[]; 59 | returns?: LegacyIdlType; 60 | }; 61 | 62 | type LegacyIdlTypeDefinition = { 63 | name: string; 64 | docs?: string[]; 65 | type: LegacyIdlTypeDefinitionTy; 66 | }; 67 | 68 | type LegacyIdlTypeDefinitionTy = 69 | | { kind: "struct"; fields: LegacyIdlField[] } 70 | | { kind: "enum"; variants: LegacyIdlEnumVariant[] } 71 | | { kind: "alias"; value: LegacyIdlType }; 72 | 73 | type LegacyIdlField = { 74 | name: string; 75 | docs?: string[]; 76 | type: LegacyIdlType; 77 | }; 78 | 79 | type LegacyIdlEnumVariant = { 80 | name: string; 81 | fields?: LegacyEnumFields; 82 | }; 83 | 84 | type LegacyEnumFields = LegacyIdlField[] | LegacyIdlType[]; 85 | 86 | type LegacyIdlEvent = { 87 | name: string; 88 | fields: LegacyIdlEventField[]; 89 | }; 90 | 91 | type LegacyIdlEventField = { 92 | name: string; 93 | type: LegacyIdlType; 94 | index: boolean; 95 | }; 96 | 97 | type LegacyIdlErrorCode = { 98 | code: number; 99 | name: string; 100 | msg?: string; 101 | }; 102 | 103 | type LegacyIdlAccountItem = LegacyIdlAccount | LegacyIdlAccounts; 104 | 105 | type LegacyIdlAccount = { 106 | name: string; 107 | isMut: boolean; 108 | isSigner: boolean; 109 | isOptional?: boolean; 110 | docs?: string[]; 111 | pda?: LegacyIdlPda; 112 | relations: string[]; 113 | }; 114 | 115 | type LegacyIdlAccounts = { 116 | name: string; 117 | accounts: LegacyIdlAccountItem[]; 118 | }; 119 | 120 | type LegacyIdlPda = { 121 | seeds: LegacyIdlSeed[]; 122 | programId?: LegacyIdlSeed; 123 | }; 124 | 125 | type LegacyIdlSeed = 126 | | { kind: "const"; type: LegacyIdlType; value: any } 127 | | { kind: "arg"; type: LegacyIdlType; path: string } 128 | | { kind: "account"; type: LegacyIdlType; account?: string; path: string }; 129 | 130 | type LegacyIdlType = 131 | | "bool" 132 | | "u8" 133 | | "i8" 134 | | "u16" 135 | | "i16" 136 | | "u32" 137 | | "i32" 138 | | "u64" 139 | | "i64" 140 | | "u128" 141 | | "i128" 142 | | "f32" 143 | | "f64" 144 | | "bytes" 145 | | "string" 146 | | "publicKey" 147 | | { vec: LegacyIdlType } 148 | | { option: LegacyIdlType } 149 | | { defined: string } 150 | | { array: [LegacyIdlType, number] } 151 | | { generic: string } 152 | | { definedWithTypeArgs: { name: string; args: LegacyIdlDefinedTypeArg[] } }; 153 | 154 | type LegacyIdlDefinedTypeArg = { generic: string } | { value: string } | { type: LegacyIdlType }; 155 | 156 | function convertLegacyIdl(legacyIdl: LegacyIdl, programAddress?: string): Idl { 157 | const address: string | undefined = programAddress ?? legacyIdl.metadata?.address; 158 | if (!address) { 159 | throw new Error("Program id missing in `idl.metadata.address` field"); 160 | } 161 | 162 | return { 163 | accounts: (legacyIdl.accounts || []).map(convertAccount), 164 | address: address, 165 | constants: (legacyIdl.constants || []).map(convertConst), 166 | errors: legacyIdl.errors?.map(convertErrorCode) || [], 167 | events: legacyIdl.events?.map(convertEvent) || [], 168 | instructions: legacyIdl.instructions.map(convertInstruction), 169 | metadata: { 170 | name: legacyIdl.name, 171 | version: legacyIdl.version, 172 | spec: "0.1.0", 173 | } as IdlMetadata, 174 | types: [ 175 | ...(legacyIdl.types || []).map(convertTypeDef), 176 | ...(legacyIdl.accounts || []).map(convertTypeDef), 177 | ...(legacyIdl.events || []).map(convertEventToTypeDef), 178 | ], 179 | }; 180 | } 181 | 182 | function getDisc(prefix: string, name: string): number[] { 183 | const hash = sha256(`${prefix}:${name}`); 184 | 185 | return Array.from(hash.slice(0, 8)); 186 | } 187 | 188 | function convertInstruction(instruction: LegacyIdlInstruction): IdlInstruction { 189 | const name = instruction.name; 190 | 191 | return { 192 | accounts: instruction.accounts.map(convertInstructionAccount), 193 | args: instruction.args.map(convertField), 194 | discriminator: getDisc("global", snakeCase(name)), 195 | name, 196 | returns: instruction.returns ? convertType(instruction.returns) : undefined, 197 | }; 198 | } 199 | 200 | function convertAccount(account: LegacyIdlTypeDefinition): IdlAccount { 201 | return { 202 | discriminator: getDisc("account", camelCase(account.name, { preserveConsecutiveUppercase: true, pascalCase: true })), 203 | name: account.name, 204 | }; 205 | } 206 | 207 | function convertTypeDef(typeDef: LegacyIdlTypeDefinition): IdlTypeDef { 208 | return { 209 | name: typeDef.name, 210 | type: convertTypeDefTy(typeDef.type), 211 | }; 212 | } 213 | 214 | function convertTypeDefTy(type: LegacyIdlTypeDefinitionTy): IdlTypeDef["type"] { 215 | switch (type.kind) { 216 | case "struct": 217 | return { 218 | fields: type.fields.map(convertField), 219 | kind: "struct", 220 | }; 221 | case "enum": 222 | return { 223 | kind: "enum", 224 | variants: type.variants.map(convertEnumVariant), 225 | }; 226 | case "alias": 227 | return { 228 | alias: convertType(type.value), 229 | kind: "type", 230 | }; 231 | } 232 | } 233 | 234 | function convertField(field: LegacyIdlField): IdlField { 235 | return { 236 | name: field.name, 237 | type: convertType(field.type), 238 | }; 239 | } 240 | 241 | function convertEnumVariant(variant: LegacyIdlEnumVariant): IdlEnumVariant { 242 | return { 243 | fields: variant.fields ? convertEnumFields(variant.fields) : undefined, 244 | name: variant.name, 245 | }; 246 | } 247 | 248 | function convertEnumFields(fields: LegacyEnumFields): IdlDefinedFields { 249 | if (Array.isArray(fields) && fields.length > 0 && typeof fields[0] === "object" && "type" in fields[0]) { 250 | return (fields as LegacyIdlField[]).map(convertField); 251 | } else { 252 | return (fields as LegacyIdlType[]).map((type) => convertType(type)); 253 | } 254 | } 255 | 256 | function convertEvent(event: LegacyIdlEvent): IdlEvent { 257 | return { 258 | discriminator: getDisc("event", event.name), 259 | name: event.name, 260 | }; 261 | } 262 | 263 | function convertErrorCode(error: LegacyIdlErrorCode): IdlErrorCode { 264 | return { 265 | code: error.code, 266 | msg: error.msg, 267 | name: error.name, 268 | }; 269 | } 270 | 271 | function convertConst(constant: LegacyIdlConst): IdlConst { 272 | return { 273 | name: constant.name, 274 | type: convertType(constant.type), 275 | value: constant.value, 276 | }; 277 | } 278 | 279 | function convertInstructionAccount(account: LegacyIdlAccountItem): IdlInstructionAccountItem { 280 | if ("accounts" in account) { 281 | return convertInstructionAccounts(account); 282 | } else { 283 | return { 284 | docs: account.docs || [], 285 | name: account.name, 286 | optional: account.isOptional || false, 287 | pda: account.pda ? convertPda(account.pda) : undefined, 288 | relations: account.relations || [], 289 | signer: account.isSigner || false, 290 | writable: account.isMut || false, 291 | }; 292 | } 293 | } 294 | 295 | function convertInstructionAccounts(accounts: LegacyIdlAccounts): IdlInstructionAccounts { 296 | return { 297 | accounts: accounts.accounts.map(convertInstructionAccount), 298 | name: accounts.name, 299 | }; 300 | } 301 | 302 | function convertPda(pda: LegacyIdlPda): { seeds: any[]; programId?: any } { 303 | return { 304 | programId: pda.programId ? convertSeed(pda.programId) : undefined, 305 | seeds: pda.seeds.map(convertSeed), 306 | }; 307 | } 308 | 309 | function convertSeed(seed: LegacyIdlSeed): any { 310 | switch (seed.kind) { 311 | case "const": 312 | return { kind: "const", type: convertType(seed.type), value: seed.value }; 313 | case "arg": 314 | return { kind: "arg", path: seed.path, type: convertType(seed.type) }; 315 | case "account": 316 | return { 317 | account: seed.account, 318 | kind: "account", 319 | path: seed.path, 320 | type: convertType(seed.type), 321 | }; 322 | } 323 | } 324 | 325 | function convertEventToTypeDef(event: LegacyIdlEvent): IdlTypeDef { 326 | return { 327 | name: event.name, 328 | type: { 329 | fields: event.fields.map((field) => ({ 330 | name: field.name, 331 | type: convertType(field.type), 332 | })), 333 | kind: "struct", 334 | }, 335 | }; 336 | } 337 | 338 | function convertType(type: LegacyIdlType): IdlType { 339 | if (typeof type === "string") { 340 | return type === "publicKey" ? "pubkey" : type; 341 | } else if ("vec" in type) { 342 | return { vec: convertType(type.vec) }; 343 | } else if ("option" in type) { 344 | return { option: convertType(type.option) }; 345 | } else if ("defined" in type) { 346 | return { defined: { generics: [], name: type.defined } } as IdlTypeDefined; 347 | } else if ("array" in type) { 348 | return { array: [convertType(type.array[0]), type.array[1]] }; 349 | } else if ("generic" in type) { 350 | return type; 351 | } else if ("definedWithTypeArgs" in type) { 352 | return { 353 | defined: { 354 | generics: type.definedWithTypeArgs.args.map(convertDefinedTypeArg), 355 | name: type.definedWithTypeArgs.name, 356 | }, 357 | } as IdlTypeDefined; 358 | } 359 | throw new Error(`Unsupported type: ${JSON.stringify(type)}`); 360 | } 361 | 362 | function convertDefinedTypeArg(arg: LegacyIdlDefinedTypeArg): any { 363 | if ("generic" in arg) { 364 | return { generic: arg.generic }; 365 | } else if ("value" in arg) { 366 | return { value: arg.value }; 367 | } else if ("type" in arg) { 368 | return { type: convertType(arg.type) }; 369 | } 370 | throw new Error(`Unsupported defined type arg: ${JSON.stringify(arg)}`); 371 | } 372 | 373 | export function convertLegacyIdlToV30(idl: any, programAddress?: string): Idl { 374 | const spec = idl.metadata?.spec; 375 | 376 | if (spec) { 377 | switch (spec) { 378 | case "0.1.0": 379 | return idl as Idl; 380 | default: 381 | throw new Error(`IDL spec not supported: ${spec ?? ""}`); 382 | } 383 | } else { 384 | const formattedIdl = convertLegacyIdl(idl as LegacyIdl, programAddress); 385 | 386 | return formattedIdl; 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /src/parsers.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from "buffer"; 2 | 3 | import { 4 | PublicKey, 5 | TransactionInstruction, 6 | SystemProgram, 7 | Connection, 8 | Message, 9 | AccountMeta, 10 | ParsedMessage, 11 | PartiallyDecodedInstruction, 12 | Finality, 13 | VersionedMessage, 14 | LoadedAddresses, 15 | VersionedTransaction, 16 | AddressLookupTableAccount, 17 | AccountInfo, 18 | } from "@solana/web3.js"; 19 | import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token"; 20 | import { BorshInstructionCoder, Idl, BorshEventCoder, utils } from "@coral-xyz/anchor"; 21 | 22 | import { 23 | IdlAccount, 24 | IdlInstructionAccountItem2, 25 | InstructionNames, 26 | InstructionParserInfo, 27 | InstructionParsers, 28 | ParsedIdlArgs, 29 | ParsedInstruction, 30 | ParserFunction, 31 | ProgramInfoType, 32 | IdlParser, 33 | UnknownInstruction, 34 | EventNames, 35 | } from "./interfaces"; 36 | import { 37 | decodeSystemInstruction, 38 | decodeTokenInstruction, 39 | decodeToken2022Instruction, 40 | decodeAssociatedTokenInstruction, 41 | decodeComputeBudgetInstruction, 42 | } from "./decoders"; 43 | import { compiledInstructionToInstruction, flattenTransactionResponse, parsedInstructionToInstruction, parseTransactionAccounts } from "./helpers"; 44 | 45 | const COMPUTE_BUDGET_PROGRAM_ID = new PublicKey("ComputeBudget111111111111111111111111111111"); 46 | 47 | function flattenIdlAccounts(accounts: IdlInstructionAccountItem2[], prefix?: string): IdlAccount[] { 48 | return accounts 49 | .map((account) => { 50 | const accName = account.name; 51 | if ("accounts" in account) { 52 | const newPrefix = prefix ? `${prefix}.${accName}` : accName; 53 | 54 | return flattenIdlAccounts(account.accounts, newPrefix); 55 | } else { 56 | return { 57 | ...account, 58 | name: prefix ? `${prefix}.${accName}` : accName, 59 | }; 60 | } 61 | }) 62 | .flat(); 63 | } 64 | 65 | /** 66 | * Class for parsing arbitrary solana transactions in various formats 67 | * - by txHash 68 | * - from raw transaction data (base64 encoded or buffer) 69 | * - @solana/web3.js getTransaction().message object 70 | * - @solana/web3.js getParsedTransaction().message or Transaction.compileMessage() object 71 | * - @solana/web3.js TransactionInstruction object 72 | */ 73 | export class SolanaParser { 74 | private instructionParsers: InstructionParsers; 75 | 76 | private idlParsers: IdlParser = new Map(); 77 | 78 | /** 79 | * Initializes parser object 80 | * `SystemProgram`, `TokenProgram` and `AssociatedTokenProgram` are supported by default 81 | * but may be overriden by providing custom idl/custom parser 82 | * @param programInfos list of objects which contains programId and corresponding idl 83 | * @param parsers list of pairs (programId, custom parser) 84 | */ 85 | constructor(programInfos: ProgramInfoType[], parsers?: InstructionParserInfo[]) { 86 | const standartParsers: InstructionParserInfo[] = [ 87 | [SystemProgram.programId.toBase58(), decodeSystemInstruction], 88 | [TOKEN_PROGRAM_ID.toBase58(), decodeTokenInstruction], 89 | [TOKEN_2022_PROGRAM_ID.toBase58(), decodeToken2022Instruction], 90 | [ASSOCIATED_TOKEN_PROGRAM_ID.toBase58(), decodeAssociatedTokenInstruction], 91 | [COMPUTE_BUDGET_PROGRAM_ID.toBase58(), decodeComputeBudgetInstruction], 92 | ]; 93 | let result: InstructionParsers; 94 | parsers = parsers || []; 95 | for (const programInfo of programInfos) { 96 | parsers.push(this.buildIdlParser(new PublicKey(programInfo.programId), programInfo.idl)); 97 | } 98 | 99 | if (!parsers) { 100 | result = new Map(standartParsers); 101 | } else { 102 | // first set provided parsers 103 | result = new Map(parsers); 104 | // append standart parsers if parser not exist yet 105 | for (const parserInfo of standartParsers) { 106 | if (!result.has(parserInfo[0])) { 107 | result.set(...parserInfo); 108 | } 109 | } 110 | } 111 | 112 | this.instructionParsers = result; 113 | } 114 | 115 | /** 116 | * Adds (or updates) parser for provided programId 117 | * @param programId program id to add parser for 118 | * @param parser parser to parse programId instructions 119 | */ 120 | addParser(programId: PublicKey, parser: ParserFunction) { 121 | this.instructionParsers.set(programId.toBase58(), parser); 122 | // Remove idlParser cache if present (custom parser overrides IDL-based one) 123 | this.idlParsers.delete(programId.toBase58()); 124 | } 125 | 126 | /** 127 | * Adds (or updates) parser for provided programId 128 | * @param programId program id to add parser for 129 | * @param idl IDL that describes anchor program 130 | */ 131 | addParserFromIdl(programId: PublicKey | string, idl: Idl) { 132 | this.instructionParsers.set(...this.buildIdlParser(new PublicKey(programId), idl)); 133 | } 134 | 135 | private buildIdlParser(programId: PublicKey, idl: Idl): InstructionParserInfo { 136 | const pubkey = programId.toBase58(); 137 | if (!this.idlParsers.has(pubkey)) { 138 | this.idlParsers.set(pubkey, { 139 | instructionCoder: new BorshInstructionCoder(idl), 140 | eventCoder: new BorshEventCoder(idl), 141 | }); 142 | } 143 | 144 | const idlParser: ParserFunction | EventNames> = (instruction: TransactionInstruction) => { 145 | const programParser = this.idlParsers.get(pubkey); 146 | if (!programParser) { 147 | return this.buildUnknownParsedInstruction(instruction.programId, instruction.keys, instruction.data); 148 | } 149 | const { instructionCoder, eventCoder } = programParser; 150 | const parsedIx = instructionCoder.decode(instruction.data); 151 | 152 | if (!parsedIx) { 153 | if (instruction.data && instruction.data.length > 8) { 154 | const eventData = utils.bytes.base64.encode(instruction.data.subarray(8)); 155 | const parsedEvent = eventCoder.decode(eventData); 156 | 157 | if (parsedEvent) { 158 | return { 159 | name: parsedEvent.name, 160 | programId: instruction.programId, 161 | args: parsedEvent.data, 162 | accounts: instruction.keys.map((meta, idx) => ({ 163 | name: `Account ${idx}`, 164 | ...meta, 165 | })), 166 | }; 167 | } 168 | } 169 | 170 | return this.buildUnknownParsedInstruction(instruction.programId, instruction.keys, instruction.data); 171 | } 172 | 173 | const ix = idl.instructions.find((instr) => instr.name === parsedIx.name); 174 | if (!ix) { 175 | return this.buildUnknownParsedInstruction(instruction.programId, instruction.keys, instruction.data, parsedIx.name); 176 | } 177 | const flatIdlAccounts = flattenIdlAccounts(ix.accounts); 178 | const accounts = instruction.keys.map((meta, idx) => { 179 | if (idx < flatIdlAccounts.length) { 180 | return { 181 | name: flatIdlAccounts[idx].name, 182 | ...meta, 183 | }; 184 | } 185 | // "Remaining accounts" are unnamed in Anchor. 186 | else { 187 | return { 188 | name: `Remaining ${idx - flatIdlAccounts.length}`, 189 | ...meta, 190 | }; 191 | } 192 | }); 193 | 194 | return { 195 | name: parsedIx.name, 196 | accounts: accounts, 197 | programId: instruction.programId, 198 | args: parsedIx.data as ParsedIdlArgs, 199 | }; 200 | }; 201 | 202 | return [pubkey, idlParser.bind(this)]; 203 | } 204 | 205 | /** 206 | * Removes parser for provided program id 207 | * @param programId program id to remove parser for 208 | */ 209 | removeParser(programId: PublicKey) { 210 | this.instructionParsers.delete(programId.toBase58()); 211 | this.idlParsers.delete(programId.toBase58()); 212 | } 213 | 214 | private buildUnknownParsedInstruction(programId: PublicKey, accounts: AccountMeta[], argData: unknown, name?: string): UnknownInstruction { 215 | return { 216 | programId, 217 | accounts, 218 | args: { unknown: argData }, 219 | name: name || "unknown", 220 | }; 221 | } 222 | 223 | /** 224 | * Parses instruction 225 | * @param instruction transaction instruction to parse 226 | * @returns parsed transaction instruction or UnknownInstruction 227 | */ 228 | parseInstruction>(instruction: TransactionInstruction): ParsedInstruction { 229 | if (!this.instructionParsers.has(instruction.programId.toBase58())) { 230 | return this.buildUnknownParsedInstruction(instruction.programId, instruction.keys, instruction.data); 231 | } else { 232 | const parser = this.instructionParsers.get(instruction.programId.toBase58()) as ParserFunction; 233 | 234 | return parser(instruction); 235 | } 236 | } 237 | 238 | /** 239 | * Parses transaction data 240 | * @param txMessage message to parse 241 | * @param altLoadedAddresses VersionedTransaction.meta.loaddedAddresses if tx is versioned 242 | * @returns list of parsed instructions 243 | */ 244 | parseTransactionData( 245 | txMessage: T, 246 | altLoadedAddresses: T extends VersionedMessage ? LoadedAddresses | undefined : undefined = undefined, 247 | ): ParsedInstruction[] { 248 | const parsedAccounts = parseTransactionAccounts(txMessage, altLoadedAddresses); 249 | 250 | return txMessage.compiledInstructions.map((instruction) => this.parseInstruction(compiledInstructionToInstruction(instruction, parsedAccounts))); 251 | } 252 | 253 | /** 254 | * Parses transaction data retrieved from Connection.getParsedTransaction 255 | * @param txParsedMessage message to parse 256 | * @returns list of parsed instructions 257 | */ 258 | parseTransactionParsedData(txParsedMessage: ParsedMessage): ParsedInstruction[] { 259 | const parsedAccounts = txParsedMessage.accountKeys.map((metaLike) => ({ 260 | isSigner: metaLike.signer, 261 | isWritable: metaLike.writable, 262 | pubkey: metaLike.pubkey, 263 | })); 264 | 265 | return txParsedMessage.instructions.map((parsedIx) => 266 | this.parseInstruction(parsedInstructionToInstruction(parsedIx as PartiallyDecodedInstruction, parsedAccounts)), 267 | ); 268 | } 269 | 270 | /** 271 | * Fetches tx from blockchain and parses it 272 | * @param connection web3 Connection 273 | * @param txId transaction id 274 | * @param flatten - true if CPI calls need to be parsed too 275 | * @returns list of parsed instructions 276 | */ 277 | async parseTransactionByHash( 278 | connection: Connection, 279 | txId: string, 280 | flatten: boolean = false, 281 | commitment: Finality = "confirmed", 282 | ): Promise[] | null> { 283 | const transaction = await connection.getTransaction(txId, { commitment: commitment, maxSupportedTransactionVersion: 0 }); 284 | if (!transaction) return null; 285 | if (flatten) { 286 | const flattened = flattenTransactionResponse(transaction); 287 | 288 | return flattened.map((ix) => this.parseInstruction(ix)); 289 | } 290 | 291 | return this.parseTransactionData(transaction.transaction.message, transaction.meta?.loadedAddresses); 292 | } 293 | 294 | /** 295 | * Parses transaction dump 296 | * @param txDump base64-encoded string or raw Buffer which contains tx dump 297 | * @returns list of parsed instructions 298 | */ 299 | async parseTransactionDump(connection: Connection, txDump: string | Buffer): Promise[]> { 300 | if (!(txDump instanceof Buffer)) txDump = Buffer.from(txDump, "base64"); 301 | const vtx = VersionedTransaction.deserialize(txDump); 302 | let loadedAddresses: LoadedAddresses = { writable: [], readonly: [] }; 303 | 304 | if (vtx.version !== "legacy") { 305 | const accountsToFetch = vtx.message.addressTableLookups.map((alt) => alt.accountKey); 306 | if (accountsToFetch.length > 0) { 307 | const fetched = await connection.getMultipleAccountsInfo(accountsToFetch); 308 | const altAccounts = fetched 309 | .filter((f) => f !== null && f.data.length > 0) 310 | .map((f) => AddressLookupTableAccount.deserialize((>f).data)); 311 | 312 | const altWritableAccounts: PublicKey[] = []; 313 | const altReadonlyAccounts: PublicKey[] = []; 314 | vtx.message.addressTableLookups.map((compiledALT, idx) => { 315 | altWritableAccounts.push(...compiledALT.writableIndexes.map((writableIdx) => altAccounts[idx].addresses[writableIdx])); 316 | altReadonlyAccounts.push(...compiledALT.readonlyIndexes.map((writableIdx) => altAccounts[idx].addresses[writableIdx])); 317 | }); 318 | loadedAddresses = { 319 | readonly: altReadonlyAccounts, 320 | writable: altWritableAccounts, 321 | }; 322 | } 323 | } 324 | 325 | return this.parseTransactionData(vtx.message, loadedAddresses); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/programs/ata.ts: -------------------------------------------------------------------------------- 1 | export declare type AssociatedTokenProgram = { 2 | name: "associated_token_program"; 3 | address: ""; 4 | metadata: { 5 | name: "associated_token_program"; 6 | version: "1.0.3"; 7 | spec: "0.1.0"; 8 | }; 9 | instructions: [ 10 | { 11 | discriminator: [0]; 12 | name: "createAssociatedTokenAccountIdempotent"; 13 | accounts: [ 14 | { 15 | name: "fundingAccount"; 16 | isMut: true; 17 | isSigner: true; 18 | }, 19 | { 20 | name: "newAccount"; 21 | isMut: true; 22 | isSigner: false; 23 | }, 24 | { 25 | name: "wallet"; 26 | isMut: false; 27 | isSigner: false; 28 | }, 29 | { 30 | name: "tokenMint"; 31 | isMut: false; 32 | isSigner: false; 33 | }, 34 | { 35 | name: "systemProgram"; 36 | isMut: false; 37 | isSigner: false; 38 | }, 39 | { 40 | name: "tokenProgram"; 41 | isMut: false; 42 | isSigner: false; 43 | }, 44 | { 45 | name: "rent"; 46 | isMut: false; 47 | isSigner: false; 48 | }, 49 | ]; 50 | args: []; 51 | }, 52 | { 53 | discriminator: []; 54 | name: "createAssociatedTokenAccount"; 55 | accounts: [ 56 | { 57 | name: "fundingAccount"; 58 | isMut: true; 59 | isSigner: true; 60 | }, 61 | { 62 | name: "newAccount"; 63 | isMut: true; 64 | isSigner: false; 65 | }, 66 | { 67 | name: "wallet"; 68 | isMut: false; 69 | isSigner: false; 70 | }, 71 | { 72 | name: "tokenMint"; 73 | isMut: false; 74 | isSigner: false; 75 | }, 76 | { 77 | name: "systemProgram"; 78 | isMut: false; 79 | isSigner: false; 80 | }, 81 | { 82 | name: "tokenProgram"; 83 | isMut: false; 84 | isSigner: false; 85 | }, 86 | { 87 | name: "rent"; 88 | isMut: false; 89 | isSigner: false; 90 | }, 91 | ]; 92 | args: []; 93 | }, 94 | ]; 95 | }; 96 | -------------------------------------------------------------------------------- /src/programs/compute.budget.ts: -------------------------------------------------------------------------------- 1 | export declare type ComputeBudget = { 2 | address: "ComputeBudget111111111111111111111111111111"; 3 | metadata: { 4 | name: "computeBudgetProgram"; 5 | version: "1.0.3"; 6 | spec: "0.1.0"; 7 | }; 8 | constants: []; 9 | errors: []; 10 | accounts: []; 11 | types: []; 12 | instructions: [ 13 | { 14 | discriminator: [0]; 15 | accounts: []; 16 | name: "requestUnits"; 17 | args: [{ type: "u32"; name: "units" }, { type: "u32"; name: "additionalFee" }]; 18 | }, 19 | { 20 | discriminator: [1]; 21 | name: "requestHeapFrame"; 22 | accounts: []; 23 | args: [{ type: "u32"; name: "bytes" }]; 24 | }, 25 | { 26 | discriminator: [2]; 27 | name: "setComputeUnitLimit"; 28 | accounts: []; 29 | args: [{ type: "u32"; name: "units" }]; 30 | }, 31 | { 32 | discriminator: [3]; 33 | name: "setComputeUnitPrice"; 34 | accounts: []; 35 | args: [{ type: "u64"; name: "microLamports" }]; 36 | }, 37 | { 38 | discriminator: [4]; 39 | name: "setLoadedAccountsDataSizeLimit"; 40 | accounts: []; 41 | args: [{ type: "u32"; name: "bytes" }]; 42 | }, 43 | ]; 44 | }; 45 | -------------------------------------------------------------------------------- /src/programs/index.ts: -------------------------------------------------------------------------------- 1 | import { splTokenProgram } from "@coral-xyz/spl-token"; 2 | import { SystemProgram } from "@coral-xyz/anchor"; 3 | 4 | import { AssociatedTokenProgram } from "./ata"; 5 | import { ComputeBudget } from "./compute.budget"; 6 | import { SplToken22 } from "./spl-token-22.program"; 7 | 8 | export type SplTokenIdl = ReturnType["idl"]; 9 | export type SystemProgramIdl = SystemProgram; 10 | export type SplToken22Idl = SplToken22; 11 | export type ComputeBudgetIdl = ComputeBudget; 12 | export type AssociatedTokenProgramIdl = AssociatedTokenProgram; 13 | -------------------------------------------------------------------------------- /src/programs/spl-token-22.program.ts: -------------------------------------------------------------------------------- 1 | export declare type SplToken22 = { 2 | accounts: [ 3 | { discriminator: [80, 188, 245, 20, 95, 138, 57, 156]; name: "Mint" }, 4 | { discriminator: [113, 66, 224, 54, 188, 119, 240, 101]; name: "Account" }, 5 | { discriminator: [224, 116, 121, 186, 68, 161, 79, 236]; name: "Multisig" }, 6 | ]; 7 | address: "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"; 8 | constants: []; 9 | errors: [ 10 | { code: 0; msg: "Lamport balance below rent-exempt threshold"; name: "NotRentExempt" }, 11 | { code: 1; msg: "Insufficient funds"; name: "InsufficientFunds" }, 12 | { code: 2; msg: "Invalid Mint"; name: "InvalidMint" }, 13 | { code: 3; msg: "Account not associated with this Mint"; name: "MintMismatch" }, 14 | { code: 4; msg: "Owner does not match"; name: "OwnerMismatch" }, 15 | { code: 5; msg: "Fixed supply"; name: "FixedSupply" }, 16 | { code: 6; msg: "Already in use"; name: "AlreadyInUse" }, 17 | { code: 7; msg: "Invalid number of provided signers"; name: "InvalidNumberOfProvidedSigners" }, 18 | { code: 8; msg: "Invalid number of required signers"; name: "InvalidNumberOfRequiredSigners" }, 19 | { code: 9; msg: "State is uninitialized"; name: "UninitializedState" }, 20 | { code: 10; msg: "Instruction does not support native tokens"; name: "NativeNotSupported" }, 21 | { code: 11; msg: "Non-native account can only be closed if its balance is zero"; name: "NonNativeHasBalance" }, 22 | { code: 12; msg: "Invalid instruction"; name: "InvalidInstruction" }, 23 | { code: 13; msg: "State is invalid for requested operation"; name: "InvalidState" }, 24 | { code: 14; msg: "Operation overflowed"; name: "Overflow" }, 25 | { code: 15; msg: "Account does not support specified authority type"; name: "AuthorityTypeNotSupported" }, 26 | { code: 16; msg: "This token mint cannot freeze accounts"; name: "MintCannotFreeze" }, 27 | { code: 17; msg: "Account is frozen"; name: "AccountFrozen" }, 28 | { code: 18; msg: "The provided decimals value different from the Mint decimals"; name: "MintDecimalsMismatch" }, 29 | { code: 19; msg: "Instruction does not support non-native tokens"; name: "NonNativeNotSupported" }, 30 | { code: 20; msg: "Extension type does not match already existing extensions"; name: "ExtensionTypeMismatch" }, 31 | { code: 21; msg: "Extension does not match the base type provided"; name: "ExtensionBaseMismatch" }, 32 | { code: 22; msg: "Extension already initialized on this account"; name: "ExtensionAlreadyInitialized" }, 33 | { code: 23; msg: "An account can only be closed if its confidential balance is zero"; name: "ConfidentialTransferAccountHasBalance" }, 34 | { code: 24; msg: "Account not approved for confidential transfers"; name: "ConfidentialTransferAccountNotApproved" }, 35 | { code: 25; msg: "Account not accepting deposits or transfers"; name: "ConfidentialTransferDepositsAndTransfersDisabled" }, 36 | { code: 26; msg: "ElGamal public key mismatch"; name: "ConfidentialTransferElGamalPubkeyMismatch" }, 37 | { code: 27; msg: "Balance mismatch"; name: "ConfidentialTransferBalanceMismatch" }, 38 | { code: 28; msg: "Mint has non-zero supply. Burn all tokens before closing the mint"; name: "MintHasSupply" }, 39 | { code: 29; msg: "No authority exists to perform the desired operation"; name: "NoAuthorityExists" }, 40 | { code: 30; msg: "Transfer fee exceeds maximum of 10,000 basis points"; name: "TransferFeeExceedsMaximum" }, 41 | { 42 | code: 31; 43 | msg: "Mint required for this account to transfer tokens, use `transfer_checked` or `transfer_checked_with_fee`"; 44 | name: "MintRequiredForTransfer"; 45 | }, 46 | { code: 32; msg: "Calculated fee does not match expected fee"; name: "FeeMismatch" }, 47 | { code: 33; msg: "Fee parameters associated with zero-knowledge proofs do not match fee parameters in mint"; name: "FeeParametersMismatch" }, 48 | { code: 34; msg: "The owner authority cannot be changed"; name: "ImmutableOwner" }, 49 | { 50 | code: 35; 51 | msg: "An account can only be closed if its withheld fee balance is zero, harvest fees to the mint and try again"; 52 | name: "AccountHasWithheldTransferFees"; 53 | }, 54 | { code: 36; msg: "No memo in previous instruction, required for recipient to receive a transfer"; name: "NoMemo" }, 55 | { code: 37; msg: "Transfer is disabled for this mint"; name: "NonTransferable" }, 56 | { code: 38; msg: "Non-transferable tokens can't be minted to an account without immutable ownership"; name: "NonTransferableNeedsImmutableOwnership" }, 57 | { 58 | code: 39; 59 | msg: "The total number of `Deposit` and `Transfer` instructions to an account cannot exceed\n the associated `maximum_pending_balance_credit_counter`"; 60 | name: "MaximumPendingBalanceCreditCounterExceeded"; 61 | }, 62 | { code: 40; msg: "Deposit amount exceeds maximum limit"; name: "MaximumDepositAmountExceeded" }, 63 | { code: 41; msg: "CPI Guard cannot be enabled or disabled in CPI"; name: "CpiGuardSettingsLocked" }, 64 | { 65 | code: 42; 66 | msg: "CPI Guard is enabled, and a program attempted to transfer user funds via CPI without using a delegate"; 67 | name: "CpiGuardTransferBlocked"; 68 | }, 69 | { code: 43; msg: "CPI Guard is enabled, and a program attempted to burn user funds via CPI without using a delegate"; name: "CpiGuardBurnBlocked" }, 70 | { 71 | code: 44; 72 | msg: "CPI Guard is enabled, and a program attempted to close an account via CPI without returning lamports to owner"; 73 | name: "CpiGuardCloseAccountBlocked"; 74 | }, 75 | { code: 45; msg: "CPI Guard is enabled, and a program attempted to approve a delegate via CPI"; name: "CpiGuardApproveBlocked" }, 76 | { code: 46; msg: "CPI Guard is enabled, and a program attempted to add or replace an authority via CPI"; name: "CpiGuardSetAuthorityBlocked" }, 77 | { code: 47; msg: "Account ownership cannot be changed while CPI Guard is enabled"; name: "CpiGuardOwnerChangeBlocked" }, 78 | { code: 48; msg: "Extension not found in account data"; name: "ExtensionNotFound" }, 79 | { code: 49; msg: "Non-confidential transfers disabled"; name: "NonConfidentialTransfersDisabled" }, 80 | { code: 50; msg: "An account can only be closed if the confidential withheld fee is zero"; name: "ConfidentialTransferFeeAccountHasWithheldFee" }, 81 | { code: 51; msg: "A mint or an account is initialized to an invalid combination of extensions"; name: "InvalidExtensionCombination" }, 82 | { code: 52; msg: "Extension allocation with overwrite must use the same length"; name: "InvalidLengthForAlloc" }, 83 | { code: 53; msg: "Failed to decrypt a confidential transfer account"; name: "AccountDecryption" }, 84 | { code: 54; msg: "Failed to generate proof"; name: "ProofGeneration" }, 85 | { code: 55; msg: "An invalid proof instruction offset was provided"; name: "InvalidProofInstructionOffset" }, 86 | { code: 56; msg: "Harvest of withheld tokens to mint is disabled"; name: "HarvestToMintDisabled" }, 87 | { code: 57; msg: "Split proof context state accounts not supported for instruction"; name: "SplitProofContextStateAccountsNotSupported" }, 88 | { code: 58; msg: "Not enough proof context state accounts provided"; name: "NotEnoughProofContextStateAccounts" }, 89 | { code: 59; msg: "Ciphertext is malformed"; name: "MalformedCiphertext" }, 90 | { code: 60; msg: "Ciphertext arithmetic failed"; name: "CiphertextArithmeticFailed" }, 91 | ]; 92 | events: []; 93 | instructions: [ 94 | { 95 | accounts: [ 96 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: true }, 97 | { docs: []; name: "rent"; optional: false; relations: []; signer: false; writable: false }, 98 | ]; 99 | args: [ 100 | { name: "decimals"; type: "u8" }, 101 | { name: "mintAuthority"; type: "pubkey" }, 102 | { name: "freezeAuthority"; type: { defined: { generics: []; name: "COption" } } }, 103 | ]; 104 | discriminator: [209, 42, 195, 4, 129, 85, 209, 44]; 105 | name: "initializeMint"; 106 | }, 107 | { 108 | accounts: [ 109 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 110 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: false }, 111 | { docs: []; name: "owner"; optional: false; relations: []; signer: false; writable: false }, 112 | { docs: []; name: "rent"; optional: false; relations: []; signer: false; writable: false }, 113 | ]; 114 | args: []; 115 | discriminator: [74, 115, 99, 93, 197, 69, 103, 7]; 116 | name: "initializeAccount"; 117 | }, 118 | { 119 | accounts: [ 120 | { docs: []; name: "multisig"; optional: false; relations: []; signer: false; writable: true }, 121 | { docs: []; name: "rent"; optional: false; relations: []; signer: false; writable: false }, 122 | ]; 123 | args: [{ name: "m"; type: "u8" }]; 124 | discriminator: [220, 130, 117, 21, 27, 227, 78, 213]; 125 | name: "initializeMultisig"; 126 | }, 127 | { 128 | accounts: [ 129 | { docs: []; name: "source"; optional: false; relations: []; signer: false; writable: true }, 130 | { docs: []; name: "destination"; optional: false; relations: []; signer: false; writable: true }, 131 | { docs: []; name: "authority"; optional: false; relations: []; signer: true; writable: false }, 132 | ]; 133 | args: [{ name: "amount"; type: "u64" }]; 134 | discriminator: [163, 52, 200, 231, 140, 3, 69, 186]; 135 | name: "transfer"; 136 | }, 137 | { 138 | accounts: [ 139 | { docs: []; name: "source"; optional: false; relations: []; signer: false; writable: true }, 140 | { docs: []; name: "delegate"; optional: false; relations: []; signer: false; writable: false }, 141 | { docs: []; name: "owner"; optional: false; relations: []; signer: true; writable: false }, 142 | ]; 143 | args: [{ name: "amount"; type: "u64" }]; 144 | discriminator: [69, 74, 217, 36, 115, 117, 97, 76]; 145 | name: "approve"; 146 | }, 147 | { 148 | accounts: [ 149 | { docs: []; name: "source"; optional: false; relations: []; signer: false; writable: true }, 150 | { docs: []; name: "owner"; optional: false; relations: []; signer: true; writable: false }, 151 | ]; 152 | args: []; 153 | discriminator: [170, 23, 31, 34, 133, 173, 93, 242]; 154 | name: "revoke"; 155 | }, 156 | { 157 | accounts: [ 158 | { docs: []; name: "owned"; optional: false; relations: []; signer: false; writable: true }, 159 | { docs: []; name: "owner"; optional: false; relations: []; signer: true; writable: false }, 160 | { docs: []; name: "signer"; optional: false; relations: []; signer: true; writable: false }, 161 | ]; 162 | args: [ 163 | { name: "authorityType"; type: { defined: { generics: []; name: "AuthorityType" } } }, 164 | { name: "newAuthority"; type: { defined: { generics: []; name: "COption" } } }, 165 | ]; 166 | discriminator: [133, 250, 37, 21, 110, 163, 26, 121]; 167 | name: "setAuthority"; 168 | }, 169 | { 170 | accounts: [ 171 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: true }, 172 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 173 | { docs: []; name: "owner"; optional: false; relations: []; signer: true; writable: false }, 174 | ]; 175 | args: [{ name: "amount"; type: "u64" }]; 176 | discriminator: [241, 34, 48, 186, 37, 179, 123, 192]; 177 | name: "mintTo"; 178 | }, 179 | { 180 | accounts: [ 181 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 182 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: true }, 183 | { docs: []; name: "authority"; optional: false; relations: []; signer: true; writable: false }, 184 | ]; 185 | args: [{ name: "amount"; type: "u64" }]; 186 | discriminator: [116, 110, 29, 56, 107, 219, 42, 93]; 187 | name: "burn"; 188 | }, 189 | { 190 | accounts: [ 191 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 192 | { docs: []; name: "destination"; optional: false; relations: []; signer: false; writable: true }, 193 | { docs: []; name: "owner"; optional: false; relations: []; signer: true; writable: false }, 194 | ]; 195 | args: []; 196 | discriminator: [125, 255, 149, 14, 110, 34, 72, 24]; 197 | name: "closeAccount"; 198 | }, 199 | { 200 | accounts: [ 201 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 202 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: false }, 203 | { docs: []; name: "owner"; optional: false; relations: []; signer: true; writable: false }, 204 | ]; 205 | args: []; 206 | discriminator: [253, 75, 82, 133, 167, 238, 43, 130]; 207 | name: "freezeAccount"; 208 | }, 209 | { 210 | accounts: [ 211 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 212 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: false }, 213 | { docs: []; name: "owner"; optional: false; relations: []; signer: true; writable: false }, 214 | ]; 215 | args: []; 216 | discriminator: [115, 152, 79, 213, 213, 169, 184, 35]; 217 | name: "thawAccount"; 218 | }, 219 | { 220 | accounts: [ 221 | { docs: []; name: "source"; optional: false; relations: []; signer: false; writable: true }, 222 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: false }, 223 | { docs: []; name: "destination"; optional: false; relations: []; signer: false; writable: true }, 224 | { docs: []; name: "authority"; optional: false; relations: []; signer: true; writable: false }, 225 | ]; 226 | args: [{ name: "amount"; type: "u64" }, { name: "decimals"; type: "u8" }]; 227 | discriminator: [119, 250, 202, 24, 253, 135, 244, 121]; 228 | name: "transferChecked"; 229 | }, 230 | { 231 | accounts: [ 232 | { docs: []; name: "source"; optional: false; relations: []; signer: false; writable: true }, 233 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: false }, 234 | { docs: []; name: "delegate"; optional: false; relations: []; signer: false; writable: false }, 235 | { docs: []; name: "owner"; optional: false; relations: []; signer: true; writable: false }, 236 | ]; 237 | args: [{ name: "amount"; type: "u64" }, { name: "decimals"; type: "u8" }]; 238 | discriminator: [47, 197, 254, 42, 58, 201, 58, 109]; 239 | name: "approveChecked"; 240 | }, 241 | { 242 | accounts: [ 243 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: true }, 244 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 245 | { docs: []; name: "owner"; optional: false; relations: []; signer: true; writable: false }, 246 | ]; 247 | args: [{ name: "amount"; type: "u64" }, { name: "decimals"; type: "u8" }]; 248 | discriminator: [229, 236, 36, 240, 118, 225, 45, 125]; 249 | name: "mintToChecked"; 250 | }, 251 | { 252 | accounts: [ 253 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 254 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: true }, 255 | { docs: []; name: "authority"; optional: false; relations: []; signer: true; writable: false }, 256 | ]; 257 | args: [{ name: "amount"; type: "u64" }, { name: "decimals"; type: "u8" }]; 258 | discriminator: [198, 121, 200, 102, 120, 208, 155, 178]; 259 | name: "burnChecked"; 260 | }, 261 | { 262 | accounts: [ 263 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 264 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: false }, 265 | { docs: []; name: "rent"; optional: false; relations: []; signer: false; writable: false }, 266 | ]; 267 | args: [{ name: "owner"; type: "pubkey" }]; 268 | discriminator: [8, 182, 149, 144, 185, 31, 209, 105]; 269 | name: "initializeAccount2"; 270 | }, 271 | { 272 | accounts: [{ docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }]; 273 | args: []; 274 | discriminator: [155, 219, 36, 36, 239, 128, 21, 65]; 275 | name: "syncNative"; 276 | }, 277 | { 278 | accounts: [ 279 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 280 | { docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: false }, 281 | ]; 282 | args: [{ name: "owner"; type: "pubkey" }]; 283 | discriminator: [23, 142, 140, 135, 21, 160, 133, 64]; 284 | name: "initializeAccount3"; 285 | }, 286 | { 287 | accounts: [ 288 | { docs: []; name: "multisig"; optional: false; relations: []; signer: false; writable: true }, 289 | { docs: []; name: "signer"; optional: false; relations: []; signer: false; writable: false }, 290 | ]; 291 | args: [{ name: "m"; type: "u8" }]; 292 | discriminator: [81, 239, 73, 39, 27, 148, 2, 146]; 293 | name: "initializeMultisig2"; 294 | }, 295 | { 296 | accounts: [{ docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: true }]; 297 | args: [ 298 | { name: "decimals"; type: "u8" }, 299 | { name: "mintAuthority"; type: "pubkey" }, 300 | { name: "freezeAuthority"; type: { defined: { generics: []; name: "COption" } } }, 301 | ]; 302 | discriminator: [95, 108, 198, 210, 72, 243, 143, 235]; 303 | name: "initializeMint2"; 304 | }, 305 | { 306 | accounts: [{ docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: false }]; 307 | args: [{ name: "extensionTypes"; type: { vec: { defined: { generics: []; name: "ExtensionType" } } } }]; 308 | discriminator: [16, 177, 210, 128, 21, 45, 111, 31]; 309 | name: "getAccountDataSize"; 310 | }, 311 | { 312 | accounts: [{ docs: []; name: "tokenAccount"; optional: false; relations: []; signer: false; writable: true }]; 313 | args: []; 314 | discriminator: [141, 50, 15, 44, 195, 247, 34, 60]; 315 | name: "initializeImmutableOwner"; 316 | }, 317 | { 318 | accounts: [{ docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: false }]; 319 | args: [{ name: "amount"; type: "u64" }]; 320 | discriminator: [160, 145, 200, 98, 242, 156, 30, 90]; 321 | name: "amountToUiAmount"; 322 | }, 323 | { 324 | accounts: [{ docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: false }]; 325 | args: [{ name: "uiAmount"; type: { defined: { generics: []; name: "&'astr" } } }]; 326 | discriminator: [173, 243, 64, 4, 103, 31, 56, 52]; 327 | name: "uiAmountToAmount"; 328 | }, 329 | { 330 | accounts: [{ docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: true }]; 331 | args: [{ name: "closeAuthority"; type: { defined: { generics: []; name: "COption" } } }]; 332 | discriminator: [117, 167, 56, 158, 201, 160, 209, 109]; 333 | name: "initializeMintCloseAuthority"; 334 | }, 335 | { 336 | accounts: [ 337 | { docs: []; name: "account"; optional: false; relations: []; signer: false; writable: true }, 338 | { docs: []; name: "payer"; optional: false; relations: []; signer: true; writable: true }, 339 | { docs: []; name: "systemProgram"; optional: false; relations: []; signer: false; writable: false }, 340 | { docs: []; name: "owner"; optional: false; relations: []; signer: true; writable: false }, 341 | ]; 342 | args: [{ name: "extensionTypes"; type: { vec: { defined: { generics: []; name: "ExtensionType" } } } }]; 343 | discriminator: [79, 177, 5, 90, 135, 125, 234, 85]; 344 | name: "reallocate"; 345 | }, 346 | { 347 | accounts: [ 348 | { docs: []; name: "payer"; optional: false; relations: []; signer: true; writable: true }, 349 | { docs: []; name: "crateNativeMint"; optional: false; relations: []; signer: false; writable: true }, 350 | { docs: []; name: "systemProgram"; optional: false; relations: []; signer: false; writable: false }, 351 | ]; 352 | args: []; 353 | discriminator: [114, 254, 53, 96, 51, 248, 117, 109]; 354 | name: "createNativeMint"; 355 | }, 356 | { 357 | accounts: [{ docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: true }]; 358 | args: []; 359 | discriminator: [242, 68, 44, 126, 194, 231, 206, 200]; 360 | name: "initializeNonTransferableMint"; 361 | }, 362 | { 363 | accounts: [{ docs: []; name: "mint"; optional: false; relations: []; signer: false; writable: true }]; 364 | args: [{ name: "delegate"; type: "pubkey" }]; 365 | discriminator: [98, 200, 9, 70, 17, 203, 130, 60]; 366 | name: "initializePermanentDelegate"; 367 | }, 368 | { 369 | accounts: [ 370 | { docs: []; name: "sourceAccount"; optional: false; relations: []; signer: false; writable: true }, 371 | { docs: []; name: "destinationAccount"; optional: false; relations: []; signer: false; writable: true }, 372 | { docs: []; name: "authority"; optional: false; relations: []; signer: true; writable: false }, 373 | ]; 374 | args: []; 375 | discriminator: [221, 166, 235, 25, 123, 95, 232, 59]; 376 | name: "withdrawExcessLamports"; 377 | }, 378 | ]; 379 | metadata: { name: "spl_token_2022"; version: "1.0.0"; spec: "0.1.0" }; 380 | types: [ 381 | { name: "AccountState"; type: { kind: "enum"; variants: [{ name: "Uninitialized" }, { name: "Initialized" }, { name: "Frozen" }] } }, 382 | { 383 | name: "AuthorityType"; 384 | type: { 385 | kind: "enum"; 386 | variants: [ 387 | { name: "MintTokens" }, 388 | { name: "FreezeAccount" }, 389 | { name: "AccountOwner" }, 390 | { name: "CloseAccount" }, 391 | { name: "TransferFeeConfig" }, 392 | { name: "WithheldWithdraw" }, 393 | { name: "CloseMint" }, 394 | { name: "InterestRate" }, 395 | { name: "PermanentDelegate" }, 396 | { name: "ConfidentialTransferMint" }, 397 | { name: "TransferHookProgramId" }, 398 | { name: "ConfidentialTransferFeeConfig" }, 399 | { name: "MetadataPointer" }, 400 | { name: "GroupPointer" }, 401 | { name: "GroupMemberPointer" }, 402 | ]; 403 | }; 404 | }, 405 | { 406 | name: "ExtensionType"; 407 | type: { 408 | kind: "enum"; 409 | variants: [ 410 | { name: "Uninitialized" }, 411 | { name: "TransferFeeConfig" }, 412 | { name: "TransferFeeAmount" }, 413 | { name: "MintCloseAuthority" }, 414 | { name: "ConfidentialTransferMint" }, 415 | { name: "ConfidentialTransferAccount" }, 416 | { name: "DefaultAccountState" }, 417 | { name: "ImmutableOwner" }, 418 | { name: "MemoTransfer" }, 419 | { name: "NonTransferable" }, 420 | { name: "InterestBearingConfig" }, 421 | { name: "CpiGuard" }, 422 | { name: "PermanentDelegate" }, 423 | { name: "NonTransferableAccount" }, 424 | { name: "TransferHook" }, 425 | { name: "TransferHookAccount" }, 426 | { name: "ConfidentialTransferFeeConfig" }, 427 | { name: "ConfidentialTransferFeeAmount" }, 428 | { name: "MetadataPointer" }, 429 | { name: "TokenMetadata" }, 430 | { name: "GroupPointer" }, 431 | { name: "TokenGroup" }, 432 | { name: "GroupMemberPointer" }, 433 | { name: "TokenGroupMember" }, 434 | { name: "VariableLenMintTest" }, 435 | { name: "AccountPaddingTest" }, 436 | { name: "MintPaddingTest" }, 437 | ]; 438 | }; 439 | }, 440 | { 441 | name: "Mint"; 442 | type: { 443 | fields: [ 444 | { name: "mintAuthority"; type: { defined: { generics: []; name: "COption" } } }, 445 | { name: "supply"; type: "u64" }, 446 | { name: "decimals"; type: "u8" }, 447 | { name: "isInitialized"; type: "bool" }, 448 | { name: "freezeAuthority"; type: { defined: { generics: []; name: "COption" } } }, 449 | ]; 450 | kind: "struct"; 451 | }; 452 | }, 453 | { 454 | name: "Account"; 455 | type: { 456 | fields: [ 457 | { name: "mint"; type: "pubkey" }, 458 | { name: "owner"; type: "pubkey" }, 459 | { name: "amount"; type: "u64" }, 460 | { name: "delegate"; type: { defined: { generics: []; name: "COption" } } }, 461 | { name: "state"; type: { defined: { generics: []; name: "AccountState" } } }, 462 | { name: "isNative"; type: { defined: { generics: []; name: "COption" } } }, 463 | { name: "delegatedAmount"; type: "u64" }, 464 | { name: "closeAuthority"; type: { defined: { generics: []; name: "COption" } } }, 465 | ]; 466 | kind: "struct"; 467 | }; 468 | }, 469 | { 470 | name: "Multisig"; 471 | type: { 472 | fields: [ 473 | { name: "m"; type: "u8" }, 474 | { name: "n"; type: "u8" }, 475 | { name: "isInitialized"; type: "bool" }, 476 | { name: "signers"; type: { array: ["pubkey", 11] } }, 477 | ]; 478 | kind: "struct"; 479 | }; 480 | }, 481 | ]; 482 | }; 483 | -------------------------------------------------------------------------------- /src/programs/token-extensions/get-account-data-size-eztension.ts: -------------------------------------------------------------------------------- 1 | import { getArrayCodec, getStructCodec } from "@solana/codecs-data-structures"; 2 | import { getU8Codec } from "@solana/codecs"; 3 | 4 | export const getAccountDataSizeLayout = getStructCodec([ 5 | ["instruction", getU8Codec()], 6 | ["extensions", getArrayCodec(getU8Codec(), { size: 1 })], 7 | ]); 8 | -------------------------------------------------------------------------------- /src/programs/token-extensions/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./transfer-fee-extension"; 2 | export * from "./token-metadata-extension"; 3 | export * from "./get-account-data-size-eztension"; 4 | -------------------------------------------------------------------------------- /src/programs/token-extensions/token-metadata-extension.ts: -------------------------------------------------------------------------------- 1 | import { getArrayCodec, getBytesCodec, getStructCodec, getTupleCodec, getUnitCodec, getDataEnumCodec, getBooleanCodec } from "@solana/codecs-data-structures"; 2 | import { getOptionCodec, getU64Codec } from "@solana/codecs"; 3 | import { fixCodecSize } from "@solana/codecs"; 4 | import { getUtf8Codec } from "@solana/codecs-strings"; 5 | 6 | export const metadataLayout = getStructCodec([ 7 | ["instruction", fixCodecSize(getBytesCodec(), 8)], 8 | ["name", getUtf8Codec()], 9 | ["symbol", getUtf8Codec()], 10 | ["uri", getUtf8Codec()], 11 | ["additionalMetadata", getArrayCodec(getTupleCodec([getUtf8Codec(), getUtf8Codec()]))], 12 | ]); 13 | 14 | const getFieldCodec = () => 15 | [ 16 | ["Name", getUnitCodec()], 17 | ["Symbol", getUnitCodec()], 18 | ["Uri", getUnitCodec()], 19 | ["Key", getStructCodec([["value", getTupleCodec([getUtf8Codec()])]])], 20 | ] as const; 21 | 22 | export const updateMetadataLayout = getStructCodec([ 23 | ["instruction", fixCodecSize(getBytesCodec(), 8)], 24 | ["field", getDataEnumCodec(getFieldCodec())], 25 | ["value", getUtf8Codec()], 26 | ]); 27 | 28 | export const removeKeyLayout = getStructCodec([ 29 | ["idempotent", getBooleanCodec()], 30 | ["key", getUtf8Codec()], 31 | ]); 32 | 33 | export const updateAuthorityLayout = getStructCodec([["newAuthority", fixCodecSize(getBytesCodec(), 32)]]); 34 | 35 | export const emitLayout = getStructCodec([ 36 | ["start", getOptionCodec(getU64Codec())], 37 | ["end", getOptionCodec(getU64Codec())], 38 | ]); 39 | -------------------------------------------------------------------------------- /src/programs/token-extensions/transfer-fee-extension.ts: -------------------------------------------------------------------------------- 1 | import { struct, u16, u8 } from "@solana/buffer-layout"; 2 | import { u64 } from "@solana/buffer-layout-utils"; 3 | import { 4 | TokenInstruction, 5 | TokenInvalidInstructionDataError, 6 | TokenInvalidInstructionKeysError, 7 | TokenInvalidInstructionProgramError, 8 | TokenInvalidInstructionTypeError, 9 | TransferFeeInstruction, 10 | } from "@solana/spl-token"; 11 | import { AccountMeta, PublicKey, TransactionInstruction } from "@solana/web3.js"; 12 | 13 | export interface SetTransferFeeInstructionData { 14 | instruction: TokenInstruction.TransferFeeExtension; 15 | transferFeeInstruction: TransferFeeInstruction.SetTransferFee; 16 | transferFeeBasisPoints: number; 17 | maximumFee: bigint; 18 | } 19 | 20 | export const setTransferFeeInstructionData = struct([ 21 | u8("instruction"), 22 | u8("transferFeeInstruction"), 23 | u16("transferFeeBasisPoints"), 24 | u64("maximumFee"), 25 | ]); 26 | 27 | /** A decoded, valid SetTransferFee instruction */ 28 | export interface DecodedSetTransferFeeInstructionUnchecked { 29 | programId: PublicKey; 30 | keys: { 31 | mint: AccountMeta; 32 | authority: AccountMeta; 33 | signers: AccountMeta[] | undefined; 34 | }; 35 | data: { 36 | instruction: TokenInstruction.TransferFeeExtension; 37 | transferFeeInstruction: TransferFeeInstruction.SetTransferFee; 38 | transferFeeBasisPoints: number; 39 | maximumFee: bigint; 40 | }; 41 | } 42 | 43 | /** 44 | * Decode a SetTransferFee instruction without validating it 45 | * 46 | * @param instruction Transaction instruction to decode 47 | * 48 | * @return Decoded, non-validated instruction 49 | */ 50 | export function decodeSetTransferFeeInstructionUnchecked({ 51 | programId, 52 | keys: [mint, authority, ...signers], 53 | data, 54 | }: TransactionInstruction): DecodedSetTransferFeeInstructionUnchecked { 55 | const { instruction, transferFeeInstruction, transferFeeBasisPoints, maximumFee } = setTransferFeeInstructionData.decode(data); 56 | 57 | return { 58 | programId, 59 | keys: { 60 | mint, 61 | authority, 62 | signers, 63 | }, 64 | data: { 65 | instruction, 66 | transferFeeInstruction, 67 | transferFeeBasisPoints, 68 | maximumFee, 69 | }, 70 | }; 71 | } 72 | 73 | /** A decoded, valid SetTransferFee instruction */ 74 | export interface DecodedSetTransferFeeInstruction { 75 | programId: PublicKey; 76 | keys: { 77 | mint: AccountMeta; 78 | authority: AccountMeta; 79 | signers: AccountMeta[] | null; 80 | }; 81 | data: { 82 | instruction: TokenInstruction.TransferFeeExtension; 83 | transferFeeInstruction: TransferFeeInstruction.SetTransferFee; 84 | transferFeeBasisPoints: number; 85 | maximumFee: bigint; 86 | }; 87 | } 88 | 89 | /** 90 | * Decode an SetTransferFee instruction and validate it 91 | * 92 | * @param instruction Transaction instruction to decode 93 | * @param programId SPL Token program account 94 | * 95 | * @return Decoded, valid instruction 96 | */ 97 | export function decodeSetTransferFeeInstruction(instruction: TransactionInstruction, programId: PublicKey): DecodedSetTransferFeeInstruction { 98 | if (!instruction.programId.equals(programId)) throw new TokenInvalidInstructionProgramError(); 99 | if (instruction.data.length !== setTransferFeeInstructionData.span) throw new TokenInvalidInstructionDataError(); 100 | 101 | const { 102 | keys: { mint, authority, signers }, 103 | data, 104 | } = decodeSetTransferFeeInstructionUnchecked(instruction); 105 | if (data.instruction !== TokenInstruction.TransferFeeExtension || data.transferFeeInstruction !== TransferFeeInstruction.SetTransferFee) 106 | throw new TokenInvalidInstructionTypeError(); 107 | if (!mint) throw new TokenInvalidInstructionKeysError(); 108 | 109 | return { 110 | programId, 111 | keys: { 112 | mint, 113 | authority, 114 | signers: signers ? signers : null, 115 | }, 116 | data, 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /tests/customParser.test.ts: -------------------------------------------------------------------------------- 1 | import { Connection, clusterApiUrl, TransactionInstruction, PublicKey } from "@solana/web3.js"; 2 | import { assert } from "chai"; 3 | 4 | import { ParsedAccount, ParsedCustomInstruction, SolanaParser } from "../src"; 5 | 6 | function customParser(instruction: TransactionInstruction): ParsedCustomInstruction { 7 | let args: unknown; 8 | let keys: ParsedAccount[]; 9 | let name: string; 10 | switch (instruction.data[0]) { 11 | case 0: 12 | args = { message: instruction.data.slice(1).toString("utf8") }; 13 | keys = [instruction.keys[0], { name: "messageFrom", ...instruction.keys[1] }]; 14 | name = "echo"; 15 | break; 16 | case 1: 17 | args = { a: instruction.data.readBigInt64LE(1), b: instruction.data.readBigInt64LE(9) }; 18 | keys = instruction.keys; 19 | name = "sum"; 20 | break; 21 | default: 22 | throw new Error("unknown instruction!"); 23 | } 24 | 25 | return { 26 | programId: instruction.programId, 27 | accounts: keys, 28 | args, 29 | name, 30 | }; 31 | } 32 | 33 | function customTest() { 34 | const connection = new Connection(clusterApiUrl("devnet")); 35 | const ix0Tx = "2QU8jyEde9qbvtrYBJJZ2iBubqodmQRSoq2pfomHdGYgTgXwuncappiet8ojGGRdEkzkhW8sXdyfCxwuGHaHYegC"; 36 | const ix1Tx = "2FQ3jpUb5Qx1jSrT1C9wkcbhbaumJ8Z15c9L3gENdaeVCpavz2VHEwivVABpRQPgnUspGmqUSuSwsyzDagERXKE1"; 37 | const parser = new SolanaParser([]); 38 | 39 | it("can take custom parser", () => { 40 | parser.addParser(new PublicKey("5wZA8owNKtmfWGBc7rocEXBvTBxMtbpVpkivXNKXNuCV"), customParser); 41 | }); 42 | 43 | it("can parse instruction 0", async () => { 44 | const parsed = await parser.parseTransactionByHash(connection, ix0Tx); 45 | if (!parsed) return Promise.reject("failed to get/parse tx"); 46 | assert.equal(parsed[0].name, "echo"); 47 | assert.equal((parsed[0].args as { message: string }).message, "test echo message"); 48 | }); 49 | 50 | it("can parse instruction 1", async () => { 51 | const parsed = await parser.parseTransactionByHash(connection, ix1Tx); 52 | if (!parsed) return Promise.reject("failed to get/parse tx"); 53 | 54 | assert.equal(parsed[0].name, "sum"); 55 | const args = parsed[0].args as { a: bigint; b: bigint }; 56 | assert.equal(args.a, BigInt(11)); 57 | assert.equal(args.b, BigInt(12)); 58 | }); 59 | } 60 | 61 | describe("Custom parser test", customTest); 62 | -------------------------------------------------------------------------------- /tests/parseComputeBudget.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import "mocha"; 3 | import assert from "assert"; 4 | 5 | import { ComputeBudgetProgram, TransactionInstruction } from "@solana/web3.js"; 6 | 7 | import { SolanaParser } from "../src"; 8 | import { ParsedIdlInstruction } from "../src/interfaces"; 9 | import { ComputeBudgetIdl } from "../src/programs"; 10 | 11 | const parser = new SolanaParser([]); 12 | 13 | describe("Test parse compute budget ixs", () => { 14 | it("can parse ixs", () => { 15 | const COMPUTE_UNIT_REQUEST_UNITS = { units: 345, additionalFee: 666 }; 16 | const COMPUTE_UNIT_LIMIT = 200_010; 17 | const COMPUTE_UNIT_PRICE = 12345; 18 | const COMPUTE_UNIT_FRAME = 100; 19 | const COMPUTE_UNIT_ACCOUNT_SIZE_LIMIT = 0x256; 20 | 21 | const setLoadedAccountsDataSizeLimitData = Buffer.alloc(5); 22 | setLoadedAccountsDataSizeLimitData[0] = 4; 23 | setLoadedAccountsDataSizeLimitData.writeUint32LE(COMPUTE_UNIT_ACCOUNT_SIZE_LIMIT, 1); 24 | 25 | const heapFrameData = Buffer.alloc(5); 26 | heapFrameData[0] = 1; 27 | heapFrameData.writeUint32LE(COMPUTE_UNIT_FRAME, 1); 28 | const ixs = [ 29 | new TransactionInstruction({ 30 | keys: [], 31 | programId: ComputeBudgetProgram.programId, 32 | data: setLoadedAccountsDataSizeLimitData, 33 | }), 34 | ComputeBudgetProgram.requestHeapFrame({ bytes: COMPUTE_UNIT_FRAME }), 35 | ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNIT_LIMIT }), 36 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: COMPUTE_UNIT_PRICE }), 37 | ComputeBudgetProgram.requestUnits(COMPUTE_UNIT_REQUEST_UNITS), 38 | new TransactionInstruction({ keys: [], programId: ComputeBudgetProgram.programId, data: heapFrameData }), 39 | ]; 40 | let parsed = parser.parseInstruction(ixs[0]) as ParsedIdlInstruction; 41 | assert.equal(parsed.name, "setLoadedAccountsDataSizeLimit"); 42 | assert.equal(parsed.args.bytes, COMPUTE_UNIT_ACCOUNT_SIZE_LIMIT); 43 | 44 | parsed = parser.parseInstruction(ixs[1]) as ParsedIdlInstruction; 45 | assert.equal(parsed.name, "requestHeapFrame"); 46 | assert.equal(parsed.args.bytes, COMPUTE_UNIT_FRAME); 47 | 48 | parsed = parser.parseInstruction(ixs[2]) as ParsedIdlInstruction; 49 | assert.equal(parsed.name, "setComputeUnitLimit"); 50 | assert.equal(parsed.args.units, COMPUTE_UNIT_LIMIT); 51 | 52 | parsed = parser.parseInstruction(ixs[3]) as ParsedIdlInstruction; 53 | assert.equal(parsed.name, "setComputeUnitPrice"); 54 | assert.equal(parsed.args.microLamports, COMPUTE_UNIT_PRICE); 55 | 56 | parsed = parser.parseInstruction(ixs[4]) as ParsedIdlInstruction; 57 | assert.equal(parsed.name, "requestUnits"); 58 | assert.equal(parsed.args.units, COMPUTE_UNIT_REQUEST_UNITS.units); 59 | assert.equal(parsed.args.additionalFee, COMPUTE_UNIT_REQUEST_UNITS.additionalFee); 60 | 61 | parsed = parser.parseInstruction(ixs[5]) as ParsedIdlInstruction; 62 | assert.equal(parsed.name, "requestHeapFrame"); 63 | assert.equal(parsed.args.bytes, COMPUTE_UNIT_FRAME); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /tests/parseDlnDstTransaction.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import "mocha"; 3 | import assert from "assert"; 4 | 5 | import { Connection, clusterApiUrl } from "@solana/web3.js"; 6 | 7 | import { SolanaParser } from "../src"; 8 | import { ParsedIdlEvent, ParsedIdlInstruction } from "../src/interfaces"; 9 | 10 | import { IDL as DlnDstIdl, DlnDst } from "./idl/dst"; 11 | import { IDL as JupIdl, Jupiter } from "./idl/jupiter_v6"; 12 | 13 | const rpcConnection = new Connection(clusterApiUrl("mainnet-beta")); 14 | const parser = new SolanaParser([ 15 | { idl: DlnDstIdl, programId: "dst5MGcFPoBeREFAA5E3tU5ij8m5uVYwkzkSAbsLbNo" }, 16 | { idl: JupIdl, programId: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4" }, 17 | ]); 18 | 19 | describe("Test parse transaction", () => { 20 | it("can parse fulfill tx", async () => { 21 | const parsed = await parser.parseTransactionByHash( 22 | rpcConnection, 23 | "4V5XFQ8ViuWi4VxHWb7bCZtWGzyPgZnKTf6ABnC1VUXd2y5wgfrn8EGmzwst1iP19ySPA7jwx87KWX3S2GYUh8Lr", 24 | true, 25 | ); 26 | 27 | const fulfillOrder = parsed?.find((pix) => pix.name === "fulfill_order") as ParsedIdlInstruction; 28 | assert.equal(fulfillOrder.args.unvalidated_order.maker_order_nonce.toString(), "1737321940254"); 29 | 30 | const swapEvent = parsed?.find((pix) => pix.name === "SwapEvent") as ParsedIdlEvent; 31 | assert.equal(swapEvent.args.output_amount.toString(), "1798522248"); 32 | }); 33 | 34 | it("can parse send_batch_unlock tx", async () => { 35 | const parsed = await parser.parseTransactionByHash( 36 | rpcConnection, 37 | "HLNFpn7Aj9AgL5umSKQyKPHgvnK5YvmLMBfJQnRZTQQ23ZFRh9wi1gxusj7WWGgFG1DFZ5zmsPnZ7N6AtC4Tzaq", 38 | false, 39 | ); 40 | 41 | const unlock = parsed?.find((v) => v.name === "send_batch_unlock") as ParsedIdlInstruction; 42 | assert.equal(unlock.accounts[10].name, "sending.bridge"); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /tests/parseDlnSrcTransaction.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import "mocha"; 3 | import assert from "assert"; 4 | 5 | import { Connection, clusterApiUrl } from "@solana/web3.js"; 6 | 7 | import { SolanaParser } from "../src"; 8 | import { ParsedIdlInstruction } from "../src/interfaces"; 9 | 10 | import { IDL as DlnSrcIdl, DlnSrc } from "./idl/src"; 11 | 12 | const rpcConnection = new Connection(clusterApiUrl("mainnet-beta")); 13 | const parser = new SolanaParser([{ idl: DlnSrcIdl, programId: "src5qyZHqTqecJV4aY6Cb6zDZLMDzrDKKezs22MPHr4" }]); 14 | 15 | describe("Test parse transaction", () => { 16 | it("can parse create tx", async () => { 17 | const parsed = await parser.parseTransactionByHash( 18 | rpcConnection, 19 | "3ggTVbZvk38HfKTQ8fcTYkvTeDc1uw75KTGUzpm1MVHFJWHhFSbQBALshBmszepZDULqTqxnMJhAPiT6UgXMUK5d", 20 | false, 21 | ); 22 | 23 | const createOrder = parsed?.find((pix) => pix.name === "create_order_with_nonce") as ParsedIdlInstruction; 24 | assert.equal(createOrder.args.order_args.give_original_amount.toString(), "3011764280"); 25 | assert.equal(createOrder.accounts[0].name, "maker"); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/parseIx.test.ts: -------------------------------------------------------------------------------- 1 | import "mocha"; 2 | import assert from "assert"; 3 | 4 | import { TransactionInstruction, Keypair } from "@solana/web3.js"; 5 | import { TOKEN_PROGRAM_ID, TokenInstruction } from "@solana/spl-token"; 6 | 7 | import { SolanaParser } from "../src/index"; 8 | import { ParsedIdlInstruction, idl } from "../src"; 9 | 10 | function parseInstructionTest() { 11 | const parser = new SolanaParser([]); 12 | it("can parse spl ix", () => { 13 | const kp1 = Keypair.generate(); 14 | const kp2 = Keypair.generate(); 15 | const kp3 = Keypair.generate(); 16 | const init3Ix = new TransactionInstruction({ 17 | programId: TOKEN_PROGRAM_ID, 18 | keys: [ 19 | { isSigner: false, isWritable: true, pubkey: kp1.publicKey }, 20 | { isSigner: false, isWritable: false, pubkey: kp2.publicKey }, 21 | ], 22 | data: Buffer.concat([Buffer.from([TokenInstruction.InitializeAccount3]), kp3.publicKey.toBuffer()]), 23 | }); 24 | const parsed = parser.parseInstruction(init3Ix) as ParsedIdlInstruction; 25 | assert.equal(parsed.args.owner.toBase58(), kp3.publicKey.toBase58()); 26 | assert.equal(parsed.name, "initializeAccount3"); 27 | }); 28 | } 29 | 30 | describe("Parse raw instruction data", parseInstructionTest); 31 | -------------------------------------------------------------------------------- /tests/parseLogs.test.ts: -------------------------------------------------------------------------------- 1 | import "mocha"; 2 | import assert from "assert"; 3 | 4 | import { clusterApiUrl, Connection, GetVersionedTransactionConfig } from "@solana/web3.js"; 5 | 6 | import { parseLogs } from "../src"; 7 | 8 | const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); 9 | 10 | const connection = new Connection(clusterApiUrl("mainnet-beta")); 11 | 12 | const computeBudgetProgramId = "ComputeBudget111111111111111111111111111111"; 13 | const systemProgramId = "11111111111111111111111111111111"; 14 | const srcProgramId = "src5qyZHqTqecJV4aY6Cb6zDZLMDzrDKKezs22MPHr4"; 15 | const execProgramId = "exe59FS5cojZkPJVDFDV8RnXCC7wd6yoBjsUtqH7Zai"; 16 | const dstProgramId = "dst5MGcFPoBeREFAA5E3tU5ij8m5uVYwkzkSAbsLbNo"; 17 | // const tokenProgramId = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; 18 | const debridgeProgramId = "DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh"; 19 | 20 | const config: GetVersionedTransactionConfig = { 21 | commitment: "confirmed", 22 | maxSupportedTransactionVersion: 0, 23 | }; 24 | 25 | describe("Test parse logs", () => { 26 | beforeEach(async () => { 27 | await sleep(2000); 28 | }); 29 | 30 | it("create order", async () => { 31 | const transaction = await connection.getTransaction("3ggTVbZvk38HfKTQ8fcTYkvTeDc1uw75KTGUzpm1MVHFJWHhFSbQBALshBmszepZDULqTqxnMJhAPiT6UgXMUK5d", config); 32 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 33 | 34 | const instruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: CreateOrderWithNonce")); 35 | 36 | assert.equal(Boolean(instruction), true); 37 | assert.equal(parsed.length, 9); 38 | assert.equal(parsed[0].programId, computeBudgetProgramId); 39 | assert.equal(parsed[2].programId, srcProgramId); 40 | assert.equal(parsed[3].programId, systemProgramId); 41 | }); 42 | 43 | it("swap and create order", async () => { 44 | const transaction = await connection.getTransaction("4U9MhiLjCLXwi8q2mC7NrejGGkCExTuo8ibUm2xHG5qu6BiVeDaLieZkxKnusJY7fUH2LTqPL6E23pxpkJQusKdn", config); 45 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 46 | 47 | const swapInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: Swap")); 48 | const createOrderInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: CreateOrderWithNonce")); 49 | 50 | assert.equal(Boolean(swapInstruction), true); 51 | assert.equal(Boolean(createOrderInstruction), true); 52 | assert.equal(parsed.length, 14); 53 | assert.equal(parsed[0].programId, computeBudgetProgramId); 54 | assert.equal(parsed[7].programId, srcProgramId); 55 | assert.equal(parsed[8].programId, systemProgramId); 56 | }); 57 | 58 | it("claim", async () => { 59 | const transaction = await connection.getTransaction("29pkpwVtAseSivXgWogcNot17QM6XUXVSzVzemEa83KRTA7wrJcvok6ExmTDTQNFRVzkoPGYDx9tQLNfYBjMjSVN", config); 60 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 61 | 62 | const claimInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: Claim")); 63 | 64 | assert.equal(Boolean(claimInstruction), true); 65 | assert.equal(parsed.length, 13); 66 | assert.equal(parsed[0].programId, computeBudgetProgramId); 67 | assert.equal(parsed[7].programId, debridgeProgramId); 68 | }); 69 | 70 | it("send", async () => { 71 | const transaction = await connection.getTransaction("smvvcUj7KCN16VLCBXsJC7VieTKM3pDNSNRBygcaipiicdq2mevB5iUk5dSRtCk7fnPsKT2jdbtMiCymEppvywL", config); 72 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 73 | 74 | const sendInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: Send")); 75 | 76 | assert.equal(Boolean(sendInstruction), true); 77 | assert.equal(parsed.length, 8); 78 | assert.equal(parsed[0].programId, computeBudgetProgramId); 79 | assert.equal(parsed[2].programId, debridgeProgramId); 80 | }); 81 | 82 | it("cancel", async () => { 83 | const transaction = await connection.getTransaction("BKguzw48fZNMkxEtZVVg2BXJB15r3fP1bykKBDCQp549bzn6KTxmSFTR9c9Lw9yxDrUbph31dhYFZLyMpfG6eMN", config); 84 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 85 | 86 | const cancelInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: MakeFallback")); 87 | 88 | assert.equal(parsed.length, 4); 89 | assert.equal(Boolean(cancelInstruction), true); 90 | assert.equal(parsed[0].programId, computeBudgetProgramId); 91 | assert.equal(parsed[2].programId, execProgramId); 92 | }); 93 | 94 | it("send unlock", async () => { 95 | const transaction = await connection.getTransaction("53bkomu4z4dR7hxXTEcSz3nawQud6dzCeUjXQ4sm56TwJr9WsCFSbBCbks1BPeyYoWwuPFw68RxWeerJPeoAJhhb", config); 96 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 97 | 98 | const sendUnlockInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: SendUnlock")); 99 | 100 | assert.equal(parsed.length, 16); 101 | assert.equal(Boolean(sendUnlockInstruction), true); 102 | assert.equal(parsed[0].programId, computeBudgetProgramId); 103 | assert.equal(parsed[6].programId, dstProgramId); 104 | assert.equal(parsed[8].programId, systemProgramId); 105 | assert.equal(parsed[10].programId, debridgeProgramId); 106 | }); 107 | 108 | it("send batch unlock", async () => { 109 | const transaction = await connection.getTransaction("3WQnzbjPUFc55rHLzq2F65ma8anT5eiZTXhVR2DmFGwYuhbEDSnuEQ7yzJzkshwfR3xxAJYBLhs7fnr5s5dpp8Ue", config); 110 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 111 | 112 | const sendBatchUnlockInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: SendBatchUnlock")); 113 | const initExtCallInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: InitExternalCallStorage")); 114 | 115 | assert.equal(parsed.length, 11); 116 | assert.equal(Boolean(sendBatchUnlockInstruction), true); 117 | assert.equal(Boolean(initExtCallInstruction), true); 118 | assert.equal(parsed[0].programId, computeBudgetProgramId); 119 | assert.equal(parsed[4].programId, dstProgramId); 120 | assert.equal(parsed[5].programId, debridgeProgramId); 121 | assert.equal(parsed[6].programId, systemProgramId); 122 | assert.equal(parsed[8].programId, debridgeProgramId); 123 | }); 124 | 125 | it("fill ext storage", async () => { 126 | const transaction = await connection.getTransaction("3UsPeqCEssRFkPTAuZhnAbo58UJAyD3ELfRfiXGLrfNkzn2UQJkeeAY2rs7rDbEikmA5tL5xc9MpZ2V4DUvGReKo", config); 127 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 128 | 129 | const fillExtCallInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: FillExtcallStorage")); 130 | 131 | assert.equal(parsed.length, 4); 132 | assert.equal(Boolean(fillExtCallInstruction), true); 133 | assert.equal(parsed[0].programId, computeBudgetProgramId); 134 | assert.equal(parsed[2].programId, execProgramId); 135 | assert.equal(parsed[3].programId, systemProgramId); 136 | }); 137 | 138 | it("execute ext storage", async () => { 139 | const transaction = await connection.getTransaction("T1sddCsAfW3S8LNXJJmJivyni6e4Mvf1yRneco5Ef6rvqE3yGo7uaDT67w6MrGprWkHPdwSa5fDhyALN84UHpzr", config); 140 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 141 | 142 | const execInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: ExecuteExtcall")); 143 | 144 | assert.equal(parsed.length, 13); 145 | assert.equal(Boolean(execInstruction), true); 146 | assert.equal(parsed[0].programId, computeBudgetProgramId); 147 | assert.equal(parsed[2].programId, execProgramId); 148 | }); 149 | 150 | it("fulfill", async () => { 151 | const transaction = await connection.getTransaction("2H1jBCXaqoy8XZFvdsbZzC9bKU3V2C43YoDEJZVBMUg5wKf12LMt4fqT8j65D7SZY1PNfgMFiTRFXqpcAt5Wdc4z", config); 152 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 153 | 154 | const fulfillInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: FulfillOrder")); 155 | 156 | assert.equal(parsed.length, 9); 157 | assert.equal(Boolean(fulfillInstruction), true); 158 | assert.equal(parsed[0].programId, computeBudgetProgramId); 159 | assert.equal(parsed[1].programId, dstProgramId); 160 | assert.equal(parsed[2].programId, systemProgramId); 161 | }); 162 | 163 | it("swap and fulfill", async () => { 164 | const transaction = await connection.getTransaction("3f4Cg5sKWMZzJLYrm5rZzZnn4kdUpyr9EjcCh7zMrj3PwgAfogibv7YiigDBdLxp8MDT7scGh4gSdEJ3BvBbWhnC", config); 165 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 166 | 167 | const swapInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: Swap")); 168 | const fulfillInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: FulfillOrder")); 169 | 170 | assert.equal(parsed.length, 19); 171 | assert.equal(Boolean(swapInstruction), true); 172 | assert.equal(Boolean(fulfillInstruction), true); 173 | assert.equal(parsed[0].programId, computeBudgetProgramId); 174 | assert.equal(parsed[14].programId, dstProgramId); 175 | assert.equal(parsed[18].programId, systemProgramId); 176 | }); 177 | 178 | it("cancel order", async () => { 179 | const transaction = await connection.getTransaction("Y4RSvrxrj4tVGTK6JqJPJGkLmSC4AJNjhsk98nGz2chw5NHa8YT2CiPPQYy2XUFmxjxLJapWmV5Cq9gAKsBU8uj", config); 180 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 181 | 182 | const cancelInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: CancelOrder")); 183 | const sendOrderCancelInstruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: SendOrderCancel")); 184 | 185 | assert.equal(parsed.length, 18); 186 | assert.equal(Boolean(cancelInstruction), true); 187 | assert.equal(Boolean(sendOrderCancelInstruction), true); 188 | assert.equal(parsed[0].programId, computeBudgetProgramId); 189 | assert.equal(parsed[2].programId, dstProgramId); 190 | assert.equal(parsed[8].programId, dstProgramId); 191 | }); 192 | 193 | /* 194 | it("claim DBR from lfg vault", async () => { 195 | const transaction = await connection.getTransaction("vExfjh4tsxGij52vzwXAFm85atUoEuSeFdkbe3nP3s7ykmqpwYYQR2ueuJuiLuFL4AdBdQWFnNBPe9jnTkq3244", config); 196 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 197 | 198 | console.log(parsed); 199 | 200 | assert.equal(parsed.length, 5); 201 | }); 202 | 203 | it("claim DBR from lfg vault 2", async () => { 204 | const transaction = await connection.getTransaction("3JvmKGK2rmXWqSLFF7rjMtUejgN6pyJLrbAeCFuYAeV4avUwLzorveYG9bDP7FxZfQnoLw3BozChU8PGSZFYcss2", config); 205 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 206 | 207 | assert.equal(parsed.length, 10); 208 | }); 209 | 210 | it("claim DBR solana", async () => { 211 | const transaction = await connection.getTransaction("4VJawfedqZ1EMeDZtaj4mibbqzLMhCHcNwuCPH3TZ3BiYRoHb9Ys5pMhQeC2cvcYex4E41cwCg3CPznypEvxty4q", config); 212 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 213 | 214 | assert.equal(parsed.length, 10); 215 | }); 216 | 217 | it("claim DBR evm", async () => { 218 | const transaction = await connection.getTransaction("32LEkwDy9ddTmuRSttDvMyEbx3cbVWhz73gDYkTVBABX1DPVLqMdgGGYkn1xkSAnmdUd379Qs61j3ngvycoJKBri", config); 219 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 220 | 221 | assert.equal(parsed.length, 5); 222 | }); 223 | 224 | 225 | it("create order", async () => { 226 | const transaction = await connection.getTransaction("4VH6mCzijNG8PM2tZBq3PkAdqtJnkBoHk54s9EEVbcuqWyXmfUGWz6WPmMhgwLGfXJpJf3qd7C8zuoSKYeav4z9C", config); 227 | const parsed = parseLogs(transaction?.meta?.logMessages ?? []); 228 | const instruction = parsed.find(({ logMessages }) => logMessages.includes("Instruction: CreateOrderWithNonce")); 229 | 230 | assert.equal(Boolean(instruction), true); 231 | assert.equal(parsed.length, 12); 232 | assert.equal(parsed[0].programId, computeBudgetProgramId); 233 | assert.equal(parsed[2].programId, srcProgramId); 234 | assert.equal(parsed[8].programId, tokenProgramId); 235 | }); 236 | */ 237 | }); 238 | -------------------------------------------------------------------------------- /tests/parseNativeTransaction.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import "mocha"; 3 | import assert from "assert"; 4 | 5 | import { Connection, clusterApiUrl } from "@solana/web3.js"; 6 | 7 | import { ParsedIdlInstruction, SolanaParser, idl } from "../src"; 8 | 9 | const rpcConnection = new Connection(clusterApiUrl("mainnet-beta")); 10 | const parser = new SolanaParser([]); 11 | 12 | describe("Test parse transaction", () => { 13 | it("can parse transfer tx", async () => { 14 | const parsed = await parser.parseTransactionByHash( 15 | rpcConnection, 16 | "5UcRhpbnCzgsp9RJ9vt3PRYSy1TMMdAFExWXhfASbwuAmYi7L1sw3u2eWQ3911veefpAFrcidmqmDT2Fkag4kQHh", 17 | false, 18 | ); 19 | 20 | const transfer = parsed?.find((pix) => pix.name === "transfer") as ParsedIdlInstruction; 21 | 22 | assert.equal(transfer.args.amount.toString(), "10000"); 23 | }); 24 | 25 | it("can parse system tx", async () => { 26 | const parsed = await parser.parseTransactionByHash( 27 | rpcConnection, 28 | "5Gvo9kn6AvAFA4caLo6nkVj4aZwCcPJW42ywCRjSmUvwokra16WbAexrKr9dDc7sqTAjJ3G46GgqWYtvvbC48ACX", 29 | false, 30 | ); 31 | 32 | const createATA = parsed?.find((pix) => pix.name === "createAssociatedTokenAccount"); 33 | 34 | assert.equal(Boolean(createATA), true); 35 | }); 36 | 37 | it("can parse base64 tx", async () => { 38 | const parsed = await parser.parseTransactionDump( 39 | rpcConnection, 40 | "Ad/g5OAEaHD3s+moNYVi7UI8R1j0SoFnqOvQv2VhmRT+qvzBcNOOdVKI4j6zAAhblJozVESD4xm/lA2bHDuOiwwBAAEEpWj8ArX39WwJz86zDyZtAlZE9cSVTjPRIN58jtbQPZxhpNDI0S/2ZBfLMb6HeLXgaGGz97EK3dyVlkLYtIg5VPpl7O120Zak9/VrCtDtHyWP+nrMkyrs29yWTK7zUYtQBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKmaseti9QY1Urgrk5uy9MkXzPc5i5Vq+PxmDQT6B2833QIDAwECAAkDECcAAAAAAAADAwEAAAEJ", 41 | ); 42 | 43 | const transfer = parsed?.find((pix) => pix.name === "transfer") as ParsedIdlInstruction; 44 | 45 | assert.equal(transfer.args.amount.toString(), "10000"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/parseSystemTransaction.test.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | 3 | import { clusterApiUrl, Connection } from "@solana/web3.js"; 4 | 5 | import { SolanaParser } from "../src/index"; 6 | 7 | function parseSysTx() { 8 | const connection = new Connection(clusterApiUrl("devnet")); 9 | const parser = new SolanaParser([]); 10 | const txId = "35wgJWUiYVRi5xEEEZmeMGiqh2anTjK17UMSp4ZGXpm8wJYxT27MqKzjaRxc3QaNZvxURTdiDmjHP8NQoeoxxe4P"; 11 | it("can parse system tx", async () => { 12 | const parsed = await parser.parseTransactionByHash(connection, txId); 13 | if (!parsed) throw new Error("failed to parse"); 14 | assert.equal(parsed[0].name, "createAccount"); 15 | assert.equal(parsed[1].name, "initializeAccount"); 16 | }); 17 | } 18 | 19 | describe("parse system transaction", parseSysTx); 20 | -------------------------------------------------------------------------------- /tests/parseTransaction.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import "mocha"; 3 | import assert from "assert"; 4 | 5 | import { BN, Idl } from "@coral-xyz/anchor"; 6 | import { PublicKey, Connection, clusterApiUrl } from "@solana/web3.js"; 7 | 8 | import { SolanaParser } from "../src"; 9 | import { ParsedAccount, ParsedIdlInstruction, ParsedInstruction } from "../src/interfaces"; 10 | 11 | import { IDL as JupiterV30Idl, JupiterV30 } from "./idl/jupiter_v2"; 12 | 13 | function stringifyAccount(account: ParsedAccount) { 14 | return `${account.name || "unknown"} @ ${account.pubkey.toBase58()}`; 15 | } 16 | 17 | function toString(value: unknown, depth: number = 0): string { 18 | if (typeof value === "string" || typeof value === "number") return `${" ".repeat(depth)}${value}`; 19 | if (value instanceof Buffer) return `${" ".repeat(depth)}0x${value.toString("hex")}`; 20 | if (value instanceof PublicKey) return `${" ".repeat(depth)}${value.toBase58()}`; 21 | if (value instanceof BN) return `${" ".repeat(depth)}${value.toString(10)}`; 22 | if (value instanceof Object) { 23 | let result = " ".repeat(depth) + "{\n"; 24 | for (const [name, val] of Object.entries(value)) { 25 | result += `${" ".repeat(depth + 1) + name} = ${toString(val, depth + 1)}\n`; 26 | } 27 | result += " ".repeat(depth) + "}"; 28 | 29 | return result; 30 | } 31 | 32 | return value as string; 33 | } 34 | 35 | function stringifyArgs(args: unknown) { 36 | const result = []; 37 | for (const [argName, argValue] of Object.entries(args as { [s: string]: unknown })) { 38 | result.push(`${argName} = ${toString(argValue)}`); 39 | } 40 | 41 | return result.length > 0 ? result.join("\n") : "None"; 42 | } 43 | 44 | function printParsedIx(parsedIx: ParsedInstruction) { 45 | console.log( 46 | `----------\nProgram: ${parsedIx.programId.toBase58()}\nName: ${parsedIx.name}\n***Accounts***\n${parsedIx.accounts 47 | .map((account) => stringifyAccount(account)) 48 | .join("\n")}\n\nargs: ${stringifyArgs(parsedIx.args)}`, 49 | ); 50 | } 51 | 52 | function parseTransactionTest() { 53 | it("can parse jupiter tx", async () => { 54 | const rpcConnection = new Connection(clusterApiUrl("mainnet-beta")); 55 | const txParser = new SolanaParser([{ idl: JupiterV30Idl, programId: "JUP2jxvXaqu7NQY1GmNF4m1vodw12LVXYxbFL2uJvfo" }]); 56 | const parsed = await txParser.parseTransactionByHash( 57 | rpcConnection, 58 | "5zgvxQjV6BisU8SfahqasBZGfXy5HJ3YxYseMBG7VbR4iypDdtdymvE1jmEMG7G39bdVBaHhLYUHUejSTtuZEpEj", 59 | false, 60 | ); 61 | parsed?.find((pix) => pix.name === "set_token_ledger") as ParsedIdlInstruction; 62 | parsed?.map((pix) => printParsedIx(pix)); 63 | if (!parsed || parsed.length != 3) return Promise.reject("Failed to parse transaction correctly"); 64 | const swap = parsed[1] as ParsedIdlInstruction; 65 | assert.equal(swap.args.platform_fee_bps.toString(), "0"); 66 | }); 67 | } 68 | 69 | describe("Test parse transaction", parseTransactionTest); 70 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": false, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | "lib": ["ES2021"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | // "module": "commonjs", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": ".", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | // "outDir": "./dist", /* Specify an output folder for all emitted files. */ 51 | "removeComments": false, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | 68 | /* Interop Constraints */ 69 | "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 70 | "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 71 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 72 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 73 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 74 | 75 | /* Type Checking */ 76 | "strict": true, /* true, */ /* Enable all strict type-checking options. */ 77 | "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 78 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 79 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 80 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 81 | // "strictPropertyInitialization": false, /* Check for class properties that are declared but not set in the constructor. */ 82 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 83 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 84 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 85 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 86 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 87 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 88 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 89 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 90 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 91 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 92 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 93 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 94 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 95 | 96 | /* Completeness */ 97 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 98 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 99 | }, 100 | } 101 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "target": "es2021", 6 | "outDir": "dist/cjs/", 7 | "rootDir": "./src", 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "moduleResolution": "node", 5 | "module": "es2022", 6 | "target": "es2021", 7 | "outDir": "dist/esm/", 8 | "rootDir": "./src", 9 | "declaration": false, 10 | "declarationMap": false, 11 | }, 12 | "include": ["src"], 13 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "moduleResolution": "node", 5 | "module": "commonjs", 6 | "types": ["mocha", "chai", "node"] 7 | }, 8 | "include": ["src", "tests", "cli"], 9 | "typedocOptions": { 10 | "entryPoints": ["./src/index.ts"], 11 | "entryPointStrategy": "expand", 12 | "plugin": "typedoc-plugin-markdown", 13 | "excludeExternals": true, 14 | "readme": "none", 15 | "externalPattern": ["**/node_modules/**"], 16 | "excludePrivate": true, 17 | "out": "docs" 18 | } 19 | } --------------------------------------------------------------------------------