Skip to content

Commit d0581f1

Browse files
Merge pull request #7903 from BitGo/BTC-2916.wasmify-more
feat(abstract-utxo): migrate to wasm-utxo and use coin name instead of network
2 parents 2701c9d + a71a702 commit d0581f1

13 files changed

Lines changed: 73 additions & 52 deletions

File tree

modules/abstract-utxo/src/address/fixedScript.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,15 @@ import {
1212
isTriple,
1313
Triple,
1414
} from '@bitgo/sdk-core';
15-
import { bitgo } from '@bitgo/utxo-lib';
16-
import * as wasmUtxo from '@bitgo/wasm-utxo';
15+
import { fixedScriptWallet } from '@bitgo/wasm-utxo';
1716

18-
import { getNetworkFromCoinName, UtxoCoinName } from '../names';
17+
import { UtxoCoinName } from '../names';
1918

20-
type ScriptType2Of3 = bitgo.outputScripts.ScriptType2Of3;
19+
type ScriptType2Of3 = fixedScriptWallet.OutputScriptType;
20+
type ChainCode = fixedScriptWallet.ChainCode;
2121

2222
export interface FixedScriptAddressCoinSpecific {
2323
outputScript?: string;
24-
redeemScript?: string;
25-
witnessScript?: string;
2624
}
2725

2826
export interface GenerateAddressOptions {
@@ -39,22 +37,27 @@ interface GenerateFixedScriptAddressOptions extends GenerateAddressOptions {
3937
}
4038

4139
function supportsAddressType(coinName: UtxoCoinName, addressType: ScriptType2Of3): boolean {
42-
const network = getNetworkFromCoinName(coinName);
43-
return bitgo.outputScripts.isSupportedScriptType(network, addressType);
40+
return fixedScriptWallet.supportsScriptType(coinName, addressType);
41+
}
42+
43+
/**
44+
* Normalize script type aliases. "p2tr" is an alias for "p2trLegacy".
45+
*/
46+
function normalizeScriptType(scriptType: ScriptType2Of3 | 'p2tr'): ScriptType2Of3 {
47+
return scriptType === 'p2tr' ? 'p2trLegacy' : scriptType;
4448
}
4549

4650
export function generateAddressWithChainAndIndex(
4751
coinName: UtxoCoinName,
48-
keychains: bitgo.RootWalletKeys | Triple<string>,
49-
chain: bitgo.ChainCode,
52+
keychains: fixedScriptWallet.WalletKeysArg | Triple<string>,
53+
chain: ChainCode,
5054
index: number,
5155
format: CreateAddressFormat | undefined
5256
): string {
5357
// Convert CreateAddressFormat to AddressFormat for wasm-utxo
5458
// 'base58' -> 'default', 'cashaddr' -> 'cashaddr'
5559
const wasmFormat = format === 'base58' ? 'default' : format;
56-
const network = getNetworkFromCoinName(coinName);
57-
return wasmUtxo.fixedScriptWallet.address(keychains, chain, index, network, wasmFormat);
60+
return fixedScriptWallet.address(keychains, chain, index, coinName, wasmFormat);
5861
}
5962

6063
/**
@@ -77,14 +80,14 @@ export function generateAddress(coinName: UtxoCoinName, params: GenerateFixedScr
7780

7881
const { keychains, chain, segwit = false, bech32 = false } = params as GenerateFixedScriptAddressOptions;
7982

80-
let derivationChain = bitgo.getExternalChainCode('p2sh');
81-
if (_.isNumber(chain) && _.isInteger(chain) && bitgo.isChainCode(chain)) {
83+
let derivationChain: ChainCode = fixedScriptWallet.ChainCode.value('p2sh', 'external');
84+
if (_.isNumber(chain) && _.isInteger(chain) && fixedScriptWallet.ChainCode.is(chain)) {
8285
derivationChain = chain;
8386
}
8487

8588
function convertFlagsToAddressType(): ScriptType2Of3 {
86-
if (bitgo.isChainCode(chain)) {
87-
return bitgo.scriptTypeForChain(chain);
89+
if (fixedScriptWallet.ChainCode.is(chain)) {
90+
return fixedScriptWallet.ChainCode.scriptType(chain);
8891
}
8992
if (_.isBoolean(segwit) && segwit) {
9093
return 'p2shP2wsh';
@@ -95,9 +98,9 @@ export function generateAddress(coinName: UtxoCoinName, params: GenerateFixedScr
9598
}
9699
}
97100

98-
const addressType = params.addressType || convertFlagsToAddressType();
101+
const addressType = normalizeScriptType(params.addressType || convertFlagsToAddressType());
99102

100-
if (addressType !== bitgo.scriptTypeForChain(derivationChain)) {
103+
if (addressType !== fixedScriptWallet.ChainCode.scriptType(derivationChain)) {
101104
throw new AddressTypeChainMismatchError(addressType, derivationChain);
102105
}
103106

@@ -110,6 +113,7 @@ export function generateAddress(coinName: UtxoCoinName, params: GenerateFixedScr
110113
case 'p2wsh':
111114
throw new P2wshUnsupportedError();
112115
case 'p2tr':
116+
case 'p2trLegacy':
113117
throw new P2trUnsupportedError();
114118
case 'p2trMusig2':
115119
throw new P2trMusig2UnsupportedError();

modules/abstract-utxo/test/unit/coins.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ import * as assert from 'assert';
22

33
import * as utxolib from '@bitgo/utxo-lib';
44

5-
import { getMainnetCoinName, utxoCoinsMainnet, utxoCoinsTestnet } from '../../src/names';
5+
import { getMainnetCoinName, getNetworkFromCoinName, utxoCoinsMainnet, utxoCoinsTestnet } from '../../src/names';
66

77
import { getUtxoCoinForNetwork, utxoCoins } from './util';
88

99
describe('utxoCoins', function () {
1010
it('has expected chain/network values for items', function () {
1111
assert.deepStrictEqual(
12-
utxoCoins.map((c) => [c.getChain(), c.getFamily(), c.getFullName(), utxolib.getNetworkName(c.network)]),
12+
utxoCoins.map((c) => [
13+
c.getChain(),
14+
c.getFamily(),
15+
c.getFullName(),
16+
utxolib.getNetworkName(getNetworkFromCoinName(c.name)),
17+
]),
1318
[
1419
['btc', 'btc', 'Bitcoin', 'bitcoin'],
1520
['tbtc', 'btc', 'Testnet Bitcoin', 'testnet'],

modules/abstract-utxo/test/unit/descriptorAddress.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import * as assert from 'node:assert';
22

33
import * as utxolib from '@bitgo/utxo-lib';
4+
import { address as wasmAddress, CoinName } from '@bitgo/wasm-utxo';
45
import { IWallet, WalletCoinSpecific } from '@bitgo/sdk-core';
56

67
import { descriptor as utxod } from '../../src';
78

89
import { getUtxoCoin } from './util';
910

10-
export function getDescriptorAddress(d: string, index: number, network: utxolib.Network): string {
11-
const derivedScript = Buffer.from(
12-
utxod.Descriptor.fromString(d, 'derivable').atDerivationIndex(index).scriptPubkey()
13-
);
14-
return utxolib.address.fromOutputScript(derivedScript, network);
11+
export function getDescriptorAddress(d: string, index: number, coinName: CoinName): string {
12+
const derivedScript = utxod.Descriptor.fromString(d, 'derivable').atDerivationIndex(index).scriptPubkey();
13+
return wasmAddress.fromOutputScriptWithCoin(derivedScript, coinName);
1514
}
1615

1716
describe('descriptor wallets', function () {
@@ -40,9 +39,9 @@ describe('descriptor wallets', function () {
4039

4140
const descFoo = getNamedDescriptor2Of2('foo', xpubs[0], xpubs[1]);
4241
const descBar = getNamedDescriptor2Of2('bar', xpubs[1], xpubs[0]);
43-
const addressFoo0 = getDescriptorAddress(descFoo.value, 0, coin.network);
44-
const addressFoo1 = getDescriptorAddress(descFoo.value, 1, coin.network);
45-
const addressBar0 = getDescriptorAddress(descBar.value, 0, coin.network);
42+
const addressFoo0 = getDescriptorAddress(descFoo.value, 0, coin.name);
43+
const addressFoo1 = getDescriptorAddress(descFoo.value, 1, coin.name);
44+
const addressBar0 = getDescriptorAddress(descBar.value, 0, coin.name);
4645

4746
it('has expected values', function () {
4847
assert.deepStrictEqual(

modules/abstract-utxo/test/unit/prebuildAndSign.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { common, HalfSignedUtxoTransaction, Wallet } from '@bitgo/sdk-core';
66
import { getSeed } from '@bitgo/sdk-test';
77

88
import { AbstractUtxoCoin, getReplayProtectionAddresses } from '../../src';
9+
import { getMainnetCoinName } from '../../src/names';
910

1011
import { defaultBitGo, encryptKeychain, getDefaultWalletKeys, getUtxoWallet, keychainsBase58, utxoCoins } from './util';
1112

@@ -295,7 +296,7 @@ function run(coin: AbstractUtxoCoin, inputScripts: ScriptType[], txFormat: TxFor
295296
}
296297

297298
utxoCoins
298-
.filter((coin) => utxolib.getMainnet(coin.network) !== utxolib.networks.bitcoinsv)
299+
.filter((coin) => getMainnetCoinName(coin.name) !== 'bsv')
299300
.forEach((coin) => {
300301
scriptTypes
301302
// Don't iterate over p2shP2pk - in no scenario would a wallet spend two p2shP2pk inputs as these

modules/abstract-utxo/test/unit/recovery/backupKeyRecovery.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { BIP32Interface } from '@bitgo/utxo-lib';
88
import * as utxolib from '@bitgo/utxo-lib';
99
import { Config, krsProviders, Triple } from '@bitgo/sdk-core';
1010
import { Dimensions } from '@bitgo/unspents';
11-
import { fixedScriptWallet } from '@bitgo/wasm-utxo';
11+
import { address as wasmAddress, fixedScriptWallet } from '@bitgo/wasm-utxo';
1212

1313
import {
1414
AbstractUtxoCoin,
@@ -17,6 +17,7 @@ import {
1717
CoingeckoApi,
1818
FormattedOfflineVaultTxInfo,
1919
} from '../../../src';
20+
import { getCoinName } from '../../../src/names';
2021
import {
2122
defaultBitGo,
2223
encryptKeychain,
@@ -319,7 +320,9 @@ function run(
319320
it((params.hasKrsOutput ? 'has' : 'has no') + ' key recovery service output', function () {
320321
const outs = recoveryTx instanceof utxolib.bitgo.UtxoPsbt ? recoveryTx.getUnsignedTx().outs : recoveryTx.outs;
321322
outs.length.should.eql(1);
322-
const outputAddresses = outs.map((o) => utxolib.address.fromOutputScript(o.script, recoveryTx.network));
323+
const outputAddresses = outs.map((o) =>
324+
wasmAddress.fromOutputScriptWithCoin(o.script, getCoinName(recoveryTx.network))
325+
);
323326
outputAddresses
324327
.includes(keyRecoveryServiceAddress)
325328
.should.eql(!!params.hasKrsOutput && params.krsProvider === 'keyternal');

modules/abstract-utxo/test/unit/recovery/crossChainRecovery.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
generateAddress,
1717
convertLtcAddressToLegacyFormat,
1818
} from '../../../src';
19+
import { isMainnetCoin, isTestnetCoin } from '../../../src/names';
1920
import {
2021
getFixture,
2122
keychainsBase58,
@@ -265,8 +266,8 @@ utxoCoins.forEach((coin) => {
265266
(otherCoin) =>
266267
coin !== otherCoin &&
267268
isSupportedCrossChainRecovery(coin, otherCoin) &&
268-
((utxolib.isMainnet(coin.network) && utxolib.isMainnet(otherCoin.network)) ||
269-
(utxolib.isTestnet(coin.network) && utxolib.isTestnet(otherCoin.network)))
269+
((isMainnetCoin(coin.name) && isMainnetCoin(otherCoin.name)) ||
270+
(isTestnetCoin(coin.name) && isTestnetCoin(otherCoin.name)))
270271
)
271272
.forEach((otherCoin) => {
272273
if (coin.amountType === 'bigint') {

modules/abstract-utxo/test/unit/recovery/mock.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { bitgo } from '@bitgo/utxo-lib';
22
import { AddressInfo, TransactionIO } from '@bitgo/blockapis';
33
import * as utxolib from '@bitgo/utxo-lib';
4+
import { address as wasmAddress, AddressFormat } from '@bitgo/wasm-utxo';
45

56
import { AbstractUtxoCoin, RecoveryProvider } from '../../../src';
67
import { Bch } from '../../../src/impl/bch';
@@ -51,7 +52,7 @@ export class MockRecoveryProvider implements RecoveryProvider {
5152
}
5253
export class MockCrossChainRecoveryProvider<TNumber extends number | bigint> implements RecoveryProvider {
5354
private addressVersion: 'cashaddr' | 'base58';
54-
private addressFormat: utxolib.addressFormat.AddressFormat;
55+
private addressFormat: AddressFormat;
5556
constructor(
5657
public coin: AbstractUtxoCoin,
5758
public unspents: Unspent<TNumber>[],
@@ -65,7 +66,7 @@ export class MockCrossChainRecoveryProvider<TNumber extends number | bigint> imp
6566

6667
async getUnspentsForAddresses(addresses: string[]): Promise<Unspent[]> {
6768
return this.tx.outs.map((o, vout: number) => {
68-
let address = utxolib.addressFormat.fromOutputScriptWithFormat(o.script, this.addressFormat, this.coin.network);
69+
let address = wasmAddress.fromOutputScriptWithCoin(o.script, this.coin.name, this.addressFormat);
6970
if (address.includes(':')) {
7071
[, address] = address.split(':');
7172
}
@@ -91,7 +92,7 @@ export class MockCrossChainRecoveryProvider<TNumber extends number | bigint> imp
9192
};
9293
}),
9394
outputs: this.tx.outs.map((o) => {
94-
let address = utxolib.addressFormat.fromOutputScriptWithFormat(o.script, this.addressFormat, this.coin.network);
95+
let address = wasmAddress.fromOutputScriptWithCoin(o.script, this.coin.name, this.addressFormat);
9596
if (address.includes(':')) {
9697
[, address] = address.split(':');
9798
}

modules/abstract-utxo/test/unit/transaction.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as _ from 'lodash';
55
import * as utxolib from '@bitgo/utxo-lib';
66
import nock = require('nock');
77
import { BIP32Interface, bitgo, testutil } from '@bitgo/utxo-lib';
8+
import { address as wasmAddress } from '@bitgo/wasm-utxo';
89
import {
910
common,
1011
FullySignedTransaction,
@@ -507,7 +508,7 @@ function run<TNumber extends number | bigint = number>(
507508
: getUnspents();
508509
const prevOutputs = unspents.map(
509510
(u): utxolib.TxOutput<TNumber> => ({
510-
script: utxolib.address.toOutputScript(u.address, coin.network),
511+
script: Buffer.from(wasmAddress.toOutputScriptWithCoin(u.address, coin.name)),
511512
value: u.value,
512513
})
513514
);

modules/abstract-utxo/test/unit/transaction/fixedScript/parsePsbt.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
33
import * as sinon from 'sinon';
44
import * as utxolib from '@bitgo/utxo-lib';
55
import { Wallet, VerificationOptions, ITransactionRecipient, Triple } from '@bitgo/sdk-core';
6-
import { fixedScriptWallet } from '@bitgo/wasm-utxo';
6+
import { address as wasmAddress, fixedScriptWallet } from '@bitgo/wasm-utxo';
77

88
import { parseTransaction } from '../../../../src/transaction/fixedScript/parseTransaction';
99
import { ParsedTransaction } from '../../../../src/transaction/types';
@@ -63,7 +63,7 @@ function getChangeInfoFromPsbt(psbt: utxolib.bitgo.UtxoPsbt): ChangeAddressInfo[
6363
const path = derivations[0].path;
6464
const { chain, index } = utxolib.bitgo.getChainAndIndexFromPath(path);
6565
return {
66-
address: utxolib.address.fromOutputScript(psbt.txOutputs[i].script, psbt.network),
66+
address: wasmAddress.fromOutputScriptWithCoin(psbt.txOutputs[i].script, getCoinName(psbt.network)),
6767
chain,
6868
index,
6969
};

modules/abstract-utxo/test/unit/txFormat.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as assert from 'assert';
22

3-
import * as utxolib from '@bitgo/utxo-lib';
43
import { Wallet } from '@bitgo/sdk-core';
54

65
import { AbstractUtxoCoin, ErrorDeprecatedTxFormat, TxFormat } from '../../src';
6+
import { getMainnetCoinName, isMainnetCoin, isTestnetCoin } from '../../src/names';
77

88
import { utxoCoins, defaultBitGo } from './util';
99

@@ -106,39 +106,38 @@ describe('txFormat', function () {
106106
// All testnet wallets default to PSBT-lite
107107
runTest({
108108
description: 'should always return psbt-lite for testnet',
109-
coinFilter: (coin) => utxolib.isTestnet(coin.network),
109+
coinFilter: (coin) => isTestnetCoin(coin.name),
110110
expectedTxFormat: 'psbt-lite',
111111
});
112112

113113
// DistributedCustody wallets default to PSBT (mainnet only, testnet already covered)
114114
runTest({
115115
description: 'should return psbt for distributedCustody wallets on mainnet',
116-
coinFilter: (coin) => utxolib.isMainnet(coin.network),
116+
coinFilter: (coin) => isMainnetCoin(coin.name),
117117
walletFilter: (w) => w.options.subType === 'distributedCustody',
118118
expectedTxFormat: 'psbt',
119119
});
120120

121121
// MuSig2 wallets default to PSBT (mainnet only, testnet already covered)
122122
runTest({
123123
description: 'should return psbt for wallets with musigKp flag on mainnet',
124-
coinFilter: (coin) => utxolib.isMainnet(coin.network),
124+
coinFilter: (coin) => isMainnetCoin(coin.name),
125125
walletFilter: (w) => Boolean(w.options.walletFlags?.some((f) => f.name === 'musigKp' && f.value === 'true')),
126126
expectedTxFormat: 'psbt',
127127
});
128128

129129
// Mainnet Bitcoin hot wallets default to PSBT
130130
runTest({
131131
description: 'should return psbt for mainnet bitcoin hot wallets',
132-
coinFilter: (coin) =>
133-
utxolib.isMainnet(coin.network) && utxolib.getMainnet(coin.network) === utxolib.networks.bitcoin,
132+
coinFilter: (coin) => isMainnetCoin(coin.name) && getMainnetCoinName(coin.name) === 'btc',
134133
walletFilter: (w) => w.options.type === 'hot',
135134
expectedTxFormat: 'psbt',
136135
});
137136

138137
// Other mainnet wallets do NOT default to PSBT
139138
runTest({
140139
description: 'should return undefined for other mainnet wallets',
141-
coinFilter: (coin) => utxolib.isMainnet(coin.network),
140+
coinFilter: (coin) => isMainnetCoin(coin.name),
142141
walletFilter: (w) => {
143142
const isHotBitcoin = w.options.type === 'hot'; // This will be bitcoin hot wallets
144143
const isDistributedCustody = w.options.subType === 'distributedCustody';
@@ -152,7 +151,7 @@ describe('txFormat', function () {
152151
// Test explicitly requested formats
153152
runTest({
154153
description: 'should respect explicitly requested legacy format on mainnet',
155-
coinFilter: (coin) => utxolib.isMainnet(coin.network),
154+
coinFilter: (coin) => isMainnetCoin(coin.name),
156155
expectedTxFormat: 'legacy',
157156
requestedTxFormat: 'legacy',
158157
});
@@ -172,7 +171,7 @@ describe('txFormat', function () {
172171
// Test that legacy format is prohibited on testnet
173172
it('should throw ErrorDeprecatedTxFormat when legacy format is requested on testnet', function () {
174173
for (const coin of utxoCoins) {
175-
if (!utxolib.isTestnet(coin.network)) {
174+
if (!isTestnetCoin(coin.name)) {
176175
continue;
177176
}
178177

0 commit comments

Comments
 (0)