From f4be3d8885a342543bfc682594993ae7beb2cddf Mon Sep 17 00:00:00 2001 From: Mullapudi Pruthvik Date: Tue, 15 Sep 2026 21:24:43 +0530 Subject: [PATCH] fix(sdk-coin-sol): allow WithdrawSol stake pool instruction JPool (JSOL) unstaking transactions sent via WalletConnect customTx intents failed with NotSupported because the SPL Stake Pool program's WithdrawSol instruction (discriminator 16) was missing from the SDK instruction allowlist. Add it to ValidInstructionTypesEnum and VALID_SYSTEM_INSTRUCTION_TYPES so these transactions validate and classify as CustomTx like other DeFi protocol instructions. Ticket: DEFI-887 --- modules/sdk-coin-sol/src/lib/constants.ts | 2 + modules/sdk-coin-sol/test/unit/utils.ts | 80 +++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/modules/sdk-coin-sol/src/lib/constants.ts b/modules/sdk-coin-sol/src/lib/constants.ts index aafdc961cb..6b68492879 100644 --- a/modules/sdk-coin-sol/src/lib/constants.ts +++ b/modules/sdk-coin-sol/src/lib/constants.ts @@ -115,6 +115,7 @@ export enum ValidInstructionTypesEnum { Burn = 'Burn', DepositSol = 'DepositSol', WithdrawStake = 'WithdrawStake', + WithdrawSol = 'WithdrawSol', Approve = 'Approve', CustomInstruction = 'CustomInstruction', PermissionlessThawIdempotent = 'PermissionlessThawIdempotent', @@ -191,6 +192,7 @@ export const VALID_SYSTEM_INSTRUCTION_TYPES: ValidInstructionTypes[] = [ ValidInstructionTypesEnum.Approve, ValidInstructionTypesEnum.DepositSol, ValidInstructionTypesEnum.WithdrawStake, + ValidInstructionTypesEnum.WithdrawSol, ValidInstructionTypesEnum.CustomInstruction, ValidInstructionTypesEnum.PermissionlessThawIdempotent, ]; diff --git a/modules/sdk-coin-sol/test/unit/utils.ts b/modules/sdk-coin-sol/test/unit/utils.ts index d496e6c7b2..ff693e894c 100644 --- a/modules/sdk-coin-sol/test/unit/utils.ts +++ b/modules/sdk-coin-sol/test/unit/utils.ts @@ -7,6 +7,7 @@ import { PublicKey, StakeProgram, SystemProgram, + Transaction, TransactionInstruction, } from '@solana/web3.js'; import { @@ -19,8 +20,11 @@ import BigNumber from 'bignumber.js'; import { TOKEN_2022_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, + createApproveInstruction, createTransferCheckedWithFeeInstruction, } from '@solana/spl-token'; +import { StakePoolInstruction } from '@solana/spl-stake-pool'; +import { TransactionType } from '@bitgo/sdk-core'; describe('SOL util library', function () { describe('isValidAddress', function () { @@ -286,6 +290,21 @@ describe('SOL util library', function () { }); Utils.getInstructionType(setComputeUnitPriceInstruction).should.equal('SetPriorityFee'); }); + it('should succeed for stake pool program WithdrawSol instruction', function () { + // JPool unstake (DEFI-887): SPL Stake Pool program, discriminator 16 = WithdrawSol + const withdrawSolInstruction = StakePoolInstruction.withdrawSol({ + stakePool: new PublicKey(testData.nonceAccount.pub), + sourcePoolAccount: new PublicKey(testData.nonceAccount.pub), + withdrawAuthority: new PublicKey(testData.nonceAccount.pub), + reserveStake: new PublicKey(testData.nonceAccount.pub), + destinationSystemAccount: new PublicKey(testData.authAccount.pub), + sourceTransferAuthority: new PublicKey(testData.authAccount.pub), + managerFeeAccount: new PublicKey(testData.nonceAccount.pub), + poolMint: new PublicKey(testData.nonceAccount.pub), + poolTokens: 1000000, + }); + Utils.getInstructionType(withdrawSolInstruction).should.equal('WithdrawSol'); + }); }); describe('validateIntructionTypes', function () { @@ -313,6 +332,67 @@ describe('SOL util library', function () { 'Invalid transaction, instruction type not supported: ' + Utils.getInstructionType(assignInstruction) ); }); + it('should succeed for JPool unstake instruction set including WithdrawSol (DEFI-887)', function () { + const ownerPubkey = new PublicKey(testData.authAccount.pub); + const poolTokenAccount = new PublicKey(testData.nonceAccount.pub); + const withdrawAuthority = new PublicKey(testData.nonceAccount.pub); + const withdrawSolInstruction = StakePoolInstruction.withdrawSol({ + stakePool: new PublicKey(testData.nonceAccount.pub), + sourcePoolAccount: poolTokenAccount, + withdrawAuthority, + reserveStake: new PublicKey(testData.nonceAccount.pub), + destinationSystemAccount: ownerPubkey, + sourceTransferAuthority: ownerPubkey, + managerFeeAccount: new PublicKey(testData.nonceAccount.pub), + poolMint: new PublicKey(testData.nonceAccount.pub), + poolTokens: 1000000, + }); + const jPoolUnstakeInstructions = [ + ComputeBudgetProgram.setComputeUnitLimit({ units: 300000 }), + ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100000 }), + createApproveInstruction(poolTokenAccount, withdrawAuthority, ownerPubkey, 1000000), + withdrawSolInstruction, + new TransactionInstruction({ + keys: [], + programId: new PublicKey(MEMO_PROGRAM_PK), + data: Buffer.from('WalletConnectDefiCustomTx'), + }), + ]; + // Previously threw: NotSupported: Invalid transaction, instruction type not supported: WithdrawSol + should.doesNotThrow(() => Utils.validateIntructionTypes(jPoolUnstakeInstructions)); + }); + }); + + describe('getTransactionType', function () { + it('should classify JPool unstake (WithdrawSol + WalletConnectDefiCustomTx memo) as CustomTx', function () { + const ownerPubkey = new PublicKey(testData.authAccount.pub); + const poolTokenAccount = new PublicKey(testData.nonceAccount.pub); + const withdrawAuthority = new PublicKey(testData.nonceAccount.pub); + const withdrawSolInstruction = StakePoolInstruction.withdrawSol({ + stakePool: new PublicKey(testData.nonceAccount.pub), + sourcePoolAccount: poolTokenAccount, + withdrawAuthority, + reserveStake: new PublicKey(testData.nonceAccount.pub), + destinationSystemAccount: ownerPubkey, + sourceTransferAuthority: ownerPubkey, + managerFeeAccount: new PublicKey(testData.nonceAccount.pub), + poolMint: new PublicKey(testData.nonceAccount.pub), + poolTokens: 1000000, + }); + const transaction = new Transaction(); + transaction.add( + ComputeBudgetProgram.setComputeUnitLimit({ units: 300000 }), + ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100000 }), + createApproveInstruction(poolTokenAccount, withdrawAuthority, ownerPubkey, 1000000), + withdrawSolInstruction, + new TransactionInstruction({ + keys: [], + programId: new PublicKey(MEMO_PROGRAM_PK), + data: Buffer.from('WalletConnectDefiCustomTx'), + }) + ); + Utils.getTransactionType(transaction).should.equal(TransactionType.CustomTx); + }); }); describe('validateRawTransaction', function () {