From 634a4537676205fd0e97a375d364113316324f57 Mon Sep 17 00:00:00 2001 From: Harit Kapadia Date: Mon, 21 Jul 2025 17:27:50 -0400 Subject: [PATCH] feat(sdk-coin-sol): implement staking activate for jito TICKET: SC-2314 --- examples/ts/sol/stake-jito.ts | 102 +++++++++++++++ modules/sdk-coin-sol/src/lib/constants.ts | 15 +++ .../sdk-coin-sol/src/lib/future/depositSol.ts | 119 ++++++++++++++++++ modules/sdk-coin-sol/src/lib/iface.ts | 4 + .../src/lib/instructionParamsFactory.ts | 91 +++++++++++--- .../src/lib/solInstructionFactory.ts | 62 +++++++-- .../src/lib/stakingActivateBuilder.ts | 30 ++++- modules/sdk-coin-sol/src/lib/utils.ts | 16 ++- modules/sdk-coin-sol/test/resources/sol.ts | 3 + .../unit/instructionParamsFactory.staking.ts | 4 + .../stakingActivateBuilder.ts | 57 +++++++++ .../transactionBuilder/transactionBuilder.ts | 1 + package.json | 1 + yarn.lock | 108 +++++++++++++++- 14 files changed, 577 insertions(+), 36 deletions(-) create mode 100644 examples/ts/sol/stake-jito.ts create mode 100644 modules/sdk-coin-sol/src/lib/future/depositSol.ts diff --git a/examples/ts/sol/stake-jito.ts b/examples/ts/sol/stake-jito.ts new file mode 100644 index 0000000000..a38fbcf794 --- /dev/null +++ b/examples/ts/sol/stake-jito.ts @@ -0,0 +1,102 @@ +/** + * Stakes JitoSOL tokens on Solana devnet. + * + * Copyright 2025, BitGo, Inc. All Rights Reserved. + */ +import { BitGoAPI } from '@bitgo/sdk-api' +import { TransactionBuilderFactory, Tsol } from '@bitgo/sdk-coin-sol' +import { coins } from '@bitgo/statics' +import { Connection, PublicKey, clusterApiUrl, Transaction, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js" +import { getStakePoolAccount, updateStakePool } from '@solana/spl-stake-pool' +import { BinTools } from 'avalanche'; + +require('dotenv').config({ path: '../../.env' }) + +const AMOUNT_LAMPORTS = 1000 +const JITO_STAKE_POOL_ADDRESS = 'Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb' +const NETWORK = 'devnet' + +const bitgo = new BitGoAPI({ + accessToken: process.env.TESTNET_ACCESS_TOKEN, + env: 'test', +}) +const coin = coins.get("tsol") +bitgo.register(coin.name, Tsol.createInstance) + +async function main() { + const account = getAccount() + const connection = new Connection(clusterApiUrl(NETWORK), 'confirmed') + const recentBlockhash = await connection.getLatestBlockhash() + const stakePoolAccount = await getStakePoolAccount(connection, new PublicKey(JITO_STAKE_POOL_ADDRESS)) + + // Account should have sufficient balance + const accountBalance = await connection.getBalance(account.publicKey) + if (accountBalance < 0.1 * LAMPORTS_PER_SOL) { + console.info(`Your account balance is ${accountBalance / LAMPORTS_PER_SOL} SOL, requesting airdrop`) + const sig = await connection.requestAirdrop(account.publicKey, 2 * LAMPORTS_PER_SOL) + await connection.confirmTransaction(sig) + console.info(`Airdrop successful: ${sig}`) + } + + // Stake pool should be up to date + const epochInfo = await connection.getEpochInfo() + if (stakePoolAccount.account.data.lastUpdateEpoch.ltn(epochInfo.epoch)) { + console.info('Stake pool is out of date.') + const usp = await updateStakePool(connection, stakePoolAccount) + const tx = new Transaction() + tx.add(...usp.updateListInstructions, ...usp.finalInstructions) + const signer = Keypair.fromSecretKey(account.secretKeyArray) + const sig = await connection.sendTransaction(tx, [signer]) + await connection.confirmTransaction(sig) + console.info(`Stake pool updated: ${sig}`) + } + + // Use BitGoAPI to build depositSol instruction + const txBuilder = new TransactionBuilderFactory(coin).getStakingActivateBuilder() + txBuilder + .amount(`${AMOUNT_LAMPORTS}`) + .sender(account.publicKey.toBase58()) + .stakingAddress(JITO_STAKE_POOL_ADDRESS) + .validator(JITO_STAKE_POOL_ADDRESS) + .isJito(true) + .nonce(recentBlockhash.blockhash) + txBuilder.sign({ key: account.secretKey }) + const tx = await txBuilder.build() + const serializedTx = tx.toBroadcastFormat() + console.info(`Transaction JSON:\n${JSON.stringify(tx.toJson(), undefined, 2)}`) + + // Send transaction + try { + const sig = await connection.sendRawTransaction(Buffer.from(serializedTx, 'base64')) + await connection.confirmTransaction(sig) + console.log(`${AMOUNT_LAMPORTS / LAMPORTS_PER_SOL} SOL deposited`, sig) + } catch (e) { + console.log('Error sending transaction') + console.error(e) + if (e.transactionMessage === 'Transaction simulation failed: Error processing Instruction 0: Provided owner is not allowed') { + console.error('If you successfully staked JitoSOL once, you cannot stake again.') + } + } +} + +const getAccount = () => { + const publicKey = process.env.ACCOUNT_PUBLIC_KEY + const secretKey = process.env.ACCOUNT_SECRET_KEY + const secretKeyArray = process.env.ACCOUNT_SECRET_KEY_ARRAY && JSON.parse(process.env.ACCOUNT_SECRET_KEY_ARRAY) + if (publicKey === undefined || secretKey === undefined || secretKeyArray === undefined) { + const { publicKey, secretKey } = Keypair.generate() + console.log('Here is a new account to save into your .env file.') + console.log(`ACCOUNT_PUBLIC_KEY=${publicKey.toBase58()}`) + console.log(`ACCOUNT_SECRET_KEY_ARRAY=${JSON.stringify(Array.from(secretKey))}`) + console.log(`ACCOUNT_SECRET_KEY=${BinTools.getInstance().bufferToB58(BinTools.getInstance().fromArrayBufferToBuffer(secretKey))}`) + throw new Error("Missing account information") + } + + return { + publicKey: new PublicKey(publicKey), + secretKey, + secretKeyArray: new Uint8Array(secretKeyArray), + } +} + +main().catch((e) => console.error(e)) diff --git a/modules/sdk-coin-sol/src/lib/constants.ts b/modules/sdk-coin-sol/src/lib/constants.ts index 65718fa580..7edd84615b 100644 --- a/modules/sdk-coin-sol/src/lib/constants.ts +++ b/modules/sdk-coin-sol/src/lib/constants.ts @@ -9,6 +9,13 @@ export const STAKE_ACCOUNT_RENT_EXEMPT_AMOUNT = 2282880; export const UNAVAILABLE_TEXT = 'UNAVAILABLE'; +export const JITO_STAKE_POOL_ADDRESS = 'Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb'; +export const JITOSOL_MINT_ADDRESS = 'J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn'; +export const JITO_STAKE_POOL_RESERVE_ACCOUNT = 'BgKUXdS29YcHCFrPm5M8oLHiTzZaMDjsebggjoaQ6KFL'; +export const JITO_STAKE_POOL_RESERVE_ACCOUNT_TESTNET = 'rrWBQqRqBXYZw3CmPCCcjFxQ2Ds4JFJd7oRQJ997dhz'; +export const JITO_MANAGER_FEE_ACCOUNT = 'feeeFLLsam6xZJFc6UQFrHqkvVt4jfmVvi2BRLkUZ4i'; +export const JITO_MANAGER_FEE_ACCOUNT_TESTNET = 'DH7tmjoQ5zjqcgfYJU22JqmXhP5EY1tkbYpgVWUS2oNo'; + // Sdk instructions, mainly to check decoded types. export enum ValidInstructionTypesEnum { AdvanceNonceAccount = 'AdvanceNonceAccount', @@ -28,6 +35,7 @@ export enum ValidInstructionTypesEnum { Split = 'Split', Authorize = 'Authorize', SetPriorityFee = 'SetPriorityFee', + DepositSol = 'DepositSol', } // Internal instructions types @@ -65,6 +73,7 @@ export const VALID_SYSTEM_INSTRUCTION_TYPES: ValidInstructionTypes[] = [ ValidInstructionTypesEnum.Split, ValidInstructionTypesEnum.Authorize, ValidInstructionTypesEnum.SetPriorityFee, + ValidInstructionTypesEnum.DepositSol, ]; /** Const to check the order of the Wallet Init instructions when decode */ @@ -89,6 +98,12 @@ export const marinadeStakingActivateInstructionsIndexes = { Memo: 2, } as const; +/** Const to check the order of the Jito Staking Activate instructions when decode */ +export const jitoStakingActivateInstructionsIndexes = { + AtaInit: 0, + DepositSol: 1, +} as const; + /** Const to check the order of the Staking Authorize instructions when decode */ export const stakingAuthorizeInstructionsIndexes = { Authorize: 0, diff --git a/modules/sdk-coin-sol/src/lib/future/depositSol.ts b/modules/sdk-coin-sol/src/lib/future/depositSol.ts new file mode 100644 index 0000000000..e053e224a6 --- /dev/null +++ b/modules/sdk-coin-sol/src/lib/future/depositSol.ts @@ -0,0 +1,119 @@ +/** + * @file Implementation of the depositSol instruction. On upgrade of + * '@solana/spl-token', this module may no longer be necessary. + */ + +import { StakePoolInstruction, STAKE_POOL_PROGRAM_ID, DepositSolParams } from '@solana/spl-stake-pool'; +import { + createAssociatedTokenAccountInstruction, + getAssociatedTokenAddressSync, + TOKEN_PROGRAM_ID, +} from '@solana/spl-token'; +import { AccountMeta, PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js'; +import assert from 'assert'; + +export const DEPOSIT_SOL_LAYOUT_CODE = 14; + +export interface DepositSolInstructionsParams { + stakePoolAddress: PublicKey; + from: PublicKey; + lamports: bigint; +} + +/** + * Construct Solana depositSol stake pool instruction from parameters. + * + * @param {DepositSolInstructionsParams} params - parameters for staking to stake pool + * @param poolMint - pool mint derived from getStakePoolAccount + * @param reserveStake - reserve account derived from getStakePoolAccount + * @param managerFeeAccount - manager fee account derived from getStakePoolAccount + * @returns {TransactionInstruction} + */ +export function depositSolInstructions( + params: DepositSolInstructionsParams, + poolMint: PublicKey, + reserveStake: PublicKey, + managerFeeAccount: PublicKey +): TransactionInstruction[] { + const { stakePoolAddress, from, lamports } = params; + + // findWithdrawAuthorityProgramAddress + const [withdrawAuthority] = PublicKey.findProgramAddressSync( + [stakePoolAddress.toBuffer(), Buffer.from('withdraw')], + STAKE_POOL_PROGRAM_ID + ); + + const associatedAddress = getAssociatedTokenAddressSync(poolMint, from); + + return [ + createAssociatedTokenAccountInstruction(from, associatedAddress, from, poolMint), + StakePoolInstruction.depositSol({ + stakePool: stakePoolAddress, + reserveStake, + fundingAccount: from, + destinationPoolAccount: associatedAddress, + managerFeeAccount: managerFeeAccount, + referralPoolAccount: associatedAddress, + poolMint: poolMint, + lamports: Number(lamports), + withdrawAuthority, + }), + ]; +} + +function parseKey(key: AccountMeta, template: { isSigner: boolean; isWritable: boolean }): PublicKey { + assert( + key.isSigner === template.isSigner && key.isWritable === template.isWritable, + 'Unexpected key metadata in DepositSol instruction' + ); + return key.pubkey; +} + +/** + * Construct Solana depositSol stake pool parameters from instruction. + * + * @param {TransactionInstruction} instruction + * @returns {DepositSolParams} + */ +export function decodeDepositSol(instruction: TransactionInstruction): DepositSolParams { + const { programId, keys, data } = instruction; + + assert( + programId.equals(STAKE_POOL_PROGRAM_ID), + 'Invalid DepositSol instruction, program ID must be the Stake Pool Program' + ); + + let i = 0; + const stakePool = parseKey(keys[i++], { isSigner: false, isWritable: true }); + const withdrawAuthority = parseKey(keys[i++], { isSigner: false, isWritable: false }); + const reserveStake = parseKey(keys[i++], { isSigner: false, isWritable: true }); + const fundingAccount = parseKey(keys[i++], { isSigner: true, isWritable: true }); + const destinationPoolAccount = parseKey(keys[i++], { isSigner: false, isWritable: true }); + const managerFeeAccount = parseKey(keys[i++], { isSigner: false, isWritable: true }); + const referralPoolAccount = parseKey(keys[i++], { isSigner: false, isWritable: true }); + const poolMint = parseKey(keys[i++], { isSigner: false, isWritable: true }); + const systemProgramProgramId = parseKey(keys[i++], { isSigner: false, isWritable: false }); + assert(systemProgramProgramId.equals(SystemProgram.programId), 'Unexpected pubkey in DepositSol instruction'); + const tokenProgramId = parseKey(keys[i++], { isSigner: false, isWritable: false }); + assert(tokenProgramId.equals(TOKEN_PROGRAM_ID), 'Unexpected pubkey in DepositSol instruction'); + const depositAuthority = keys.length > 10 ? parseKey(keys[i++], { isSigner: true, isWritable: false }) : undefined; + assert(keys.length <= 11, 'Too many keys in DepositSol instruction'); + + const layoutCode = data.readUint8(0); + assert(layoutCode === DEPOSIT_SOL_LAYOUT_CODE, 'Incorrect layout code in DepositSol data'); + assert(data.length === 9, 'Incorrect data size for DepositSol layout'); + const lamports = data.readBigInt64LE(1); + + return { + stakePool, + depositAuthority, + withdrawAuthority, + reserveStake, + fundingAccount, + destinationPoolAccount, + managerFeeAccount, + referralPoolAccount, + poolMint, + lamports: Number(lamports), + }; +} diff --git a/modules/sdk-coin-sol/src/lib/iface.ts b/modules/sdk-coin-sol/src/lib/iface.ts index b5a88b616d..f50b8d104e 100644 --- a/modules/sdk-coin-sol/src/lib/iface.ts +++ b/modules/sdk-coin-sol/src/lib/iface.ts @@ -2,6 +2,7 @@ import { TransactionExplanation as BaseTransactionExplanation, Recipient } from import { DecodedCloseAccountInstruction } from '@solana/spl-token'; import { Blockhash, StakeInstructionType, SystemInstructionType, TransactionSignature } from '@solana/web3.js'; import { InstructionBuilderTypes } from './constants'; +import { StakePoolInstructionType } from '@solana/spl-stake-pool'; // TODO(STLX-9890): Add the interfaces for validityWindow and SequenceId export interface SolanaKeys { @@ -86,6 +87,8 @@ export interface StakingActivate { amount: string; validator: string; isMarinade?: boolean; + isJito?: boolean; + isTestnet?: boolean; }; } @@ -149,6 +152,7 @@ export interface AtaClose { export type ValidInstructionTypes = | SystemInstructionType | StakeInstructionType + | StakePoolInstructionType | 'Memo' | 'InitializeAssociatedTokenAccount' | 'CloseAssociatedTokenAccount' diff --git a/modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts b/modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts index 0cae83f3e3..db4e747372 100644 --- a/modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts +++ b/modules/sdk-coin-sol/src/lib/instructionParamsFactory.ts @@ -23,7 +23,13 @@ import { import { NotSupported, TransactionType } from '@bitgo/sdk-core'; import { coins, SolCoin } from '@bitgo/statics'; import assert from 'assert'; -import { InstructionBuilderTypes, ValidInstructionTypesEnum, walletInitInstructionIndexes } from './constants'; +import { + InstructionBuilderTypes, + JITO_STAKE_POOL_ADDRESS, + JITO_STAKE_POOL_RESERVE_ACCOUNT_TESTNET, + ValidInstructionTypesEnum, + walletInitInstructionIndexes, +} from './constants'; import { AtaClose, AtaInit, @@ -41,6 +47,8 @@ import { SetPriorityFee, } from './iface'; import { getInstructionType } from './utils'; +import { DepositSolParams } from '@solana/spl-stake-pool'; +import { decodeDepositSol } from './future/depositSol'; /** * Construct instructions params from Solana instructions @@ -241,6 +249,14 @@ function parseSendInstructions( return instructionData; } +function stakingInstructionsIsMarinade(si: StakingInstructions): boolean { + return !!(si.delegate === undefined && si.depositSol === undefined); +} + +function stakingInstructionsIsJito(si: StakingInstructions): boolean { + return !!(si.delegate === undefined && si.depositSol?.stakePool.toString() === JITO_STAKE_POOL_ADDRESS); +} + /** * Parses Solana instructions to create staking tx and delegate tx instructions params * Only supports Nonce, StakingActivate and Memo Solana instructions @@ -250,8 +266,8 @@ function parseSendInstructions( */ function parseStakingActivateInstructions( instructions: TransactionInstruction[] -): Array { - const instructionData: Array = []; +): Array { + const instructionData: Array = []; const stakingInstructions = {} as StakingInstructions; for (const instruction of instructions) { const type = getInstructionType(instruction); @@ -284,21 +300,52 @@ function parseStakingActivateInstructions( case ValidInstructionTypesEnum.StakingDelegate: stakingInstructions.delegate = StakeInstruction.decodeDelegate(instruction); break; + + case ValidInstructionTypesEnum.DepositSol: + stakingInstructions.depositSol = decodeDepositSol(instruction); + break; + + case ValidInstructionTypesEnum.InitializeAssociatedTokenAccount: + instructionData.push({ + type: InstructionBuilderTypes.CreateAssociatedTokenAccount, + params: { + mintAddress: instruction.keys[ataInitInstructionKeysIndexes.MintAddress].pubkey.toString(), + ataAddress: instruction.keys[ataInitInstructionKeysIndexes.ATAAddress].pubkey.toString(), + ownerAddress: instruction.keys[ataInitInstructionKeysIndexes.OwnerAddress].pubkey.toString(), + payerAddress: instruction.keys[ataInitInstructionKeysIndexes.PayerAddress].pubkey.toString(), + tokenName: findTokenName(instruction.keys[ataInitInstructionKeysIndexes.MintAddress].pubkey.toString()), + }, + }); + break; } } validateStakingInstructions(stakingInstructions); + const stakingActivate: StakingActivate = { type: InstructionBuilderTypes.StakingActivate, params: { - fromAddress: stakingInstructions.create?.fromPubkey.toString() || '', - stakingAddress: stakingInstructions.initialize?.stakePubkey.toString() || '', - amount: stakingInstructions.create?.lamports.toString() || '', + fromAddress: + stakingInstructions.create?.fromPubkey.toString() || + stakingInstructions.depositSol?.fundingAccount.toString() || + '', + stakingAddress: + stakingInstructions.initialize?.stakePubkey.toString() || + stakingInstructions.depositSol?.stakePool.toString() || + '', + amount: + stakingInstructions.create?.lamports.toString() || stakingInstructions.depositSol?.lamports.toString() || '', validator: stakingInstructions.delegate?.votePubkey.toString() || stakingInstructions.initialize?.authorized.staker.toString() || + stakingInstructions.depositSol?.stakePool.toString() || '', - isMarinade: stakingInstructions.delegate === undefined, + isMarinade: stakingInstructionsIsMarinade(stakingInstructions), + isJito: stakingInstructionsIsJito(stakingInstructions), + ...(stakingInstructionsIsJito(stakingInstructions) && + stakingInstructions.depositSol?.reserveStake.toString() === JITO_STAKE_POOL_RESERVE_ACCOUNT_TESTNET + ? { isTestnet: true } + : {}), }, }; instructionData.push(stakingActivate); @@ -351,22 +398,23 @@ interface StakingInstructions { initialize?: InitializeStakeParams; delegate?: DelegateStakeParams; authorize?: AuthorizeStakeParams[]; + depositSol?: DepositSolParams; } function validateStakingInstructions(stakingInstructions: StakingInstructions) { - if (!stakingInstructions.create) { - throw new NotSupported('Invalid staking activate transaction, missing create stake account instruction'); - } - - if (!stakingInstructions.initialize && stakingInstructions.delegate) { - return; - } else if (!stakingInstructions.delegate && stakingInstructions.initialize) { - return; - } else if (!stakingInstructions.delegate && !stakingInstructions.initialize) { - // If both are missing something is wrong - throw new NotSupported( - 'Invalid staking activate transaction, missing initialize stake account/delegate instruction' - ); + if (stakingInstructionsIsJito(stakingInstructions)) { + if (!stakingInstructions.depositSol) { + throw new NotSupported('Invalid staking activate transaction, missing deposit sol instruction'); + } + } else { + if (!stakingInstructions.create) { + throw new NotSupported('Invalid staking activate transaction, missing create stake account instruction'); + } + if (!stakingInstructions.delegate && !stakingInstructions.initialize) { + throw new NotSupported( + 'Invalid staking activate transaction, missing initialize stake account/delegate instruction' + ); + } } } @@ -714,6 +762,9 @@ function parseAtaInitInstructions( }; instructionData.push(ataInit); break; + case ValidInstructionTypesEnum.DepositSol: + // AtaInit is a part of spl-stake-pool's depositSol process + break; default: throw new NotSupported( 'Invalid transaction, instruction type not supported: ' + getInstructionType(instruction) diff --git a/modules/sdk-coin-sol/src/lib/solInstructionFactory.ts b/modules/sdk-coin-sol/src/lib/solInstructionFactory.ts index 8bdd3b87d9..bdecd13428 100644 --- a/modules/sdk-coin-sol/src/lib/solInstructionFactory.ts +++ b/modules/sdk-coin-sol/src/lib/solInstructionFactory.ts @@ -18,7 +18,15 @@ import { } from '@solana/web3.js'; import assert from 'assert'; import BigNumber from 'bignumber.js'; -import { InstructionBuilderTypes, MEMO_PROGRAM_PK } from './constants'; +import { + InstructionBuilderTypes, + JITO_MANAGER_FEE_ACCOUNT, + JITO_MANAGER_FEE_ACCOUNT_TESTNET, + JITO_STAKE_POOL_RESERVE_ACCOUNT, + JITO_STAKE_POOL_RESERVE_ACCOUNT_TESTNET, + JITOSOL_MINT_ADDRESS, + MEMO_PROGRAM_PK, +} from './constants'; import { AtaClose, AtaInit, @@ -36,6 +44,7 @@ import { SetPriorityFee, } from './iface'; import { getSolTokenFromTokenName } from './utils'; +import { depositSolInstructions } from './future/depositSol'; /** * Construct Solana instructions from instructions params @@ -231,30 +240,59 @@ function createNonceAccountInstruction(data: WalletInit): TransactionInstruction */ function stakingInitializeInstruction(data: StakingActivate): TransactionInstruction[] { const { - params: { fromAddress, stakingAddress, amount, validator, isMarinade }, + params: { fromAddress, stakingAddress, amount, validator, isMarinade, isJito, isTestnet }, } = data; assert(fromAddress, 'Missing fromAddress param'); assert(stakingAddress, 'Missing stakingAddress param'); assert(amount, 'Missing amount param'); assert(validator, 'Missing validator param'); assert(isMarinade !== undefined, 'Missing isMarinade param'); + assert(isJito !== undefined, 'Missing isJito param'); + assert([isMarinade, isJito].filter((x) => x).length <= 1, 'At most one of isMarinade and isJito can be true'); const fromPubkey = new PublicKey(fromAddress); const stakePubkey = new PublicKey(stakingAddress); const validatorPubkey = new PublicKey(validator); const tx = new Transaction(); - const stakerPubkey = isMarinade ? validatorPubkey : fromPubkey; - const walletInitStaking = StakeProgram.createAccount({ - fromPubkey, - stakePubkey, - authorized: new Authorized(stakerPubkey, fromPubkey), // staker and withdrawer - lockup: new Lockup(0, 0, fromPubkey), // No minimum epoch to withdraw - lamports: new BigNumber(amount).toNumber(), - }); - tx.add(walletInitStaking); + if (isJito) { + const stakePoolMint = new PublicKey(JITOSOL_MINT_ADDRESS); + const stakePoolReserveStake = new PublicKey( + isTestnet ? JITO_STAKE_POOL_RESERVE_ACCOUNT_TESTNET : JITO_STAKE_POOL_RESERVE_ACCOUNT + ); + const stakePoolManagerFeeAccount = new PublicKey( + isTestnet ? JITO_MANAGER_FEE_ACCOUNT_TESTNET : JITO_MANAGER_FEE_ACCOUNT + ); + const instructions = depositSolInstructions( + { + stakePoolAddress: stakePubkey, + from: fromPubkey, + lamports: BigInt(amount), + }, + stakePoolMint, + stakePoolReserveStake, + stakePoolManagerFeeAccount + ); + tx.add(...instructions); + } else if (isMarinade) { + const walletInitStaking = StakeProgram.createAccount({ + fromPubkey, + stakePubkey, + authorized: new Authorized(validatorPubkey, fromPubkey), // staker and withdrawer + lockup: new Lockup(0, 0, fromPubkey), // No minimum epoch to withdraw + lamports: new BigNumber(amount).toNumber(), + }); + tx.add(walletInitStaking); + } else { + const walletInitStaking = StakeProgram.createAccount({ + fromPubkey, + stakePubkey, + authorized: new Authorized(fromPubkey, fromPubkey), // staker and withdrawer + lockup: new Lockup(0, 0, fromPubkey), // No minimum epoch to withdraw + lamports: new BigNumber(amount).toNumber(), + }); + tx.add(walletInitStaking); - if (!isMarinade) { const delegateStaking = StakeProgram.delegate({ stakePubkey: new PublicKey(stakingAddress), authorizedPubkey: new PublicKey(fromAddress), diff --git a/modules/sdk-coin-sol/src/lib/stakingActivateBuilder.ts b/modules/sdk-coin-sol/src/lib/stakingActivateBuilder.ts index ed784294ec..ba2071a321 100644 --- a/modules/sdk-coin-sol/src/lib/stakingActivateBuilder.ts +++ b/modules/sdk-coin-sol/src/lib/stakingActivateBuilder.ts @@ -1,4 +1,4 @@ -import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import { BaseCoin as CoinConfig, NetworkType } from '@bitgo/statics'; import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core'; import { Transaction } from './transaction'; import { TransactionBuilder } from './transactionBuilder'; @@ -13,9 +13,12 @@ export class StakingActivateBuilder extends TransactionBuilder { protected _stakingAddress: string; protected _validator: string; protected _isMarinade = false; + protected _isJito = false; + protected _isTestnet: boolean; constructor(_coinConfig: Readonly) { super(_coinConfig); + this._isTestnet = this._coinConfig.network.type === NetworkType.TESTNET; } protected get transactionType(): TransactionType { @@ -33,6 +36,8 @@ export class StakingActivateBuilder extends TransactionBuilder { this.amount(activateInstruction.params.amount); this.validator(activateInstruction.params.validator); this.isMarinade(activateInstruction.params.isMarinade ?? false); + this.isJito(activateInstruction.params.isJito ?? false); + this.isTestnet(activateInstruction.params.isTestnet ?? this._coinConfig.network.type === NetworkType.TESTNET); } } } @@ -89,6 +94,22 @@ export class StakingActivateBuilder extends TransactionBuilder { return this; } + /** + * Set isJito flag + * @param {boolean} flag - true if the transaction is for Jito, false by default if not set + * @returns {StakingActivateBuilder} This staking builder + */ + isJito(flag: boolean): this { + this._isJito = flag; + // this._coinConfig.network.type === NetworkType.TESTNET + return this; + } + + isTestnet(flag: boolean): this { + this._isTestnet = flag; + return this; + } + /** @inheritdoc */ protected async buildImplementation(): Promise { assert(this._sender, 'Sender must be set before building the transaction'); @@ -96,6 +117,11 @@ export class StakingActivateBuilder extends TransactionBuilder { assert(this._validator, 'Validator must be set before building the transaction'); assert(this._amount, 'Amount must be set before building the transaction'); assert(this._isMarinade !== undefined, 'isMarinade must be set before building the transaction'); + assert(this._isJito !== undefined, 'isJito must be set before building the transaction'); + assert( + [this._isMarinade, this._isJito].filter((x) => x).length <= 1, + 'At most one of isMarinade and isJito can be true' + ); if (this._sender === this._stakingAddress) { throw new BuildTransactionError('Sender address cannot be the same as the Staking address'); @@ -109,6 +135,8 @@ export class StakingActivateBuilder extends TransactionBuilder { amount: this._amount, validator: this._validator, isMarinade: this._isMarinade, + isJito: this._isJito, + isTestnet: this._isTestnet, }, }; this._instructionsData = [stakingAccountData]; diff --git a/modules/sdk-coin-sol/src/lib/utils.ts b/modules/sdk-coin-sol/src/lib/utils.ts index 807617f024..c7f3ee23af 100644 --- a/modules/sdk-coin-sol/src/lib/utils.ts +++ b/modules/sdk-coin-sol/src/lib/utils.ts @@ -13,6 +13,7 @@ import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, + DecodedCloseAccountInstruction, } from '@solana/spl-token'; import { Keypair, @@ -48,8 +49,10 @@ import { validInstructionData2, ValidInstructionTypesEnum, walletInitInstructionIndexes, + jitoStakingActivateInstructionsIndexes, } from './constants'; import { ValidInstructionTypes } from './iface'; +import { STAKE_POOL_INSTRUCTION_LAYOUTS, STAKE_POOL_PROGRAM_ID } from '@solana/spl-stake-pool'; const DECODED_BLOCK_HASH_LENGTH = 32; // https://docs.solana.com/developing/programming-model/transactions#blockhash-format const DECODED_SIGNATURE_LENGTH = 64; // https://docs.solana.com/terminology#signature @@ -279,7 +282,7 @@ export function getTransactionType(transaction: SolTransaction): TransactionType // check if deactivate instruction does not exist because deactivate can be include a transfer instruction const memoInstruction = instructions.find((instruction) => getInstructionType(instruction) === 'Memo'); const memoData = memoInstruction?.data.toString('utf-8'); - if (instructions.filter((instruction) => getInstructionType(instruction) === 'Deactivate').length == 0) { + if (instructions.filter((instruction) => getInstructionType(instruction) === 'Deactivate').length === 0) { for (const instruction of instructions) { const instructionType = getInstructionType(instruction); // Check if memo instruction is there and if it contains 'PrepareForRevoke' because Marinade staking deactivate transaction will have this @@ -295,6 +298,7 @@ export function getTransactionType(transaction: SolTransaction): TransactionType return TransactionType.WalletInitialization; } else if ( matchTransactionTypeByInstructionsOrder(instructions, marinadeStakingActivateInstructionsIndexes) || + matchTransactionTypeByInstructionsOrder(instructions, jitoStakingActivateInstructionsIndexes) || matchTransactionTypeByInstructionsOrder(instructions, stakingActivateInstructionsIndexes) ) { return TransactionType.StakingActivate; @@ -335,7 +339,7 @@ export function getInstructionType(instruction: TransactionInstruction): ValidIn case TOKEN_PROGRAM_ID.toString(): case TOKEN_2022_PROGRAM_ID.toString(): try { - let decodedInstruction; + let decodedInstruction: DecodedCloseAccountInstruction | undefined; if (instruction.programId.toString() !== TOKEN_2022_PROGRAM_ID.toString()) { decodedInstruction = decodeCloseAccountInstruction(instruction); } else { @@ -349,6 +353,14 @@ export function getInstructionType(instruction: TransactionInstruction): ValidIn return 'TokenTransfer'; } return 'TokenTransfer'; + case STAKE_POOL_PROGRAM_ID.toString(): + const discriminator = instruction.data.readUint8(0); + const layoutKey = Object.entries(STAKE_POOL_INSTRUCTION_LAYOUTS).find(([_, v]) => v.index === discriminator)?.[0]; + if (layoutKey === undefined) { + throw new Error('Instruction type incorrect; unknown discriminator'); + } + const instructionKey = layoutKey as keyof typeof STAKE_POOL_INSTRUCTION_LAYOUTS; + return instructionKey; case StakeProgram.programId.toString(): return StakeInstruction.decodeInstructionType(instruction); case ASSOCIATED_TOKEN_PROGRAM_ID.toString(): diff --git a/modules/sdk-coin-sol/test/resources/sol.ts b/modules/sdk-coin-sol/test/resources/sol.ts index 8b35485b93..3de105a434 100644 --- a/modules/sdk-coin-sol/test/resources/sol.ts +++ b/modules/sdk-coin-sol/test/resources/sol.ts @@ -157,6 +157,9 @@ export const STAKING_ACTIVATE_SIGNED_TX = export const MARINADE_STAKING_ACTIVATE_SIGNED_TX = 'AuRFS0r7hJ+/+WuDQbbwdjSgxfnKOWi94EnWEha9uaBPt8VZOXiOoSiSoES34VkyBNLlLqlfK0fP3d5eJR+srQvN04gqzpOZPTVzqiomyMXqwQ6FYoQg5nEkdiDVny8SsyhRnAeDMzexkKD+3rwSGP0E+XN/2crTL6PZRnip42YFAgADBUXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItBYnsvugEnYfm5Gbz5TLtMncgFHZ8JMpkxTTlJIzJovekAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAah2BeRN1QqmDQ3vf4qerJVf1NcinhyK2ikncAAAAAABqfVFxksXFEhjMlMPUrxf1ja7gibof1E49vZigAAAADjMtr5L6vs6LY/96RABeX9/Zr6FYdWthxalfkEs7jQgQICAgABNAAAAADgkwQAAAAAAMgAAAAAAAAABqHYF5E3VCqYNDe9/ip6slV/U1yKeHIraKSdwAAAAAADAgEEdAAAAACx+Xl4mhxH0TxI2HovJxcQ63+TJglRFzFikL1sKdr12UXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItBAAAAAAAAAAAAAAAAAAAAAEXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItB'; +export const JITO_STAKING_ACTIVATE_SIGNED_TX = + 'AXagGlsxiPLvRLV3WaC9YMjBrUlPISHX0xIMRtrHvYLJyRVfyPpCabQcE8aBvXkz95moq1Q+jROMQzXZOWkTXQ0BAAYMReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0Ecg6pe+BOG2OETfAVS9ftz6va1oE4onLBolJ2N+ZOOhLZtVF4GKFOAVZnUC4e8/Xev/fZDinazOce3aExJcA5A/NFB6YMsrxCtkXSVyg8nG1spPNRwJ+pzcAftQOs5oL0Eij4Iw7SVvhf0VCfYm+xbgMfiaVwYZNdnQ9s5vtNG1gzFbtdH7X73TLJSD5AC95Nt/ZLk3yZg4fDJYZ3WbbyJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABU5Z4kwFGooUp7HpeX8OEs36dJAhZlMZWmpRKm8WZgK4yXJY9OJInxuz0QKRSODYMLWhOZ2v8QhASOe9jb6fhZBoFO1Mr2ihdGcv2shgMaY+hOoV76HUS3IpP229sAFlAGp9UXGSxcUSGMyUw9SvF/WNruCJuh/UTj29mKAAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCp4zLa+S+r7Oi2P/ekQAXl/f2a+hWHVrYcWpX5BLO40IECCAcAAQADBgsKAAkKBAcFAAECAQMGCwkO4JMEAAAAAAA='; + export const STAKING_ACTIVATE_SIGNED_TX_WITH_MEMO = 'AsTWc6tgb0h6qBA/kcVgr35lpWYxit9d99IscSJ5OUHkTz4AUK0dI7MNX9kw1GMIvxGKg7uw709b/9K1CeUgRgHdrX1nKO30P/91RhNMJpknfdDHmq48duVvvPRhlXirbMNm0yqn2q4iEWk3U8pS4ASPAU2L0jlk1NSqnw5sxMcOAgAICkXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItBYnsvugEnYfm5Gbz5TLtMncgFHZ8JMpkxTTlJIzJovekAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALH5eXiaHEfRPEjYei8nFxDrf5MmCVEXMWKQvWwp2vXZBUpTWpkpIQZNJOhxYNo4fHw1td28kruB5B+oQEEFRI0GodgXkTdUKpg0N73+KnqyVX9TXIp4citopJ3AAAAAAAah2BelAgULaAeR5s5tuI4eW3FQ9h/GeQpOtNEAAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAGp9UXGSxcUSGMyUw9SvF/WNruCJuh/UTj29mKAAAAAAan1RcZNYTQ/u2bs0MdEyBr5UQoG1e4VmzFN1/0AAAA4zLa+S+r7Oi2P/ekQAXl/f2a+hWHVrYcWpX5BLO40IEEAgIAATQAAAAA4JMEAAAAAADIAAAAAAAAAAah2BeRN1QqmDQ3vf4qerJVf1NcinhyK2ikncAAAAAABQIBCHQAAAAAReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0FF5Xm8+SU89otH3/H7OjpcLCG6xbI8Ca4SpuBVa/CLQQAAAAAAAAAAAAAAAAAAAABF5Xm8+SU89otH3/H7OjpcLCG6xbI8Ca4SpuBVa/CLQQUGAQMHCQYABAIAAAAEAAl0ZXN0IG1lbW8='; diff --git a/modules/sdk-coin-sol/test/unit/instructionParamsFactory.staking.ts b/modules/sdk-coin-sol/test/unit/instructionParamsFactory.staking.ts index e6f5266cd2..1f314cbe4a 100644 --- a/modules/sdk-coin-sol/test/unit/instructionParamsFactory.staking.ts +++ b/modules/sdk-coin-sol/test/unit/instructionParamsFactory.staking.ts @@ -67,6 +67,7 @@ describe('Instruction Parser Staking Tests: ', function () { validator: validator.toString(), amount, isMarinade: false, + isJito: false, }, }; @@ -137,6 +138,7 @@ describe('Instruction Parser Staking Tests: ', function () { validator: validator.toString(), amount, isMarinade: false, + isJito: false, }, }; @@ -189,6 +191,7 @@ describe('Instruction Parser Staking Tests: ', function () { validator: validator.toString(), amount, isMarinade: false, + isJito: false, }, }; @@ -236,6 +239,7 @@ describe('Instruction Parser Staking Tests: ', function () { validator: validator.toString(), amount, isMarinade: false, + isJito: false, }, }; diff --git a/modules/sdk-coin-sol/test/unit/transactionBuilder/stakingActivateBuilder.ts b/modules/sdk-coin-sol/test/unit/transactionBuilder/stakingActivateBuilder.ts index d6483d3158..6f63454b58 100644 --- a/modules/sdk-coin-sol/test/unit/transactionBuilder/stakingActivateBuilder.ts +++ b/modules/sdk-coin-sol/test/unit/transactionBuilder/stakingActivateBuilder.ts @@ -4,6 +4,7 @@ import * as testData from '../../resources/sol'; import { getBuilderFactory } from '../getBuilderFactory'; import { KeyPair, Utils, Transaction } from '../../../src'; import { coins } from '@bitgo/statics'; +import { JITO_STAKE_POOL_ADDRESS, JITOSOL_MINT_ADDRESS } from '../../../src/lib/constants'; describe('Sol Staking Activate Builder', () => { const factory = getBuilderFactory('tsol'); @@ -77,6 +78,7 @@ describe('Sol Staking Activate Builder', () => { amount: amount, validator: validator.pub, isMarinade: true, + isJito: false, }, }, ]); @@ -96,6 +98,58 @@ describe('Sol Staking Activate Builder', () => { tx2.fromRawTransaction(rawTx); }); + it('Jito: build a create staking signed tx', async () => { + const txBuilder = factory.getStakingActivateBuilder(); + txBuilder + .amount(amount) + .sender(wallet.pub) + .stakingAddress(JITO_STAKE_POOL_ADDRESS) + .validator(JITO_STAKE_POOL_ADDRESS) + .isJito(true) + .nonce(recentBlockHash); + txBuilder.sign({ key: wallet.prv }); + const tx = await txBuilder.build(); + const txJson = tx.toJson(); + txJson.instructionsData.should.deepEqual([ + { + type: 'CreateAssociatedTokenAccount', + params: { + ataAddress: '2vJrx2Bn7PifLZDRaSCpphE9WtZsx1k43SRyiQDhE1As', + mintAddress: JITOSOL_MINT_ADDRESS, + ownerAddress: wallet.pub, + payerAddress: wallet.pub, + tokenName: 'sol:jitosol', + }, + }, + { + type: 'Activate', + params: { + fromAddress: wallet.pub, + stakingAddress: JITO_STAKE_POOL_ADDRESS, + amount: amount, + validator: JITO_STAKE_POOL_ADDRESS, + isMarinade: false, + isJito: true, + isTestnet: true, + }, + }, + ]); + tx.inputs.length.should.equal(1); + tx.inputs[0].should.deepEqual({ + address: wallet.pub, + value: amount, + coin: 'tsol', + }); + tx.outputs.length.should.equal(1); + const rawTx = tx.toBroadcastFormat(); + should.equal(Utils.isValidRawTransaction(rawTx), true); + should.equal(rawTx, testData.JITO_STAKING_ACTIVATE_SIGNED_TX); + factory.from(testData.JITO_STAKING_ACTIVATE_SIGNED_TX); + const coin = coins.get('tsol'); + const tx2 = new Transaction(coin); + tx2.fromRawTransaction(rawTx); + }); + it('build a create and delegate staking signed tx with memo', async () => { const txBuilder = factory.getStakingActivateBuilder(); txBuilder @@ -154,6 +208,7 @@ describe('Sol Staking Activate Builder', () => { amount: amount, validator: validator.pub, isMarinade: true, + isJito: false, }, }, ]); @@ -215,6 +270,7 @@ describe('Sol Staking Activate Builder', () => { amount: amount, validator: validator.pub, isMarinade: true, + isJito: false, }, }, ]); @@ -279,6 +335,7 @@ describe('Sol Staking Activate Builder', () => { amount: amount, validator: validator.pub, isMarinade: true, + isJito: false, }, }, ]); diff --git a/modules/sdk-coin-sol/test/unit/transactionBuilder/transactionBuilder.ts b/modules/sdk-coin-sol/test/unit/transactionBuilder/transactionBuilder.ts index fd625df1c5..70e2fc4b7d 100644 --- a/modules/sdk-coin-sol/test/unit/transactionBuilder/transactionBuilder.ts +++ b/modules/sdk-coin-sol/test/unit/transactionBuilder/transactionBuilder.ts @@ -108,6 +108,7 @@ describe('Sol Transaction Builder', async () => { amount: '300000', validator: 'CyjoLt3kjqB57K7ewCBHmnHq3UgEj3ak6A7m6EsBsuhA', isMarinade: false, + isJito: false, }, }, ]); diff --git a/package.json b/package.json index ab2a00c26b..a83c2da694 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,7 @@ "test:prepare-release": "mocha --require ts-node/register ./scripts/tests/prepareRelease/prepare-release-main.test.ts" }, "dependencies": { + "@solana/spl-stake-pool": "^1.1.8", "axios": "^1.8.2", "terser": "^5.14.2", "tmp": "^0.2.3" diff --git a/yarn.lock b/yarn.lock index 2b71c02df3..b6f5521874 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5123,6 +5123,96 @@ dependencies: buffer "~6.0.3" +"@solana/codecs-core@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz#1a2d76b9c7b9e7b7aeb3bd78be81c2ba21e3ce22" + integrity sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ== + dependencies: + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-data-structures@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-rc.1.tgz#d47b2363d99fb3d643f5677c97d64a812982b888" + integrity sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-numbers@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz#f34978ddf7ea4016af3aaed5f7577c1d9869a614" + integrity sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-strings@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz#e1d9167075b8c5b0b60849f8add69c0f24307018" + integrity sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.npmjs.org/@solana/codecs/-/codecs-2.0.0-rc.1.tgz#146dc5db58bd3c28e04b4c805e6096c2d2a0a875" + integrity sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-data-structures" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/codecs-strings" "2.0.0-rc.1" + "@solana/options" "2.0.0-rc.1" + +"@solana/errors@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.npmjs.org/@solana/errors/-/errors-2.0.0-rc.1.tgz#3882120886eab98a37a595b85f81558861b29d62" + integrity sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ== + dependencies: + chalk "^5.3.0" + commander "^12.1.0" + +"@solana/options@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.npmjs.org/@solana/options/-/options-2.0.0-rc.1.tgz#06924ba316dc85791fc46726a51403144a85fc4d" + integrity sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-data-structures" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/codecs-strings" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/spl-stake-pool@^1.1.8": + version "1.1.8" + resolved "https://registry.npmjs.org/@solana/spl-stake-pool/-/spl-stake-pool-1.1.8.tgz#5769f44997d7a05c18daacf2fb3de7e5a6831a10" + integrity sha512-g/d8pFPI9NI1QWObSCkzMz4QpEUepWRLSERGV3gaAwfxWb3mG5I0KQqDW5UgZ8iT9srhzg7me+gG4ad9toQsNg== + dependencies: + "@solana/buffer-layout" "^4.0.1" + "@solana/spl-token" "0.4.9" + "@solana/web3.js" "^1.95.3" + bn.js "^5.2.0" + buffer "^6.0.3" + buffer-layout "^1.2.2" + superstruct "^2.0.2" + +"@solana/spl-token-group@^0.0.7": + version "0.0.7" + resolved "https://registry.npmjs.org/@solana/spl-token-group/-/spl-token-group-0.0.7.tgz#83c00f0cd0bda33115468cd28b89d94f8ec1fee4" + integrity sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug== + dependencies: + "@solana/codecs" "2.0.0-rc.1" + +"@solana/spl-token-metadata@^0.1.6": + version "0.1.6" + resolved "https://registry.npmjs.org/@solana/spl-token-metadata/-/spl-token-metadata-0.1.6.tgz#d240947aed6e7318d637238022a7b0981b32ae80" + integrity sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA== + dependencies: + "@solana/codecs" "2.0.0-rc.1" + "@solana/spl-token@0.3.1": version "0.3.1" resolved "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.1.tgz#20ba93f8e86b0913e6bfa49fc0708f7fd3bdaf5e" @@ -5132,7 +5222,18 @@ "@solana/buffer-layout-utils" "^0.2.0" "@solana/web3.js" "^1.41.0" -"@solana/web3.js@1.92.1", "@solana/web3.js@1.95.8", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.41.0": +"@solana/spl-token@0.4.9": + version "0.4.9" + resolved "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.4.9.tgz#24d032d2935f237925c3b058ba6bb1e1ece5428c" + integrity sha512-g3wbj4F4gq82YQlwqhPB0gHFXfgsC6UmyGMxtSLf/BozT/oKd59465DbnlUK8L8EcimKMavxsVAMoLcEdeCicg== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/buffer-layout-utils" "^0.2.0" + "@solana/spl-token-group" "^0.0.7" + "@solana/spl-token-metadata" "^0.1.6" + buffer "^6.0.3" + +"@solana/web3.js@1.92.1", "@solana/web3.js@1.95.8", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.41.0", "@solana/web3.js@^1.95.3": version "1.95.8" resolved "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.95.8.tgz#2d49abda23f7a79a3cc499ab6680f7be11786ee1" integrity sha512-sBHzNh7dHMrmNS5xPD1d0Xa2QffW/RXaxu/OysRXBfwTp+LYqGGmMtCYYwrHPrN5rjAmJCsQRNAwv4FM0t3B6g== @@ -7933,6 +8034,11 @@ buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-layout@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" + integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== + buffer-to-arraybuffer@^0.0.5: version "0.0.5" resolved "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a"