@ledgerhq/device-signer-kit-solana 0.0.0-multisig-20250822145545 → 0.0.0-patch-transactionInspector-20250909141112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/internal/app-binder/services/TransactionInspector.js +1 -1
- package/lib/cjs/internal/app-binder/services/TransactionInspector.js.map +3 -3
- package/lib/esm/internal/app-binder/services/TransactionInspector.js +1 -1
- package/lib/esm/internal/app-binder/services/TransactionInspector.js.map +3 -3
- package/lib/types/internal/app-binder/services/TransactionInspector.d.ts +12 -4
- package/lib/types/internal/app-binder/services/TransactionInspector.d.ts.map +1 -1
- package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
- package/package.json +8 -8
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var T=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var K=(d,t)=>{for(var a in t)T(d,a,{get:t[a],enumerable:!0})},S=(d,t,a,c)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of k(t))!g.call(d,o)&&o!==a&&T(d,o,{get:()=>t[o],enumerable:!(c=I(t,o))||c.enumerable});return d};var P=d=>S(T({},"__esModule",{value:!0}),d);var x={};K(x,{SolanaTransactionTypes:()=>f,TransactionInspector:()=>b});module.exports=P(x);var s=require("@solana/spl-token"),u=require("@solana/web3.js"),A=require("buffer"),f=(a=>(a.STANDARD="Standard",a.SPL="SPL",a))(f||{});class b{constructor(t,a){this.rawTransactionBytes=t;this.lookedUpAddresses=a}inspectTransactionType(){try{const t=this.normaliseMessage(this.rawTransactionBytes);for(const a of t.compiledInstructions){const c=t.allKeys[a.programIdIndex],o=a.data?.[0];if(!!c&&(c.equals(s.TOKEN_PROGRAM_ID)||c.equals(s.TOKEN_2022_PROGRAM_ID)))try{const i=new u.TransactionInstruction({programId:c,keys:a.accountKeyIndexes.map(r=>{const e=t.allKeys[r];if(!e)throw new Error(`TransactionInspector: missing key at index ${r} in allKeys`);return{pubkey:e,isSigner:!1,isWritable:!1}}),data:A.Buffer.from(a.data)});switch(i.data[0]){case s.TokenInstruction.Transfer:{const{keys:{destination:r}}=(0,s.decodeTransferInstruction)(i);return{transactionType:"SPL",data:{tokenAddress:r.pubkey.toBase58()}}}case s.TokenInstruction.TransferChecked:{const{keys:{destination:r,mint:e}}=(0,s.decodeTransferCheckedInstruction)(i);return{transactionType:"SPL",data:{tokenAddress:r.pubkey.toBase58(),mintAddress:e.pubkey.toBase58()}}}case s.TokenInstruction.InitializeAccount:{const{keys:{account:r,mint:e}}=(0,s.decodeInitializeAccountInstruction)(i);return{transactionType:"SPL",data:{createATA:{address:r.pubkey.toBase58(),mintAddress:e.pubkey.toBase58()}}}}default:break}}catch{}if(c&&c.equals(s.ASSOCIATED_TOKEN_PROGRAM_ID)){const i=n=>{const y=a.accountKeyIndexes[n];return y!==void 0?t.allKeys[y]:void 0},r=i(1),e=i(3);if(r&&e)return{transactionType:"SPL",data:{createATA:{address:r.toBase58(),mintAddress:e.toBase58()}}}}const m=o===s.TokenInstruction.Transfer||o===s.TokenInstruction.TransferChecked||o===s.TokenInstruction.InitializeAccount;if(!c&&m){const i=a.accountKeyIndexes,r=e=>{const n=i[e];if(n===void 0)return;const y=t.allKeys[n];return y?y.toBase58():void 0};if(o===s.TokenInstruction.Transfer){const e=r(1);return e?{transactionType:"SPL",data:{tokenAddress:e}}:{transactionType:"SPL",data:{}}}if(o===s.TokenInstruction.TransferChecked){const e=r(1),n=r(2);return n||e?{transactionType:"SPL",data:{...n?{tokenAddress:n}:{},...e?{mintAddress:e}:{}}}:{transactionType:"SPL",data:{}}}if(o===s.TokenInstruction.InitializeAccount){const e=r(0),n=r(1);return e||n?{transactionType:"SPL",data:{createATA:{address:e??"",mintAddress:n??""}}}:{transactionType:"SPL",data:{}}}}}return{transactionType:"Standard",data:{}}}catch{return{transactionType:"Standard",data:{}}}}normaliseMessage(t){const a=this.tryDeserialiseVersioned(t);if(a){const e=a.message;let n;return typeof e.getAccountKeys=="function"?n=e.getAccountKeys({accountKeysFromLookups:this.lookedUpAddresses}).keySegments().flat():n=[...e.staticAccountKeys],{compiledInstructions:e.compiledInstructions.map(l=>({programIdIndex:l.programIdIndex,accountKeyIndexes:Array.from(l.accountKeyIndexes??[]),data:l.data instanceof Uint8Array?l.data:A.Buffer.from(l.data??[])})),allKeys:n}}const c=u.Transaction.from(t),o=new Map,p=e=>{if(!e)return;const n=e.toBase58();o.has(n)||o.set(n,e)};p(c.feePayer??null);for(const e of c.instructions){p(e.programId);for(const n of e.keys)p(n.pubkey)}const m=Array.from(o.values()),i=new Map(m.map((e,n)=>[e.toBase58(),n]));return{compiledInstructions:c.instructions.map(e=>({programIdIndex:i.get(e.programId.toBase58())??-1,accountKeyIndexes:e.keys.map(n=>i.get(n.pubkey.toBase58())??-1),data:e.data})),allKeys:m}}tryDeserialiseVersioned(t){try{return u.VersionedTransaction.deserialize(t)}catch{try{return{message:u.VersionedMessage.deserialize(t)}}catch{return null}}}}0&&(module.exports={SolanaTransactionTypes,TransactionInspector});
|
|
2
2
|
//# sourceMappingURL=TransactionInspector.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/internal/app-binder/services/TransactionInspector.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n ASSOCIATED_TOKEN_PROGRAM_ID,\n decodeInitializeAccountInstruction,\n decodeTransferCheckedInstruction,\n decodeTransferInstruction,\n TOKEN_2022_PROGRAM_ID,\n TOKEN_PROGRAM_ID,\n TokenInstruction,\n} from \"@solana/spl-token\";\nimport {\n type PublicKey,\n Transaction,\n TransactionInstruction,\n VersionedMessage,\n VersionedTransaction,\n} from \"@solana/web3.js\";\nimport { Buffer } from \"buffer\";\n\nexport enum SolanaTransactionTypes {\n STANDARD = \"Standard\",\n SPL = \"SPL\",\n}\n\nexport interface TxInspectorResult {\n transactionType: SolanaTransactionTypes;\n data: {\n tokenAddress?: string;\n mintAddress?: string;\n createATA?: {\n address: string;\n mintAddress: string;\n };\n };\n}\n\ntype NormalizedCompiledIx = {\n programIdIndex: number;\n accountKeyIndexes: number[];\n data: Uint8Array;\n};\n\ntype NormalizedMessage = {\n compiledInstructions: NormalizedCompiledIx[];\n allKeys: PublicKey[];\n};\n\ntype LoadedAddresses = { writable: PublicKey[]; readonly: PublicKey[] };\n\nexport class TransactionInspector {\n /**\n * @param rawTransactionBytes - the raw tx bytes (legacy or v0)\n */\n constructor(private readonly rawTransactionBytes: Uint8Array) {}\n\n public inspectTransactionType(): TxInspectorResult {\n try {\n const message = this.normaliseMessage(this.rawTransactionBytes);\n\n for (const ixMeta of message.compiledInstructions) {\n const programId = message.allKeys[ixMeta.programIdIndex];\n if (!programId) continue;\n\n // Associated Token Account (ATA) Program: detect ATA creation\n if (programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {\n // expected accounts: [payer, ata, owner, mint, systemProgram, tokenProgram, rent?]\n const accountPks = ixMeta.accountKeyIndexes\n .map((i) => message.allKeys[i])\n .filter(Boolean) as PublicKey[];\n const ataPk = accountPks[1];\n const mintPk = accountPks[3];\n if (ataPk && mintPk) {\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n createATA: {\n address: ataPk.toBase58(),\n mintAddress: mintPk.toBase58(),\n },\n },\n };\n }\n continue;\n }\n\n // Token Program (classic or 2022)\n const isTokenProgram =\n programId.equals(TOKEN_PROGRAM_ID) ||\n programId.equals(TOKEN_2022_PROGRAM_ID);\n if (!isTokenProgram) continue;\n\n // minimal TransactionInstruction for the decoders\n const instruction = new TransactionInstruction({\n programId,\n keys: ixMeta.accountKeyIndexes.map((i) => {\n if (!message.allKeys[i]) {\n throw new Error(\n `TransactionInspector: missing key at index ${i} in allKeys`,\n );\n }\n return {\n pubkey: message.allKeys[i],\n isSigner: false,\n isWritable: false,\n };\n }),\n data: Buffer.from(ixMeta.data),\n });\n\n const instructionType = instruction.data[0];\n\n try {\n switch (instructionType) {\n case TokenInstruction.Transfer: {\n const {\n keys: { destination },\n } = decodeTransferInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: { tokenAddress: destination.pubkey.toBase58() },\n };\n }\n case TokenInstruction.TransferChecked: {\n const {\n keys: { destination, mint },\n } = decodeTransferCheckedInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n tokenAddress: destination.pubkey.toBase58(),\n mintAddress: mint.pubkey.toBase58(),\n },\n };\n }\n case TokenInstruction.InitializeAccount: {\n // InitializeAccount != ATA creation, ATA is via the Associated Token Account Program above.\n const {\n keys: { account, mint },\n } = decodeInitializeAccountInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n createATA: {\n address: account.pubkey.toBase58(),\n mintAddress: mint.pubkey.toBase58(),\n },\n },\n };\n }\n default:\n // not a token instruction we care about\u2014keep scanning.\n break;\n }\n } catch {\n // if a decoder throws (bad match), keep scanning other instructions.\n continue;\n }\n }\n\n return { transactionType: SolanaTransactionTypes.STANDARD, data: {} };\n } catch {\n return { transactionType: SolanaTransactionTypes.STANDARD, data: {} };\n }\n }\n\n /**\n * normalise any tx (legacy or v0) into { compiledInstructions, allKeys }.\n * if LUT accounts are provided, looked-up keys are included in allKeys.\n */\n private normaliseMessage(rawBytes: Uint8Array): NormalizedMessage {\n const vtx = this.tryDeserialiseVersioned(rawBytes);\n if (vtx) {\n const msg = vtx.message as VersionedMessage & {\n getAccountKeys?: (opts?: {\n accountKeysFromLookups?: LoadedAddresses;\n }) => {\n staticAccountKeys: PublicKey[];\n accountKeysFromLookups?: LoadedAddresses;\n keySegments: () => PublicKey[][];\n };\n compiledInstructions: Array<{\n programIdIndex: number;\n accountKeyIndexes?: number[];\n accounts?: number[];\n data: Uint8Array | string | number[];\n }>;\n staticAccountKeys: PublicKey[];\n };\n\n // build the full key array in the exact index order used by compiledInstructions\n let allKeys: PublicKey[];\n if (typeof msg.getAccountKeys === \"function\") {\n const mak = msg.getAccountKeys();\n allKeys = mak.keySegments().flat();\n } else {\n // very old builds: fall back to concatenation (same order)\n allKeys = [...msg.staticAccountKeys];\n }\n\n const compiledInstructions: NormalizedCompiledIx[] =\n msg.compiledInstructions.map((ix) => ({\n programIdIndex: ix.programIdIndex,\n accountKeyIndexes: Array.from(ix.accountKeyIndexes ?? []),\n data:\n ix.data instanceof Uint8Array\n ? ix.data\n : Buffer.from(ix.data ?? []),\n }));\n\n return { compiledInstructions, allKeys };\n }\n\n // legacy fallback\n const legacy = Transaction.from(rawBytes);\n\n const allKeyMap = new Map<string, PublicKey>();\n const add = (pk?: PublicKey | null) => {\n if (!pk) return;\n const k = pk.toBase58();\n if (!allKeyMap.has(k)) allKeyMap.set(k, pk);\n };\n\n add(legacy.feePayer ?? null);\n for (const ix of legacy.instructions) {\n add(ix.programId);\n for (const k of ix.keys) add(k.pubkey);\n }\n const allKeys = Array.from(allKeyMap.values());\n const indexByB58 = new Map(allKeys.map((pk, i) => [pk.toBase58(), i]));\n\n const compiledInstructions: NormalizedCompiledIx[] =\n legacy.instructions.map((ix) => ({\n programIdIndex: indexByB58.get(ix.programId.toBase58()) ?? -1,\n accountKeyIndexes: ix.keys.map(\n (k) => indexByB58.get(k.pubkey.toBase58()) ?? -1,\n ),\n data: ix.data,\n }));\n\n return { compiledInstructions, allKeys };\n }\n\n private tryDeserialiseVersioned(\n rawBytes: Uint8Array,\n ): VersionedTransaction | null {\n try {\n return VersionedTransaction.deserialize(rawBytes);\n } catch {\n try {\n const msg = VersionedMessage.deserialize(rawBytes);\n // wrap in a dummy VersionedTransaction-like shape just for uniform handling\n return { message: msg } as VersionedTransaction;\n } catch {\n return null;\n }\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,4BAAAE,EAAA,yBAAAC,IAAA,eAAAC,EAAAJ,GAAA,IAAAK,EAQO,6BACPC,EAMO,2BACPC,EAAuB,kBAEXL,OACVA,EAAA,SAAW,WACXA,EAAA,IAAM,MAFIA,OAAA,IA8BL,MAAMC,CAAqB,
|
|
6
|
-
"names": ["TransactionInspector_exports", "__export", "SolanaTransactionTypes", "TransactionInspector", "__toCommonJS", "import_spl_token", "import_web3", "import_buffer", "rawTransactionBytes", "message", "ixMeta", "programId", "
|
|
4
|
+
"sourcesContent": ["import {\n ASSOCIATED_TOKEN_PROGRAM_ID,\n decodeInitializeAccountInstruction,\n decodeTransferCheckedInstruction,\n decodeTransferInstruction,\n TOKEN_2022_PROGRAM_ID,\n TOKEN_PROGRAM_ID,\n TokenInstruction,\n} from \"@solana/spl-token\";\nimport {\n type PublicKey,\n Transaction,\n TransactionInstruction,\n VersionedMessage,\n VersionedTransaction,\n} from \"@solana/web3.js\";\nimport { Buffer } from \"buffer\";\n\nexport enum SolanaTransactionTypes {\n STANDARD = \"Standard\",\n SPL = \"SPL\",\n}\n\nexport interface TxInspectorResult {\n transactionType: SolanaTransactionTypes;\n data: {\n tokenAddress?: string;\n mintAddress?: string;\n createATA?: {\n address: string;\n mintAddress: string;\n };\n };\n}\n\ntype NormalizedCompiledIx = {\n programIdIndex: number;\n accountKeyIndexes: number[];\n data: Uint8Array;\n};\n\ntype NormalizedMessage = {\n compiledInstructions: NormalizedCompiledIx[];\n allKeys: PublicKey[]; // may exclude LUT keys if not provided\n};\n\ntype LoadedAddresses = { writable: PublicKey[]; readonly: PublicKey[] };\n\nexport class TransactionInspector {\n /**\n * @param rawTransactionBytes - raw bytes of the transaction to inspect\n * @param lookedUpAddresses - optional hydrated LUT keys\n */\n constructor(\n private readonly rawTransactionBytes: Uint8Array,\n private readonly lookedUpAddresses?: LoadedAddresses,\n ) {}\n\n public inspectTransactionType(): TxInspectorResult {\n try {\n const message = this.normaliseMessage(this.rawTransactionBytes);\n\n for (const ixMeta of message.compiledInstructions) {\n const programId = message.allKeys[ixMeta.programIdIndex];\n const instructionType = ixMeta.data?.[0];\n\n const isTokenProgram =\n !!programId &&\n (programId.equals(TOKEN_PROGRAM_ID) ||\n programId.equals(TOKEN_2022_PROGRAM_ID));\n\n // exact decode when we know it's the token program (classic or 2022)\n if (isTokenProgram) {\n try {\n const instruction = new TransactionInstruction({\n programId,\n keys: ixMeta.accountKeyIndexes.map((i) => {\n const pk = message.allKeys[i];\n if (!pk) {\n throw new Error(\n `TransactionInspector: missing key at index ${i} in allKeys`,\n );\n }\n return { pubkey: pk, isSigner: false, isWritable: false };\n }),\n data: Buffer.from(ixMeta.data),\n });\n\n switch (instruction.data[0]) {\n case TokenInstruction.Transfer: {\n const {\n keys: { destination },\n } = decodeTransferInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: { tokenAddress: destination.pubkey.toBase58() },\n };\n }\n case TokenInstruction.TransferChecked: {\n const {\n keys: { destination, mint },\n } = decodeTransferCheckedInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n tokenAddress: destination.pubkey.toBase58(),\n mintAddress: mint.pubkey.toBase58(),\n },\n };\n }\n case TokenInstruction.InitializeAccount: {\n const {\n keys: { account, mint },\n } = decodeInitializeAccountInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n createATA: {\n address: account.pubkey.toBase58(),\n mintAddress: mint.pubkey.toBase58(),\n },\n },\n };\n }\n default:\n // keep scanning\n break;\n }\n } catch {\n // if the decoder throws, keep scanning other instructions\n }\n }\n\n // detect ATA creation (exact: only when programId is present & matches)\n if (programId && programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {\n // expected accounts: [payer, ata, owner, mint, systemProgram, tokenProgram, rent?]\n const getPk = (idx: number) => {\n const keyIndex = ixMeta.accountKeyIndexes[idx];\n return keyIndex !== undefined\n ? message.allKeys[keyIndex]\n : undefined;\n };\n const ataPk = getPk(1);\n const mintPk = getPk(3);\n if (ataPk && mintPk) {\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n createATA: {\n address: ataPk.toBase58(),\n mintAddress: mintPk.toBase58(),\n },\n },\n };\n }\n }\n\n // fallback when programId is unresolved\n // map account positions directly and return any addresses that are available in the static set\n const looksLikeSplOpcode =\n instructionType === TokenInstruction.Transfer ||\n instructionType === TokenInstruction.TransferChecked ||\n instructionType === TokenInstruction.InitializeAccount;\n\n if (!programId && looksLikeSplOpcode) {\n const keys = ixMeta.accountKeyIndexes;\n\n const keyAt = (pos: number): string | undefined => {\n const keyIndex = keys[pos];\n if (keyIndex === undefined) return undefined;\n const pk = message.allKeys[keyIndex];\n return pk ? pk.toBase58() : undefined;\n };\n\n if (instructionType === TokenInstruction.Transfer) {\n // accounts: [source, destination, owner, (...signers)]\n const destination = keyAt(1);\n if (destination) {\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: { tokenAddress: destination },\n };\n }\n // if can't resolve any useful address, still mark SPL\n return { transactionType: SolanaTransactionTypes.SPL, data: {} };\n }\n\n if (instructionType === TokenInstruction.TransferChecked) {\n // accounts: [source, mint, destination, owner, (...signers)]\n const mint = keyAt(1);\n const destination = keyAt(2);\n if (destination || mint) {\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n ...(destination ? { tokenAddress: destination } : {}),\n ...(mint ? { mintAddress: mint } : {}),\n },\n };\n }\n return { transactionType: SolanaTransactionTypes.SPL, data: {} };\n }\n\n if (instructionType === TokenInstruction.InitializeAccount) {\n // accounts: [account, mint, owner, rent]\n const account = keyAt(0);\n const mint = keyAt(1);\n if (account || mint) {\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n createATA: {\n address: account ?? \"\",\n mintAddress: mint ?? \"\",\n },\n },\n };\n }\n return { transactionType: SolanaTransactionTypes.SPL, data: {} };\n }\n }\n }\n\n // nothing matched\n return { transactionType: SolanaTransactionTypes.STANDARD, data: {} };\n } catch {\n return { transactionType: SolanaTransactionTypes.STANDARD, data: {} };\n }\n }\n\n /**\n * normalize legacy or v0 messages into { compiledInstructions, allKeys }.\n * if `lookedUpAddresses` is provided, it will be included; otherwise only static keys are present.\n */\n private normaliseMessage(rawBytes: Uint8Array): NormalizedMessage {\n const vtx = this.tryDeserialiseVersioned(rawBytes);\n if (vtx) {\n const msg = vtx.message as VersionedMessage & {\n getAccountKeys?: (opts?: {\n accountKeysFromLookups?: LoadedAddresses;\n }) => {\n staticAccountKeys: PublicKey[];\n accountKeysFromLookups?: LoadedAddresses;\n keySegments: () => PublicKey[][];\n };\n compiledInstructions: Array<{\n programIdIndex: number;\n accountKeyIndexes?: number[];\n accounts?: number[];\n data: Uint8Array | string | number[];\n }>;\n staticAccountKeys: PublicKey[];\n };\n\n // build key list in the exact index order used by compiledInstructions.\n let allKeys: PublicKey[];\n if (typeof msg.getAccountKeys === \"function\") {\n const mak = msg.getAccountKeys({\n accountKeysFromLookups: this.lookedUpAddresses, // may be undefined\n });\n allKeys = mak.keySegments().flat(); // static + (optionally) looked-up, in correct order\n } else {\n allKeys = [...msg.staticAccountKeys];\n }\n\n const compiledInstructions: NormalizedCompiledIx[] =\n msg.compiledInstructions.map((ix) => ({\n programIdIndex: ix.programIdIndex,\n accountKeyIndexes: Array.from(ix.accountKeyIndexes ?? []),\n data:\n ix.data instanceof Uint8Array\n ? ix.data\n : Buffer.from(ix.data ?? []),\n }));\n\n return { compiledInstructions, allKeys };\n }\n\n // legacy fallback\n const legacy = Transaction.from(rawBytes);\n\n const allKeyMap = new Map<string, PublicKey>();\n const add = (pk?: PublicKey | null) => {\n if (!pk) return;\n const k = pk.toBase58();\n if (!allKeyMap.has(k)) allKeyMap.set(k, pk);\n };\n\n add(legacy.feePayer ?? null);\n for (const ix of legacy.instructions) {\n add(ix.programId);\n for (const k of ix.keys) add(k.pubkey);\n }\n const allKeys = Array.from(allKeyMap.values());\n const indexByB58 = new Map(allKeys.map((pk, i) => [pk.toBase58(), i]));\n\n const compiledInstructions: NormalizedCompiledIx[] =\n legacy.instructions.map((ix) => ({\n programIdIndex: indexByB58.get(ix.programId.toBase58()) ?? -1,\n accountKeyIndexes: ix.keys.map(\n (k) => indexByB58.get(k.pubkey.toBase58()) ?? -1,\n ),\n data: ix.data,\n }));\n\n return { compiledInstructions, allKeys };\n }\n\n private tryDeserialiseVersioned(\n rawBytes: Uint8Array,\n ): VersionedTransaction | null {\n try {\n return VersionedTransaction.deserialize(rawBytes);\n } catch {\n try {\n const msg = VersionedMessage.deserialize(rawBytes);\n return { message: msg } as VersionedTransaction;\n } catch {\n return null;\n }\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["TransactionInspector_exports", "__export", "SolanaTransactionTypes", "TransactionInspector", "__toCommonJS", "import_spl_token", "import_web3", "import_buffer", "rawTransactionBytes", "lookedUpAddresses", "message", "ixMeta", "programId", "instructionType", "instruction", "i", "pk", "destination", "mint", "account", "getPk", "idx", "keyIndex", "ataPk", "mintPk", "looksLikeSplOpcode", "keys", "keyAt", "pos", "rawBytes", "vtx", "msg", "allKeys", "ix", "legacy", "allKeyMap", "add", "k", "indexByB58"]
|
|
7
7
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ASSOCIATED_TOKEN_PROGRAM_ID as m,decodeInitializeAccountInstruction as
|
|
1
|
+
import{ASSOCIATED_TOKEN_PROGRAM_ID as m,decodeInitializeAccountInstruction as T,decodeTransferCheckedInstruction as A,decodeTransferInstruction as f,TOKEN_2022_PROGRAM_ID as I,TOKEN_PROGRAM_ID as k,TokenInstruction as i}from"@solana/spl-token";import{Transaction as g,TransactionInstruction as K,VersionedMessage as S,VersionedTransaction as P}from"@solana/web3.js";import{Buffer as p}from"buffer";var b=(s=>(s.STANDARD="Standard",s.SPL="SPL",s))(b||{});class M{constructor(a,s){this.rawTransactionBytes=a;this.lookedUpAddresses=s}inspectTransactionType(){try{const a=this.normaliseMessage(this.rawTransactionBytes);for(const s of a.compiledInstructions){const o=a.allKeys[s.programIdIndex],c=s.data?.[0];if(!!o&&(o.equals(k)||o.equals(I)))try{const r=new K({programId:o,keys:s.accountKeyIndexes.map(t=>{const e=a.allKeys[t];if(!e)throw new Error(`TransactionInspector: missing key at index ${t} in allKeys`);return{pubkey:e,isSigner:!1,isWritable:!1}}),data:p.from(s.data)});switch(r.data[0]){case i.Transfer:{const{keys:{destination:t}}=f(r);return{transactionType:"SPL",data:{tokenAddress:t.pubkey.toBase58()}}}case i.TransferChecked:{const{keys:{destination:t,mint:e}}=A(r);return{transactionType:"SPL",data:{tokenAddress:t.pubkey.toBase58(),mintAddress:e.pubkey.toBase58()}}}case i.InitializeAccount:{const{keys:{account:t,mint:e}}=T(r);return{transactionType:"SPL",data:{createATA:{address:t.pubkey.toBase58(),mintAddress:e.pubkey.toBase58()}}}}default:break}}catch{}if(o&&o.equals(m)){const r=n=>{const d=s.accountKeyIndexes[n];return d!==void 0?a.allKeys[d]:void 0},t=r(1),e=r(3);if(t&&e)return{transactionType:"SPL",data:{createATA:{address:t.toBase58(),mintAddress:e.toBase58()}}}}const l=c===i.Transfer||c===i.TransferChecked||c===i.InitializeAccount;if(!o&&l){const r=s.accountKeyIndexes,t=e=>{const n=r[e];if(n===void 0)return;const d=a.allKeys[n];return d?d.toBase58():void 0};if(c===i.Transfer){const e=t(1);return e?{transactionType:"SPL",data:{tokenAddress:e}}:{transactionType:"SPL",data:{}}}if(c===i.TransferChecked){const e=t(1),n=t(2);return n||e?{transactionType:"SPL",data:{...n?{tokenAddress:n}:{},...e?{mintAddress:e}:{}}}:{transactionType:"SPL",data:{}}}if(c===i.InitializeAccount){const e=t(0),n=t(1);return e||n?{transactionType:"SPL",data:{createATA:{address:e??"",mintAddress:n??""}}}:{transactionType:"SPL",data:{}}}}}return{transactionType:"Standard",data:{}}}catch{return{transactionType:"Standard",data:{}}}}normaliseMessage(a){const s=this.tryDeserialiseVersioned(a);if(s){const e=s.message;let n;return typeof e.getAccountKeys=="function"?n=e.getAccountKeys({accountKeysFromLookups:this.lookedUpAddresses}).keySegments().flat():n=[...e.staticAccountKeys],{compiledInstructions:e.compiledInstructions.map(u=>({programIdIndex:u.programIdIndex,accountKeyIndexes:Array.from(u.accountKeyIndexes??[]),data:u.data instanceof Uint8Array?u.data:p.from(u.data??[])})),allKeys:n}}const o=g.from(a),c=new Map,y=e=>{if(!e)return;const n=e.toBase58();c.has(n)||c.set(n,e)};y(o.feePayer??null);for(const e of o.instructions){y(e.programId);for(const n of e.keys)y(n.pubkey)}const l=Array.from(c.values()),r=new Map(l.map((e,n)=>[e.toBase58(),n]));return{compiledInstructions:o.instructions.map(e=>({programIdIndex:r.get(e.programId.toBase58())??-1,accountKeyIndexes:e.keys.map(n=>r.get(n.pubkey.toBase58())??-1),data:e.data})),allKeys:l}}tryDeserialiseVersioned(a){try{return P.deserialize(a)}catch{try{return{message:S.deserialize(a)}}catch{return null}}}}export{b as SolanaTransactionTypes,M as TransactionInspector};
|
|
2
2
|
//# sourceMappingURL=TransactionInspector.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/internal/app-binder/services/TransactionInspector.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n ASSOCIATED_TOKEN_PROGRAM_ID,\n decodeInitializeAccountInstruction,\n decodeTransferCheckedInstruction,\n decodeTransferInstruction,\n TOKEN_2022_PROGRAM_ID,\n TOKEN_PROGRAM_ID,\n TokenInstruction,\n} from \"@solana/spl-token\";\nimport {\n type PublicKey,\n Transaction,\n TransactionInstruction,\n VersionedMessage,\n VersionedTransaction,\n} from \"@solana/web3.js\";\nimport { Buffer } from \"buffer\";\n\nexport enum SolanaTransactionTypes {\n STANDARD = \"Standard\",\n SPL = \"SPL\",\n}\n\nexport interface TxInspectorResult {\n transactionType: SolanaTransactionTypes;\n data: {\n tokenAddress?: string;\n mintAddress?: string;\n createATA?: {\n address: string;\n mintAddress: string;\n };\n };\n}\n\ntype NormalizedCompiledIx = {\n programIdIndex: number;\n accountKeyIndexes: number[];\n data: Uint8Array;\n};\n\ntype NormalizedMessage = {\n compiledInstructions: NormalizedCompiledIx[];\n allKeys: PublicKey[];\n};\n\ntype LoadedAddresses = { writable: PublicKey[]; readonly: PublicKey[] };\n\nexport class TransactionInspector {\n /**\n * @param rawTransactionBytes - the raw tx bytes (legacy or v0)\n */\n constructor(private readonly rawTransactionBytes: Uint8Array) {}\n\n public inspectTransactionType(): TxInspectorResult {\n try {\n const message = this.normaliseMessage(this.rawTransactionBytes);\n\n for (const ixMeta of message.compiledInstructions) {\n const programId = message.allKeys[ixMeta.programIdIndex];\n if (!programId) continue;\n\n // Associated Token Account (ATA) Program: detect ATA creation\n if (programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {\n // expected accounts: [payer, ata, owner, mint, systemProgram, tokenProgram, rent?]\n const accountPks = ixMeta.accountKeyIndexes\n .map((i) => message.allKeys[i])\n .filter(Boolean) as PublicKey[];\n const ataPk = accountPks[1];\n const mintPk = accountPks[3];\n if (ataPk && mintPk) {\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n createATA: {\n address: ataPk.toBase58(),\n mintAddress: mintPk.toBase58(),\n },\n },\n };\n }\n continue;\n }\n\n // Token Program (classic or 2022)\n const isTokenProgram =\n programId.equals(TOKEN_PROGRAM_ID) ||\n programId.equals(TOKEN_2022_PROGRAM_ID);\n if (!isTokenProgram) continue;\n\n // minimal TransactionInstruction for the decoders\n const instruction = new TransactionInstruction({\n programId,\n keys: ixMeta.accountKeyIndexes.map((i) => {\n if (!message.allKeys[i]) {\n throw new Error(\n `TransactionInspector: missing key at index ${i} in allKeys`,\n );\n }\n return {\n pubkey: message.allKeys[i],\n isSigner: false,\n isWritable: false,\n };\n }),\n data: Buffer.from(ixMeta.data),\n });\n\n const instructionType = instruction.data[0];\n\n try {\n switch (instructionType) {\n case TokenInstruction.Transfer: {\n const {\n keys: { destination },\n } = decodeTransferInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: { tokenAddress: destination.pubkey.toBase58() },\n };\n }\n case TokenInstruction.TransferChecked: {\n const {\n keys: { destination, mint },\n } = decodeTransferCheckedInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n tokenAddress: destination.pubkey.toBase58(),\n mintAddress: mint.pubkey.toBase58(),\n },\n };\n }\n case TokenInstruction.InitializeAccount: {\n // InitializeAccount != ATA creation, ATA is via the Associated Token Account Program above.\n const {\n keys: { account, mint },\n } = decodeInitializeAccountInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n createATA: {\n address: account.pubkey.toBase58(),\n mintAddress: mint.pubkey.toBase58(),\n },\n },\n };\n }\n default:\n // not a token instruction we care about\u2014keep scanning.\n break;\n }\n } catch {\n // if a decoder throws (bad match), keep scanning other instructions.\n continue;\n }\n }\n\n return { transactionType: SolanaTransactionTypes.STANDARD, data: {} };\n } catch {\n return { transactionType: SolanaTransactionTypes.STANDARD, data: {} };\n }\n }\n\n /**\n * normalise any tx (legacy or v0) into { compiledInstructions, allKeys }.\n * if LUT accounts are provided, looked-up keys are included in allKeys.\n */\n private normaliseMessage(rawBytes: Uint8Array): NormalizedMessage {\n const vtx = this.tryDeserialiseVersioned(rawBytes);\n if (vtx) {\n const msg = vtx.message as VersionedMessage & {\n getAccountKeys?: (opts?: {\n accountKeysFromLookups?: LoadedAddresses;\n }) => {\n staticAccountKeys: PublicKey[];\n accountKeysFromLookups?: LoadedAddresses;\n keySegments: () => PublicKey[][];\n };\n compiledInstructions: Array<{\n programIdIndex: number;\n accountKeyIndexes?: number[];\n accounts?: number[];\n data: Uint8Array | string | number[];\n }>;\n staticAccountKeys: PublicKey[];\n };\n\n // build the full key array in the exact index order used by compiledInstructions\n let allKeys: PublicKey[];\n if (typeof msg.getAccountKeys === \"function\") {\n const mak = msg.getAccountKeys();\n allKeys = mak.keySegments().flat();\n } else {\n // very old builds: fall back to concatenation (same order)\n allKeys = [...msg.staticAccountKeys];\n }\n\n const compiledInstructions: NormalizedCompiledIx[] =\n msg.compiledInstructions.map((ix) => ({\n programIdIndex: ix.programIdIndex,\n accountKeyIndexes: Array.from(ix.accountKeyIndexes ?? []),\n data:\n ix.data instanceof Uint8Array\n ? ix.data\n : Buffer.from(ix.data ?? []),\n }));\n\n return { compiledInstructions, allKeys };\n }\n\n // legacy fallback\n const legacy = Transaction.from(rawBytes);\n\n const allKeyMap = new Map<string, PublicKey>();\n const add = (pk?: PublicKey | null) => {\n if (!pk) return;\n const k = pk.toBase58();\n if (!allKeyMap.has(k)) allKeyMap.set(k, pk);\n };\n\n add(legacy.feePayer ?? null);\n for (const ix of legacy.instructions) {\n add(ix.programId);\n for (const k of ix.keys) add(k.pubkey);\n }\n const allKeys = Array.from(allKeyMap.values());\n const indexByB58 = new Map(allKeys.map((pk, i) => [pk.toBase58(), i]));\n\n const compiledInstructions: NormalizedCompiledIx[] =\n legacy.instructions.map((ix) => ({\n programIdIndex: indexByB58.get(ix.programId.toBase58()) ?? -1,\n accountKeyIndexes: ix.keys.map(\n (k) => indexByB58.get(k.pubkey.toBase58()) ?? -1,\n ),\n data: ix.data,\n }));\n\n return { compiledInstructions, allKeys };\n }\n\n private tryDeserialiseVersioned(\n rawBytes: Uint8Array,\n ): VersionedTransaction | null {\n try {\n return VersionedTransaction.deserialize(rawBytes);\n } catch {\n try {\n const msg = VersionedMessage.deserialize(rawBytes);\n // wrap in a dummy VersionedTransaction-like shape just for uniform handling\n return { message: msg } as VersionedTransaction;\n } catch {\n return null;\n }\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,OACE,+BAAAA,EACA,sCAAAC,EACA,oCAAAC,EACA,6BAAAC,EACA,yBAAAC,EACA,oBAAAC,EACA,oBAAAC,MACK,oBACP,OAEE,eAAAC,EACA,0BAAAC,EACA,oBAAAC,EACA,wBAAAC,MACK,kBACP,OAAS,UAAAC,MAAc,SAEhB,IAAKC,OACVA,EAAA,SAAW,WACXA,EAAA,IAAM,MAFIA,OAAA,IA8BL,MAAMC,CAAqB,
|
|
6
|
-
"names": ["ASSOCIATED_TOKEN_PROGRAM_ID", "decodeInitializeAccountInstruction", "decodeTransferCheckedInstruction", "decodeTransferInstruction", "TOKEN_2022_PROGRAM_ID", "TOKEN_PROGRAM_ID", "TokenInstruction", "Transaction", "TransactionInstruction", "VersionedMessage", "VersionedTransaction", "Buffer", "SolanaTransactionTypes", "TransactionInspector", "rawTransactionBytes", "message", "ixMeta", "programId", "
|
|
4
|
+
"sourcesContent": ["import {\n ASSOCIATED_TOKEN_PROGRAM_ID,\n decodeInitializeAccountInstruction,\n decodeTransferCheckedInstruction,\n decodeTransferInstruction,\n TOKEN_2022_PROGRAM_ID,\n TOKEN_PROGRAM_ID,\n TokenInstruction,\n} from \"@solana/spl-token\";\nimport {\n type PublicKey,\n Transaction,\n TransactionInstruction,\n VersionedMessage,\n VersionedTransaction,\n} from \"@solana/web3.js\";\nimport { Buffer } from \"buffer\";\n\nexport enum SolanaTransactionTypes {\n STANDARD = \"Standard\",\n SPL = \"SPL\",\n}\n\nexport interface TxInspectorResult {\n transactionType: SolanaTransactionTypes;\n data: {\n tokenAddress?: string;\n mintAddress?: string;\n createATA?: {\n address: string;\n mintAddress: string;\n };\n };\n}\n\ntype NormalizedCompiledIx = {\n programIdIndex: number;\n accountKeyIndexes: number[];\n data: Uint8Array;\n};\n\ntype NormalizedMessage = {\n compiledInstructions: NormalizedCompiledIx[];\n allKeys: PublicKey[]; // may exclude LUT keys if not provided\n};\n\ntype LoadedAddresses = { writable: PublicKey[]; readonly: PublicKey[] };\n\nexport class TransactionInspector {\n /**\n * @param rawTransactionBytes - raw bytes of the transaction to inspect\n * @param lookedUpAddresses - optional hydrated LUT keys\n */\n constructor(\n private readonly rawTransactionBytes: Uint8Array,\n private readonly lookedUpAddresses?: LoadedAddresses,\n ) {}\n\n public inspectTransactionType(): TxInspectorResult {\n try {\n const message = this.normaliseMessage(this.rawTransactionBytes);\n\n for (const ixMeta of message.compiledInstructions) {\n const programId = message.allKeys[ixMeta.programIdIndex];\n const instructionType = ixMeta.data?.[0];\n\n const isTokenProgram =\n !!programId &&\n (programId.equals(TOKEN_PROGRAM_ID) ||\n programId.equals(TOKEN_2022_PROGRAM_ID));\n\n // exact decode when we know it's the token program (classic or 2022)\n if (isTokenProgram) {\n try {\n const instruction = new TransactionInstruction({\n programId,\n keys: ixMeta.accountKeyIndexes.map((i) => {\n const pk = message.allKeys[i];\n if (!pk) {\n throw new Error(\n `TransactionInspector: missing key at index ${i} in allKeys`,\n );\n }\n return { pubkey: pk, isSigner: false, isWritable: false };\n }),\n data: Buffer.from(ixMeta.data),\n });\n\n switch (instruction.data[0]) {\n case TokenInstruction.Transfer: {\n const {\n keys: { destination },\n } = decodeTransferInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: { tokenAddress: destination.pubkey.toBase58() },\n };\n }\n case TokenInstruction.TransferChecked: {\n const {\n keys: { destination, mint },\n } = decodeTransferCheckedInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n tokenAddress: destination.pubkey.toBase58(),\n mintAddress: mint.pubkey.toBase58(),\n },\n };\n }\n case TokenInstruction.InitializeAccount: {\n const {\n keys: { account, mint },\n } = decodeInitializeAccountInstruction(instruction);\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n createATA: {\n address: account.pubkey.toBase58(),\n mintAddress: mint.pubkey.toBase58(),\n },\n },\n };\n }\n default:\n // keep scanning\n break;\n }\n } catch {\n // if the decoder throws, keep scanning other instructions\n }\n }\n\n // detect ATA creation (exact: only when programId is present & matches)\n if (programId && programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {\n // expected accounts: [payer, ata, owner, mint, systemProgram, tokenProgram, rent?]\n const getPk = (idx: number) => {\n const keyIndex = ixMeta.accountKeyIndexes[idx];\n return keyIndex !== undefined\n ? message.allKeys[keyIndex]\n : undefined;\n };\n const ataPk = getPk(1);\n const mintPk = getPk(3);\n if (ataPk && mintPk) {\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n createATA: {\n address: ataPk.toBase58(),\n mintAddress: mintPk.toBase58(),\n },\n },\n };\n }\n }\n\n // fallback when programId is unresolved\n // map account positions directly and return any addresses that are available in the static set\n const looksLikeSplOpcode =\n instructionType === TokenInstruction.Transfer ||\n instructionType === TokenInstruction.TransferChecked ||\n instructionType === TokenInstruction.InitializeAccount;\n\n if (!programId && looksLikeSplOpcode) {\n const keys = ixMeta.accountKeyIndexes;\n\n const keyAt = (pos: number): string | undefined => {\n const keyIndex = keys[pos];\n if (keyIndex === undefined) return undefined;\n const pk = message.allKeys[keyIndex];\n return pk ? pk.toBase58() : undefined;\n };\n\n if (instructionType === TokenInstruction.Transfer) {\n // accounts: [source, destination, owner, (...signers)]\n const destination = keyAt(1);\n if (destination) {\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: { tokenAddress: destination },\n };\n }\n // if can't resolve any useful address, still mark SPL\n return { transactionType: SolanaTransactionTypes.SPL, data: {} };\n }\n\n if (instructionType === TokenInstruction.TransferChecked) {\n // accounts: [source, mint, destination, owner, (...signers)]\n const mint = keyAt(1);\n const destination = keyAt(2);\n if (destination || mint) {\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n ...(destination ? { tokenAddress: destination } : {}),\n ...(mint ? { mintAddress: mint } : {}),\n },\n };\n }\n return { transactionType: SolanaTransactionTypes.SPL, data: {} };\n }\n\n if (instructionType === TokenInstruction.InitializeAccount) {\n // accounts: [account, mint, owner, rent]\n const account = keyAt(0);\n const mint = keyAt(1);\n if (account || mint) {\n return {\n transactionType: SolanaTransactionTypes.SPL,\n data: {\n createATA: {\n address: account ?? \"\",\n mintAddress: mint ?? \"\",\n },\n },\n };\n }\n return { transactionType: SolanaTransactionTypes.SPL, data: {} };\n }\n }\n }\n\n // nothing matched\n return { transactionType: SolanaTransactionTypes.STANDARD, data: {} };\n } catch {\n return { transactionType: SolanaTransactionTypes.STANDARD, data: {} };\n }\n }\n\n /**\n * normalize legacy or v0 messages into { compiledInstructions, allKeys }.\n * if `lookedUpAddresses` is provided, it will be included; otherwise only static keys are present.\n */\n private normaliseMessage(rawBytes: Uint8Array): NormalizedMessage {\n const vtx = this.tryDeserialiseVersioned(rawBytes);\n if (vtx) {\n const msg = vtx.message as VersionedMessage & {\n getAccountKeys?: (opts?: {\n accountKeysFromLookups?: LoadedAddresses;\n }) => {\n staticAccountKeys: PublicKey[];\n accountKeysFromLookups?: LoadedAddresses;\n keySegments: () => PublicKey[][];\n };\n compiledInstructions: Array<{\n programIdIndex: number;\n accountKeyIndexes?: number[];\n accounts?: number[];\n data: Uint8Array | string | number[];\n }>;\n staticAccountKeys: PublicKey[];\n };\n\n // build key list in the exact index order used by compiledInstructions.\n let allKeys: PublicKey[];\n if (typeof msg.getAccountKeys === \"function\") {\n const mak = msg.getAccountKeys({\n accountKeysFromLookups: this.lookedUpAddresses, // may be undefined\n });\n allKeys = mak.keySegments().flat(); // static + (optionally) looked-up, in correct order\n } else {\n allKeys = [...msg.staticAccountKeys];\n }\n\n const compiledInstructions: NormalizedCompiledIx[] =\n msg.compiledInstructions.map((ix) => ({\n programIdIndex: ix.programIdIndex,\n accountKeyIndexes: Array.from(ix.accountKeyIndexes ?? []),\n data:\n ix.data instanceof Uint8Array\n ? ix.data\n : Buffer.from(ix.data ?? []),\n }));\n\n return { compiledInstructions, allKeys };\n }\n\n // legacy fallback\n const legacy = Transaction.from(rawBytes);\n\n const allKeyMap = new Map<string, PublicKey>();\n const add = (pk?: PublicKey | null) => {\n if (!pk) return;\n const k = pk.toBase58();\n if (!allKeyMap.has(k)) allKeyMap.set(k, pk);\n };\n\n add(legacy.feePayer ?? null);\n for (const ix of legacy.instructions) {\n add(ix.programId);\n for (const k of ix.keys) add(k.pubkey);\n }\n const allKeys = Array.from(allKeyMap.values());\n const indexByB58 = new Map(allKeys.map((pk, i) => [pk.toBase58(), i]));\n\n const compiledInstructions: NormalizedCompiledIx[] =\n legacy.instructions.map((ix) => ({\n programIdIndex: indexByB58.get(ix.programId.toBase58()) ?? -1,\n accountKeyIndexes: ix.keys.map(\n (k) => indexByB58.get(k.pubkey.toBase58()) ?? -1,\n ),\n data: ix.data,\n }));\n\n return { compiledInstructions, allKeys };\n }\n\n private tryDeserialiseVersioned(\n rawBytes: Uint8Array,\n ): VersionedTransaction | null {\n try {\n return VersionedTransaction.deserialize(rawBytes);\n } catch {\n try {\n const msg = VersionedMessage.deserialize(rawBytes);\n return { message: msg } as VersionedTransaction;\n } catch {\n return null;\n }\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["ASSOCIATED_TOKEN_PROGRAM_ID", "decodeInitializeAccountInstruction", "decodeTransferCheckedInstruction", "decodeTransferInstruction", "TOKEN_2022_PROGRAM_ID", "TOKEN_PROGRAM_ID", "TokenInstruction", "Transaction", "TransactionInstruction", "VersionedMessage", "VersionedTransaction", "Buffer", "SolanaTransactionTypes", "TransactionInspector", "rawTransactionBytes", "lookedUpAddresses", "message", "ixMeta", "programId", "instructionType", "instruction", "i", "pk", "destination", "mint", "account", "getPk", "idx", "keyIndex", "ataPk", "mintPk", "looksLikeSplOpcode", "keys", "keyAt", "pos", "rawBytes", "vtx", "msg", "allKeys", "ix", "legacy", "allKeyMap", "add", "k", "indexByB58"]
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PublicKey } from "@solana/web3.js";
|
|
1
2
|
export declare enum SolanaTransactionTypes {
|
|
2
3
|
STANDARD = "Standard",
|
|
3
4
|
SPL = "SPL"
|
|
@@ -13,18 +14,25 @@ export interface TxInspectorResult {
|
|
|
13
14
|
};
|
|
14
15
|
};
|
|
15
16
|
}
|
|
17
|
+
type LoadedAddresses = {
|
|
18
|
+
writable: PublicKey[];
|
|
19
|
+
readonly: PublicKey[];
|
|
20
|
+
};
|
|
16
21
|
export declare class TransactionInspector {
|
|
17
22
|
private readonly rawTransactionBytes;
|
|
23
|
+
private readonly lookedUpAddresses?;
|
|
18
24
|
/**
|
|
19
|
-
* @param rawTransactionBytes -
|
|
25
|
+
* @param rawTransactionBytes - raw bytes of the transaction to inspect
|
|
26
|
+
* @param lookedUpAddresses - optional hydrated LUT keys
|
|
20
27
|
*/
|
|
21
|
-
constructor(rawTransactionBytes: Uint8Array);
|
|
28
|
+
constructor(rawTransactionBytes: Uint8Array, lookedUpAddresses?: LoadedAddresses | undefined);
|
|
22
29
|
inspectTransactionType(): TxInspectorResult;
|
|
23
30
|
/**
|
|
24
|
-
*
|
|
25
|
-
* if
|
|
31
|
+
* normalize legacy or v0 messages into { compiledInstructions, allKeys }.
|
|
32
|
+
* if `lookedUpAddresses` is provided, it will be included; otherwise only static keys are present.
|
|
26
33
|
*/
|
|
27
34
|
private normaliseMessage;
|
|
28
35
|
private tryDeserialiseVersioned;
|
|
29
36
|
}
|
|
37
|
+
export {};
|
|
30
38
|
//# sourceMappingURL=TransactionInspector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransactionInspector.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/services/TransactionInspector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TransactionInspector.d.ts","sourceRoot":"","sources":["../../../../../src/internal/app-binder/services/TransactionInspector.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,SAAS,EAKf,MAAM,iBAAiB,CAAC;AAGzB,oBAAY,sBAAsB;IAChC,QAAQ,aAAa;IACrB,GAAG,QAAQ;CACZ;AAED,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,sBAAsB,CAAC;IACxC,IAAI,EAAE;QACJ,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE;YACV,OAAO,EAAE,MAAM,CAAC;YAChB,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC;KACH,CAAC;CACH;AAaD,KAAK,eAAe,GAAG;IAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC;AAExE,qBAAa,oBAAoB;IAM7B,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IANrC;;;OAGG;gBAEgB,mBAAmB,EAAE,UAAU,EAC/B,iBAAiB,CAAC,EAAE,eAAe,YAAA;IAG/C,sBAAsB,IAAI,iBAAiB;IA4KlD;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA0ExB,OAAO,CAAC,uBAAuB;CAchC"}
|