
{"id":136514,"date":"2026-02-19T07:26:40","date_gmt":"2026-02-19T07:26:40","guid":{"rendered":"https:\/\/mycryptomania.com\/?p=136514"},"modified":"2026-02-19T07:26:40","modified_gmt":"2026-02-19T07:26:40","slug":"how-to-decode-solana-transaction-data-and-how-it-differs-from-evm","status":"publish","type":"post","link":"https:\/\/mycryptomania.com\/?p=136514","title":{"rendered":"How to Decode Solana Transaction Data (and how it differs from EVM)"},"content":{"rendered":"<p>Photo by <a href=\"https:\/\/unsplash.com\/@guerrillabuzz?utm_source=medium&amp;utm_medium=referral\">GuerrillaBuzz<\/a> on\u00a0<a href=\"https:\/\/unsplash.com\/?utm_source=medium&amp;utm_medium=referral\">Unsplash<\/a><\/p>\n<p>If you\u2019re coming from the EVM world, decoding Solana transactions can feel like learning a new language. In Ethereum, you\u2019d use ABIs and tools like <a href=\"https:\/\/docs.ethers.org\/v6\/\">ethers.js<\/a> to decode transaction data. In Solana, the process is different\u200a\u2014\u200abut once you understand it, it\u2019s just as powerful.<\/p>\n<p><strong>This guide is for:<\/strong><br \/> \u2022 EVM developers moving to Solana<br \/> \u2022 Backend engineers <strong>building indexers, bridges, analytics platforms,\u00a0\u2026<\/strong><br \/> \u2022 Anyone <strong>decoding on-chain Solana\u00a0data<\/strong><\/p>\n<p>I\u2019ll walk you through decoding Solana transaction data using a real-world example: an on-chain auction program (i.e., smart contract). We\u2019ll use the <a href=\"https:\/\/github.com\/debridge-finance\/solana-tx-parser-public\/tree\/master\">Solana Transaction Parser library from Debridge Finance<\/a> and learn how to handle custom program instructions that aren\u2019t decoded by\u00a0default.<\/p>\n<p><strong>Source code:<\/strong> <a href=\"https:\/\/github.com\/AndreAugusto11\/example-usage-solana-parser\">https:\/\/github.com\/AndreAugusto11\/example-usage-solana-parser<\/a><\/p>\n<h3>Getting Started<\/h3>\n<p>Before we dive in, clone the repository and set up your environment:<\/p>\n<p>git clone https:\/\/github.com\/AndreAugusto11\/example-usage-solana-parser.git<br \/>cd example-usage-solana-parser<br \/>npm install<\/p>\n<p>You\u2019ll also need TypeScript tooling:<\/p>\n<p>npm install -D typescript ts-node<\/p>\n<p><strong>Requirements:<\/strong><\/p>\n<p>Node.js 18+ (recommended)npm<\/p>\n<h3>EVM vs Solana: Understanding the Fundamentals<\/h3>\n<p>Before we dive into code, let\u2019s understand how transaction decoding differs between these two ecosystems.<\/p>\n<h4>The EVM Approach: ABIs and Function Selectors<\/h4>\n<p>In Ethereum and other EVM chains, transaction decoding relies\u00a0on:<\/p>\n<p><strong>ABIs (Application Binary Interfaces):<\/strong> JSON files that describe contract interfaces, including function signatures, parameter types, and return values. When you call a contract function, your transaction data includes:<\/p>\n<p>0x23b872dd  \/\/ Function selector (first 4 bytes of keccak256 hash)<br \/>0000000&#8230;  \/\/ Encoded parameters (ABI-encoded)<\/p>\n<p><strong>Function selectors:<\/strong> The first 4 bytes of the keccak256 hash of the function signature (e.g., transfer(address,uint256)). Tools like ethers.js and web3.js use the ABI to automatically decode these transactions.<\/p>\n<p><strong>The workflow is straightforward:<\/strong><\/p>\n<p>Get the contract ABI (from Etherscan or the\u00a0project)Pass it to your library (ethers.js, web3.js)The library handles all decoding automatically<\/p>\n<h4>The Solana Approach: IDLs and Discriminators<\/h4>\n<p>Solana\u2019s architecture is fundamentally different. Programs are stateless and transactions can call multiple programs in sequence. This requires a different decoding approach:<\/p>\n<p><strong>IDLs (Interface Description Language):<\/strong> Similar to ABIs, but specific to the Anchor framework (the most popular Solana development framework). An IDL describes:<\/p>\n<p>Instructions (equivalent to contract functions)Accounts (data structures stored on-chain)Types and their serialization format<\/p>\n<p><strong>Discriminators:<\/strong> 8-byte identifiers (not 4 bytes like EVM) derived from the sha256 hash of a namespace and name. For instructions, this is global:instruction_name. For accounts, it&#8217;s account:account_name.<\/p>\n<p>c7385526 92f3259e  \/\/ 8-byte discriminator (first 8 bytes of sha256)<br \/>0000000&#8230;          \/\/ Borsh-serialized parameters<\/p>\n<p><strong>The key differences:<\/strong><\/p>\n<p>Comparison between EVM and\u00a0Solana<\/p>\n<h4>Why Solana Decoding is More\u00a0Manual<\/h4>\n<p>In the EVM world, most tools come with built-in ABI decoding. Pass in an ABI, and you\u2019re done. In Solana, while the Anchor framework provides IDLs, you often need\u00a0to:<\/p>\n<p><strong>Fetch and fix IDLs:<\/strong> IDLs from explorers may be incomplete<strong>Calculate discriminators manually:<\/strong> These aren\u2019t always included in IDL\u00a0files<strong>Implement custom parsers:<\/strong> The Solana Transaction Parser library doesn\u2019t know about your custom\u00a0programs<strong>Handle Borsh serialization:<\/strong> You need to understand the binary layout of your\u00a0data<\/p>\n<p>This might seem more complex, but it gives you fine-grained control over transaction parsing and a deeper understanding of what\u2019s happening on-chain.<\/p>\n<h4>What We\u2019ll\u00a0Build<\/h4>\n<p>In this tutorial, we\u2019ll decode transactions from Mayan Swift\u2019s auction program (Mayan Swift is a fast bridge between multiple blockchains, serving as a very important piece in the Solana ecosystem, <a href=\"https:\/\/docs.mayan.finance\/architecture\/swift\">https:\/\/docs.mayan.finance\/architecture\/swift<\/a>). You\u2019ll learn\u00a0to:<\/p>\n<p>Extract and prepare IDL\u00a0filesCalculate discriminators for instructions and\u00a0accountsBuild a custom decoder that transforms raw buffers into readable\u00a0dataIntegrate your decoder with the Solana Transaction Parser<\/p>\n<p>By the end, you\u2019ll be able to decode any Anchor-based Solana program\u2019s transactions.<\/p>\n<h3>The Problem: Unknown Instructions<\/h3>\n<p>Now let\u2019s see this in action. We want to decode data from Mayan Swift\u2019s auction program (9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H).<\/p>\n<p>Here\u2019s our starting point\u200a\u2014\u200aa transaction hash from a bid instruction: Ahy9GEyiPzkrw54Js6rw43bD6m6V3zmDDK6nn6e8N2tskrbkiozhsMjcdBLvCgH5JAc8CFyUZiwWpyCNqQ4wmQb.Feel free to choose another from <a href=\"https:\/\/solscan.io\/account\/9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H\">https:\/\/solscan.io\/account\/9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H<\/a><\/p>\n<p>Let\u2019s try to parse it without any custom decoders. Create a file src\/no-custom-decoder.ts:<\/p>\n<p>import { Connection, clusterApiUrl } from &#8220;@solana\/web3.js&#8221;;<br \/>import { SolanaParser } from &#8220;@debridge-finance\/solana-transaction-parser&#8221;;const rpcConnection = new Connection(clusterApiUrl(&#8220;mainnet-beta&#8221;));<br \/>const txParser = new SolanaParser([]);const parsed = await txParser.parseTransactionByHash(<br \/>  rpcConnection, <br \/>&#8220;Ahy9GEyiPzkrw54Js6rw43bD6m6V3zmDDK6nn6e8N2tskrbkiozhsMjcdBLvCgH5JAc8CFyUZiwWpyCNqQ4wmQb&#8221;<br \/>);console.log(JSON.stringify(parsed, null, 2));<\/p>\n<p>Run it\u00a0with:<\/p>\n<p>npx ts-node src\/no-custom-decoder.ts<\/p>\n<p>The parser successfully decodes some instructions automatically\u200a\u2014\u200athe compute budget instructions and system program calls are recognized because they\u2019re common programs already loaded into the parser by\u00a0default:<\/p>\n<p>[<br \/>  {<br \/>    name: &#8216;setComputeUnitLimit&#8217;,<br \/>    accounts: [],<br \/>    args: { units: 74200 },<br \/>    programId: &#8216;ComputeBudget111111111111111111111111111111&#8217;<br \/>  },<br \/>  {<br \/>    name: &#8216;setComputeUnitPrice&#8217;,<br \/>    accounts: [],<br \/>    args: { microLamports: 60000 },<br \/>    programId: &#8216;ComputeBudget111111111111111111111111111111&#8217;<br \/>  },<br \/>  \/\/ &#8230; but then we hit our auction instruction:<br \/>  {<br \/>    programId: &#8216;9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H&#8217;,<br \/>    accounts: [ &#8230; ],<br \/>    args: {<br \/>      unknown: &lt;Buffer c7 38 55 26 92 f3 25 9e 00 00 00 00 00 00 00 00 &#8230;&gt;<br \/>    },<br \/>    name: &#8216;unknown&#8217;<br \/>  }<br \/>]<\/p>\n<p>That unknown Buffer is your encoded instruction data\u2014255 bytes of Borsh-serialized parameters that the parser can&#8217;t decode without the program&#8217;s IDL. This is where our work\u00a0begins.<\/p>\n<h3>Step 1: Get the\u00a0IDL<\/h3>\n<p>First, fetch the IDL for your program. For Anchor programs, you can find this on\u00a0Solscan:<\/p>\n<p><strong>Solscan:<\/strong> <a href=\"https:\/\/solscan.io\/account\/9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H#anchorProgramIdl\">https:\/\/solscan.io\/account\/9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H#anchorProgramIdl<\/a><\/p>\n<p>Save the JSON file to src\/idl\/swift_auction.json.<\/p>\n<h3>Step 2: Convert IDL to TypeScript<\/h3>\n<p>Install the Anchor IDL to TypeScript converter, such that the tool can parse its data\u00a0types:<\/p>\n<p>cargo install simple-anchor-idl-ts<\/p>\n<p>Run the converter:<\/p>\n<p>simple-anchor-idl-ts src\/idl\/swift_auction.json<\/p>\n<p>This creates src\/idl\/swift_auction.ts with TypeScript type definitions.<\/p>\n<h3>Step 3: Fix the IDL (Making it Anchor-Compliant)<\/h3>\n<p>The IDL exported from Solscan is incomplete and won\u2019t work with Anchor\u2019s TypeScript client. We need to fix three critical issues to make it compliant with the Anchor IDL specification. Think of this as the difference between a draft ABI and a production-ready one.<\/p>\n<h4>Issue #1: Missing address and metadata\u00a0fields<\/h4>\n<p>The Anchor IDL specification requires these fields at the root level, but Solscan\u2019s export omits them. Without these, the Anchor TypeScript client won\u2019t recognize the IDL structure.<\/p>\n<p><strong>Why it matters:<\/strong> The address field ties the IDL to the specific on-chain program, while metadata provides versioning and spec information that tools use for compatibility checks.<\/p>\n<p>At the top of your IDL type definition, add:<\/p>\n<p>export type SwiftAuctionIDLType = {<br \/>  &#8220;version&#8221;: &#8220;0.1.0&#8221;,<br \/>  &#8220;name&#8221;: &#8220;swift_auction&#8221;,<br \/>  &#8220;address&#8221;: &#8220;9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H&#8221;,  \/\/ Add this<br \/>  &#8220;metadata&#8221;: {                                               \/\/ Add this<br \/>    &#8220;name&#8221;: &#8220;Swift Auction&#8221;, <br \/>    &#8220;version&#8221;: &#8220;0.1.0&#8221;, <br \/>    &#8220;spec&#8221;: &#8220;&#8221; <br \/>  },<br \/>  &#8220;instructions&#8221;: [<br \/>    \/\/ &#8230;<br \/>  ]<br \/>}<\/p>\n<p>Don\u2019t forget to update the corresponding SwiftAuctionIDL object (around line 408) with the same\u00a0fields.<\/p>\n<h4>Issue #2: Missing discriminators<\/h4>\n<p>This is the critical part. Remember those 8-byte discriminators we discussed earlier? They\u2019re how Solana programs route instructions and identify account types. Without them, we can\u2019t match instruction data to the correct decoder function.<\/p>\n<p><strong>Why it matters:<\/strong> When a transaction comes in with data starting with c7 38 55 26 92 f3 25 9e, we need to know that it maps to the bid instruction. Discriminators make this possible.<\/p>\n<p>Create a helper script calculate-discriminator.js:<\/p>\n<p>const crypto = require(&#8216;crypto&#8217;);const name = process.argv[2];<br \/>if (!name) {<br \/>  console.log(&#8216;Usage: node calculate-discriminator.js &lt;namespace:name&gt;&#8217;);<br \/>  process.exit(1);<br \/>}const hash = crypto.createHash(&#8216;sha256&#8217;).update(name).digest();<br \/>const discriminator = Array.from(hash.slice(0, 8));<br \/>console.log(`Discriminator: &#8220;discriminator&#8221;: [${discriminator.join(&#8216;, &#8216;)}]`);<\/p>\n<p>Now calculate discriminators for each instruction using the global: namespace:<\/p>\n<p>node calculate-discriminator.js global:bid<br \/># Output: &#8220;discriminator&#8221;: [199, 56, 85, 38, 146, 243, 37, 158]node calculate-discriminator.js global:postAuction<br \/># Output: &#8220;discriminator&#8221;: [123, 68, 53, 83, 90, 211, 160, 63]node calculate-discriminator.js global:closeAuction<br \/># Output: &#8220;discriminator&#8221;: [214, 18, 110, 197, 2, 7, 153, 74]node calculate-discriminator.js global:updateConfig<br \/># Output: &#8220;discriminator&#8221;: [102, 77, 158, 235, 28, 216, 191, 121]<\/p>\n<p>And for accounts using the account: namespace:<\/p>\n<p>node calculate-discriminator.js account:auctionState<br \/># Output: &#8220;discriminator&#8221;: [110, 220, 44, 10, 38, 234, 132, 215]node calculate-discriminator.js account:auctionTime<br \/># Output: &#8220;discriminator&#8221;: [173, 208, 94, 49, 209, 242, 0, 214]<\/p>\n<p>Add these discriminators to each instruction and account definition in your IDL file. For\u00a0example:<\/p>\n<p>{<br \/>  &#8220;name&#8221;: &#8220;bid&#8221;,<br \/>  &#8220;discriminator&#8221;: [199, 56, 85, 38, 146, 243, 37, 158],  \/\/ Add this array<br \/>  &#8220;accounts&#8221;: [ &#8230; ],<br \/>  &#8220;args&#8221;: [ &#8230; ]<br \/>}<\/p>\n<p><strong>Pro tip:<\/strong> Notice how the first bytes of our unknown buffer (c7 38 55 26&#8230;) match the bid discriminator? That&#8217;s how we&#8217;ll route it to the right\u00a0decoder!<\/p>\n<h4>Issue #3: Fix defined type references<\/h4>\n<p>The IDL converter sometimes generates incorrect type references. The Anchor specification expects a specific format for custom type definitions.<\/p>\n<p><strong>Why it matters:<\/strong> Without the correct format, the TypeScript types won\u2019t compile, and your decoder won\u2019t\u00a0work.<\/p>\n<p>Find any instances of:<\/p>\n<p>&#8220;defined&#8221;: &#8220;OrderInfo&#8221;<\/p>\n<p>And change them\u00a0to:<\/p>\n<p>&#8220;defined&#8221;: { <br \/>  &#8220;generics&#8221;: [], <br \/>  &#8220;name&#8221;: &#8220;OrderInfo&#8221; <br \/>}<\/p>\n<p>This matches the Anchor IDL specification for type references and ensures proper TypeScript type generation.<\/p>\n<h4>Verification<\/h4>\n<p>After these fixes, your IDL should be fully Anchor-compliant. You can verify this by checking\u00a0that:<\/p>\n<p>The root object has address and metadata\u00a0fieldsEvery instruction and account has a discriminator arrayAll defined types use the object\u00a0notation<\/p>\n<p>Now we\u2019re ready to build the actual\u00a0decoder!<\/p>\n<h3>Step 4: Build Your Custom\u00a0Decoder<\/h3>\n<p>Now comes the exciting part: transforming raw bytes into meaningful data. We\u2019ll build a decoder that mimics how Anchor programs read instruction data on-chain.<\/p>\n<h4>Understanding the Decoding\u00a0Strategy<\/h4>\n<p>When a Solana program receives instruction data, it follows this\u00a0pattern:<\/p>\n<p><strong>Read the discriminator<\/strong> (first 8 bytes) to identify which instruction was\u00a0called<strong>Deserialize the remaining bytes<\/strong> using Borsh according to the instruction\u2019s argument structure<\/p>\n<p>Our decoder will do the same thing, but in TypeScript rather than\u00a0Rust.<\/p>\n<h4>Setting Up the\u00a0Parser<\/h4>\n<p>Create src\/custom-parsers\/swift-auction-parser.ts:<\/p>\n<p>import { TransactionInstruction } from &#8220;@solana\/web3.js&#8221;;<br \/>import { SwiftAuctionIDL, SwiftAuctionIDLType } from &#8220;..\/idl\/swift_auction&#8221;;<br \/>import { <br \/>  ParsedIdlInstruction, <br \/>  ParsedInstruction <br \/>} from &#8220;@debridge-finance\/solana-transaction-parser&#8221;;<br \/>import { decodeOrderInfo } from &#8220;..\/decodeOrderInfo&#8221;;<br \/>import BN from &#8220;bn.js&#8221;;\/\/ Extract function selectors (first byte of each discriminator)<br \/>\/\/ This creates a lookup table: { &#8220;bid&#8221;: 199, &#8220;postAuction&#8221;: 123, &#8230; }<br \/>const FUNCTION_SELECTORS = SwiftAuctionIDL[&#8220;instructions&#8221;].reduce((acc, i) =&gt; {<br \/>  acc[i.name] = i.discriminator[0];<br \/>  return acc;<br \/>}, {} as Record&lt;string, number&gt;);function decodeSwiftAuctionInstruction(<br \/>  instruction: TransactionInstruction<br \/>): ParsedInstruction&lt;SwiftAuctionIDLType&gt; {<\/p>\n<p>  \/\/ The first byte tells us which instruction this is<br \/>  const instruction_selector = instruction.data[0];<\/p>\n<p>  \/\/ Everything after byte 0 is the Borsh-serialized arguments<br \/>  const remaining_data = instruction.data.slice(1);  \/\/ Route to the appropriate decoder based on the selector<br \/>  switch (instruction_selector) {<br \/>    case FUNCTION_SELECTORS[&#8220;bid&#8221;]:<br \/>      return decodeBidInstruction(instruction, remaining_data);<\/p>\n<p>    case FUNCTION_SELECTORS[&#8220;postAuction&#8221;]:<br \/>      \/\/ You would implement this similarly<br \/>      throw new Error(&#8220;postAuction decoder not yet implemented&#8221;);<\/p>\n<p>    \/\/ Add more cases for other instructions as needed<br \/>    default:<br \/>      throw new Error(`Unknown instruction selector: ${instruction_selector}`);<br \/>  }<br \/>}export { decodeSwiftAuctionInstruction };<\/p>\n<p><strong>What\u2019s happening here:<\/strong><\/p>\n<p>We use only the <strong>first byte<\/strong> of the discriminator as a selector (in practice, using all 8 bytes is more collision-resistant, but the first byte works for small instruction sets)We slice off the first byte and pass the remaining data to instruction-specific decodersEach decoder knows the exact memory layout of its arguments<\/p>\n<h4>Decoding the Bid Instruction<\/h4>\n<p>Now let\u2019s implement the decoder for the bid instruction:<\/p>\n<p>function decodeBidInstruction(<br \/>  instruction: TransactionInstruction, <br \/>  data: Buffer<br \/>): ParsedIdlInstruction&lt;SwiftAuctionIDLType, &#8220;bid&#8221;&gt; {<\/p>\n<p>  return {<br \/>    name: &#8220;bid&#8221;,<br \/>    programId: instruction.programId,<\/p>\n<p>    \/\/ Map instruction accounts to their semantic names<br \/>    \/\/ The order matches the IDL&#8217;s account definition<br \/>    accounts: [<br \/>      { name: &#8220;config&#8221;, pubkey: instruction.keys[0].pubkey },<br \/>      { name: &#8220;driver&#8221;, pubkey: instruction.keys[1].pubkey },<br \/>      { name: &#8220;auctionState&#8221;, pubkey: instruction.keys[2].pubkey },<br \/>      { name: &#8220;systemProgram&#8221;, pubkey: instruction.keys[3].pubkey },<br \/>    ],<\/p>\n<p>    \/\/ Decode the arguments according to the IDL structure<br \/>    args: {<br \/>      \/\/ The &#8216;order&#8217; field is a complex struct defined in the IDL<br \/>      \/\/ We need a custom decoder for it (see below)<br \/>      order: decodeOrderInfo(data),<\/p>\n<p>      \/\/ The &#8216;amountBid&#8217; field is a u64 at the end of the buffer<br \/>      \/\/ Read it as a big-endian 64-bit unsigned integer<br \/>      amountBid: new BN(data.readBigUInt64LE(data.length &#8211; 8)),<br \/>    }<br \/>  } as ParsedIdlInstruction&lt;SwiftAuctionIDLType, &#8220;bid&#8221;&gt;;<br \/>}<\/p>\n<p><strong>Key points:<\/strong><\/p>\n<p><strong>Accounts mapping:<\/strong> The IDL tells us each account\u2019s purpose. We map instruction.keys array indices to semantic\u00a0names<strong>Argument layout:<\/strong> According to the IDL, a bid instruction has two arguments: order (a struct) and amountBid (a\u00a0u64)<strong>Borsh deserialization:<\/strong> The order struct comes first in the buffer, followed by the u64 at the\u00a0end<\/p>\n<h4>Decoding Complex\u00a0Structs<\/h4>\n<p>The OrderInfo struct contains multiple fields with different types. You&#8217;ll need to create src\/decodeOrderInfo.ts to handle\u00a0this:<\/p>\n<p>import BN from &#8220;bn.js&#8221;;export function decodeOrderInfo(data: Buffer) {<br \/>  let offset = 0;<\/p>\n<p>  \/\/ Read each field according to the IDL type definition<br \/>  \/\/ Borsh serializes fields sequentially in declaration order<\/p>\n<p>  const trader = Array.from(data.slice(offset, offset + 32));<br \/>  offset += 32;<\/p>\n<p>  const chainSource = data.readUInt16LE(offset);<br \/>  offset += 2;<\/p>\n<p>  const tokenIn = Array.from(data.slice(offset, offset + 32));<br \/>  offset += 32;<\/p>\n<p>  const addrDest = Array.from(data.slice(offset, offset + 32));<br \/>  offset += 32;<\/p>\n<p>  const chainDest = data.readUInt16LE(offset);<br \/>  offset += 2;<\/p>\n<p>  const tokenOut = Array.from(data.slice(offset, offset + 32));<br \/>  offset += 32;<\/p>\n<p>  \/\/ Continue for all fields defined in the OrderInfo struct&#8230;<br \/>  const amountOutMin = new BN(data.slice(offset, offset + 8), &#8216;le&#8217;);<br \/>  offset += 8;<\/p>\n<p>  \/\/ &#8230; and so on for the remaining fields<\/p>\n<p>  return {<br \/>    trader,<br \/>    chainSource,<br \/>    tokenIn,<br \/>    addrDest,<br \/>    chainDest,<br \/>    tokenOut,<br \/>    amountOutMin,<br \/>    \/\/ &#8230; other fields<br \/>  };<br \/>}<\/p>\n<p><strong>The pattern:<\/strong><\/p>\n<p>Start at offset\u00a00Read each field according to its type (u8, u16, u64, [u8; 32],\u00a0etc.)Advance the offset by the field\u2019s\u00a0sizeReturn an object matching the IDL\u2019s type definition<\/p>\n<p>This is exactly how Borsh deserialization works\u200a\u2014\u200asequential, fixed-layout reads.<\/p>\n<h4>Integrating with the\u00a0Parser<\/h4>\n<p>Finally, register your custom decoder with the Solana Transaction Parser. Create src\/auction-custom-decoder.ts:<\/p>\n<p>import { Connection, clusterApiUrl } from &#8220;@solana\/web3.js&#8221;;<br \/>import { SolanaParser } from &#8220;@debridge-finance\/solana-transaction-parser&#8221;;<br \/>import { decodeSwiftAuctionInstruction } from &#8220;.\/custom-parsers\/swift-auction-parser&#8221;;const SWIFT_AUCTION_PROGRAM_ID = &#8220;9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H&#8221;;const rpcConnection = new Connection(clusterApiUrl(&#8220;mainnet-beta&#8221;));\/\/ Create parser with custom decoder<br \/>const txParser = new SolanaParser([<br \/>  {<br \/>    programId: SWIFT_AUCTION_PROGRAM_ID,<br \/>    instructionParser: decodeSwiftAuctionInstruction,<br \/>  }<br \/>]);\/\/ Parse the same transaction we tried earlier<br \/>const parsed = await txParser.parseTransactionByHash(<br \/>  rpcConnection,<br \/>  &#8220;Ahy9GEyiPzkrw54Js6rw43bD6m6V3zmDDK6nn6e8N2tskrbkiozhsMjcdBLvCgH5JAc8CFyUZiwWpyCNqQ4wmQb&#8221;<br \/>);console.log(JSON.stringify(parsed, null, 2));<\/p>\n<p>Run it\u00a0with:<\/p>\n<p>npx ts-node src\/auction-custom-decoder.ts<\/p>\n<p>The parser will now use your custom decoder whenever it encounters an instruction from the Swift Auction\u00a0program!<\/p>\n<h3>Running the\u00a0Examples<\/h3>\n<p>To see the difference between parsed and unparsed transactions:<\/p>\n<p><strong>Without custom\u00a0decoder:<\/strong><\/p>\n<p>npx ts-node src\/no-custom-decoder.ts<\/p>\n<p><strong>With custom\u00a0decoder:<\/strong><\/p>\n<p>npx ts-node src\/auction-custom-decoder.ts<\/p>\n<p>The second command will show the fully decoded bid instruction with all its parameters visible and readable.<\/p>\n<h3>The Result: From Bytes to\u00a0Meaning<\/h3>\n<p>Let\u2019s compare what we had before and after implementing our custom\u00a0decoder.<\/p>\n<h4>Before (Unknown\u00a0Buffer):<\/h4>\n<p>{<br \/>  programId: &#8216;9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H&#8217;,<br \/>  accounts: [ &#8230; ],<br \/>  args: {<br \/>    unknown: &lt;Buffer c7 38 55 26 92 f3 25 9e 00 00 00 00 00 00 00 00 &#8230;&gt;<br \/>  },<br \/>  name: &#8216;unknown&#8217;<br \/>}<\/p>\n<h4>After (Fully Decoded):<\/h4>\n<p>{<br \/>  name: &#8216;bid&#8217;,<br \/>  programId: &#8216;9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H&#8217;,<br \/>  accounts: [<br \/>    { name: &#8216;config&#8217;, pubkey: &#8217;93boUvm9QnkHTa5sUGMuFegLaxYsJQVMrNCAz7HojnY5&#8242; },<br \/>    { name: &#8216;driver&#8217;, pubkey: &#8216;B88xH3Jmhq4WEaiRno2mYmsxV35MmgSY45ZmQnbL8yft&#8217; },<br \/>    { name: &#8216;auctionState&#8217;, pubkey: &#8216;Cq8nomBLmD4LwrYBA4J3Wk6C4GtcRT72Nb5e1RJcdk8C&#8217; },<br \/>    { name: &#8216;systemProgram&#8217;, pubkey: &#8216;11111111111111111111111111111111&#8217; }<br \/>  ],<br \/>  args: {<br \/>    order: {<br \/>      trader: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 90, 122, 111, &#8230;],<br \/>      chainSource: 30,<br \/>      tokenIn: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 53, 137, 252, &#8230;],<br \/>      addrDest: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 90, 122, 111, &#8230;],<br \/>      chainDest: 4,<br \/>      tokenOut: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 211, 152, 50, &#8230;],<br \/>      amountOutMin: &lt;BN: 39ccb90fc0&gt;,  \/\/ 250,000,000,000 (250 USDC)<br \/>      gasDrop: &lt;BN: 0&gt;,<br \/>      feeCancel: &lt;BN: 1edf&gt;,          \/\/ 7,903 lamports<br \/>      feeRefund: &lt;BN: 45e&gt;,            \/\/ 1,118 lamports<br \/>      deadline: &lt;BN: 685e7892&gt;,        \/\/ Unix timestamp<br \/>      addrRef: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 165, 170, 110, 33, &#8230;],<br \/>      feeRateRef: 0,<br \/>      feeRateMayan: 3,                 \/\/ 0.03% fee<br \/>      auctionMode: 2,                  \/\/ Dutch auction<br \/>      keyRnd: [74, 172, 120, 171, 220, 249, 5, 206, &#8230;]  \/\/ Random key<br \/>    },<br \/>    amountBid: &lt;BN: 3a1d331485&gt;       \/\/ 250,123,456,645 lamports<br \/>  }<br \/>}<\/p>\n<p>Now we can see <strong>exactly<\/strong> what this transaction does:<\/p>\n<p><strong>Instruction:<\/strong> A user is placing a bid in an\u00a0auction<strong>Accounts:<\/strong> The bid interacts with a specific config, driver, and auction state\u00a0account<strong>Parameters:<\/strong>The order details show a cross-chain swap from chain 30 to chain\u00a04<\/p>\n<p>2. The minimum output amount is 250\u00a0USDC<\/p>\n<p>3. The bid amount is ~250.12\u00a0SOL<\/p>\n<p>4. It\u2019s a Dutch auction (mode 2) with a 0.03% Mayan\u00a0fee<\/p>\n<p>5. Includes deadline, fee structures, and referral information<\/p>\n<h3>What We Accomplished<\/h3>\n<p>Remember our theoretical comparison at the beginning? We\u2019ve successfully:<\/p>\n<p><strong>Fetched the IDL<\/strong> (Solana\u2019s equivalent of an\u00a0ABI)<strong>Calculated 8-byte discriminators<\/strong> (vs EVM\u2019s 4-byte selectors)<strong>Implemented Borsh deserialization<\/strong> (vs ABI decoding)<strong>Built a custom parser<\/strong> (vs automatic ethers.js decoding)<\/p>\n<h3>Key Takeaways for EVM Developers<\/h3>\n<p>If you\u2019re transitioning from Ethereum to Solana, here are the essential mindset\u00a0shifts:<\/p>\n<h4>1. IDLs vs ABIs: Similar Purpose, Different Implementation<\/h4>\n<p><strong>EVM:<\/strong> ABIs are universally supported. Pass one to ethers.js and you\u2019re\u00a0done.<\/p>\n<p><strong>Solana:<\/strong> IDLs are Anchor-specific and require more manual setup. But this gives you visibility into exactly how data flows through your programs.<\/p>\n<h4>2. Discriminators vs Function Selectors: More Bytes, More\u00a0Context<\/h4>\n<p><strong>EVM:<\/strong> 4-byte function selectors from keccak256(functionSignature).slice(0,4)<\/p>\n<p>Example: transfer(address,uint256) \u2192 0xa9059cbb<\/p>\n<p><strong>Solana:<\/strong> 8-byte discriminators from sha256(namespace:name).slice(0,8)<\/p>\n<p>Example: global:bid \u2192 [199, 56, 85, 38, 146, 243, 37,\u00a0158]<\/p>\n<p>The extra bytes reduce collision risk (which has been used in the past in contract exploits) and support namespacing for instructions, accounts, and\u00a0events.<\/p>\n<h4>3. Decoding Tools: Automatic vs\u00a0Manual<\/h4>\n<p><strong>EVM ecosystem:<\/strong><\/p>\n<p>const contract = new ethers.Contract(address, abi, provider);<br \/>const tx = await contract.populateTransaction.transfer(to, amount);<br \/>\/\/ Everything decoded automatically<\/p>\n<p><strong>Solana ecosystem:<\/strong><\/p>\n<p>const txParser = new SolanaParser([customDecoder]);<br \/>const parsed = await txParser.parseTransactionByHash(connection, hash);<br \/>\/\/ Custom decoders required for new programs<\/p>\n<p>The Solana approach requires more initial work but teaches you the internals of program communication.<\/p>\n<h3>Next Steps<\/h3>\n<p>Now that you can decode transaction data, you\u00a0can:<\/p>\n<p>Build transaction explorersCreate analytics dashboardsBuild indexers for your\u00a0dApps<\/p>\n<p>The full source code is available on GitHub: <a href=\"https:\/\/github.com\/AndreAugusto11\/example-usage-solana-parser\">https:\/\/github.com\/AndreAugusto11\/example-usage-solana-parser<\/a><\/p>\n<p><strong>Additional Resources:<\/strong><\/p>\n<p><a href=\"https:\/\/github.com\/debridge-finance\/solana-tx-parser-public\">DeBridge Finance Solana Transaction Parser<\/a><a href=\"https:\/\/www.anchor-lang.com\/\">Anchor Framework Documentation<\/a><a href=\"https:\/\/spl.solana.com\/\">Solana Program\u00a0Library<\/a><\/p>\n<p>Welcome to Solana!\u00a0\ud83d\ude80<\/p>\n<p><em>Found this helpful? Follow me for more tutorials and cross-chain insights.<\/em><\/p>\n<p><a href=\"https:\/\/medium.com\/coinmonks\/how-to-decode-solana-transaction-data-and-how-it-differs-from-evm-4e04b41e2f9f\">How to Decode Solana Transaction Data (and how it differs from EVM)<\/a> was originally published in <a href=\"https:\/\/medium.com\/coinmonks\">Coinmonks<\/a> on Medium, where people are continuing the conversation by highlighting and responding to this story.<\/p>","protected":false},"excerpt":{"rendered":"<p>Photo by GuerrillaBuzz on\u00a0Unsplash If you\u2019re coming from the EVM world, decoding Solana transactions can feel like learning a new language. In Ethereum, you\u2019d use ABIs and tools like ethers.js to decode transaction data. In Solana, the process is different\u200a\u2014\u200abut once you understand it, it\u2019s just as powerful. This guide is for: \u2022 EVM developers [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":136515,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-136514","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-interesting"],"_links":{"self":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/136514"}],"collection":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=136514"}],"version-history":[{"count":0,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/136514\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/media\/136515"}],"wp:attachment":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=136514"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=136514"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=136514"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}