|
3 | 3 | * |
4 | 4 | * Copyright 2025, BitGo, Inc. All Rights Reserved. |
5 | 5 | */ |
6 | | -import { BitGoAPI } from '@bitgo/sdk-api' |
7 | | -import { TransactionBuilderFactory, Tsol } from '@bitgo/sdk-coin-sol' |
8 | | -import { coins } from '@bitgo/statics' |
9 | | -import { Connection, PublicKey, clusterApiUrl, Transaction, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js" |
10 | | -import { getStakePoolAccount, updateStakePool } from '@solana/spl-stake-pool' |
| 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, Transaction, Keypair, LAMPORTS_PER_SOL } from '@solana/web3.js'; |
| 11 | +import { getStakePoolAccount, updateStakePool } from '@solana/spl-stake-pool'; |
| 12 | +import { getAssociatedTokenAddressSync } from '@solana/spl-token'; |
11 | 13 | import * as bs58 from 'bs58'; |
12 | 14 |
|
13 | | -require('dotenv').config({ path: '../../.env' }) |
| 15 | +require('dotenv').config({ path: '../../.env' }); |
14 | 16 |
|
15 | | -const AMOUNT_LAMPORTS = 1000 |
16 | | -const JITO_STAKE_POOL_ADDRESS = 'Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb' |
17 | | -const NETWORK = 'devnet' |
| 17 | +const AMOUNT_LAMPORTS = 1000; |
| 18 | +const JITO_STAKE_POOL_ADDRESS = 'Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb'; |
| 19 | +const NETWORK = 'devnet'; |
18 | 20 |
|
19 | 21 | const bitgo = new BitGoAPI({ |
20 | 22 | accessToken: process.env.TESTNET_ACCESS_TOKEN, |
21 | 23 | env: 'test', |
22 | | -}) |
23 | | -const coin = coins.get("tsol") |
24 | | -bitgo.register(coin.name, Tsol.createInstance) |
| 24 | +}); |
| 25 | +const coin = coins.get('tsol'); |
| 26 | +bitgo.register(coin.name, Tsol.createInstance); |
25 | 27 |
|
26 | 28 | async function main() { |
27 | | - const account = getAccount() |
28 | | - const connection = new Connection(clusterApiUrl(NETWORK), 'confirmed') |
29 | | - const recentBlockhash = await connection.getLatestBlockhash() |
30 | | - const stakePoolAccount = await getStakePoolAccount(connection, new PublicKey(JITO_STAKE_POOL_ADDRESS)) |
31 | | - |
| 29 | + const account = getAccount(); |
| 30 | + const connection = new Connection(clusterApiUrl(NETWORK), 'confirmed'); |
| 31 | + const recentBlockhash = await connection.getLatestBlockhash(); |
| 32 | + const stakePoolAccount = await getStakePoolAccount(connection, new PublicKey(JITO_STAKE_POOL_ADDRESS)); |
| 33 | + const associatedTokenAddress = getAssociatedTokenAddressSync( |
| 34 | + stakePoolAccount.account.data.poolMint, |
| 35 | + account.publicKey |
| 36 | + ); |
| 37 | + const associatedTokenAccountExists = !!(await connection.getAccountInfo(associatedTokenAddress)); |
32 | 38 |
|
33 | 39 | // Account should have sufficient balance |
34 | | - const accountBalance = await connection.getBalance(account.publicKey) |
| 40 | + const accountBalance = await connection.getBalance(account.publicKey); |
35 | 41 | if (accountBalance < 0.1 * LAMPORTS_PER_SOL) { |
36 | | - console.info(`Your account balance is ${accountBalance / LAMPORTS_PER_SOL} SOL, requesting airdrop`) |
37 | | - const sig = await connection.requestAirdrop(account.publicKey, 2 * LAMPORTS_PER_SOL) |
38 | | - await connection.confirmTransaction(sig) |
39 | | - console.info(`Airdrop successful: ${sig}`) |
| 42 | + console.info(`Your account balance is ${accountBalance / LAMPORTS_PER_SOL} SOL, requesting airdrop`); |
| 43 | + const sig = await connection.requestAirdrop(account.publicKey, 2 * LAMPORTS_PER_SOL); |
| 44 | + await connection.confirmTransaction(sig); |
| 45 | + console.info(`Airdrop successful: ${sig}`); |
40 | 46 | } |
41 | 47 |
|
42 | 48 | // Stake pool should be up to date |
43 | | - const epochInfo = await connection.getEpochInfo() |
| 49 | + const epochInfo = await connection.getEpochInfo(); |
44 | 50 | if (stakePoolAccount.account.data.lastUpdateEpoch.ltn(epochInfo.epoch)) { |
45 | | - console.info('Stake pool is out of date.') |
46 | | - const usp = await updateStakePool(connection, stakePoolAccount) |
47 | | - const tx = new Transaction() |
48 | | - tx.add(...usp.updateListInstructions, ...usp.finalInstructions) |
49 | | - const signer = Keypair.fromSecretKey(account.secretKeyArray) |
50 | | - const sig = await connection.sendTransaction(tx, [signer]) |
51 | | - await connection.confirmTransaction(sig) |
52 | | - console.info(`Stake pool updated: ${sig}`) |
| 51 | + console.info('Stake pool is out of date.'); |
| 52 | + const usp = await updateStakePool(connection, stakePoolAccount); |
| 53 | + const tx = new Transaction(); |
| 54 | + tx.add(...usp.updateListInstructions, ...usp.finalInstructions); |
| 55 | + const signer = Keypair.fromSecretKey(account.secretKeyArray); |
| 56 | + const sig = await connection.sendTransaction(tx, [signer]); |
| 57 | + await connection.confirmTransaction(sig); |
| 58 | + console.info(`Stake pool updated: ${sig}`); |
53 | 59 | } |
54 | 60 |
|
55 | 61 | // Use BitGoAPI to build depositSol instruction |
56 | | - const txBuilder = new TransactionBuilderFactory(coin).getStakingActivateBuilder() |
| 62 | + const txBuilder = new TransactionBuilderFactory(coin).getStakingActivateBuilder(); |
57 | 63 | txBuilder |
58 | 64 | .amount(`${AMOUNT_LAMPORTS}`) |
59 | 65 | .sender(account.publicKey.toBase58()) |
60 | 66 | .stakingAddress(JITO_STAKE_POOL_ADDRESS) |
61 | 67 | .validator(JITO_STAKE_POOL_ADDRESS) |
62 | | - .stakingTypeParams({ |
63 | | - type: 'JITO', |
| 68 | + .stakingType(SolStakingTypeEnum.JITO) |
| 69 | + .extraParams({ |
64 | 70 | stakePoolData: { |
65 | | - managerFeeAccount: stakePoolAccount.account.data.managerFeeAccount.toString(), |
66 | | - poolMint: stakePoolAccount.account.data.poolMint.toString(), |
67 | | - reserveStake: stakePoolAccount.account.data.toString(), |
68 | | - } |
| 71 | + managerFeeAccount: stakePoolAccount.account.data.managerFeeAccount.toBase58(), |
| 72 | + poolMint: stakePoolAccount.account.data.poolMint.toBase58(), |
| 73 | + reserveStake: stakePoolAccount.account.data.reserveStake.toBase58(), |
| 74 | + }, |
| 75 | + createAssociatedTokenAccount: !associatedTokenAccountExists, |
69 | 76 | }) |
70 | | - .nonce(recentBlockhash.blockhash) |
71 | | - txBuilder.sign({ key: account.secretKey }) |
72 | | - const tx = await txBuilder.build() |
73 | | - const serializedTx = tx.toBroadcastFormat() |
74 | | - console.info(`Transaction JSON:\n${JSON.stringify(tx.toJson(), undefined, 2)}`) |
| 77 | + .nonce(recentBlockhash.blockhash); |
| 78 | + txBuilder.sign({ key: account.secretKey }); |
| 79 | + const tx = await txBuilder.build(); |
| 80 | + const serializedTx = tx.toBroadcastFormat(); |
| 81 | + console.info(`Transaction JSON:\n${JSON.stringify(tx.toJson(), undefined, 2)}`); |
75 | 82 |
|
76 | 83 | // Send transaction |
77 | 84 | try { |
78 | | - const sig = await connection.sendRawTransaction(Buffer.from(serializedTx, 'base64')) |
79 | | - await connection.confirmTransaction(sig) |
80 | | - console.log(`${AMOUNT_LAMPORTS / LAMPORTS_PER_SOL} SOL deposited`, sig) |
| 85 | + const sig = await connection.sendRawTransaction(Buffer.from(serializedTx, 'base64')); |
| 86 | + await connection.confirmTransaction(sig); |
| 87 | + console.log(`${AMOUNT_LAMPORTS / LAMPORTS_PER_SOL} SOL deposited`, sig); |
81 | 88 | } catch (e) { |
82 | | - console.log('Error sending transaction') |
83 | | - console.error(e) |
84 | | - if (e.transactionMessage === 'Transaction simulation failed: Error processing Instruction 0: Provided owner is not allowed') { |
85 | | - console.error('If you successfully staked JitoSOL once, you cannot stake again.') |
86 | | - } |
| 89 | + console.log('Error sending transaction'); |
| 90 | + console.error(e); |
87 | 91 | } |
88 | 92 | } |
89 | 93 |
|
90 | 94 | const getAccount = () => { |
91 | | - const publicKey = process.env.ACCOUNT_PUBLIC_KEY |
92 | | - const secretKey = process.env.ACCOUNT_SECRET_KEY |
| 95 | + const publicKey = process.env.ACCOUNT_PUBLIC_KEY; |
| 96 | + const secretKey = process.env.ACCOUNT_SECRET_KEY; |
93 | 97 | if (publicKey === undefined || secretKey === undefined) { |
94 | | - const { publicKey, secretKey } = Keypair.generate() |
95 | | - console.log('# Here is a new account to save into your .env file.') |
96 | | - console.log(`ACCOUNT_PUBLIC_KEY=${publicKey.toBase58()}`) |
97 | | - console.log(`ACCOUNT_SECRET_KEY=${bs58.encode(secretKey)}`) |
98 | | - throw new Error("Missing account information") |
| 98 | + const { publicKey, secretKey } = Keypair.generate(); |
| 99 | + console.log('# Here is a new account to save into your .env file.'); |
| 100 | + console.log(`ACCOUNT_PUBLIC_KEY=${publicKey.toBase58()}`); |
| 101 | + console.log(`ACCOUNT_SECRET_KEY=${bs58.encode(secretKey)}`); |
| 102 | + throw new Error('Missing account information'); |
99 | 103 | } |
100 | 104 |
|
101 | 105 | return { |
102 | 106 | publicKey: new PublicKey(publicKey), |
103 | 107 | secretKey, |
104 | 108 | secretKeyArray: new Uint8Array(bs58.decode(secretKey)), |
105 | | - } |
106 | | -} |
| 109 | + }; |
| 110 | +}; |
107 | 111 |
|
108 | | -main().catch((e) => console.error(e)) |
| 112 | +main().catch((e) => console.error(e)); |
0 commit comments