diff --git a/bitgo-wasm-solana-0.0.1.tgz b/bitgo-wasm-solana-0.0.1.tgz new file mode 100644 index 0000000000..63e15cbeb8 Binary files /dev/null and b/bitgo-wasm-solana-0.0.1.tgz differ diff --git a/modules/sdk-coin-sol/package.json b/modules/sdk-coin-sol/package.json index 0aac32cdf0..0271715fa5 100644 --- a/modules/sdk-coin-sol/package.json +++ b/modules/sdk-coin-sol/package.json @@ -42,6 +42,7 @@ "dependencies": { "@bitgo/public-types": "5.63.0", "@bitgo/sdk-core": "^36.28.0", + "@bitgo/wasm-solana": "file:../../bitgo-wasm-solana-0.0.1.tgz", "@bitgo/sdk-lib-mpc": "^10.8.1", "@bitgo/statics": "^58.22.0", "@solana/spl-stake-pool": "1.1.8", 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/lib/transaction.ts b/modules/sdk-coin-sol/src/lib/transaction.ts index 1ddbc53c79..68b60a4b94 100644 --- a/modules/sdk-coin-sol/src/lib/transaction.ts +++ b/modules/sdk-coin-sol/src/lib/transaction.ts @@ -51,6 +51,11 @@ import { validateRawMsgInstruction, } from './utils'; import { SolStakingTypeEnum } from '@bitgo/public-types'; +import { + parseTransaction as wasmParseTransaction, + ParsedTransaction as WasmParsedTransaction, + InstructionParams as WasmInstructionParams, +} from '@bitgo/wasm-solana'; export class Transaction extends BaseTransaction { protected _solTransaction: SolTransaction; @@ -287,50 +292,309 @@ export class Transaction extends BaseTransaction { if (this._solTransaction.signature && this._solTransaction.signature !== null) { this._id = base58.encode(this._solTransaction.signature); } + + // Use existing getTransactionType for now - it handles all edge cases + // TODO: Replace with WASM-based type detection once all instruction types are supported const transactionType = getTransactionType(this._solTransaction); - switch (transactionType) { - case TransactionType.WalletInitialization: - this.setTransactionType(TransactionType.WalletInitialization); - break; - case TransactionType.Send: - this.setTransactionType(TransactionType.Send); - break; - case TransactionType.StakingActivate: - this.setTransactionType(TransactionType.StakingActivate); - break; - case TransactionType.StakingDeactivate: - this.setTransactionType(TransactionType.StakingDeactivate); - break; - case TransactionType.StakingWithdraw: - this.setTransactionType(TransactionType.StakingWithdraw); - break; - case TransactionType.AssociatedTokenAccountInitialization: - this.setTransactionType(TransactionType.AssociatedTokenAccountInitialization); - break; - case TransactionType.CloseAssociatedTokenAccount: - this.setTransactionType(TransactionType.CloseAssociatedTokenAccount); - break; - case TransactionType.StakingAuthorize: - this.setTransactionType(TransactionType.StakingAuthorize); - break; - case TransactionType.StakingAuthorizeRaw: - this.setTransactionType(TransactionType.StakingAuthorizeRaw); - break; - case TransactionType.StakingDelegate: - this.setTransactionType(TransactionType.StakingDelegate); - break; - case TransactionType.CustomTx: - this.setTransactionType(TransactionType.CustomTx); - break; - } + this.setTransactionType(transactionType); + if (transactionType !== TransactionType.StakingAuthorizeRaw) { - this.loadInputsAndOutputs(); + this.loadInputsAndOutputs(rawTransaction); } } catch (e) { throw e; } } + /** + * Parse transaction using WASM and return the parsed result. + * This can be used for transaction explanation without going through instructionParamsFactory. + */ + parseWithWasm(rawTransaction: string): WasmParsedTransaction { + const txBytes = Buffer.from(rawTransaction, 'base64'); + return wasmParseTransaction(txBytes); + } + + /** + * Determine transaction type from WASM-parsed instructions. + * This replaces the old getTransactionType() which used @solana/web3.js decoders. + */ + private getTransactionTypeFromWasm(wasmParsed: WasmParsedTransaction): TransactionType { + const instructions = wasmParsed.instructionsData; + // Filter out NonceAdvance and SetPriorityFee/SetComputeUnitLimit as they're just metadata + const nonNonceInstructions = instructions.filter( + (i) => i.type !== 'NonceAdvance' && i.type !== 'SetPriorityFee' && i.type !== 'SetComputeUnitLimit' + ); + + if (nonNonceInstructions.length === 0) { + return TransactionType.CustomTx; + } + + // Check for memo with WalletConnectDefiCustomTx + const memoInstr = nonNonceInstructions.find((i) => i.type === 'Memo'); + if (memoInstr && memoInstr.type === 'Memo' && memoInstr.memo.includes('WalletConnectDefiCustomTx')) { + return TransactionType.CustomTx; + } + + // Get instruction types for pattern matching + const types = nonNonceInstructions.map((i) => i.type); + + // Check for Marinade deactivate (Transfer + PrepareForRevoke memo) + // Must be checked before general Transfer check + if (memoInstr && memoInstr.type === 'Memo' && memoInstr.memo.includes('PrepareForRevoke')) { + return TransactionType.StakingDeactivate; + } + + // Transfer or Token Transfer = Send (check early, before ATA init) + // A tx with CreateATA + TokenTransfer is still a Send transaction + if (types.includes('Transfer') || types.includes('TokenTransfer')) { + return TransactionType.Send; + } + + // Wallet initialization: CreateNonceAccount (which is CreateAccount + InitializeNonceAccount) + if (types.includes('CreateNonceAccount')) { + return TransactionType.WalletInitialization; + } + + // Staking activate patterns + if (types.includes('StakingActivate') || types.includes('StakePoolDepositSol')) { + return TransactionType.StakingActivate; + } + + // Staking deactivate patterns + if (types.includes('StakingDeactivate') || types.includes('StakePoolWithdrawStake')) { + return TransactionType.StakingDeactivate; + } + + // Staking withdraw + if (types.includes('StakingWithdraw')) { + return TransactionType.StakingWithdraw; + } + + // Staking authorize + if (types.includes('StakingAuthorize')) { + return TransactionType.StakingAuthorize; + } + + // Staking delegate + if (types.includes('StakingDelegate')) { + return TransactionType.StakingDelegate; + } + + // ATA initialization (only if no Transfer/TokenTransfer - those are Send) + if (types.includes('CreateAssociatedTokenAccount')) { + return TransactionType.AssociatedTokenAccountInitialization; + } + + // ATA close + if (types.includes('CloseAssociatedTokenAccount')) { + return TransactionType.CloseAssociatedTokenAccount; + } + + // Default to CustomTx + return TransactionType.CustomTx; + } + + /** + * Convert a WASM instruction to BitGoJS InstructionParams format. + * This is the "grug" approach - simple mapping from wasm output to expected format. + */ + private mapWasmInstructionToBitGoJS(wasmInstr: WasmInstructionParams, coinName: string): InstructionParams | null { + switch (wasmInstr.type) { + case 'Transfer': + return { + type: InstructionBuilderTypes.Transfer, + params: { + fromAddress: wasmInstr.fromAddress, + toAddress: wasmInstr.toAddress, + amount: wasmInstr.amount, + }, + }; + + case 'NonceAdvance': + return { + type: InstructionBuilderTypes.NonceAdvance, + params: { + walletNonceAddress: wasmInstr.walletNonceAddress, + authWalletAddress: wasmInstr.authWalletAddress, + }, + }; + + case 'Memo': + return { + type: InstructionBuilderTypes.Memo, + params: { memo: wasmInstr.memo }, + }; + + case 'CreateNonceAccount': + return { + type: InstructionBuilderTypes.CreateNonceAccount, + params: { + fromAddress: wasmInstr.fromAddress, + nonceAddress: wasmInstr.nonceAddress, + authAddress: wasmInstr.authAddress, + amount: wasmInstr.amount, + }, + }; + + case 'TokenTransfer': + return { + type: InstructionBuilderTypes.TokenTransfer, + params: { + fromAddress: wasmInstr.fromAddress, + toAddress: wasmInstr.toAddress, + amount: wasmInstr.amount, + tokenName: coinName, + sourceAddress: wasmInstr.sourceAddress, + tokenAddress: wasmInstr.tokenAddress, + }, + }; + + case 'CreateAssociatedTokenAccount': + return { + type: InstructionBuilderTypes.CreateAssociatedTokenAccount, + params: { + mintAddress: wasmInstr.mintAddress, + ataAddress: wasmInstr.ataAddress, + ownerAddress: wasmInstr.ownerAddress, + payerAddress: wasmInstr.payerAddress, + tokenName: coinName, + }, + }; + + case 'CloseAssociatedTokenAccount': + return { + type: InstructionBuilderTypes.CloseAssociatedTokenAccount, + params: { + accountAddress: wasmInstr.accountAddress, + destinationAddress: wasmInstr.destinationAddress, + authorityAddress: wasmInstr.authorityAddress, + }, + }; + + case 'StakingActivate': + return { + type: InstructionBuilderTypes.StakingActivate, + params: { + fromAddress: wasmInstr.fromAddress, + stakingAddress: wasmInstr.stakingAddress, + amount: wasmInstr.amount, + validator: wasmInstr.validator, + stakingType: wasmInstr.stakingType === 'JITO' ? SolStakingTypeEnum.JITO : SolStakingTypeEnum.NATIVE, + }, + }; + + case 'StakingDeactivate': + return { + type: InstructionBuilderTypes.StakingDeactivate, + params: { + fromAddress: wasmInstr.fromAddress, + stakingAddress: wasmInstr.stakingAddress, + stakingType: SolStakingTypeEnum.NATIVE, + }, + }; + + case 'StakingWithdraw': + return { + type: InstructionBuilderTypes.StakingWithdraw, + params: { + fromAddress: wasmInstr.fromAddress, + stakingAddress: wasmInstr.stakingAddress, + amount: wasmInstr.amount, + }, + }; + + case 'StakingDelegate': + return { + type: InstructionBuilderTypes.StakingDelegate, + params: { + stakingAddress: wasmInstr.stakingAddress, + fromAddress: wasmInstr.fromAddress, + validator: wasmInstr.validator, + }, + }; + + case 'StakingAuthorize': + return { + type: InstructionBuilderTypes.StakingAuthorize, + params: { + stakingAddress: wasmInstr.stakingAddress, + oldAuthorizeAddress: wasmInstr.oldAuthorizeAddress, + newAuthorizeAddress: wasmInstr.newAuthorizeAddress, + }, + }; + + case 'SetComputeUnitLimit': + return { + type: InstructionBuilderTypes.SetComputeUnitLimit, + params: { units: wasmInstr.units }, + }; + + case 'SetPriorityFee': + return { + type: InstructionBuilderTypes.SetPriorityFee, + params: { fee: wasmInstr.fee }, + }; + + case 'StakePoolDepositSol': + // Jito liquid staking deposit - maps to StakingActivate with JITO type + return { + type: InstructionBuilderTypes.StakingActivate, + params: { + fromAddress: wasmInstr.fundingAccount, + stakingAddress: wasmInstr.destinationPoolAccount, + amount: wasmInstr.lamports, + validator: wasmInstr.stakePool, // Use stake pool as "validator" for Jito + stakingType: SolStakingTypeEnum.JITO, + }, + }; + + case 'StakePoolWithdrawStake': + // Jito liquid staking withdrawal - maps to StakingDeactivate with JITO type + return { + type: InstructionBuilderTypes.StakingDeactivate, + params: { + fromAddress: wasmInstr.sourceTransferAuthority, + stakingAddress: wasmInstr.sourcePoolAccount, + amount: wasmInstr.poolTokens, + stakingType: SolStakingTypeEnum.JITO, + }, + }; + + case 'Unknown': + // Unknown instructions become CustomInstruction + return { + type: InstructionBuilderTypes.CustomInstruction, + params: { + programId: wasmInstr.programId, + data: wasmInstr.data, + keys: wasmInstr.accounts.map((acc) => ({ + pubkey: acc.pubkey, + isSigner: acc.isSigner, + isWritable: acc.isWritable, + })), + }, + }; + + default: + return null; + } + } + + /** + * Convert all WASM instructions to BitGoJS InstructionParams format. + */ + mapWasmInstructionsToBitGoJS(wasmParsed: WasmParsedTransaction, coinName: string): InstructionParams[] { + const result: InstructionParams[] = []; + for (const instr of wasmParsed.instructionsData) { + const mapped = this.mapWasmInstructionToBitGoJS(instr, coinName); + if (mapped) { + result.push(mapped); + } + } + return result; + } + /** @inheritdoc */ toJson(): TxData { if (!this._solTransaction) { @@ -389,20 +653,26 @@ export class Transaction extends BaseTransaction { /** * Load the input and output data on this transaction. + * @param rawTransaction - Optional raw transaction for WASM parsing (testnet only) */ - loadInputsAndOutputs(): void { + loadInputsAndOutputs(rawTransaction?: string): void { if (!this._solTransaction || this._solTransaction.instructions?.length === 0) { return; } const outputs: Entry[] = []; const inputs: Entry[] = []; - const instructionParams = instructionParamsFactory( - this.type, - this._solTransaction.instructions, - this._coinConfig.name, - this._instructionsData, - this._useTokenAddressTokenName - ); + + // Use WASM parsing for testnet when raw transaction is available + const useWasm = rawTransaction && this._coinConfig.name === 'tsol'; + const instructionParams = useWasm + ? this.mapWasmInstructionsToBitGoJS(this.parseWithWasm(rawTransaction), this._coinConfig.name) + : instructionParamsFactory( + this.type, + this._solTransaction.instructions, + this._coinConfig.name, + this._instructionsData, + this._useTokenAddressTokenName + ); for (const instruction of instructionParams) { switch (instruction.type) { diff --git a/modules/sdk-coin-sol/src/sol.ts b/modules/sdk-coin-sol/src/sol.ts index 2cd8d4379e..d5a33ec69f 100644 --- a/modules/sdk-coin-sol/src/sol.ts +++ b/modules/sdk-coin-sol/src/sol.ts @@ -56,7 +56,9 @@ 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 { parseTransaction as wasmParseTransaction } from '@bitgo/wasm-solana'; import { KeyPair as SolKeyPair, Transaction, TransactionBuilder, TransactionBuilderFactory } from './lib'; +import { TransactionExplanation as SolLibTransactionExplanation } from './lib/iface'; import { getAssociatedTokenAccountAddress, getSolTokenFromAddress, @@ -66,6 +68,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 @@ -695,6 +698,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, @@ -740,9 +744,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; @@ -766,6 +777,181 @@ export class Sol extends BaseCoin { return explainedTransaction as SolTransactionExplanation; } + /** + * Explain a Solana transaction using WASM parsing (bypasses @solana/web3.js rebuild). + * This provides a simpler, more direct parsing path. + * @param params + */ + explainTransactionWithWasm(params: ExplainTransactionOptions): SolLibTransactionExplanation { + const txBytes = Buffer.from(params.txBase64, 'base64'); + const parsed = wasmParseTransaction(txBytes); + + const outputs: TransactionRecipient[] = []; + const tokenEnablements: ITokenEnablement[] = []; + let outputAmount = new BigNumber(0); + let memo: string | undefined; + let transactionType = 'Send'; // Default type + + // Process instructions to derive type, outputs, memo, and tokenEnablements + for (const instr of parsed.instructionsData) { + switch (instr.type) { + case 'Transfer': + outputs.push({ + address: instr.toAddress, + amount: instr.amount, + }); + outputAmount = outputAmount.plus(instr.amount); + transactionType = 'Send'; + break; + + case 'TokenTransfer': + outputs.push({ + address: instr.toAddress, + amount: instr.amount, + tokenName: findTokenName(instr.tokenAddress ?? '', undefined, true), + }); + // Token transfers don't add to outputAmount (matches original behavior) + transactionType = 'Send'; + break; + + case 'CreateNonceAccount': + outputs.push({ + address: instr.nonceAddress, + amount: instr.amount, + }); + outputAmount = outputAmount.plus(instr.amount); + transactionType = 'WalletInitialization'; + break; + + case 'StakingActivate': + outputs.push({ + address: instr.stakingAddress, + amount: instr.amount, + }); + outputAmount = outputAmount.plus(instr.amount); + transactionType = 'StakingActivate'; + break; + + case 'StakingDeactivate': + transactionType = 'StakingDeactivate'; + break; + + case 'StakingWithdraw': + outputs.push({ + address: instr.fromAddress, + amount: instr.amount, + }); + outputAmount = outputAmount.plus(instr.amount); + transactionType = 'StakingWithdraw'; + break; + + case 'StakingDelegate': + transactionType = 'StakingDelegate'; + break; + + case 'StakingAuthorize': + transactionType = 'StakingAuthorize'; + break; + + case 'CreateAssociatedTokenAccount': + tokenEnablements.push({ + address: instr.ataAddress, + tokenName: findTokenName(instr.mintAddress, undefined, true), + tokenAddress: instr.mintAddress, + }); + // If no other type-determining instruction, this is ATA init + if (outputs.length === 0) { + transactionType = 'AssociatedTokenAccountInitialization'; + } + break; + + case 'CloseAssociatedTokenAccount': + transactionType = 'CloseAssociatedTokenAccount'; + break; + + case 'StakePoolDepositSol': + // Jito liquid staking deposit - maps to StakingActivate with JITO type + outputs.push({ + address: instr.stakePool, + amount: instr.lamports, + }); + outputAmount = outputAmount.plus(instr.lamports); + transactionType = 'StakingActivate'; + break; + + case 'StakePoolWithdrawStake': + // Jito liquid staking withdraw - maps to StakingDeactivate + transactionType = 'StakingDeactivate'; + break; + + case 'Memo': + memo = instr.memo; + break; + + case 'NonceAdvance': + // NonceAdvance is handled via durableNonce field, not instructionsData (BitGoJS convention) + break; + } + } + + // Calculate fee: lamportsPerSignature * numSignatures + (rent * numATAs) + const lamportsPerSignature = parseInt(params.feeInfo?.fee || '0', 10); + const rentPerAta = parseInt(params.tokenAccountRentExemptAmount || '0', 10); + const signatureFee = lamportsPerSignature * parsed.numSignatures; + const rentFee = rentPerAta * tokenEnablements.length; + const totalFee = (signatureFee + rentFee).toString(); + + // Get transaction id from first signature (base58 encoded) or UNAVAILABLE + let txId = 'UNAVAILABLE'; + if (parsed.signatures.length > 0) { + const firstSig = parsed.signatures[0]; + // Signatures from WASM are base64 encoded, check if it's not all zeros + const sigBytes = Buffer.from(firstSig, 'base64'); + const isEmptySignature = sigBytes.every((b) => b === 0); + if (!isEmptySignature) { + txId = base58.encode(sigBytes); + } + } + + // Build durableNonce from WASM parsed data + const durableNonce = parsed.durableNonce + ? { + walletNonceAddress: parsed.durableNonce.walletNonceAddress, + authWalletAddress: parsed.durableNonce.authWalletAddress, + } + : undefined; + + return { + displayOrder: [ + 'id', + 'type', + 'blockhash', + 'durableNonce', + 'outputAmount', + 'changeAmount', + 'outputs', + 'changeOutputs', + 'tokenEnablements', + 'fee', + 'memo', + ], + id: txId, + type: transactionType, + changeOutputs: [], + changeAmount: '0', + outputAmount: outputAmount.toFixed(0), + outputs, + fee: { + fee: totalFee, + feeRate: lamportsPerSignature, + }, + memo, + blockhash: parsed.nonce, + durableNonce, + tokenEnablements, + }; + } + /** @inheritDoc */ async getSignablePayload(serializedTx: string): Promise { const factory = this.getBuilder(); 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..69e57ad315 --- /dev/null +++ b/modules/sdk-coin-sol/test/unit/jitoWasmVerification.ts @@ -0,0 +1,49 @@ +/** + * 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 () { + // First, verify the raw WASM parsing returns StakePoolDepositSol + const { parseTransaction } = require('@bitgo/wasm-solana'); + const txBytes = Buffer.from(JITO_TX_BASE64, 'base64'); + const wasmParsed = parseTransaction(txBytes); + + // 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('300000'); + + // 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/yarn.lock b/yarn.lock index 714e987b19..c94458b91b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -996,6 +996,10 @@ monocle-ts "^2.3.13" newtype-ts "^0.3.5" +"@bitgo/wasm-solana@file:bitgo-wasm-solana-0.0.1.tgz": + version "0.0.1" + resolved "file:bitgo-wasm-solana-0.0.1.tgz#d0ed28c9f8d1006848a4d3ddd6226ec84941b70c" + "@bitgo/wasm-utxo@^1.27.0": version "1.27.0" resolved "https://registry.npmjs.org/@bitgo/wasm-utxo/-/wasm-utxo-1.27.0.tgz#c8ebe108ce8b55d3df70cd3968211a6ef3001bef"