From 3f55f392117be5d72693091a1f84f6e8c43f4b4e Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Fri, 11 Sep 2026 11:48:41 +0200 Subject: [PATCH] feat(sdk-coin-btc): register tbtcstxprivate1 Register the isolated Stacks private-1 regtest coin identity while reusing the tbtcreg codec and Bitcoin testnet transaction semantics. Refs: WAL-2041 --- modules/abstract-utxo/src/impl/btc/index.ts | 1 + .../src/impl/btc/tbtcstxprivate1.ts | 17 +++++++++++ modules/abstract-utxo/src/names.ts | 14 ++++++++- .../test/unit/tbtcstxprivate1.ts | 29 +++++++++++++++++++ modules/bitgo/src/v2/coinFactory.ts | 2 ++ modules/bitgo/src/v2/coins/index.ts | 4 +-- modules/sdk-coin-btc/src/index.ts | 4 ++- modules/statics/src/networks.ts | 6 ++++ modules/statics/src/utxo.ts | 10 +++++++ .../unit/fixtures/expectedColdFeatures.ts | 1 + 10 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 modules/abstract-utxo/src/impl/btc/tbtcstxprivate1.ts create mode 100644 modules/abstract-utxo/test/unit/tbtcstxprivate1.ts diff --git a/modules/abstract-utxo/src/impl/btc/index.ts b/modules/abstract-utxo/src/impl/btc/index.ts index 57bbffc872..c72d9cf4dd 100644 --- a/modules/abstract-utxo/src/impl/btc/index.ts +++ b/modules/abstract-utxo/src/impl/btc/index.ts @@ -4,4 +4,5 @@ export * from './tbtc4'; export * from './tbtcsig'; export * from './tbtcbgsig'; export * from './tbtcstx'; +export * from './tbtcstxprivate1'; export * from './inscriptionBuilder'; diff --git a/modules/abstract-utxo/src/impl/btc/tbtcstxprivate1.ts b/modules/abstract-utxo/src/impl/btc/tbtcstxprivate1.ts new file mode 100644 index 0000000000..db493427a9 --- /dev/null +++ b/modules/abstract-utxo/src/impl/btc/tbtcstxprivate1.ts @@ -0,0 +1,17 @@ +import { BitGoBase } from '@bitgo/sdk-core'; + +import { UtxoCoinName } from '../../names'; + +import { Tbtcstx } from './tbtcstx'; + +export class Tbtcstxprivate1 extends Tbtcstx { + readonly name: UtxoCoinName = 'tbtcstxprivate1'; + + constructor(bitgo: BitGoBase) { + super(bitgo); + } + + static createInstance(bitgo: BitGoBase): Tbtcstxprivate1 { + return new Tbtcstxprivate1(bitgo); + } +} diff --git a/modules/abstract-utxo/src/names.ts b/modules/abstract-utxo/src/names.ts index da48699493..73644138b3 100644 --- a/modules/abstract-utxo/src/names.ts +++ b/modules/abstract-utxo/src/names.ts @@ -9,6 +9,7 @@ export const utxoCoinsTestnet = [ 'tbtcsig', 'tbtcbgsig', 'tbtcstx', + 'tbtcstxprivate1', 'tbch', 'tbcha', 'tbsv', @@ -21,7 +22,13 @@ export const utxoCoinsTestnet = [ ] as const; export type UtxoCoinNameMainnet = (typeof utxoCoinsMainnet)[number]; -export type UtxoCoinNameTestnet = `t${UtxoCoinNameMainnet}` | 'tbtcsig' | 'tbtc4' | 'tbtcbgsig' | 'tbtcstx'; +export type UtxoCoinNameTestnet = + | `t${UtxoCoinNameMainnet}` + | 'tbtcsig' + | 'tbtc4' + | 'tbtcbgsig' + | 'tbtcstx' + | 'tbtcstxprivate1'; export type UtxoCoinName = UtxoCoinNameMainnet | UtxoCoinNameTestnet; export function toWasmUtxoCoinName(coinName: UtxoCoinName | WasmUtxoCoinName): WasmUtxoCoinName { @@ -52,6 +59,7 @@ export function getMainnetCoinName(coinName: UtxoCoinName): UtxoCoinNameMainnet case 'tbtcsig': case 'tbtcbgsig': case 'tbtcstx': + case 'tbtcstxprivate1': return 'btc'; default: return coinName.slice(1) as UtxoCoinNameMainnet; @@ -84,6 +92,10 @@ function getBaseNameFromMainnet(coinName: UtxoCoinNameMainnet): string { } export function getFullNameFromCoinName(coinName: UtxoCoinName): string { + if (coinName === 'tbtcstxprivate1') { + return 'Stacks Bitcoin (Private-1 Regtest)'; + } + let prefix: string; switch (coinName) { case 'tbtc4': diff --git a/modules/abstract-utxo/test/unit/tbtcstxprivate1.ts b/modules/abstract-utxo/test/unit/tbtcstxprivate1.ts new file mode 100644 index 0000000000..6f0e4b89dc --- /dev/null +++ b/modules/abstract-utxo/test/unit/tbtcstxprivate1.ts @@ -0,0 +1,29 @@ +import assert from 'assert'; + +import { Networks } from '@bitgo/statics'; +import * as utxolib from '@bitgo/utxo-lib'; + +import { Tbtcstxprivate1 } from '../../src'; + +import { defaultBitGo, getNetworkForCoinName } from './util'; + +describe('tbtcstxprivate1', function () { + const coin = Tbtcstxprivate1.createInstance(defaultBitGo); + + it('uses the private-1 public coin name with the testnet utxolib network', function () { + assert.strictEqual(coin.getChain(), 'tbtcstxprivate1'); + assert.strictEqual(coin.getFullName(), 'Stacks Bitcoin (Private-1 Regtest)'); + assert.strictEqual(coin.wasmName, 'tbtcreg'); + assert.strictEqual(Networks.test.bitcoinStxPrivate1.utxolibName, 'testnet'); + assert.strictEqual(getNetworkForCoinName(coin.name), utxolib.networks.testnet); + }); + + it('encodes and decodes private-1 addresses through the tbtcreg codec', function () { + const script = Buffer.from(`0014${'11'.repeat(20)}`, 'hex'); + const address = coin.addressCodec.encode(script); + + assert.match(address, /^bcrt1q/); + assert.deepStrictEqual(Buffer.from(coin.addressCodec.decode(address)), script); + assert.strictEqual(coin.isValidAddress(address), true); + }); +}); diff --git a/modules/bitgo/src/v2/coinFactory.ts b/modules/bitgo/src/v2/coinFactory.ts index 65de5d08fa..bf57af815d 100644 --- a/modules/bitgo/src/v2/coinFactory.ts +++ b/modules/bitgo/src/v2/coinFactory.ts @@ -164,6 +164,7 @@ import { Tbtc4, Tbtcbgsig, Tbtcstx, + Tbtcstxprivate1, Tcanton, Tcelo, Tcoredao, @@ -340,6 +341,7 @@ export function registerCoinConstructors(coinFactory: CoinFactory, coinMap: Coin coinFactory.register('tbtc4', Tbtc4.createInstance); coinFactory.register('tbtcbgsig', Tbtcbgsig.createInstance); coinFactory.register('tbtcstx', Tbtcstx.createInstance); + coinFactory.register('tbtcstxprivate1', Tbtcstxprivate1.createInstance); coinFactory.register('tcanton', Tcanton.createInstance); coinFactory.register('tcelo', Tcelo.createInstance); coinFactory.register('tcoredao', Tcoredao.createInstance); diff --git a/modules/bitgo/src/v2/coins/index.ts b/modules/bitgo/src/v2/coins/index.ts index 786f858451..7dc2880656 100644 --- a/modules/bitgo/src/v2/coins/index.ts +++ b/modules/bitgo/src/v2/coins/index.ts @@ -16,7 +16,7 @@ import { Bera, Tbera, BeraToken } from '@bitgo/sdk-coin-bera'; import { Bld, Tbld } from '@bitgo/sdk-coin-bld'; import { Bsc, BscToken, Tbsc } from '@bitgo/sdk-coin-bsc'; import { Bsv, Tbsv } from '@bitgo/sdk-coin-bsv'; -import { Btc, Tbtc, Tbtcsig, Tbtc4, Tbtcbgsig, Tbtcstx } from '@bitgo/sdk-coin-btc'; +import { Btc, Tbtc, Tbtcsig, Tbtc4, Tbtcbgsig, Tbtcstx, Tbtcstxprivate1 } from '@bitgo/sdk-coin-btc'; import { Btg } from '@bitgo/sdk-coin-btg'; import { Canton, Tcanton } from '@bitgo/sdk-coin-canton'; import { Celo, CeloToken, Tcelo } from '@bitgo/sdk-coin-celo'; @@ -95,7 +95,7 @@ export { Bch, Tbch }; export { Bera, Tbera, BeraToken }; export { Bsc, BscToken, Tbsc }; export { Bsv, Tbsv }; -export { Btc, Tbtc, Tbtcsig, Tbtc4, Tbtcbgsig, Tbtcstx }; +export { Btc, Tbtc, Tbtcsig, Tbtc4, Tbtcbgsig, Tbtcstx, Tbtcstxprivate1 }; export { Btg }; export { Canton, Tcanton }; export { Celo, CeloToken, Tcelo }; diff --git a/modules/sdk-coin-btc/src/index.ts b/modules/sdk-coin-btc/src/index.ts index d9d896c193..0f623d0b2c 100644 --- a/modules/sdk-coin-btc/src/index.ts +++ b/modules/sdk-coin-btc/src/index.ts @@ -1,5 +1,5 @@ import { BitGoBase } from '@bitgo/sdk-core'; -import { Btc, Tbtc, Tbtc4, Tbtcsig, Tbtcbgsig, Tbtcstx } from '@bitgo/abstract-utxo'; +import { Btc, Tbtc, Tbtc4, Tbtcsig, Tbtcbgsig, Tbtcstx, Tbtcstxprivate1 } from '@bitgo/abstract-utxo'; export { Btc, VerifyRecoveryTransactionOptions, InscriptionBuilder } from '@bitgo/abstract-utxo'; export { Tbtc } from '@bitgo/abstract-utxo'; @@ -7,6 +7,7 @@ export { Tbtcsig } from '@bitgo/abstract-utxo'; export { Tbtc4 } from '@bitgo/abstract-utxo'; export { Tbtcbgsig } from '@bitgo/abstract-utxo'; export { Tbtcstx } from '@bitgo/abstract-utxo'; +export { Tbtcstxprivate1 } from '@bitgo/abstract-utxo'; export const register = (sdk: BitGoBase): void => { sdk.register('btc', Btc.createInstance); @@ -15,4 +16,5 @@ export const register = (sdk: BitGoBase): void => { sdk.register('tbtcbgsig', Tbtcbgsig.createInstance); sdk.register('tbtc4', Tbtc4.createInstance); sdk.register('tbtcstx', Tbtcstx.createInstance); + sdk.register('tbtcstxprivate1', Tbtcstxprivate1.createInstance); }; diff --git a/modules/statics/src/networks.ts b/modules/statics/src/networks.ts index 36bc66cdf9..370f0e49ae 100644 --- a/modules/statics/src/networks.ts +++ b/modules/statics/src/networks.ts @@ -549,6 +549,11 @@ class BitcoinStx extends Testnet implements UtxoNetwork { explorerUrl = undefined; } +class BitcoinStxPrivate1 extends BitcoinStx { + name = 'BitcoinStxPrivate1'; + // Stacks private-1 regtest uses testnet transaction semantics in utxo-lib. +} + class BitcoinCash extends Mainnet implements UtxoNetwork { name = 'BitcoinCash'; family = CoinFamily.BCH; @@ -3079,6 +3084,7 @@ export const Networks = { bitcoinTestnet4: Object.freeze(new BitcoinTestnet4()), bitcoinBitGoSignet: Object.freeze(new BitcoinBitGoSignet()), bitcoinStx: Object.freeze(new BitcoinStx()), + bitcoinStxPrivate1: Object.freeze(new BitcoinStxPrivate1()), bitcoinCash: Object.freeze(new BitcoinCashTestnet()), bitcoinGold: Object.freeze(new BitcoinGoldTestnet()), bitcoinSV: Object.freeze(new BitcoinSVTestnet()), diff --git a/modules/statics/src/utxo.ts b/modules/statics/src/utxo.ts index c39c575ee7..66a40398be 100644 --- a/modules/statics/src/utxo.ts +++ b/modules/statics/src/utxo.ts @@ -271,6 +271,16 @@ export const utxoCoins: Readonly[] = [ BaseUnit.BTC, BTC_FEATURES ), + utxo( + 'b1f9e6d0-8a4c-4c67-9d0a-2c7e5b8f3146', + 'tbtcstxprivate1', + // Stacks private-1 Bitcoin regtest, represented by Bitcoin testnet transaction semantics in utxo-lib. + 'Stacks Bitcoin (Private-1 Regtest)', + Networks.test.bitcoinStxPrivate1, + UnderlyingAsset.BTC, + BaseUnit.BTC, + BTC_FEATURES + ), utxo( '8feb110d-0d68-44ce-ae97-b8c30ec870a9', 'btg', diff --git a/modules/statics/test/unit/fixtures/expectedColdFeatures.ts b/modules/statics/test/unit/fixtures/expectedColdFeatures.ts index 8adcbe7deb..0eed084328 100644 --- a/modules/statics/test/unit/fixtures/expectedColdFeatures.ts +++ b/modules/statics/test/unit/fixtures/expectedColdFeatures.ts @@ -50,6 +50,7 @@ export const expectedColdFeatures = { 'tbtc4', 'tbtcbgsig', 'tbtcstx', + 'tbtcstxprivate1', 'tcelo', 'tcspr', 'tdash',