Skip to content

Commit 98ed8b5

Browse files
committed
Merge branch 'master' into validate-data-on-tx
2 parents 489974e + be6980a commit 98ed8b5

File tree

82 files changed

+4017
-683
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+4017
-683
lines changed

examples/ts/sol/stake-jito.ts

Lines changed: 65 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,106 +3,110 @@
33
*
44
* Copyright 2025, BitGo, Inc. All Rights Reserved.
55
*/
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';
1113
import * as bs58 from 'bs58';
1214

13-
require('dotenv').config({ path: '../../.env' })
15+
require('dotenv').config({ path: '../../.env' });
1416

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';
1820

1921
const bitgo = new BitGoAPI({
2022
accessToken: process.env.TESTNET_ACCESS_TOKEN,
2123
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);
2527

2628
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));
3238

3339
// Account should have sufficient balance
34-
const accountBalance = await connection.getBalance(account.publicKey)
40+
const accountBalance = await connection.getBalance(account.publicKey);
3541
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}`);
4046
}
4147

4248
// Stake pool should be up to date
43-
const epochInfo = await connection.getEpochInfo()
49+
const epochInfo = await connection.getEpochInfo();
4450
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}`);
5359
}
5460

5561
// Use BitGoAPI to build depositSol instruction
56-
const txBuilder = new TransactionBuilderFactory(coin).getStakingActivateBuilder()
62+
const txBuilder = new TransactionBuilderFactory(coin).getStakingActivateBuilder();
5763
txBuilder
5864
.amount(`${AMOUNT_LAMPORTS}`)
5965
.sender(account.publicKey.toBase58())
6066
.stakingAddress(JITO_STAKE_POOL_ADDRESS)
6167
.validator(JITO_STAKE_POOL_ADDRESS)
62-
.stakingTypeParams({
63-
type: 'JITO',
68+
.stakingType(SolStakingTypeEnum.JITO)
69+
.extraParams({
6470
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,
6976
})
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)}`);
7582

7683
// Send transaction
7784
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);
8188
} 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);
8791
}
8892
}
8993

9094
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;
9397
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');
99103
}
100104

101105
return {
102106
publicKey: new PublicKey(publicKey),
103107
secretKey,
104108
secretKeyArray: new Uint8Array(bs58.decode(secretKey)),
105-
}
106-
}
109+
};
110+
};
107111

108-
main().catch((e) => console.error(e))
112+
main().catch((e) => console.error(e));

modules/abstract-eth/src/lib/transactionBuilder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
248248
case TransactionType.FlushERC721:
249249
this.setContract(transactionJson.to);
250250
const erc721Data = decodeFlushERC721TokensData(transactionJson.data, transactionJson.to);
251-
if (erc721Data.forwarderVersion >= 4) {
251+
if (erc721Data.forwarderVersion === 4) {
252252
this.forwarderVersion(erc721Data.forwarderVersion);
253253
}
254254
this.forwarderAddress(erc721Data.forwarderAddress);
@@ -258,7 +258,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
258258
case TransactionType.FlushERC1155:
259259
this.setContract(transactionJson.to);
260260
const erc1155Data = decodeFlushERC1155TokensData(transactionJson.data, transactionJson.to);
261-
if (erc1155Data.forwarderVersion >= 4) {
261+
if (erc1155Data.forwarderVersion === 4) {
262262
this.forwarderVersion(erc1155Data.forwarderVersion);
263263
}
264264
this.forwarderAddress(erc1155Data.forwarderAddress);

modules/abstract-eth/src/lib/utils.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export function decodeFlushERC721TokensData(
259259
forwarderAddress: string;
260260
tokenAddress: string;
261261
tokenId: string;
262-
forwarderVersion: number;
262+
forwarderVersion?: number;
263263
} {
264264
if (data.startsWith(flushERC721ForwarderTokensMethodIdV4)) {
265265
if (!to) {
@@ -284,7 +284,6 @@ export function decodeFlushERC721TokensData(
284284
forwarderAddress: addHexPrefix(forwarderAddress as string),
285285
tokenAddress: addHexPrefix(tokenAddress as string),
286286
tokenId: new BigNumber(bufferToHex(tokenId as Buffer)).toFixed(),
287-
forwarderVersion: 0,
288287
};
289288
}
290289
throw new BuildTransactionError(`Invalid flush ERC721 bytecode: ${data}`);
@@ -332,7 +331,7 @@ export function decodeFlushERC1155TokensData(
332331
forwarderAddress: string;
333332
tokenAddress: string;
334333
tokenId: string;
335-
forwarderVersion: number;
334+
forwarderVersion?: number;
336335
} {
337336
if (data.startsWith(flushERC1155ForwarderTokensMethodIdV4)) {
338337
if (!to) {
@@ -357,7 +356,6 @@ export function decodeFlushERC1155TokensData(
357356
forwarderAddress: addHexPrefix(forwarderAddress as string),
358357
tokenAddress: addHexPrefix(tokenAddress as string),
359358
tokenId: new BigNumber(bufferToHex(tokenId as Buffer)).toFixed(),
360-
forwarderVersion: 0,
361359
};
362360
}
363361
throw new BuildTransactionError(`Invalid flush ERC1155 bytecode: ${data}`);

modules/abstract-eth/test/unit/utils.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('Abstract ETH Utils', () => {
3939
const forwarderAddress = '0x8f977e912ef500548a0c3be6ddde9899f1199b81';
4040
const tokenAddress = '0xdf7decb1baa8f529f0c8982cbb4be50357195299';
4141
const tokenId = '12345';
42-
const forwarderVersion = 0;
42+
const forwarderVersion = 2;
4343

4444
const encoded = flushERC721TokensData(forwarderAddress, tokenAddress, tokenId, forwarderVersion);
4545
const decoded = decodeFlushERC721TokensData(encoded);
@@ -48,7 +48,7 @@ describe('Abstract ETH Utils', () => {
4848
decoded.forwarderAddress.toLowerCase().should.equal(forwarderAddress.toLowerCase());
4949
decoded.tokenAddress.toLowerCase().should.equal(tokenAddress.toLowerCase());
5050
decoded.tokenId.should.equal(tokenId);
51-
decoded.forwarderVersion.should.equal(0);
51+
should.not.exist(decoded.forwarderVersion);
5252
});
5353

5454
it('should decode flush ERC721 data correctly for v4+', () => {
@@ -64,7 +64,8 @@ describe('Abstract ETH Utils', () => {
6464
decoded.forwarderAddress.toLowerCase().should.equal(forwarderAddress.toLowerCase());
6565
decoded.tokenAddress.toLowerCase().should.equal(tokenAddress.toLowerCase());
6666
decoded.tokenId.should.equal(tokenId);
67-
decoded.forwarderVersion.should.equal(4);
67+
should.exist(decoded.forwarderVersion);
68+
should.equal(decoded.forwarderVersion, 4);
6869
});
6970

7071
it('should handle large token IDs for ERC721', () => {
@@ -133,7 +134,7 @@ describe('Abstract ETH Utils', () => {
133134
const forwarderAddress = '0x8f977e912ef500548a0c3be6ddde9899f1199b81';
134135
const tokenAddress = '0xdf7decb1baa8f529f0c8982cbb4be50357195299';
135136
const tokenId = '99999';
136-
const forwarderVersion = 0;
137+
const forwarderVersion = 2;
137138

138139
const encoded = flushERC1155TokensData(forwarderAddress, tokenAddress, tokenId, forwarderVersion);
139140
const decoded = decodeFlushERC1155TokensData(encoded);
@@ -142,7 +143,7 @@ describe('Abstract ETH Utils', () => {
142143
decoded.forwarderAddress.toLowerCase().should.equal(forwarderAddress.toLowerCase());
143144
decoded.tokenAddress.toLowerCase().should.equal(tokenAddress.toLowerCase());
144145
decoded.tokenId.should.equal(tokenId);
145-
decoded.forwarderVersion.should.equal(0);
146+
should.not.exist(decoded.forwarderVersion);
146147
});
147148

148149
it('should decode flush ERC1155 data correctly for v4+', () => {
@@ -158,7 +159,8 @@ describe('Abstract ETH Utils', () => {
158159
decoded.forwarderAddress.toLowerCase().should.equal(forwarderAddress.toLowerCase());
159160
decoded.tokenAddress.toLowerCase().should.equal(tokenAddress.toLowerCase());
160161
decoded.tokenId.should.equal(tokenId);
161-
decoded.forwarderVersion.should.equal(4);
162+
should.exist(decoded.forwarderVersion);
163+
should.equal(decoded.forwarderVersion, 4);
162164
});
163165

164166
it('should handle token ID 0 for ERC1155', () => {

modules/abstract-substrate/src/lib/utils.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { hexToU8a, isHex, u8aToHex, u8aToU8a } from '@polkadot/util';
88
import { base64Decode, signatureVerify } from '@polkadot/util-crypto';
99
import { UnsignedTransaction } from '@substrate/txwrapper-core';
1010
import { DecodedSignedTx, DecodedSigningPayload, TypeRegistry } from '@substrate/txwrapper-core/lib/types';
11-
import { construct } from '@substrate/txwrapper-polkadot';
11+
import { construct, decode } from '@substrate/txwrapper-polkadot';
1212
import bs58 from 'bs58';
1313
import base32 from 'hi-base32';
1414
import nacl from 'tweetnacl';
@@ -30,6 +30,7 @@ import {
3030
BatchArgs,
3131
MoveStakeArgs,
3232
} from './iface';
33+
import { SingletonRegistry } from './singletonRegistry';
3334

3435
export class Utils implements BaseUtils {
3536
/** @inheritdoc */
@@ -317,6 +318,31 @@ export class Utils implements BaseUtils {
317318
getMaterial(networkType: NetworkType): Material {
318319
throw new Error('Method not implemented.');
319320
}
321+
322+
/**
323+
* Decodes a substrate transaction from raw transaction hex
324+
*
325+
* @param {string} txHex - The raw transaction hex string to decode (signed or unsigned)
326+
* @param {Material} material - Network material containing metadata and chain information
327+
* @param {boolean} [isImmortalEra] - Whether the transaction uses immortal era (optional)
328+
* @returns {DecodedSignedTx | DecodedSigningPayload} The decoded transaction object
329+
*/
330+
decodeTransaction(txHex: string, material: Material, isImmortalEra = false): DecodedSignedTx | DecodedSigningPayload {
331+
try {
332+
const registry = SingletonRegistry.getInstance(material);
333+
334+
// Attempt to decode as a signed transaction or unsigned transaction
335+
const decoded = decode(txHex, {
336+
metadataRpc: material.metadata,
337+
registry,
338+
isImmortalEra,
339+
});
340+
341+
return decoded;
342+
} catch (error) {
343+
throw new Error(`Failed to decode transaction: ${error}`);
344+
}
345+
}
320346
}
321347

322348
const utils = new Utils();

modules/bitgo/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,10 @@
147147
"@types/lodash": "^4.14.121",
148148
"aws-sdk": "^2.1155.0",
149149
"io-ts-types": "^0.5.16",
150-
"karma": "^6.4.2",
151-
"karma-chrome-launcher": "^3.1.0",
152-
"karma-jasmine": "^4.0.0",
153-
"karma-typescript": "^5.0.3",
150+
"karma": "6.4.4",
151+
"karma-chrome-launcher": "3.2.0",
152+
"karma-jasmine": "5.1.0",
153+
"karma-typescript": "5.5.4",
154154
"keccak": "3.0.3",
155155
"libsodium-wrappers-sumo": "^0.7.9",
156156
"puppeteer": "2.1.1",

modules/bitgo/src/v2/coinFactory.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
VetTokenConfig,
3838
TaoTokenConfig,
3939
PolyxTokenConfig,
40+
JettonTokenConfig,
4041
} from '@bitgo/statics';
4142
import {
4243
Ada,
@@ -99,6 +100,7 @@ import {
99100
Injective,
100101
Iota,
101102
Islm,
103+
JettonToken,
102104
Lnbtc,
103105
Ltc,
104106
Mon,
@@ -528,6 +530,10 @@ export function registerCoinConstructors(coinFactory: CoinFactory, coinMap: Coin
528530
({ name, coinConstructor }) => coinFactory.register(name, coinConstructor)
529531
);
530532

533+
JettonToken.createTokenConstructors([...tokens.bitcoin.ton.tokens, ...tokens.testnet.ton.tokens]).forEach(
534+
({ name, coinConstructor }) => coinFactory.register(name, coinConstructor)
535+
);
536+
531537
VetToken.createTokenConstructors().forEach(({ name, coinConstructor }) =>
532538
coinFactory.register(name, coinConstructor)
533539
);
@@ -971,6 +977,9 @@ export function getTokenConstructor(tokenConfig: TokenConfig): CoinConstructor |
971977
case 'flr':
972978
case 'tflr':
973979
return FlrToken.createTokenConstructor(tokenConfig as EthLikeTokenConfig);
980+
case 'ton':
981+
case 'tton':
982+
return JettonToken.createTokenConstructor(tokenConfig as JettonTokenConfig);
974983
default:
975984
return undefined;
976985
}

modules/bitgo/src/v2/coins/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import { Stx, Tstx, Sip10Token } from '@bitgo/sdk-coin-stx';
6060
import { Sui, Tsui, SuiToken } from '@bitgo/sdk-coin-sui';
6161
import { Tao, Ttao, TaoToken } from '@bitgo/sdk-coin-tao';
6262
import { Tia, Ttia } from '@bitgo/sdk-coin-tia';
63-
import { Ton, Tton } from '@bitgo/sdk-coin-ton';
63+
import { Ton, Tton, JettonToken } from '@bitgo/sdk-coin-ton';
6464
import { Trx, Ttrx } from '@bitgo/sdk-coin-trx';
6565
import { StellarToken, Txlm, Xlm } from '@bitgo/sdk-coin-xlm';
6666
import { Vet, Tvet, VetToken } from '@bitgo/sdk-coin-vet';
@@ -131,7 +131,7 @@ export { Stx, Tstx, Sip10Token };
131131
export { Sui, Tsui, SuiToken };
132132
export { Tao, Ttao, TaoToken };
133133
export { Tia, Ttia };
134-
export { Ton, Tton };
134+
export { Ton, Tton, JettonToken };
135135
export { Bld, Tbld };
136136
export { Sei, Tsei };
137137
export { Injective, Tinjective };

0 commit comments

Comments
 (0)