|
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