Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
8360a49
feat: add new tokens from AMS API
asset-metadata-bot[bot] Jun 11, 2026
83a050c
refactor(sdk-core): extract deriveMPCWalletAddress from verifyMPCWall…
rajangarg047 Jun 11, 2026
c17f6b4
fix(sdk-coin-trx): allow TSS TRC20 consolidation without recipients
bhavidhingra Jun 11, 2026
9a5888d
feat: add 'advanced' wallet generation in type
danielpeng1 Jun 11, 2026
dfca3a1
Merge pull request #9006 from BitGo/WCN-685/external-signing-type-adv…
danielpeng1 Jun 11, 2026
b158331
feat(sdk-coin-canton): handle ISO timestamp vs microsecond mismatch
abhijeet848 Jun 11, 2026
5013469
fix: update package deps to fix vuln
prajwalu142 Jun 11, 2026
7c94395
Merge pull request #9010 from BitGo/updateAuditDependencies
prajwalu142 Jun 12, 2026
7c3f7c0
fix: migrate tcanton:usd1 token to testnet
damodarnaik699 Jun 11, 2026
eeae274
feat(sdk-coin-flrp): add createPairedWallet method to Flrp
ashutoshkumar-6 Jun 12, 2026
c8943b6
Merge pull request #8998 from BitGo/SCAAS-9714
damodarnaik699 Jun 12, 2026
82e0b42
feat: add new tokens from AMS API
asset-metadata-bot[bot] Jun 12, 2026
629332d
feat(sdk-core): add pre-hashed signable support for Avalanche atomic …
ArunBala-Bitgo Jun 12, 2026
04788e1
Merge pull request #9007 from BitGo/SCAAS-9727
abhijeet848 Jun 12, 2026
5e0016d
Merge pull request #9004 from BitGo/fix/coins-392-trx-token-tss-conso…
bhavidhingra Jun 12, 2026
4698398
fix(sdk-coin-sui): handle gas coin empty with only address balance
abhishekagrawal080 Jun 12, 2026
c3ce599
Merge pull request #9012 from BitGo/CSHLD-983
abhishekagrawal080 Jun 12, 2026
43978ef
Merge pull request #8995 from BitGo/ams-bot-tokens
manas-at-bitgo Jun 12, 2026
59bc044
Merge pull request #9001 from BitGo/rajangarg047/wcn-913-extract-deri…
rajangarg047 Jun 12, 2026
f5c2224
feat(sdk-core): add deriveAddress primitive to BaseCoin
rajangarg047 Jun 11, 2026
0ec7f61
Merge pull request #9000 from BitGo/rajangarg047/wcn-912-derive-addre…
rajangarg047 Jun 12, 2026
c3aff4f
fix(sdk-core): skip keychain fetch in createAddress for OFC wallets
zahin-mohammad Jun 12, 2026
c578250
feat: skip forceV1Auth when HMAC present for SSO
vinhkhangtieu Jun 12, 2026
448229a
feat(sdk-coin-sol): implement deriveAddress for SOL
rajangarg047 Jun 12, 2026
59dc3d2
feat(abstract-eth): implement deriveAddress for MPC/TSS ETH wallets
rajangarg047 Jun 12, 2026
b0e8643
Merge pull request #9014 from BitGo/zahinmohammad/wcn-942-ofc-createa…
zahin-mohammad Jun 12, 2026
44c361f
feat(abstract-utxo): implement deriveAddress for fixed-script UTXO wa…
rajangarg047 Jun 12, 2026
3a61f6a
Merge pull request #9015 from BitGo/ANT-963
vinhkhangtieu Jun 12, 2026
8592357
Merge pull request #8578 from BitGo/feat/SI-287-flrp-create-paired-wa…
ashutoshkumar-6 Jun 15, 2026
b3c6f5b
Merge pull request #9011 from BitGo/CECHO-1295
mohd-kashif Jun 15, 2026
3be25b7
feat: exclude CVE related to esbuild's Deno distribution for Node.js …
mohd-kashif Jun 15, 2026
c54e674
fix(sdk-coin-sol): skip checking recipients in case of ATA tx
MohammedRyaan786 Jun 15, 2026
faa763c
Merge pull request #9026 from BitGo/CHALO-624
MohammedRyaan786 Jun 15, 2026
3f286af
feat(express): add POST /api/v2/:coin/address/derive endpoint
rajangarg047 Jun 12, 2026
95b7c38
feat(sdk-core): add getEddsaMPCv2RecoveryKeyShares helper
vibhavgo Jun 2, 2026
8edf50d
Merge pull request #9022 from BitGo/CECHO-1295-2
mohd-kashif Jun 15, 2026
0d63d7e
feat: add exclusions for new CVEs affecting dependencies and clarify …
mohd-kashif Jun 15, 2026
ec131f7
Merge pull request #9029 from BitGo/CECHO-1353
mohd-kashif Jun 15, 2026
ff0cbb7
feat(sdk-api): pass recipient addresses to v1 billing fee endpoint
davidkaplanbitgo Jun 15, 2026
092d3fc
Merge pull request #9028 from BitGo/t1-3579-waive-paygo-fees-v1-to-v2…
davidkaplanbitgo Jun 15, 2026
c9a818e
Merge pull request #8918 from BitGo/WCI-396/sdk-core
vibhavgo Jun 16, 2026
54ab33e
Merge pull request #9018 from BitGo/rajangarg047/wcn-915-btc-derive-a…
rajangarg047 Jun 16, 2026
c2f51e7
Merge pull request #9017 from BitGo/rajangarg047/wcn-916-eth-mpc-deri…
rajangarg047 Jun 16, 2026
7f4bec1
Merge pull request #9016 from BitGo/rajangarg047/wcn-917-sol-derive-a…
rajangarg047 Jun 16, 2026
88064e8
Merge pull request #9013 from BitGo/rajangarg047/wcn-914-express-deri…
rajangarg047 Jun 16, 2026
3d5723d
feat(statics): onboard GoUSD and SCAASACME tokens
baltiyal Jun 16, 2026
9b1582c
Merge pull request #9031 from BitGo/feat/SCAAS-9540-SCAAS-9748-onboar…
baltiyal Jun 16, 2026
282355a
Merge remote-tracking branch 'origin/master' into master-into-rel-latest
alextse-bg Jun 16, 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
40 changes: 40 additions & 0 deletions .iyarc
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,43 @@ GHSA-2w8x-224x-785m
# - The xmp bypass produces live HTML markup in output, but since we discard all tags and use
# the result as plain text in Error messages, there is no DOM rendering path and no XSS risk
GHSA-rpr9-rxv7-x643

# Excluded because:
# - CVE affects esbuild's Deno distribution only: binary downloads without SHA-256 integrity verification
# - BitGoJS is a Node.js project; the Node.js esbuild distribution already includes binaryIntegrityCheck()
# - esbuild is a dev-time build tool (via babylonlabs-io-btc-staking-ts), not runtime production code
# - The attacker-controlled NPM_CONFIG_REGISTRY vector does not apply to our controlled CI environment
GHSA-gv7w-rqvm-qjhr

# Excluded because:
# - ws: Memory exhaustion DoS by sending many tiny fragments/data chunks to exhaust server memory
# - Transitive dependency via @cosmjs/socket, @ethersproject/providers, @polkadot/rpc-provider,
# jayson, rpc-websockets (via @solana/web3.js), and avalanche — all requiring ws <8.21.0
# - Our usage is exclusively as a WebSocket CLIENT for blockchain RPC connections, not as a server
# - The DoS vector requires an attacker to send crafted frames to a ws server we control; we do not
# expose any ws server surfaces in production
GHSA-96hv-2xvq-fx4p

# Excluded because:
# - form-data: CRLF injection via unescaped multipart field names and filenames
# - Transitive dependency via superagent (abstract-cosmos, express, supertest) and @aptos-labs/ts-sdk
# - The injection requires attacker-controlled field names or filenames in multipart requests
# - All form-data field names and filenames in our codebase are code-controlled constants,
# not derived from user input — no untrusted data flows into form field names or filenames
GHSA-hmw2-7cc7-3qxx

# Excluded because:
# - protobufjs: DoS through unbounded Any expansion during JSON conversion (parseAny recursion)
# - Transitive dependency via @cosmjs (abstract-cosmos, babylonlabs-io-btc-staking-ts) and
# @hashgraph/proto, @hashgraph/sdk (sdk-coin-hbar) — all requiring protobufjs <=7.5.x
# - Input to protobuf decoding comes from trusted blockchain RPC responses, not arbitrary user data
# - Patched version (7.6.1) requires upstream @cosmjs and @hashgraph dependency updates
GHSA-wcpc-wj8m-hjx6

# Excluded because:
# - tmp: path traversal via type-confusion in _assertPath (non-string prefix/postfix/template)
# - Transitive dependency via cypress (web-demo), karma (bitgo module), and lerna/nx (dev tooling)
# - All usages are dev-time only; tmp is never used in production or runtime code
# - The prefix/postfix/template args are all hard-coded string constants in calling code,
# not user-supplied — the type-confusion vector does not apply
GHSA-7c78-jf6q-g5cm
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
/modules/sdk-core/src/bitgo/lightning/ @BitGo/btc-team
/modules/sdk-core/test/unit/bitgo/lightning/ @BitGo/btc-team
/modules/sdk-core/test/unit/bitgo/wallet/resourceManagement.ts @BitGo/ethalt-team
/modules/sdk-core/test/unit/bitgo/utils/tss/preHashedSignable.ts @BitGo/ethalt-team
/modules/sdk-lib-mpc/ @BitGo/wallet-core @BitGo/wallet-core-india @BitGo/hsm
/modules/deser-lib/ @BitGo/wallet-core @BitGo/wallet-core-india @BitGo/hsm
/modules/sdk-rpc-wrapper @BitGo/ethalt-team
Expand Down
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ module.exports = {
'WCN-',
'WCI-',
'COIN-',
'COINS-',
'COINFLP-',
'FIAT-',
'ME-',
Expand Down
40 changes: 40 additions & 0 deletions modules/abstract-eth/src/abstractEthLikeNewCoins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ import {
VerifyTransactionOptions,
Wallet,
verifyMPCWalletAddress,
deriveMPCWalletAddress,
TssVerifyAddressOptions,
isTssVerifyAddressOptions,
DeriveAddressOptions,
DeriveAddressResult,
} from '@bitgo/sdk-core';
import { getDerivationPath } from '@bitgo/sdk-lib-mpc';
import { bip32 } from '@bitgo/secp256k1';
Expand Down Expand Up @@ -3040,6 +3043,43 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
throw new Error(`Base address verification not supported for wallet version ${params.walletVersion}`);
}

/**
* Locally derive an ETH wallet receive address from a derivation path, using the wallet's
* commonKeychain only (no private keys, no network access). The inverse of
* {@link isWalletAddress}: it *produces* the address via the same secp256k1 MPC derivation
* that isWalletAddress checks against (`deriveMPCWalletAddress` + `KeyPair.getAddress()`),
* so derive and verify can never diverge.
*
* Supports MPC/TSS wallets only (wallet versions 3, 5, 6). Legacy BIP32 forwarder wallets
* (versions 1, 2, 4) derive per-index forwarder contract addresses and are handled separately.
* @param params keychains (commonKeychain), derivation index, walletVersion, and optional SMC seed
* @returns the derived address, the index used, and the HD derivation path
*/
async deriveAddress(params: DeriveAddressOptions): Promise<DeriveAddressResult> {
const isMpcWallet = params.walletVersion === 3 || params.walletVersion === 5 || params.walletVersion === 6;
if (!isMpcWallet) {
throw new Error(
`deriveAddress currently supports only MPC/TSS ETH wallets (wallet versions 3, 5, 6). ` +
`Legacy BIP32 forwarder wallets (versions 1, 2, 4) are not yet supported. ` +
`Got walletVersion ${params.walletVersion}.`
);
}

const { address, derivationPath } = await deriveMPCWalletAddress(
{
// extractCommonKeychain validates the commonKeychain is present at runtime
keychains: (params.keychains ?? []) as TssVerifyAddressOptions['keychains'],
index: params.index,
derivedFromParentWithSeed: params.derivedFromParentWithSeed,
multisigTypeVersion: params.multisigTypeVersion,
keyCurve: 'secp256k1',
},
(pubKey) => new KeyPairLib({ pub: pubKey }).getAddress()
);

return { address, index: params.index, derivationPath };
}

/**
*
* @param {TransactionPrebuild} txPrebuild
Expand Down
33 changes: 32 additions & 1 deletion modules/abstract-utxo/src/abstractUtxoCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
Wallet,
isValidPrv,
isValidXprv,
DeriveAddressOptions,
DeriveAddressResult,
} from '@bitgo/sdk-core';

import {
Expand Down Expand Up @@ -75,7 +77,7 @@ import {
} from './transaction/descriptor/verifyTransaction';
import { assertDescriptorWalletAddress, getDescriptorMapFromWallet, isDescriptorWallet } from './descriptor';
import { getFullNameFromCoinName, getMainnetCoinName, isMainnetCoin, UtxoCoinName, UtxoCoinNameMainnet } from './names';
import { assertFixedScriptWalletAddress } from './address/fixedScript';
import { assertFixedScriptWalletAddress, generateAddress } from './address/fixedScript';
import { ParsedTransaction } from './transaction/types';
import { decodeDescriptorPsbt, decodePsbt, encodeTransaction, stringToBufferTryFormats } from './transaction/decode';
import { fetchKeychains, toBip32Triple, UtxoKeychain } from './keychains';
Expand Down Expand Up @@ -716,6 +718,35 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
return true;
}

/**
* Locally derive a fixed-script (2-of-3 multisig) wallet receive address from the xpub triple
* and a chain/index, using public keys only (no private keys, no network access). This is the
* inverse of {@link isWalletAddress}, delegating to the same `generateAddress` used by the
* verification path, so derive and verify can never diverge.
*
* The `chain` code selects the script type (e.g. 0/1 = P2SH, 20/21 = P2WSH/bech32, 30/31 = P2TR);
* an optional `format` overrides the address encoding.
* @param params keychains (xpub triple via `pub`), chain, index, and optional format
* @returns the derived address and the chain/index used
*/
async deriveAddress(params: DeriveAddressOptions): Promise<DeriveAddressResult> {
const { keychains, chain, index, format } = params;

if (!keychains) {
throw new Error('missing required param keychains');
}

const address = generateAddress(this.name, {
// fixed-script (multisig) coins derive from the xpub triple via `pub`
keychains: keychains as { pub: string }[],
chain,
index,
format,
});

return { address, chain, index };
}

/**
* @param addressType
* @returns true iff coin supports spending from unspentType
Expand Down
29 changes: 28 additions & 1 deletion modules/abstract-utxo/test/unit/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { fixedScriptWallet } from '@bitgo/wasm-utxo';

import { assertFixedScriptWalletAddress, generateAddress } from '../../src';

import { keychainsBase58 } from './util';
import { getUtxoCoin, keychainsBase58 } from './util';

const keychains = keychainsBase58.map((k) => ({ pub: k.pub }));

Expand Down Expand Up @@ -202,3 +202,30 @@ describe('assertFixedScriptWalletAddress', function () {
});
});
});

describe('AbstractUtxoCoin.deriveAddress', function () {
const coin = getUtxoCoin('btc');

// Bullish scope: legacy P2SH (chain 0) + bech32 P2WSH (chain 20).
for (const { label, chain } of [
{ label: 'legacy P2SH', chain: 0 },
{ label: 'bech32 P2WSH', chain: 20 },
]) {
it(`derives the ${label} address (chain ${chain}) matching generateAddress`, async function () {
const result = await coin.deriveAddress({ keychains, chain, index: 0 });
result.address.should.equal(generateAddress('btc', { keychains, chain, index: 0 }));
result.chain!.should.equal(chain);
result.index!.should.equal(0);
});

it(`round-trips with isWalletAddress for ${label} (chain ${chain})`, async function () {
const { address } = await coin.deriveAddress({ keychains, chain, index: 3 });
const verified = await coin.isWalletAddress({ address, keychains, chain, index: 3 });
verified.should.equal(true);
});
}

it('throws if keychains are missing', async function () {
await assert.rejects(async () => coin.deriveAddress({ chain: 0, index: 0 }), /keychains/);
});
});
15 changes: 15 additions & 0 deletions modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,20 @@ export async function handleV2IsWalletAddress(
return await wallet.baseCoin.isWalletAddress(req.decoded as any);
}

/**
* handle v2 deriveAddress - locally derive and return a wallet receive address from a
* derivation path, using public key material only.
*
* Offline by design: operates purely on the request body (keychains + chain/index), with no
* `wallets().get` lookup and no network access. The inverse of {@link handleV2IsWalletAddress}.
* @param req
*/
export async function handleV2DeriveAddress(req: ExpressApiRouteRequest<'express.v2.address.derive', 'post'>) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.decoded.coin);
return await coin.deriveAddress(req.decoded as any);
}

/**
* handle v2 approve transaction
* @param req
Expand Down Expand Up @@ -1963,6 +1977,7 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
prepareBitGo(config),
typedPromiseWrapper(handleV2IsWalletAddress),
]);
router.post('express.v2.address.derive', [prepareBitGo(config), typedPromiseWrapper(handleV2DeriveAddress)]);

router.post('express.wallet.share', [prepareBitGo(config), typedPromiseWrapper(handleV2ShareWallet)]);
app.post(
Expand Down
9 changes: 9 additions & 0 deletions modules/express/src/typedRoutes/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import { PostWalletEnableTokens } from './v2/walletEnableTokens';
import { PostWalletSweep } from './v2/walletSweep';
import { PostWalletAccelerateTx } from './v2/walletAccelerateTx';
import { PostIsWalletAddress } from './v2/isWalletAddress';
import { PostDeriveAddress } from './v2/deriveAddress';
import { GetAccountResources } from './v2/accountResources';
import { GetResourceDelegations } from './v2/resourceDelegations';
import { PostDelegateResources } from './v2/delegateResources';
Expand Down Expand Up @@ -235,6 +236,12 @@ export const ExpressV2WalletIsWalletAddressApiSpec = apiSpec({
},
});

export const ExpressV2AddressDeriveApiSpec = apiSpec({
'express.v2.address.derive': {
post: PostDeriveAddress,
},
});

export const ExpressV2WalletSendManyApiSpec = apiSpec({
'express.wallet.sendmany': {
post: PostSendMany,
Expand Down Expand Up @@ -399,6 +406,7 @@ export type ExpressApi = typeof ExpressPingApiSpec &
typeof ExpressWalletFanoutUnspentsApiSpec &
typeof ExpressV2WalletCreateAddressApiSpec &
typeof ExpressV2WalletIsWalletAddressApiSpec &
typeof ExpressV2AddressDeriveApiSpec &
typeof ExpressKeychainLocalApiSpec &
typeof ExpressKeychainChangePasswordApiSpec &
typeof ExpressLightningWalletPaymentApiSpec &
Expand Down Expand Up @@ -444,6 +452,7 @@ export const ExpressApi: ExpressApi = {
...ExpressV2WalletCreateAddressApiSpec,
...ExpressV2WalletConsolidateAccountApiSpec,
...ExpressV2WalletIsWalletAddressApiSpec,
...ExpressV2AddressDeriveApiSpec,
...ExpressKeychainLocalApiSpec,
...ExpressKeychainChangePasswordApiSpec,
...ExpressLightningWalletPaymentApiSpec,
Expand Down
96 changes: 96 additions & 0 deletions modules/express/src/typedRoutes/api/v2/deriveAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as t from 'io-ts';
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
import { BitgoExpressError } from '../../schemas/error';
import { CreateAddressFormat } from '../../schemas/address';

/**
* Path parameters for locally deriving a wallet address
*/
export const DeriveAddressParams = {
/** Blockchain identifier (e.g., 'btc', 'eth', 'tbtc', 'teth', 'sol') */
coin: t.string,
} as const;

/**
* A keychain entry for local derivation. Public key material only — no private keys.
* Modelled as a union so a keychain must carry at least one of `pub` / `commonKeychain`:
* - `pub` (xpub) for BIP32 multisig coins (UTXO, legacy EVM)
* - `commonKeychain` for TSS/MPC coins (SOL, EVM MPC) — identical across keychains
*
* (A keychain may legitimately carry both; TSS keychains commonly do.)
*/
export const DeriveAddressKeychainCodec = t.union([t.type({ pub: t.string }), t.type({ commonKeychain: t.string })]);

/**
* Request body for locally deriving a wallet receive address
*/
export const DeriveAddressBody = {
/**
* Keychains for derivation (public key material only).
* BIP32 multisig: the user/backup/bitgo xpub triple via `pub`.
* TSS/MPC: the `commonKeychain`.
*/
keychains: t.array(DeriveAddressKeychainCodec),
/** Derivation index for the address (caller-supplied; the endpoint is stateless) */
index: t.number,
/** Derivation chain code: UTXO script-type / external(0) vs internal(1) selector */
chain: optional(t.number),
/** Address format override (e.g. 'p2sh', 'p2wsh' for UTXO; 'cashaddr' / 'base58') */
format: optional(CreateAddressFormat),
/** Wallet version, to disambiguate derivation strategy (e.g. EVM forwarder vs MPC) */
walletVersion: optional(t.number),
/**
* Seed from the user keychain's derivedFromParentWithSeed field (SMC TSS wallets);
* makes the derivation path `{prefix}/{index}` instead of `m/{index}`.
*/
derivedFromParentWithSeed: optional(t.string),
} as const;

/**
* Response for locally deriving a wallet address
*/
export const DeriveAddressResponse = {
/** The derived address and related derivation info */
200: t.intersection([
t.type({
/** The derived address */
address: t.string,
/** The derivation index used */
index: t.number,
}),
t.partial({
/** The derivation chain code used */
chain: t.number,
/** Coin-specific address data (e.g. redeemScript/witnessScript for UTXO) */
coinSpecific: t.UnknownRecord,
/** The HD derivation path actually used */
derivationPath: t.string,
}),
]),
/** Invalid request parameters or derivation failed */
400: BitgoExpressError,
} as const;

/**
* Locally derive and return a wallet receive address from a derivation path.
*
* Unlike `iswalletaddress` (which checks a candidate address), this *produces* the address
* offline from public key material only — the xpub triple for BIP32 multisig coins, or the
* commonKeychain for TSS/MPC coins. No private keys, no wallet lookup, and no network access:
* the handler operates purely on the request body and can run in an air-gapped Express.
*
* Pairs with `iswalletaddress` for a derive→verify round-trip: derive the address here, then
* verify it against the same keychains to independently confirm correctness.
*
* @operationId express.v2.address.derive
* @tag Express
*/
export const PostDeriveAddress = httpRoute({
path: '/api/v2/{coin}/address/derive',
method: 'POST',
request: httpRequest({
params: DeriveAddressParams,
body: DeriveAddressBody,
}),
response: DeriveAddressResponse,
});
Loading
Loading