Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
61ea5a0
feat(sdk-core): add wrap() and unwrap() to DefiVault
kamleshmugdiya Sep 3, 2026
8a81acf
fix: derive safe child key for spend sharing
pranishnepal Sep 4, 2026
d176d9b
chore(deps): bump @bitgo/wasm-utxo
OttoAllmendinger Sep 4, 2026
c0fc9cd
Merge pull request #9657 from BitGo/WCN-2429
pranishnepal Sep 4, 2026
0fb22b5
ci(root): align node versions across release and ci
zahin-mohammad Sep 4, 2026
b53a591
Merge pull request #9664 from BitGo/zahinmohammad/wcn-2596-fix-bitgoj…
zahin-mohammad Sep 5, 2026
792ae06
feat: add new tokens from AMS API
asset-metadata-bot[bot] Sep 5, 2026
6267706
fix(sdk-coin-sol): support Token-2022 close ATA
MohammedRyaan786 Sep 5, 2026
4a45c72
Merge pull request #9667 from BitGo/CHALO-1417-close-ata-flow
MohammedRyaan786 Sep 5, 2026
f866cea
Merge pull request #9569 from BitGo/claude/defi-661-changes-16c33b
kamleshmugdiya Sep 7, 2026
5867e36
Merge pull request #9666 from BitGo/ams-bot-tokens
prajwalu142 Sep 7, 2026
8a60a1e
Merge pull request #9658 from BitGo/otto/WAL-2025-bump-bitgo-wasm-utxo
OttoAllmendinger Sep 7, 2026
7eec2da
feat(abstract-utxo): recognize ZIP-316 shielded zcash addresses
abhi-bitgo Sep 7, 2026
5794974
feat(utxo-descriptors): add PoX-5 primitives
OttoAllmendinger Sep 4, 2026
b33f151
feat(utxo-staking): add PoX-5 recovery policy
OttoAllmendinger Sep 4, 2026
7890a48
fix(utxo-staking): correct PoX-5 recovery policy
OttoAllmendinger Sep 7, 2026
b0012c8
feat(statics): enable ZAMA staking metadata
Vijay-Jagannathan Sep 6, 2026
2f17a35
Merge pull request #9668 from BitGo/CSHLD-1652-shielded-zcash-address
abhi-bitgo Sep 7, 2026
850f00d
Merge pull request #9659 from BitGo/otto/WAL-2022-pox5-recovery-policy
OttoAllmendinger Sep 7, 2026
16ef938
refactor(utxo-staking): classify PoX-5 recovery branches
OttoAllmendinger Sep 7, 2026
91f39d3
Merge pull request #9669 from BitGo/otto/WAL-2024-pox5-thin-adapter
OttoAllmendinger Sep 7, 2026
f9b64ca
fix: align child derivation path field with WP
danielpeng1 Sep 7, 2026
6b1c996
Merge pull request #9623 from BitGo/SI-609-zama-staking-statics
Vijay-Jagannathan Sep 8, 2026
f9e04f9
Merge pull request #9671 from BitGo/WCN-2599/safe-derived-path
danielpeng1 Sep 8, 2026
35a5b5c
feat: add new tokens from AMS API
asset-metadata-bot[bot] Sep 8, 2026
29d050f
feat(sdk-core): wire RedPallas MPCv2 into MPC dispatch
kisslove-dewangan Sep 7, 2026
b41cf96
Merge pull request #9670 from BitGo/WCI-1570
kisslove-dewangan Sep 8, 2026
9dbf67a
feat(sdk-coin-sol): remove check for confidential extension
abhishekagrawal080 Sep 8, 2026
fd577e7
Merge pull request #9673 from BitGo/ams-bot-tokens
prajwalu142 Sep 8, 2026
b3097f3
Merge pull request #9676 from BitGo/CSHLD-1620
prajwalu142 Sep 8, 2026
4a1ef6c
feat: add new tokens from AMS API
asset-metadata-bot[bot] Sep 8, 2026
37ab5d0
feat(sdk-lib-mpc): add VrfDkg wrapper for MPS VRF keygen
danielpeng1 Sep 8, 2026
c4328d6
Merge pull request #9677 from BitGo/ams-bot-tokens
prajwalu142 Sep 8, 2026
8e04e14
Merge pull request #9672 from BitGo/WCN-2583/safes-mps-vrf-dkg-wrapper
danielpeng1 Sep 8, 2026
46387be
Merge origin/master into rel/latest conflict-resolution branch
zahin-mohammad Sep 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,10 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Setup node 22
- name: Setup node from .nvmrc
uses: actions/setup-node@v6
with:
node-version: 22
node-version-file: .nvmrc

- name: restore lerna dependencies
id: lerna-cache
Expand All @@ -217,7 +217,7 @@ jobs:
path: |
node_modules
modules/*/node_modules
key: ${{ runner.os }}-node22-${{ hashFiles('yarn.lock') }}-${{ hashFiles('tsconfig.packages.json') }}-${{ hashFiles('**/package.json') }}
key: ${{ runner.os }}-node${{ hashFiles('.nvmrc') }}-${{ hashFiles('yarn.lock') }}-${{ hashFiles('tsconfig.packages.json') }}-${{ hashFiles('**/package.json') }}

- name: Install Packages
if: steps.lerna-cache.outputs.cache-hit != 'true' || contains( github.event.pull_request.labels.*.name, 'SKIP_CACHE')
Expand Down Expand Up @@ -262,10 +262,10 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Setup node 22
- name: Setup node from .nvmrc
uses: actions/setup-node@v6
with:
node-version: 22 # this just needs to pass our lock file requirement for compilation
node-version-file: .nvmrc

- name: Build Info
run: |
Expand Down Expand Up @@ -458,10 +458,10 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Setup node 22
- name: Setup node from .nvmrc
uses: actions/setup-node@v6
with:
node-version: 22
node-version-file: .nvmrc

- name: restore lerna dependencies
id: lerna-cache
Expand All @@ -470,7 +470,7 @@ jobs:
path: |
node_modules
modules/*/node_modules
key: ${{ runner.os }}-node22-${{ hashFiles('yarn.lock') }}-${{ hashFiles('tsconfig.packages.json')}}-${{ hashFiles('**/package.json') }}
key: ${{ runner.os }}-node${{ hashFiles('.nvmrc') }}-${{ hashFiles('yarn.lock') }}-${{ hashFiles('tsconfig.packages.json')}}-${{ hashFiles('**/package.json') }}

- name: Install Packages
if: steps.lerna-cache.outputs.cache-hit != 'true' || contains( github.event.pull_request.labels.*.name, 'SKIP_CACHE')
Expand Down
27 changes: 11 additions & 16 deletions .github/workflows/npmjs-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,13 @@ jobs:
NPM_CONFIG_PROVENANCE: true

# WCN-2091: fail the release BEFORE bitgo publishes if the shrinkwrap it
# would ship pins any transitive that violates our declared engines (Node
# >=20). Runs after pass 1 because the shrinkwrap generator resolves
# newly-published siblings from the registry. Pack + install here, not
# --package-lock-only, so `engine-strict=true` actually validates every
# frozen entry's engines. If this fails, siblings are already on npm but
# bitgo isn't — fix the shrinkwrap issue and re-run in recovery-mode.
# would ship pins any transitive that is incompatible with the Node.js
# version pinned in .nvmrc. Runs after pass 1 because the shrinkwrap
# generator resolves newly-published siblings from the registry. Pack +
# install here, not --package-lock-only, so `engine-strict=true` actually
# validates every frozen entry's engines. If this fails, siblings are
# already on npm but bitgo isn't — fix the shrinkwrap issue and re-run in
# recovery-mode.
- name: Pre-publish shrinkwrap check — pack bitgo tarball
if: inputs.dry-run == false
env:
Expand All @@ -325,13 +326,13 @@ jobs:
echo "PREPUB_TARBALL=$tarball" >> "$GITHUB_ENV"
echo "Packed: $tarball"

- name: Pre-publish shrinkwrap check — setup Node 20
- name: Pre-publish shrinkwrap check — setup Node.js from .nvmrc
if: inputs.dry-run == false
uses: actions/setup-node@v6
with:
node-version: '20.x'
node-version-file: '.nvmrc'

- name: Pre-publish shrinkwrap check — install tarball on Node 20 with engine-strict
- name: Pre-publish shrinkwrap check — install tarball on repository Node.js with engine-strict
if: inputs.dry-run == false
run: |
workdir="$(mktemp -d)"
Expand All @@ -340,18 +341,12 @@ jobs:
npm init -y >/dev/null
echo "Verifying $PREPUB_TARBALL installs on $(node --version) with engine-strict=true"
if ! npm install "$PREPUB_TARBALL" --no-audit --no-fund --ignore-scripts 2>install.log; then
echo "::error::Pre-publish shrinkwrap check FAILED — bitgo tarball cannot be installed on Node 20 with engine-strict. Fix before publishing."
echo "::error::Pre-publish shrinkwrap check FAILED — bitgo tarball cannot be installed on the repository Node.js version with engine-strict. Fix before publishing."
cat install.log
exit 1
fi
echo "✅ bitgo tarball installs cleanly on $(node --version) with engine-strict."

- name: Pre-publish shrinkwrap check — restore release Node version
if: inputs.dry-run == false
uses: actions/setup-node@v6
with:
node-version-file: ".nvmrc"

- name: Publish bitgo (pass 2)
if: inputs.dry-run == false
run: |
Expand Down
73 changes: 73 additions & 0 deletions examples/ts/defi-vault-wrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Wrap native ETH into WETH (and unwrap it back) on staging.
*
* Wrap issues a single WETH9 `deposit()` call; unwrap issues `withdraw(uint256)`.
* The wallet-platform builds the calldata and resolves the WETH9 address from the
* vault binding — the SDK only forwards vaultId and amount.
*
* Set DEFI_WRAP_DIRECTION=unwrap to run the reverse direction.
*
* Wrap does not need to be awaited before depositing: the client is free to call
* depositToVault() without waiting for the wrap to confirm.
*
* Usage:
* STAGING_ACCESS_TOKEN=<token> \
* STAGING_WALLET_ID=<walletId> \
* STAGING_WALLET_PASSPHRASE=<passphrase> \
* DEFI_VAULT_ID=<vaultId> \
* DEFI_WRAP_AMOUNT=<amountInBaseUnits> \
* DEFI_WRAP_DIRECTION=<wrap|unwrap> \
* npx ts-node examples/ts/defi-vault-wrap.ts
*
* Copyright 2026, BitGo, Inc. All Rights Reserved.
*/
import { BitGo } from 'bitgo';

require('dotenv').config({ path: '../../.env' });

const config = {
accessToken: '',
env: 'staging',
walletId: '',
vaultId: 'tbaseeth-weth-test',
amount: '1000000000000000000', // 1 ETH — 18dp base units, kept as a string
direction: 'wrap' as 'wrap' | 'unwrap',
passphrase: '',
coin: 'tbaseeth',
otp: '000000',
};

const bitgoTest = new BitGo({
env: 'staging',
});

async function main() {
console.log('Connecting to staging...');
bitgoTest.authenticateWithAccessToken({ accessToken: config.accessToken });
//await bitgoTest.unlock({ otp: config.otp, duration: 3600 });
const wallet = await bitgoTest.coin(config.coin).wallets().get({ id: config.walletId });
console.log('Wallet ID :', wallet.id());
console.log('Vault ID :', config.vaultId);
console.log('Direction :', config.direction);
console.log('Amount :', config.amount, config.direction === 'wrap' ? '(ETH base units)' : '(WETH base units)');

const params = {
vaultId: config.vaultId,
amount: config.amount,
...(config.passphrase ? { walletPassphrase: config.passphrase } : {}),
};

console.log(`\nStarting ${config.direction}...`);
const result = config.direction === 'wrap' ? await wallet.defi.wrap(params) : await wallet.defi.unwrap(params);

console.log(`\n${config.direction} submitted:`);
console.log(' txRequestId :', result.txRequestId);
// operationId is reserved for milestone M5 and is undefined today.
console.log('\nFull result:', JSON.stringify(result, null, 2));
}

main().catch((e) => {
console.error('Error:', e.message);
if (e.stack) console.error(e.stack);
process.exit(1);
});
2 changes: 1 addition & 1 deletion modules/abstract-utxo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"@bitgo/utxo-core": "^1.41.3",
"@bitgo/utxo-descriptors": "^1.5.3",
"@bitgo/utxo-ord": "^1.34.3",
"@bitgo/wasm-utxo": "^5.0.0",
"@bitgo/wasm-utxo": "^5.1.0",
"@types/lodash": "^4.14.121",
"@types/superagent": "4.1.15",
"bignumber.js": "^9.0.2",
Expand Down
55 changes: 55 additions & 0 deletions modules/abstract-utxo/src/impl/zec/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { address as wasmAddress, fixedScriptWallet, isCoinName } from '@bitgo/wasm-utxo';

export type ZcashAddressKind = 'transparent' | 'shielded';

/**
* Whether `address` is a well-formed ZIP-316 Unified Address for `network`
* with an Orchard receiver. BitGo only supports Orchard, so a UA without one
* (e.g. Sapling- or transparent-only) is not considered valid here.
*/
export function isShieldedZcashAddress(address: string, network: fixedScriptWallet.ZcashNetworkName): boolean {
try {
return fixedScriptWallet.ZcashUnifiedAddress.parse(address, network).hasOrchardReceiver;
} catch {
return false;
}
}

/**
* Classify a Zcash address string as transparent or shielded, validating it in
* the process. Returns undefined if the address is neither a valid transparent
* address nor a well-formed ZIP-316 Unified Address for `network`.
*/
export function getZcashAddressKind(
address: string,
network: fixedScriptWallet.ZcashNetworkName
): ZcashAddressKind | undefined {
// ZcashNetworkName also permits 'zcash'/'zcashTest', which toOutputScriptWithCoin
// doesn't accept (it takes a CoinName, i.e. 'zec'/'tzec'). Skip straight to the
// shielded check for those rather than relying on an unsafe cast + caught throw.
if (isCoinName(network)) {
try {
wasmAddress.toOutputScriptWithCoin(address, network);
return 'transparent';
} catch {
// not a valid transparent address; fall through to shielded check
}
}
return isShieldedZcashAddress(address, network) ? 'shielded' : undefined;
}

/**
* Standalone counterpart to `Zec.isValidAddress`, parameterized by `network`
* instead of requiring a coin instance. Accepts transparent addresses and
* shielded ZIP-316 Unified Addresses.
*
* Not structurally identical to `Zec.isValidAddress`: the base class also
* round-trips the parsed script through each known encoding format (see
* `AbstractUtxoCoin.isValidAddress`), whereas this only calls
* `toOutputScriptWithCoin` once via `getZcashAddressKind`. They agree in
* practice since zec/tzec have no alternate transparent-address encoding to
* round-trip against, but that's not guaranteed to remain true.
*/
export function isValidZcashAddress(address: string, network: fixedScriptWallet.ZcashNetworkName): boolean {
return getZcashAddressKind(address, network) !== undefined;
}
1 change: 1 addition & 0 deletions modules/abstract-utxo/src/impl/zec/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './zec';
export * from './tzec';
export * from './address';
10 changes: 10 additions & 0 deletions modules/abstract-utxo/src/impl/zec/zec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
* @prettier
*/
import { BitGoBase } from '@bitgo/sdk-core';
import { fixedScriptWallet } from '@bitgo/wasm-utxo';

import { AbstractUtxoCoin } from '../../abstractUtxoCoin';
import { UtxoCoinName } from '../../names';

import { isShieldedZcashAddress } from './address';

export class Zec extends AbstractUtxoCoin {
readonly name: UtxoCoinName = 'zec';

Expand All @@ -16,4 +19,11 @@ export class Zec extends AbstractUtxoCoin {
static createInstance(bitgo: BitGoBase): Zec {
return new Zec(bitgo);
}

isValidAddress(address: string, param?: { anyFormat?: boolean; allowLightning?: boolean } | boolean): boolean {
if (super.isValidAddress(address, param)) {
return true;
}
return isShieldedZcashAddress(address, this.name as fixedScriptWallet.ZcashNetworkName);
}
}
79 changes: 79 additions & 0 deletions modules/abstract-utxo/test/unit/impl/zec/unit/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import assert from 'node:assert/strict';

import { BitGoAPI } from '@bitgo/sdk-api';

import {
Zec,
Tzec,
getZcashAddressKind,
isShieldedZcashAddress,
isValidZcashAddress,
} from '../../../../../src/impl/zec';

// ZIP-316 unified-address test vectors, copied from
// BitGoWASM/packages/wasm-utxo/test/fixtures/zcash/unified_address.json so
// both repos test against the same known-good data.
const zip316Mainnet = {
unified:
'u1pg2aaph7jp8rpf6yhsza25722sg5fcn3vaca6ze27hqjw7jvvhhuxkpcg0ge9xh6drsgdkda8qjq5chpehkcpxf87rnjryjqwymdheptpvnljqqrjqzjwkc2ma6hcq666kgwfytxwac8eyex6ndgr6ezte66706e3vaqrd25dzvzkc69kw0jgywtd0cmq52q5lkw6uh7hyvzjse8ksx',
};
const testnetWallet = {
unified:
'utest1w5m0qcnp8egl8qa296n70n8nvj0tqnzk90p7f48v7mjhhdrdqs8vgqydslg5plmzefawefnpmgmlm6hcy38m972erwxs04s02cq2prhguz8kqly75m6zjy56m08d5jnycgtpqtjeprte576gkmrxyszepgx76yzuwhh7m4lfz9jaq7unjk0x5ant46juxz73hsc6q4v3dqtzww00vps',
transparentAddress: 'tmM4DvLVJKXZt5ydn1tqYTHvahpKSwgjuRk',
};

describe('Zcash address validation', function () {
let bitgo: BitGoAPI;
let zec;
let tzec;

before(function () {
bitgo = new BitGoAPI({ env: 'mock' });
bitgo.register('zec', Zec.createInstance);
bitgo.register('tzec', Tzec.createInstance);
zec = bitgo.coin('zec');
tzec = bitgo.coin('tzec');
});

it('recognizes a mainnet unified address as shielded', function () {
assert.strictEqual(zec.isValidAddress(zip316Mainnet.unified), true);
assert.strictEqual(getZcashAddressKind(zip316Mainnet.unified, 'zec'), 'shielded');
assert.strictEqual(isShieldedZcashAddress(zip316Mainnet.unified, 'zec'), true);
assert.strictEqual(isValidZcashAddress(zip316Mainnet.unified, 'zec'), true);
});

it('recognizes a testnet unified address as shielded', function () {
assert.strictEqual(tzec.isValidAddress(testnetWallet.unified), true);
assert.strictEqual(getZcashAddressKind(testnetWallet.unified, 'tzec'), 'shielded');
assert.strictEqual(isShieldedZcashAddress(testnetWallet.unified, 'tzec'), true);
assert.strictEqual(isValidZcashAddress(testnetWallet.unified, 'tzec'), true);
});

it('recognizes a testnet transparent address as transparent', function () {
assert.strictEqual(tzec.isValidAddress(testnetWallet.transparentAddress), true);
assert.strictEqual(getZcashAddressKind(testnetWallet.transparentAddress, 'tzec'), 'transparent');
assert.strictEqual(isValidZcashAddress(testnetWallet.transparentAddress, 'tzec'), true);
});

it('recognizes a mainnet transparent (P2PKH) address as transparent', function () {
const address = 't1cN2ZVWzWcVRrnfeQzmkpLhzQ4dYRv8yRY';
assert.strictEqual(zec.isValidAddress(address), true);
assert.strictEqual(getZcashAddressKind(address, 'zec'), 'transparent');
assert.strictEqual(isValidZcashAddress(address, 'zec'), true);
});

it('rejects a garbage string', function () {
const garbage = 'not-a-real-address';
assert.strictEqual(zec.isValidAddress(garbage), false);
assert.strictEqual(getZcashAddressKind(garbage, 'zec'), undefined);
assert.strictEqual(isValidZcashAddress(garbage, 'zec'), false);
});

it('rejects a unified address checked against the wrong network', function () {
assert.strictEqual(tzec.isValidAddress(zip316Mainnet.unified), false);
assert.strictEqual(getZcashAddressKind(zip316Mainnet.unified, 'tzec'), undefined);
assert.strictEqual(isShieldedZcashAddress(zip316Mainnet.unified, 'tzec'), false);
assert.strictEqual(isValidZcashAddress(zip316Mainnet.unified, 'tzec'), false);
});
});
10 changes: 5 additions & 5 deletions modules/bitgo/test/v2/unit/keychains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,25 @@ describe('V2 Keychains', function () {
scope.done();
});

it('should add a safe child keychain with derivedFromParentWithHardenedPath', async function () {
it('should add a safe child keychain with derivedFromParentWithPath', async function () {
const scope = nock(bgUrl)
.post('/api/v2/tltc/key', function (body) {
body.pub.should.equal('pub');
body.parent.should.equal('parent-key-id');
body.safeId.should.equal('safe-id');
body.derivedFromParentWithHardenedPath.should.equal("m/7'");
body.derivedFromParentWithPath.should.equal("m/7'");
should.equal(body.path, undefined);
should.equal(body.derivedFromParentWithSeed, undefined);
return true;
})
.reply(200, { id: 'child-key-id', derivedFromParentWithHardenedPath: "m/7'", path: '/0/0' });
.reply(200, { id: 'child-key-id', derivedFromParentWithPath: "m/7'", path: '/0/0' });
const result = await keychains.add({
pub: 'pub',
parent: 'parent-key-id',
safeId: 'safe-id',
derivedFromParentWithHardenedPath: "m/7'",
derivedFromParentWithPath: "m/7'",
});
result.derivedFromParentWithHardenedPath.should.equal("m/7'");
result.derivedFromParentWithPath.should.equal("m/7'");
scope.done();
});
});
Expand Down
Loading
Loading