Skip to content

Commit 917b985

Browse files
committed
feat(sdk-coin-sol): add example for unstaking jito
Ticket: SC-3176
1 parent 08dc11f commit 917b985

File tree

2 files changed

+110
-1
lines changed

2 files changed

+110
-1
lines changed

examples/ts/sol/stake-jito.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ require('dotenv').config({ path: '../../.env' });
1616

1717
const AMOUNT_LAMPORTS = 1000;
1818
const JITO_STAKE_POOL_ADDRESS = 'Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb';
19-
const NETWORK = 'devnet';
19+
const NETWORK = 'testnet';
2020

2121
const bitgo = new BitGoAPI({
2222
accessToken: process.env.TESTNET_ACCESS_TOKEN,

examples/ts/sol/unstake-jito.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* Unstakes JitoSOL tokens on Solana devnet.
3+
*
4+
* Copyright 2025, BitGo, Inc. All Rights Reserved.
5+
*/
6+
import { SolStakingTypeEnum } from '@bitgo/public-types';
7+
import { BitGoAPI } from '@bitgo/sdk-api';
8+
import { TransactionBuilderFactory, Tsol } from '@bitgo/sdk-coin-sol';
9+
import { coins } from '@bitgo/statics';
10+
import { Connection, PublicKey, clusterApiUrl, Keypair } from '@solana/web3.js';
11+
import { getStakePoolAccount } from '@solana/spl-stake-pool';
12+
import * as bs58 from 'bs58';
13+
14+
require('dotenv').config({ path: '../../.env' });
15+
16+
const AMOUNT_TOKENS = 100;
17+
const JITO_STAKE_POOL_ADDRESS = 'Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb';
18+
const NETWORK = 'testnet';
19+
// You must find a validator. Try prepareWithdrawAccounts.
20+
21+
const bitgo = new BitGoAPI({
22+
accessToken: process.env.TESTNET_ACCESS_TOKEN,
23+
env: 'test',
24+
});
25+
const coin = coins.get('tsol');
26+
bitgo.register(coin.name, Tsol.createInstance);
27+
28+
async function main() {
29+
const account = getAccount();
30+
const { validatorAddress } = getValidator();
31+
const connection = new Connection(clusterApiUrl(NETWORK), 'confirmed');
32+
const recentBlockhash = await connection.getLatestBlockhash();
33+
const stakePoolAddress = new PublicKey(JITO_STAKE_POOL_ADDRESS);
34+
const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
35+
console.info('Validator list account', stakePoolAccount.account.data.validatorList.toBase58());
36+
37+
const transferAuthority = Keypair.generate();
38+
const stakeAccount = Keypair.generate();
39+
console.info('Transfer authority public key:', transferAuthority.publicKey.toBase58());
40+
console.info('Stake account public key:', stakeAccount.publicKey.toBase58());
41+
42+
// Use BitGoAPI to build withdrawStake instruction
43+
const txBuilder = new TransactionBuilderFactory(coin).getStakingDeactivateBuilder();
44+
txBuilder
45+
.amount(`${AMOUNT_TOKENS}`)
46+
.sender(account.publicKey.toBase58())
47+
.stakingAddress(JITO_STAKE_POOL_ADDRESS)
48+
.unstakingAddress(stakeAccount.publicKey.toBase58())
49+
.stakingType(SolStakingTypeEnum.JITO)
50+
.extraParams({
51+
stakePoolData: {
52+
managerFeeAccount: stakePoolAccount.account.data.managerFeeAccount.toBase58(),
53+
poolMint: stakePoolAccount.account.data.poolMint.toBase58(),
54+
validatorListAccount: stakePoolAccount.account.data.validatorList.toBase58(),
55+
},
56+
validatorAddress,
57+
transferAuthorityAddress: transferAuthority.publicKey.toBase58(),
58+
})
59+
.nonce(recentBlockhash.blockhash);
60+
61+
txBuilder.sign({ key: account.secretKey });
62+
txBuilder.sign({ key: bs58.encode(stakeAccount.secretKey) });
63+
txBuilder.sign({ key: bs58.encode(transferAuthority.secretKey) });
64+
65+
const tx = await txBuilder.build();
66+
const serializedTx = tx.toBroadcastFormat();
67+
console.info(serializedTx);
68+
console.info(`Transaction JSON:\n${JSON.stringify(tx.toJson(), undefined, 2)}`);
69+
70+
// Send transaction
71+
try {
72+
const sig = await connection.sendRawTransaction(Buffer.from(serializedTx, 'base64'));
73+
await connection.confirmTransaction(sig);
74+
console.log(`${AMOUNT_TOKENS} tokens withdrawn`, sig);
75+
} catch (e) {
76+
console.log('Error sending transaction');
77+
console.error(e);
78+
}
79+
}
80+
81+
const getAccount = () => {
82+
const publicKey = process.env.ACCOUNT_PUBLIC_KEY;
83+
const secretKey = process.env.ACCOUNT_SECRET_KEY;
84+
if (publicKey === undefined || secretKey === undefined) {
85+
const { publicKey, secretKey } = Keypair.generate();
86+
console.log('# Here is a new account to save into your .env file.');
87+
console.log(`ACCOUNT_PUBLIC_KEY=${publicKey.toBase58()}`);
88+
console.log(`ACCOUNT_SECRET_KEY=${bs58.encode(secretKey)}`);
89+
throw new Error('Missing account information');
90+
}
91+
92+
return {
93+
publicKey: new PublicKey(publicKey),
94+
secretKey,
95+
secretKeyArray: new Uint8Array(bs58.decode(secretKey)),
96+
};
97+
};
98+
99+
const getValidator = () => {
100+
const validatorAddress = process.env.VALIDATOR_PUBLIC_KEY;
101+
if (validatorAddress === undefined) {
102+
console.log('# You must select a validator, then define the entry below');
103+
console.log('VALIDATOR_PUBLIC_KEY=');
104+
throw new Error('Missing validator address');
105+
}
106+
return { validatorAddress };
107+
};
108+
109+
main().catch((e) => console.error(e));

0 commit comments

Comments
 (0)