Skip to content

Commit 2569f78

Browse files
2 parents d198116 + 8874bf0 commit 2569f78

File tree

100 files changed

+5550
-775
lines changed

Some content is hidden

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

100 files changed

+5550
-775
lines changed

.github/workflows/ci.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,43 @@ jobs:
134134
if: matrix.check == 'audit'
135135
run: yarn run improved-yarn-audit --min-severity high
136136

137+
license-analysis:
138+
runs-on: ubuntu-latest
139+
140+
steps:
141+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
142+
143+
- name: Setup node 22
144+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
145+
with:
146+
node-version: 22
147+
148+
- name: restore lerna dependencies
149+
id: lerna-cache
150+
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
151+
with:
152+
path: |
153+
node_modules
154+
modules/*/node_modules
155+
key: ${{ runner.os }}-node22-${{ hashFiles('yarn.lock') }}-${{ hashFiles('tsconfig.packages.json') }}-${{ hashFiles('package.json') }}
156+
157+
- name: Install Packages
158+
if: steps.lerna-cache.outputs.cache-hit != 'true'
159+
run: yarn install --with-frozen-lockfile --ignore-scripts
160+
161+
- name: build packages
162+
env:
163+
# Workaround for https://github.com/nodejs/node/issues/51555
164+
DISABLE_V8_COMPILE_CACHE: '1'
165+
run: yarn run postinstall
166+
167+
- name: Run Fossa Analysis
168+
uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0
169+
with:
170+
api-key: ${{ secrets.FOSSA_API_KEY }}
171+
branch: ${{ github.head_ref || github.ref_name }}
172+
project: BitGo/BitGoJS
173+
137174
browser-test:
138175
runs-on: ubuntu-22.04
139176

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-lightning/src/codecs/api/wallet.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,15 @@ export const WatchOnlyAccount = t.type({
5252

5353
export type WatchOnlyAccount = t.TypeOf<typeof WatchOnlyAccount>;
5454

55-
export const WatchOnly = t.type({
56-
master_key_birthday_timestamp: t.string,
57-
master_key_fingerprint: t.string,
58-
accounts: t.array(WatchOnlyAccount),
59-
});
55+
export const WatchOnly = t.intersection([
56+
t.type({
57+
accounts: t.array(WatchOnlyAccount),
58+
}),
59+
t.partial({
60+
master_key_birthday_timestamp: t.string,
61+
master_key_fingerprint: t.string,
62+
}),
63+
]);
6064

6165
export type WatchOnly = t.TypeOf<typeof WatchOnly>;
6266

modules/abstract-lightning/src/lightning/lightningUtils.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,18 @@ function convertXpubPrefix(xpub: string, purpose: ExtendedKeyPurpose, isMainnet:
154154
/**
155155
* Derives watch-only accounts from the master HD node for the given purposes and network.
156156
*/
157-
function deriveWatchOnlyAccounts(masterHDNode: utxolib.BIP32Interface, isMainnet: boolean): WatchOnlyAccount[] {
157+
export function deriveWatchOnlyAccounts(
158+
masterHDNode: utxolib.BIP32Interface,
159+
isMainnet: boolean,
160+
params: { onlyAddressCreationAccounts?: boolean } = { onlyAddressCreationAccounts: false }
161+
): WatchOnlyAccount[] {
158162
// https://github.com/lightningnetwork/lnd/blob/master/docs/remote-signing.md#required-accounts
159163
if (masterHDNode.isNeutered()) {
160164
throw new Error('masterHDNode must not be neutered');
161165
}
162-
163-
const purposes = [PURPOSE_WRAPPED_P2WKH, PURPOSE_P2WKH, PURPOSE_P2TR, PURPOSE_ALL_OTHERS] as const;
164-
166+
const purposes = params.onlyAddressCreationAccounts
167+
? ([PURPOSE_WRAPPED_P2WKH, PURPOSE_P2WKH, PURPOSE_P2TR] as const)
168+
: ([PURPOSE_WRAPPED_P2WKH, PURPOSE_P2WKH, PURPOSE_P2TR, PURPOSE_ALL_OTHERS] as const);
165169
return purposes.flatMap((purpose) => {
166170
const maxAccount = purpose === PURPOSE_ALL_OTHERS ? 255 : 0;
167171
const coinType = purpose !== PURPOSE_ALL_OTHERS || isMainnet ? 0 : 1;

0 commit comments

Comments
 (0)