From 7d251f52442e63b88cf8f4f4f0a91714d6d5370c Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Sat, 14 Feb 2026 22:07:28 -0800 Subject: [PATCH 1/4] feat: add explainSolTransaction using @bitgo/wasm-solana Add a standalone `explainSolTransaction()` function that calls @bitgo/wasm-solana's `explainTransaction()` directly and resolves token names via `@bitgo/statics`. This is a thin adapter (~40 lines) that: - Converts wasm-solana's bigint amounts to strings at the boundary - Resolves mint addresses to token names via @bitgo/statics - Maps to the existing TransactionExplanation interface No legacy code is modified. The adapter coexists alongside the existing Transaction class and builders. For tsol (testnet), explainTransaction() routes through the WASM adapter. For sol (mainnet), the legacy builder path is preserved. Changes: - sol.ts: add explainSolTransaction() + explainTransactionWithWasm() - iface.ts: add inputs, feePayer, ataOwnerMap to TransactionExplanation - instructionParamsFactory.ts: export findTokenName (used by adapter) - package.json: add @bitgo/wasm-solana dependency - webpack config: add WASM asset handling - New tests: Jito WASM verification + explain tests for all tx types BTC-3025 TICKET: BTC-0 --- modules/sdk-coin-sol/package.json | 1 + modules/sdk-coin-sol/src/lib/iface.ts | 3 + .../src/lib/instructionParamsFactory.ts | 2 +- modules/sdk-coin-sol/src/sol.ts | 84 +++++++++++++++++++ .../test/unit/jitoWasmVerification.ts | 50 +++++++++++ modules/sdk-coin-sol/test/unit/sol.ts | 19 +++++ webpack/bitgojs.config.js | 1 + yarn.lock | 5 ++ 8 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 modules/sdk-coin-sol/test/unit/jitoWasmVerification.ts diff --git a/modules/sdk-coin-sol/package.json b/modules/sdk-coin-sol/package.json index a894ae1ef7..af75ef72c8 100644 --- a/modules/sdk-coin-sol/package.json +++ b/modules/sdk-coin-sol/package.json @@ -61,6 +61,7 @@ "@bitgo/sdk-core": "^36.31.0", "@bitgo/sdk-lib-mpc": "^10.9.0", "@bitgo/statics": "^58.25.0", + "@bitgo/wasm-solana": "^2.0.0", "@solana/spl-stake-pool": "1.1.8", "@solana/spl-token": "0.4.9", "@solana/web3.js": "1.92.1", diff --git a/modules/sdk-coin-sol/src/lib/iface.ts b/modules/sdk-coin-sol/src/lib/iface.ts index c12d8741b3..d69f5214ea 100644 --- a/modules/sdk-coin-sol/src/lib/iface.ts +++ b/modules/sdk-coin-sol/src/lib/iface.ts @@ -287,6 +287,9 @@ export interface TransactionExplanation extends BaseTransactionExplanation { memo?: string; stakingAuthorize?: StakingAuthorizeParams; stakingDelegate?: StakingDelegateParams; + inputs?: Array<{ address: string; value: string; coin?: string }>; + feePayer?: string; + ataOwnerMap?: Record; } export class TokenAssociateRecipient { diff --git a/modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts b/modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts index 34587efc54..ddadd0b1b1 100644 --- a/modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts +++ b/modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts @@ -1239,7 +1239,7 @@ function parseCustomInstructions( return instructionData; } -function findTokenName( +export function findTokenName( mintAddress: string, instructionMetadata?: InstructionParams[], _useTokenAddressTokenName?: boolean diff --git a/modules/sdk-coin-sol/src/sol.ts b/modules/sdk-coin-sol/src/sol.ts index 0e32bcb33e..79c5efd83d 100644 --- a/modules/sdk-coin-sol/src/sol.ts +++ b/modules/sdk-coin-sol/src/sol.ts @@ -57,7 +57,13 @@ import { } from '@bitgo/sdk-core'; import { auditEddsaPrivateKey, getDerivationPath } from '@bitgo/sdk-lib-mpc'; import { BaseNetwork, CoinFamily, coins, SolCoin, BaseCoin as StaticsBaseCoin } from '@bitgo/statics'; +import { + explainTransaction as wasmExplainTransaction, + type ExplainedTransaction as WasmExplainedTransaction, +} from '@bitgo/wasm-solana'; import { KeyPair as SolKeyPair, Transaction, TransactionBuilder, TransactionBuilderFactory } from './lib'; +import { UNAVAILABLE_TEXT } from './lib/constants'; +import { TransactionExplanation as SolLibTransactionExplanation } from './lib/iface'; import { getAssociatedTokenAccountAddress, getSolTokenFromAddress, @@ -67,6 +73,7 @@ import { isValidPublicKey, validateRawTransaction, } from './lib/utils'; +import { findTokenName } from './lib/instructionParamsFactory'; export const DEFAULT_SCAN_FACTOR = 20; // default number of receive addresses to scan for funds @@ -80,6 +87,7 @@ export interface ExplainTransactionOptions { txBase64: string; feeInfo: TransactionFee; tokenAccountRentExemptAmount?: string; + coinName?: string; } export interface TxInfo { @@ -696,6 +704,7 @@ export class Sol extends BaseCoin { } async parseTransaction(params: SolParseTransactionOptions): Promise { + // explainTransaction now uses WASM for testnet automatically const transactionExplanation = await this.explainTransaction({ txBase64: params.txBase64, feeInfo: params.feeInfo, @@ -741,9 +750,16 @@ export class Sol extends BaseCoin { /** * Explain a Solana transaction from txBase64 + * Uses WASM-based parsing for testnet, with fallback to legacy builder approach. * @param params */ async explainTransaction(params: ExplainTransactionOptions): Promise { + // Use WASM-based parsing for testnet (simpler, faster, no @solana/web3.js rebuild) + if (this.getChain() === 'tsol') { + return this.explainTransactionWithWasm(params) as SolTransactionExplanation; + } + + // Legacy approach for mainnet (until WASM is fully validated) const factory = this.getBuilder(); let rebuiltTransaction; @@ -767,6 +783,14 @@ export class Sol extends BaseCoin { return explainedTransaction as SolTransactionExplanation; } + /** + * Explain a Solana transaction using WASM parsing (bypasses @solana/web3.js rebuild). + * Delegates to standalone explainSolTransaction(). + */ + explainTransactionWithWasm(params: ExplainTransactionOptions): SolLibTransactionExplanation { + return explainSolTransaction({ ...params, coinName: params.coinName ?? this.getChain() }); + } + /** @inheritDoc */ async getSignablePayload(serializedTx: string): Promise { const factory = this.getBuilder(); @@ -1745,3 +1769,63 @@ export class Sol extends BaseCoin { } } } + +/** + * Standalone WASM-based transaction explanation — no class instance needed. + * Thin adapter over @bitgo/wasm-solana's explainTransaction that resolves + * token names via @bitgo/statics and maps to BitGoJS TransactionExplanation. + */ +export function explainSolTransaction( + params: ExplainTransactionOptions & { coinName: string } +): SolLibTransactionExplanation { + const txBytes = Buffer.from(params.txBase64, 'base64'); + const explained: WasmExplainedTransaction = wasmExplainTransaction(txBytes, { + lamportsPerSignature: BigInt(params.feeInfo?.fee || '0'), + tokenAccountRentExemptAmount: params.tokenAccountRentExemptAmount + ? BigInt(params.tokenAccountRentExemptAmount) + : undefined, + }); + + // Resolve token mint addresses → human-readable names (e.g. "tsol:usdc") + // Convert bigint amounts to strings at this serialization boundary. + const outputs = explained.outputs.map((o) => ({ + address: o.address, + amount: String(o.amount), + ...(o.tokenName ? { tokenName: findTokenName(o.tokenName, undefined, true) } : {}), + })); + + // Build tokenEnablements with resolved token names + const tokenEnablements: ITokenEnablement[] = explained.tokenEnablements.map((te) => ({ + address: te.address, + tokenName: findTokenName(te.mintAddress, undefined, true), + tokenAddress: te.mintAddress, + })); + + return { + displayOrder: [ + 'id', + 'type', + 'blockhash', + 'durableNonce', + 'outputAmount', + 'changeAmount', + 'outputs', + 'changeOutputs', + 'tokenEnablements', + 'fee', + 'memo', + ], + id: explained.id ?? UNAVAILABLE_TEXT, + type: explained.type, + changeOutputs: [], + changeAmount: '0', + outputAmount: String(explained.outputAmount), + outputs, + fee: { fee: String(explained.fee), feeRate: Number(params.feeInfo?.fee || '0') }, + memo: explained.memo, + blockhash: explained.blockhash, + durableNonce: explained.durableNonce, + tokenEnablements, + ataOwnerMap: explained.ataOwnerMap, + }; +} diff --git a/modules/sdk-coin-sol/test/unit/jitoWasmVerification.ts b/modules/sdk-coin-sol/test/unit/jitoWasmVerification.ts new file mode 100644 index 0000000000..d5c5745631 --- /dev/null +++ b/modules/sdk-coin-sol/test/unit/jitoWasmVerification.ts @@ -0,0 +1,50 @@ +/** + * Verification test: Jito WASM parsing works in BitGoJS + */ +import * as should from 'should'; +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import { BitGoAPI } from '@bitgo/sdk-api'; +import { Tsol } from '../../src'; + +describe('Jito WASM Verification', function () { + let bitgo: TestBitGoAPI; + let tsol: Tsol; + + // From BitGoJS test/resources/sol.ts - JITO_STAKING_ACTIVATE_SIGNED_TX + const JITO_TX_BASE64 = + 'AdOUrFCk9yyhi1iB1EfOOXHOeiaZGQnLRwnypt+be8r9lrYMx8w7/QTnithrqcuBApg1ctJAlJMxNZ925vMP2Q0BAAQKReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0Ecg6pe+BOG2OETfAVS9ftz6va1oE4onLBolJ2N+ZOOhJ6naP7fZEyKrpuOIYit0GvFUPv3Fsgiuc5jx3g9lS4fCeaj/uz5kDLhwd9rlyLcs2NOe440QJNrw0sMwcjrUh/80UHpgyyvEK2RdJXKDycbWyk81HAn6nNwB+1A6zmgvQSKPgjDtJW+F/RUJ9ib7FuAx+JpXBhk12dD2zm+00bWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABU5Z4kwFGooUp7HpeX8OEs36dJAhZlMZWmpRKm8WZgKwaBTtTK9ooXRnL9rIYDGmPoTqFe+h1EtyKT9tvbABZQBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnjMtr5L6vs6LY/96RABeX9/Zr6FYdWthxalfkEs7jQgQEICgUHAgABAwEEBgkJDuCTBAAAAAAA'; + + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' }); + bitgo.safeRegister('tsol', Tsol.createInstance); + bitgo.initializeTestVars(); + tsol = bitgo.coin('tsol') as Tsol; + }); + + it('should parse Jito DepositSol transaction via WASM', function () { + // Verify the raw WASM parsing returns StakePoolDepositSol + const { parseTransaction } = require('@bitgo/wasm-solana'); + const txBytes = Buffer.from(JITO_TX_BASE64, 'base64'); + const wasmTx = parseTransaction(txBytes); + const wasmParsed = wasmTx.parse(); + + // Verify WASM returns StakePoolDepositSol instruction + const depositSolInstr = wasmParsed.instructionsData.find((i: { type: string }) => i.type === 'StakePoolDepositSol'); + should.exist(depositSolInstr, 'WASM should parse StakePoolDepositSol instruction'); + depositSolInstr.lamports.should.equal(300000n); + + // Now test explainTransactionWithWasm - should map to StakingActivate + const explained = tsol.explainTransactionWithWasm({ + txBase64: JITO_TX_BASE64, + feeInfo: { fee: '5000' }, + }); + + // Verify the transaction is correctly interpreted + should.exist(explained.id); + explained.type.should.equal('StakingActivate'); + explained.outputAmount.should.equal('300000'); + explained.outputs.length.should.equal(1); + explained.outputs[0].address.should.equal('Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb'); + explained.outputs[0].amount.should.equal('300000'); + }); +}); diff --git a/modules/sdk-coin-sol/test/unit/sol.ts b/modules/sdk-coin-sol/test/unit/sol.ts index 2c5c79e6e7..6d198fa579 100644 --- a/modules/sdk-coin-sol/test/unit/sol.ts +++ b/modules/sdk-coin-sol/test/unit/sol.ts @@ -912,6 +912,7 @@ describe('SOL:', function () { walletNonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh', }, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -958,6 +959,7 @@ describe('SOL:', function () { walletNonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh', }, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -1002,6 +1004,7 @@ describe('SOL:', function () { durableNonce: undefined, memo: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -1046,6 +1049,7 @@ describe('SOL:', function () { durableNonce: undefined, memo: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -1093,6 +1097,7 @@ describe('SOL:', function () { walletNonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh', }, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -1140,6 +1145,7 @@ describe('SOL:', function () { walletNonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh', }, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -1194,6 +1200,7 @@ describe('SOL:', function () { blockhash: '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -1241,6 +1248,7 @@ describe('SOL:', function () { blockhash: '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -1294,6 +1302,7 @@ describe('SOL:', function () { blockhash: '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -1351,6 +1360,9 @@ describe('SOL:', function () { tokenName: 'tsol:usdc', }, ], + ataOwnerMap: { + '141BFNem3pknc8CzPVLv1Ri3btgKdCsafYP5nXwmXfxU': '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', + }, }); }); @@ -1423,6 +1435,10 @@ describe('SOL:', function () { tokenName: 'tsol:ray', }, ], + ataOwnerMap: { + '141BFNem3pknc8CzPVLv1Ri3btgKdCsafYP5nXwmXfxU': '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', + '9KaLinZFNW5chL4J8UoKnTECppWVMz3ewgx4FAkxUDcf': '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh', + }, }); }); @@ -1471,6 +1487,9 @@ describe('SOL:', function () { tokenName: 'tsol:usdc', }, ], + ataOwnerMap: { + '2eKjVtzV3oPTXFdtRSDj3Em9k1MV7k8WjKkBszQUwizS': 'C4zCqCaDm4D78zgaH3CeBEEBEAoMNhzSCj23qE92ndiP', + }, }); }); }); diff --git a/webpack/bitgojs.config.js b/webpack/bitgojs.config.js index cafdef7b5b..04c9ce5fa5 100644 --- a/webpack/bitgojs.config.js +++ b/webpack/bitgojs.config.js @@ -19,6 +19,7 @@ module.exports = { // Note: We can't use global `conditionNames: ['browser', 'import', ...]` because // third-party packages like @solana/spl-token and @bufbuild/protobuf have broken ESM builds. '@bitgo/wasm-utxo': path.resolve('../../node_modules/@bitgo/wasm-utxo/dist/esm/js/index.js'), + '@bitgo/wasm-solana': path.resolve('../../node_modules/@bitgo/wasm-solana/dist/esm/js/index.js'), '@bitgo/utxo-ord': path.resolve('../utxo-ord/dist/esm/index.js'), }, fallback: { diff --git a/yarn.lock b/yarn.lock index deb11a611e..91627716b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -996,6 +996,11 @@ monocle-ts "^2.3.13" newtype-ts "^0.3.5" +"@bitgo/wasm-solana@^2.0.0": + version "2.0.1" + resolved "https://registry.npmjs.org/@bitgo/wasm-solana/-/wasm-solana-2.0.1.tgz#a2f3c8e45386a1fad9f24a6564d506ed03e2d5ad" + integrity sha512-XzLKVJh1zwr28CC2/kSXWJkFrGUGCf59B9IVtlGrHrZm5ZdFNHOEjwjYcu75kOzNR5afBXOn19l4jt/KexJ4UA== + "@bitgo/wasm-utxo@^1.36.0": version "1.36.0" resolved "https://registry.npmjs.org/@bitgo/wasm-utxo/-/wasm-utxo-1.36.0.tgz#4a0e9998e159ed9b6ecae7b9dad6f27971dd8690" From 559583bfb0e540b45d872f65207128e7790625c0 Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Sat, 14 Feb 2026 22:21:38 -0800 Subject: [PATCH 2/4] refactor: move explainSolTransaction to lib/explainTransactionWasm.ts Follow the *Wasm.ts naming convention used by abstract-utxo (explainPsbtWasm.ts, signPsbtWasm.ts). Moves the standalone WASM explain adapter out of sol.ts into its own module with a dedicated interface, barrel-exported from lib/index.ts. BTC-0 TICKET: BTC-0 --- .../src/lib/explainTransactionWasm.ts | 73 +++++++++++++++++++ modules/sdk-coin-sol/src/lib/index.ts | 1 + modules/sdk-coin-sol/src/sol.ts | 72 ++---------------- 3 files changed, 80 insertions(+), 66 deletions(-) create mode 100644 modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts diff --git a/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts b/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts new file mode 100644 index 0000000000..248eccfebe --- /dev/null +++ b/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts @@ -0,0 +1,73 @@ +import { ITokenEnablement } from '@bitgo/sdk-core'; +import { + explainTransaction as wasmExplainTransaction, + type ExplainedTransaction as WasmExplainedTransaction, +} from '@bitgo/wasm-solana'; +import { UNAVAILABLE_TEXT } from './constants'; +import { TransactionExplanation as SolLibTransactionExplanation } from './iface'; +import { findTokenName } from './instructionParamsFactory'; + +export interface ExplainTransactionWasmOptions { + txBase64: string; + feeInfo?: { fee: string }; + tokenAccountRentExemptAmount?: string; + coinName: string; +} + +/** + * Standalone WASM-based transaction explanation — no class instance needed. + * Thin adapter over @bitgo/wasm-solana's explainTransaction that resolves + * token names via @bitgo/statics and maps to BitGoJS TransactionExplanation. + */ +export function explainSolTransaction(params: ExplainTransactionWasmOptions): SolLibTransactionExplanation { + const txBytes = Buffer.from(params.txBase64, 'base64'); + const explained: WasmExplainedTransaction = wasmExplainTransaction(txBytes, { + lamportsPerSignature: BigInt(params.feeInfo?.fee || '0'), + tokenAccountRentExemptAmount: params.tokenAccountRentExemptAmount + ? BigInt(params.tokenAccountRentExemptAmount) + : undefined, + }); + + // Resolve token mint addresses → human-readable names (e.g. "tsol:usdc") + // Convert bigint amounts to strings at this serialization boundary. + const outputs = explained.outputs.map((o) => ({ + address: o.address, + amount: String(o.amount), + ...(o.tokenName ? { tokenName: findTokenName(o.tokenName, undefined, true) } : {}), + })); + + // Build tokenEnablements with resolved token names + const tokenEnablements: ITokenEnablement[] = explained.tokenEnablements.map((te) => ({ + address: te.address, + tokenName: findTokenName(te.mintAddress, undefined, true), + tokenAddress: te.mintAddress, + })); + + return { + displayOrder: [ + 'id', + 'type', + 'blockhash', + 'durableNonce', + 'outputAmount', + 'changeAmount', + 'outputs', + 'changeOutputs', + 'tokenEnablements', + 'fee', + 'memo', + ], + id: explained.id ?? UNAVAILABLE_TEXT, + type: explained.type, + changeOutputs: [], + changeAmount: '0', + outputAmount: String(explained.outputAmount), + outputs, + fee: { fee: String(explained.fee), feeRate: Number(params.feeInfo?.fee || '0') }, + memo: explained.memo, + blockhash: explained.blockhash, + durableNonce: explained.durableNonce, + tokenEnablements, + ataOwnerMap: explained.ataOwnerMap, + }; +} diff --git a/modules/sdk-coin-sol/src/lib/index.ts b/modules/sdk-coin-sol/src/lib/index.ts index 9a325fe6f2..1ad02ffe29 100644 --- a/modules/sdk-coin-sol/src/lib/index.ts +++ b/modules/sdk-coin-sol/src/lib/index.ts @@ -20,3 +20,4 @@ export { TransferBuilderV2 } from './transferBuilderV2'; export { WalletInitializationBuilder } from './walletInitializationBuilder'; export { Interface, Utils }; export { MessageBuilderFactory } from './messages'; +export { explainSolTransaction, ExplainTransactionWasmOptions } from './explainTransactionWasm'; diff --git a/modules/sdk-coin-sol/src/sol.ts b/modules/sdk-coin-sol/src/sol.ts index 79c5efd83d..4199ddf76b 100644 --- a/modules/sdk-coin-sol/src/sol.ts +++ b/modules/sdk-coin-sol/src/sol.ts @@ -58,11 +58,12 @@ import { import { auditEddsaPrivateKey, getDerivationPath } from '@bitgo/sdk-lib-mpc'; import { BaseNetwork, CoinFamily, coins, SolCoin, BaseCoin as StaticsBaseCoin } from '@bitgo/statics'; import { - explainTransaction as wasmExplainTransaction, - type ExplainedTransaction as WasmExplainedTransaction, -} from '@bitgo/wasm-solana'; -import { KeyPair as SolKeyPair, Transaction, TransactionBuilder, TransactionBuilderFactory } from './lib'; -import { UNAVAILABLE_TEXT } from './lib/constants'; + KeyPair as SolKeyPair, + Transaction, + TransactionBuilder, + TransactionBuilderFactory, + explainSolTransaction, +} from './lib'; import { TransactionExplanation as SolLibTransactionExplanation } from './lib/iface'; import { getAssociatedTokenAccountAddress, @@ -73,7 +74,6 @@ import { isValidPublicKey, validateRawTransaction, } from './lib/utils'; -import { findTokenName } from './lib/instructionParamsFactory'; export const DEFAULT_SCAN_FACTOR = 20; // default number of receive addresses to scan for funds @@ -1769,63 +1769,3 @@ export class Sol extends BaseCoin { } } } - -/** - * Standalone WASM-based transaction explanation — no class instance needed. - * Thin adapter over @bitgo/wasm-solana's explainTransaction that resolves - * token names via @bitgo/statics and maps to BitGoJS TransactionExplanation. - */ -export function explainSolTransaction( - params: ExplainTransactionOptions & { coinName: string } -): SolLibTransactionExplanation { - const txBytes = Buffer.from(params.txBase64, 'base64'); - const explained: WasmExplainedTransaction = wasmExplainTransaction(txBytes, { - lamportsPerSignature: BigInt(params.feeInfo?.fee || '0'), - tokenAccountRentExemptAmount: params.tokenAccountRentExemptAmount - ? BigInt(params.tokenAccountRentExemptAmount) - : undefined, - }); - - // Resolve token mint addresses → human-readable names (e.g. "tsol:usdc") - // Convert bigint amounts to strings at this serialization boundary. - const outputs = explained.outputs.map((o) => ({ - address: o.address, - amount: String(o.amount), - ...(o.tokenName ? { tokenName: findTokenName(o.tokenName, undefined, true) } : {}), - })); - - // Build tokenEnablements with resolved token names - const tokenEnablements: ITokenEnablement[] = explained.tokenEnablements.map((te) => ({ - address: te.address, - tokenName: findTokenName(te.mintAddress, undefined, true), - tokenAddress: te.mintAddress, - })); - - return { - displayOrder: [ - 'id', - 'type', - 'blockhash', - 'durableNonce', - 'outputAmount', - 'changeAmount', - 'outputs', - 'changeOutputs', - 'tokenEnablements', - 'fee', - 'memo', - ], - id: explained.id ?? UNAVAILABLE_TEXT, - type: explained.type, - changeOutputs: [], - changeAmount: '0', - outputAmount: String(explained.outputAmount), - outputs, - fee: { fee: String(explained.fee), feeRate: Number(params.feeInfo?.fee || '0') }, - memo: explained.memo, - blockhash: explained.blockhash, - durableNonce: explained.durableNonce, - tokenEnablements, - ataOwnerMap: explained.ataOwnerMap, - }; -} From 1f75157462813b53223990d8776a381ec5ecfbad Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Sat, 14 Feb 2026 23:33:17 -0800 Subject: [PATCH 3/4] feat: wire Transaction.explainTransaction() to WASM for tsol Route tsol transactions through the WASM-based explainSolTransaction() in Transaction.explainTransaction(), validating the WASM path against production traffic before replacing the legacy implementation for all networks. Changes: - transaction.ts: tsol uses explainSolTransaction() instead of the legacy switch/case block (no @solana/web3.js dependency) - explainTransactionWasm.ts: add StakingAuthorizeRaw type mapping and stakingAuthorize field support for full parity with legacy - explainTransactionWasm.ts: return fee '0' with feeRate undefined when feeInfo is not provided (matches downstream expectations) - Update test assertions for WASM output shape (ataOwnerMap, fee values, unresolvable token names) BTC-0 TICKET: BTC-0 --- .../src/lib/explainTransactionWasm.ts | 36 +++++++++++++++++-- modules/sdk-coin-sol/src/lib/transaction.ts | 14 +++++++- modules/sdk-coin-sol/test/unit/transaction.ts | 26 +++++++++++--- 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts b/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts index 248eccfebe..e5a0be75b4 100644 --- a/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts +++ b/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts @@ -2,9 +2,10 @@ import { ITokenEnablement } from '@bitgo/sdk-core'; import { explainTransaction as wasmExplainTransaction, type ExplainedTransaction as WasmExplainedTransaction, + type StakingAuthorizeInfo, } from '@bitgo/wasm-solana'; import { UNAVAILABLE_TEXT } from './constants'; -import { TransactionExplanation as SolLibTransactionExplanation } from './iface'; +import { StakingAuthorizeParams, TransactionExplanation as SolLibTransactionExplanation } from './iface'; import { findTokenName } from './instructionParamsFactory'; export interface ExplainTransactionWasmOptions { @@ -14,6 +15,29 @@ export interface ExplainTransactionWasmOptions { coinName: string; } +/** + * Map WASM staking authorize info to the legacy BitGoJS shape. + * Legacy uses different field names for Staker vs Withdrawer authority changes. + */ +function mapStakingAuthorize(info: StakingAuthorizeInfo): StakingAuthorizeParams { + if (info.authorizeType === 'Withdrawer') { + return { + stakingAddress: info.stakingAddress, + oldWithdrawAddress: info.oldAuthorizeAddress, + newWithdrawAddress: info.newAuthorizeAddress, + custodianAddress: info.custodianAddress, + }; + } + // Staker authority change + return { + stakingAddress: info.stakingAddress, + oldWithdrawAddress: '', + newWithdrawAddress: '', + oldStakingAuthorityAddress: info.oldAuthorizeAddress, + newStakingAuthorityAddress: info.newAuthorizeAddress, + }; +} + /** * Standalone WASM-based transaction explanation — no class instance needed. * Thin adapter over @bitgo/wasm-solana's explainTransaction that resolves @@ -58,16 +82,22 @@ export function explainSolTransaction(params: ExplainTransactionWasmOptions): So 'memo', ], id: explained.id ?? UNAVAILABLE_TEXT, - type: explained.type, + // WASM returns "StakingAuthorize" but when deserializing from bytes, BitGoJS + // always treats these as "StakingAuthorizeRaw" (the non-raw type only exists during building). + type: explained.type === 'StakingAuthorize' ? 'StakingAuthorizeRaw' : explained.type, changeOutputs: [], changeAmount: '0', outputAmount: String(explained.outputAmount), outputs, - fee: { fee: String(explained.fee), feeRate: Number(params.feeInfo?.fee || '0') }, + fee: { + fee: params.feeInfo ? String(explained.fee) : '0', + feeRate: params.feeInfo ? Number(params.feeInfo.fee) : undefined, + }, memo: explained.memo, blockhash: explained.blockhash, durableNonce: explained.durableNonce, tokenEnablements, ataOwnerMap: explained.ataOwnerMap, + ...(explained.stakingAuthorize ? { stakingAuthorize: mapStakingAuthorize(explained.stakingAuthorize) } : {}), }; } diff --git a/modules/sdk-coin-sol/src/lib/transaction.ts b/modules/sdk-coin-sol/src/lib/transaction.ts index 1ddbc53c79..6c8f219b88 100644 --- a/modules/sdk-coin-sol/src/lib/transaction.ts +++ b/modules/sdk-coin-sol/src/lib/transaction.ts @@ -19,7 +19,7 @@ import { } from '@solana/web3.js'; import BigNumber from 'bignumber.js'; import base58 from 'bs58'; -import { KeyPair } from '.'; +import { explainSolTransaction, KeyPair } from '.'; import { InstructionBuilderTypes, UNAVAILABLE_TEXT, @@ -503,6 +503,18 @@ export class Transaction extends BaseTransaction { /** @inheritDoc */ explainTransaction(): TransactionExplanation { + // Testnet uses WASM-based parsing (no @solana/web3.js dependency). + // This validates the WASM path against production traffic before + // replacing the legacy implementation for all networks. + if (this._coinConfig.name === 'tsol') { + return explainSolTransaction({ + txBase64: this.toBroadcastFormat(), + feeInfo: this._lamportsPerSignature ? { fee: this._lamportsPerSignature.toString() } : undefined, + tokenAccountRentExemptAmount: this._tokenAccountRentExemptAmount, + coinName: this._coinConfig.name, + }); + } + if (validateRawMsgInstruction(this._solTransaction.instructions)) { return this.explainRawMsgAuthorizeTransaction(); } diff --git a/modules/sdk-coin-sol/test/unit/transaction.ts b/modules/sdk-coin-sol/test/unit/transaction.ts index effb9aa0fc..7573988425 100644 --- a/modules/sdk-coin-sol/test/unit/transaction.ts +++ b/modules/sdk-coin-sol/test/unit/transaction.ts @@ -267,6 +267,7 @@ describe('Sol Transaction', () => { blockhash: '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -305,7 +306,7 @@ describe('Sol Transaction', () => { }, ], fee: { - fee: 'UNAVAILABLE', + fee: '0', feeRate: undefined, }, memo: undefined, @@ -315,6 +316,7 @@ describe('Sol Transaction', () => { walletNonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh', }, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -364,7 +366,7 @@ describe('Sol Transaction', () => { }, ], fee: { - fee: 'UNAVAILABLE', + fee: '0', feeRate: undefined, }, memo: 'memo text', @@ -374,6 +376,7 @@ describe('Sol Transaction', () => { walletNonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh', }, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -421,6 +424,7 @@ describe('Sol Transaction', () => { blockhash: '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -468,6 +472,7 @@ describe('Sol Transaction', () => { blockhash: '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -508,7 +513,7 @@ describe('Sol Transaction', () => { }, ], fee: { - fee: 'UNAVAILABLE', + fee: '0', feeRate: undefined, }, memo: 'memo text', @@ -518,6 +523,7 @@ describe('Sol Transaction', () => { walletNonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh', }, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -566,6 +572,7 @@ describe('Sol Transaction', () => { blockhash: '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -688,6 +695,7 @@ describe('Sol Transaction', () => { blockhash: '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -731,6 +739,7 @@ describe('Sol Transaction', () => { blockhash: 'GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -779,6 +788,7 @@ describe('Sol Transaction', () => { blockhash: 'GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -831,6 +841,7 @@ describe('Sol Transaction', () => { authWalletAddress: sender, }, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -878,6 +889,7 @@ describe('Sol Transaction', () => { blockhash: '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -921,7 +933,9 @@ describe('Sol Transaction', () => { { address: 'DesU7XscZjng8yj5VX6AZsk3hWSW4sQ3rTG2LuyQ2P4H', amount: '10000', - tokenName: 'tsol:ams', + // WASM path resolves token name via @bitgo/statics; this mint is not registered, + // so the raw mint address is returned instead of the human-readable name. + tokenName: 'F4uLeXioFz3hw13MposuwaQbMcZbCjqvEGPPeRRB1Byf', }, ], fee: { @@ -932,6 +946,7 @@ describe('Sol Transaction', () => { blockhash: '5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen', durableNonce: undefined, tokenEnablements: [], + ataOwnerMap: {}, }); }); @@ -984,7 +999,7 @@ describe('Sol Transaction', () => { }, ], fee: { - fee: 'UNAVAILABLE', + fee: '0', feeRate: undefined, }, memo: 'memo text', @@ -994,6 +1009,7 @@ describe('Sol Transaction', () => { walletNonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh', }, tokenEnablements: [], + ataOwnerMap: {}, }); }); }); From a59880db5c63e396a366e0e445223aa6f020943d Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Sun, 15 Feb 2026 00:27:01 -0800 Subject: [PATCH 4/4] feat: add inputs and feePayer to WASM explain return Populate the inputs and feePayer fields in explainSolTransaction so consumers (wallet-platform) can call the SDK instead of wasm-solana directly. Update 26 test assertions to expect these new fields. BTC-3025 TICKET: BTC-0 --- .../src/lib/explainTransactionWasm.ts | 7 ++ modules/sdk-coin-sol/src/lib/iface.ts | 2 +- modules/sdk-coin-sol/test/unit/sol.ts | 69 +++++++++++ modules/sdk-coin-sol/test/unit/transaction.ts | 109 ++++++++++++++++++ 4 files changed, 186 insertions(+), 1 deletion(-) diff --git a/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts b/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts index e5a0be75b4..98d589a7c5 100644 --- a/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts +++ b/modules/sdk-coin-sol/src/lib/explainTransactionWasm.ts @@ -60,6 +60,11 @@ export function explainSolTransaction(params: ExplainTransactionWasmOptions): So ...(o.tokenName ? { tokenName: findTokenName(o.tokenName, undefined, true) } : {}), })); + const inputs = explained.inputs.map((i) => ({ + address: i.address, + value: String(i.value), + })); + // Build tokenEnablements with resolved token names const tokenEnablements: ITokenEnablement[] = explained.tokenEnablements.map((te) => ({ address: te.address, @@ -89,6 +94,8 @@ export function explainSolTransaction(params: ExplainTransactionWasmOptions): So changeAmount: '0', outputAmount: String(explained.outputAmount), outputs, + inputs, + feePayer: explained.feePayer, fee: { fee: params.feeInfo ? String(explained.fee) : '0', feeRate: params.feeInfo ? Number(params.feeInfo.fee) : undefined, diff --git a/modules/sdk-coin-sol/src/lib/iface.ts b/modules/sdk-coin-sol/src/lib/iface.ts index d69f5214ea..951b0e9da5 100644 --- a/modules/sdk-coin-sol/src/lib/iface.ts +++ b/modules/sdk-coin-sol/src/lib/iface.ts @@ -287,7 +287,7 @@ export interface TransactionExplanation extends BaseTransactionExplanation { memo?: string; stakingAuthorize?: StakingAuthorizeParams; stakingDelegate?: StakingDelegateParams; - inputs?: Array<{ address: string; value: string; coin?: string }>; + inputs?: { address: string; value: string; coin?: string }[]; feePayer?: string; ataOwnerMap?: Record; } diff --git a/modules/sdk-coin-sol/test/unit/sol.ts b/modules/sdk-coin-sol/test/unit/sol.ts index 6d198fa579..446f1ebc54 100644 --- a/modules/sdk-coin-sol/test/unit/sol.ts +++ b/modules/sdk-coin-sol/test/unit/sol.ts @@ -901,6 +901,13 @@ describe('SOL:', function () { amount: '300000', }, ], + inputs: [ + { + address: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', + value: '300000', + }, + ], + feePayer: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', fee: { fee: '5000', feeRate: 5000, @@ -948,6 +955,13 @@ describe('SOL:', function () { amount: '300000', }, ], + inputs: [ + { + address: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', + value: '300000', + }, + ], + feePayer: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', fee: { fee: '5000', feeRate: 5000, @@ -996,6 +1010,13 @@ describe('SOL:', function () { amount: '300000', }, ], + inputs: [ + { + address: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', + value: '300000', + }, + ], + feePayer: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', fee: { fee: '10000', feeRate: 5000, @@ -1041,6 +1062,13 @@ describe('SOL:', function () { amount: '300000', }, ], + inputs: [ + { + address: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', + value: '300000', + }, + ], + feePayer: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', fee: { fee: '10000', feeRate: 5000, @@ -1086,6 +1114,13 @@ describe('SOL:', function () { tokenName: 'tsol:usdc', }, ], + inputs: [ + { + address: '12f6D3WubGVeQoH2m8kTvvcrasWdXWwtVzUCyRNDZxA2', + value: '300000', + }, + ], + feePayer: '12f6D3WubGVeQoH2m8kTvvcrasWdXWwtVzUCyRNDZxA2', fee: { fee: '5000', feeRate: 5000, @@ -1134,6 +1169,13 @@ describe('SOL:', function () { tokenName: 'tsol:usdc', }, ], + inputs: [ + { + address: '12f6D3WubGVeQoH2m8kTvvcrasWdXWwtVzUCyRNDZxA2', + value: '300000', + }, + ], + feePayer: '12f6D3WubGVeQoH2m8kTvvcrasWdXWwtVzUCyRNDZxA2', fee: { fee: '5000', feeRate: 5000, @@ -1192,6 +1234,13 @@ describe('SOL:', function () { amount: '10000', }, ], + inputs: [ + { + address: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', + value: '10000', + }, + ], + feePayer: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', fee: { fee: '10000', feeRate: 5000, @@ -1240,6 +1289,8 @@ describe('SOL:', function () { changeAmount: '0', outputAmount: '0', outputs: [], + inputs: [], + feePayer: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', fee: { fee: '5000', feeRate: 5000, @@ -1294,6 +1345,13 @@ describe('SOL:', function () { amount: '10000', }, ], + inputs: [ + { + address: '7dRuGFbU2y2kijP6o1LYNzVyz4yf13MooqoionCzv5Za', + value: '10000', + }, + ], + feePayer: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', fee: { fee: '5000', feeRate: 5000, @@ -1346,6 +1404,8 @@ describe('SOL:', function () { changeAmount: '0', outputAmount: '0', outputs: [], + inputs: [], + feePayer: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', fee: { fee: '3005000', feeRate: 5000, @@ -1416,6 +1476,8 @@ describe('SOL:', function () { changeAmount: '0', outputAmount: '0', outputs: [], + inputs: [], + feePayer: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe', fee: { fee: '6005000', feeRate: 5000, @@ -1476,6 +1538,13 @@ describe('SOL:', function () { tokenName: 'tsol:usdc', }, ], + inputs: [ + { + address: '5U3bH5b6XtG99aVWLqwVzYPVpQiFHytBD68Rz2eFPZd7', + value: '10000', + }, + ], + feePayer: '5U3bH5b6XtG99aVWLqwVzYPVpQiFHytBD68Rz2eFPZd7', fee: { fee: '3005000', feeRate: 5000 }, memo: undefined, blockhash: '27E3MXFvXMUNYeMJeX1pAbERGsJfUbkaZTfgMgpmNN5g', diff --git a/modules/sdk-coin-sol/test/unit/transaction.ts b/modules/sdk-coin-sol/test/unit/transaction.ts index 7573988425..a5f72c69d0 100644 --- a/modules/sdk-coin-sol/test/unit/transaction.ts +++ b/modules/sdk-coin-sol/test/unit/transaction.ts @@ -259,6 +259,13 @@ describe('Sol Transaction', () => { amount: '10000', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '5000', feeRate: 5000, @@ -305,6 +312,13 @@ describe('Sol Transaction', () => { amount: '10000', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '0', feeRate: undefined, @@ -365,6 +379,21 @@ describe('Sol Transaction', () => { amount: '10000', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + { + address: sender, + value: '10000', + }, + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '0', feeRate: undefined, @@ -416,6 +445,13 @@ describe('Sol Transaction', () => { amount: '10000', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '5000', feeRate: 5000, @@ -464,6 +500,13 @@ describe('Sol Transaction', () => { amount: '10000', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '10000', feeRate: 5000, @@ -512,6 +555,13 @@ describe('Sol Transaction', () => { amount: '10000', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '0', feeRate: undefined, @@ -564,6 +614,13 @@ describe('Sol Transaction', () => { amount: '10000', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '10000', feeRate: 5000, @@ -687,6 +744,13 @@ describe('Sol Transaction', () => { amount: '10000', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '10000', feeRate: 5000, @@ -731,6 +795,8 @@ describe('Sol Transaction', () => { changeAmount: '0', outputAmount: '0', outputs: [], + inputs: [], + feePayer: sender, fee: { fee: '5000', feeRate: 5000, @@ -780,6 +846,13 @@ describe('Sol Transaction', () => { amount: '10000', }, ], + inputs: [ + { + address: stakeAccount.pub, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '5000', feeRate: 5000, @@ -830,6 +903,13 @@ describe('Sol Transaction', () => { amount: '10000', }, ], + inputs: [ + { + address: stakeAccount.pub, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '5000', feeRate: 5000, @@ -881,6 +961,13 @@ describe('Sol Transaction', () => { tokenName: 'tsol:usdc', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '5000', feeRate: 5000, @@ -938,6 +1025,13 @@ describe('Sol Transaction', () => { tokenName: 'F4uLeXioFz3hw13MposuwaQbMcZbCjqvEGPPeRRB1Byf', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '5000', feeRate: 5000, @@ -998,6 +1092,21 @@ describe('Sol Transaction', () => { tokenName: 'tsol:usdc', }, ], + inputs: [ + { + address: sender, + value: '10000', + }, + { + address: sender, + value: '10000', + }, + { + address: sender, + value: '10000', + }, + ], + feePayer: sender, fee: { fee: '0', feeRate: undefined,