Skip to content

Commit dc8384e

Browse files
Merge pull request #7891 from BitGo/BTC-2916.use-coinname
feat(abstract-utxo): deprecate `network` for AbstractUtxoCoin internally
2 parents b32a426 + bd9ace7 commit dc8384e

36 files changed

Lines changed: 220 additions & 165 deletions

modules/abstract-utxo/src/abstractUtxoCoin.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { randomBytes } from 'crypto';
44
import _ from 'lodash';
55
import * as utxolib from '@bitgo/utxo-lib';
66
import { bip32 } from '@bitgo/secp256k1';
7-
import { bitgo, getMainnet, isMainnet, isTestnet } from '@bitgo/utxo-lib';
7+
import { bitgo, getMainnet, isMainnet } from '@bitgo/utxo-lib';
88
import {
99
AddressCoinSpecific,
1010
BaseCoin,
@@ -80,6 +80,7 @@ import {
8080
getFullNameFromCoinName,
8181
getMainnetCoinName,
8282
getNetworkFromCoinName,
83+
isTestnetCoin,
8384
UtxoCoinName,
8485
UtxoCoinNameMainnet,
8586
} from './names';
@@ -387,7 +388,10 @@ export abstract class AbstractUtxoCoin
387388
this.amountType = amountType;
388389
}
389390

390-
/** @deprecated - will be removed when we drop support for utxolib */
391+
/**
392+
* @deprecated - will be removed when we drop support for utxolib
393+
* Use `name` property instead.
394+
*/
391395
get network(): utxolib.Network {
392396
return getNetworkFromCoinName(this.name);
393397
}
@@ -546,7 +550,7 @@ export abstract class AbstractUtxoCoin
546550
}
547551

548552
if (utxolib.bitgo.isPsbt(input)) {
549-
return decodePsbtWith(input, this.network, decodeWith);
553+
return decodePsbtWith(input, this.name, decodeWith);
550554
} else {
551555
if (decodeWith !== 'utxolib') {
552556
console.error('received decodeWith hint %s, ignoring for legacy transaction', decodeWith);
@@ -688,7 +692,7 @@ export abstract class AbstractUtxoCoin
688692
throw new Error('keychains must be a triple');
689693
}
690694
assertDescriptorWalletAddress(
691-
this.network,
695+
this.name,
692696
params,
693697
getDescriptorMapFromWallet(wallet, toBip32Triple(keychains), getPolicyForEnv(this.bitgo.env))
694698
);
@@ -705,7 +709,7 @@ export abstract class AbstractUtxoCoin
705709
throw new Error('missing required param keychains');
706710
}
707711

708-
assertFixedScriptWalletAddress(this.network, {
712+
assertFixedScriptWalletAddress(this.name, {
709713
address,
710714
keychains,
711715
format: params.format ?? 'base58',
@@ -763,9 +767,9 @@ export abstract class AbstractUtxoCoin
763767
.send({ psbt: buffer.toString('hex') })
764768
.result();
765769
if (psbt instanceof utxolib.bitgo.UtxoPsbt) {
766-
return decodePsbtWith(response.psbt, this.network, 'utxolib') as T;
770+
return decodePsbtWith(response.psbt, this.name, 'utxolib') as T;
767771
} else {
768-
return decodePsbtWith(response.psbt, this.network, 'wasm-utxo') as T;
772+
return decodePsbtWith(response.psbt, this.name, 'wasm-utxo') as T;
769773
}
770774
}
771775

@@ -862,7 +866,7 @@ export abstract class AbstractUtxoCoin
862866
* @returns {boolean}
863867
*/
864868
isBitGoTaintedUnspent<TNumber extends number | bigint>(unspent: Unspent<TNumber>): boolean {
865-
return isReplayProtectionUnspent<TNumber>(unspent, this.network);
869+
return isReplayProtectionUnspent(unspent, this.name);
866870
}
867871

868872
/**
@@ -873,7 +877,7 @@ export abstract class AbstractUtxoCoin
873877
override async explainTransaction<TNumber extends number | bigint = number>(
874878
params: ExplainTransactionOptions<TNumber>
875879
): Promise<TransactionExplanation> {
876-
return explainTx(this.decodeTransactionFromPrebuild(params), params, this.network);
880+
return explainTx(this.decodeTransactionFromPrebuild(params), params, this.name);
877881
}
878882

879883
/**
@@ -968,14 +972,14 @@ export abstract class AbstractUtxoCoin
968972
getDefaultTxFormat(wallet: Wallet, requestedFormat?: TxFormat): TxFormat | undefined {
969973
// If format is explicitly requested, use it
970974
if (requestedFormat !== undefined) {
971-
if (isTestnet(this.network) && requestedFormat === 'legacy') {
975+
if (isTestnetCoin(this.name) && requestedFormat === 'legacy') {
972976
throw new ErrorDeprecatedTxFormat(requestedFormat);
973977
}
974978

975979
return requestedFormat;
976980
}
977981

978-
if (isTestnet(this.network)) {
982+
if (isTestnetCoin(this.name)) {
979983
return 'psbt-lite';
980984
}
981985

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import {
1212
isTriple,
1313
Triple,
1414
} from '@bitgo/sdk-core';
15-
import * as utxolib from '@bitgo/utxo-lib';
1615
import { bitgo } from '@bitgo/utxo-lib';
1716
import * as wasmUtxo from '@bitgo/wasm-utxo';
1817

18+
import { getNetworkFromCoinName, UtxoCoinName } from '../names';
19+
1920
type ScriptType2Of3 = bitgo.outputScripts.ScriptType2Of3;
2021

2122
export interface FixedScriptAddressCoinSpecific {
@@ -37,12 +38,13 @@ interface GenerateFixedScriptAddressOptions extends GenerateAddressOptions {
3738
keychains: { pub: string }[];
3839
}
3940

40-
function supportsAddressType(network: utxolib.Network, addressType: ScriptType2Of3): boolean {
41-
return utxolib.bitgo.outputScripts.isSupportedScriptType(network, addressType);
41+
function supportsAddressType(coinName: UtxoCoinName, addressType: ScriptType2Of3): boolean {
42+
const network = getNetworkFromCoinName(coinName);
43+
return bitgo.outputScripts.isSupportedScriptType(network, addressType);
4244
}
4345

4446
export function generateAddressWithChainAndIndex(
45-
network: utxolib.Network,
47+
coinName: UtxoCoinName,
4648
keychains: bitgo.RootWalletKeys | Triple<string>,
4749
chain: bitgo.ChainCode,
4850
index: number,
@@ -51,6 +53,7 @@ export function generateAddressWithChainAndIndex(
5153
// Convert CreateAddressFormat to AddressFormat for wasm-utxo
5254
// 'base58' -> 'default', 'cashaddr' -> 'cashaddr'
5355
const wasmFormat = format === 'base58' ? 'default' : format;
56+
const network = getNetworkFromCoinName(coinName);
5457
return wasmUtxo.fixedScriptWallet.address(keychains, chain, index, network, wasmFormat);
5558
}
5659

@@ -66,7 +69,7 @@ export function generateAddressWithChainAndIndex(
6669
* @param params.bech32 {boolean} Deprecated
6770
* @returns {string} The generated address
6871
*/
69-
export function generateAddress(network: utxolib.Network, params: GenerateFixedScriptAddressOptions): string {
72+
export function generateAddress(coinName: UtxoCoinName, params: GenerateFixedScriptAddressOptions): string {
7073
let derivationIndex = 0;
7174
if (_.isInteger(params.index) && (params.index as number) > 0) {
7275
derivationIndex = params.index as number;
@@ -94,11 +97,11 @@ export function generateAddress(network: utxolib.Network, params: GenerateFixedS
9497

9598
const addressType = params.addressType || convertFlagsToAddressType();
9699

97-
if (addressType !== utxolib.bitgo.scriptTypeForChain(derivationChain)) {
100+
if (addressType !== bitgo.scriptTypeForChain(derivationChain)) {
98101
throw new AddressTypeChainMismatchError(addressType, derivationChain);
99102
}
100103

101-
if (!supportsAddressType(network, addressType)) {
104+
if (!supportsAddressType(coinName, addressType)) {
102105
switch (addressType) {
103106
case 'p2sh':
104107
throw new Error(`internal error: p2sh should always be supported`);
@@ -120,7 +123,7 @@ export function generateAddress(network: utxolib.Network, params: GenerateFixedS
120123
}
121124

122125
return generateAddressWithChainAndIndex(
123-
network,
126+
coinName,
124127
keychains.map((k) => k.pub) as Triple<string>,
125128
derivationChain,
126129
derivationIndex,
@@ -133,7 +136,7 @@ type Keychain = {
133136
};
134137

135138
export function assertFixedScriptWalletAddress(
136-
network: utxolib.Network,
139+
coinName: UtxoCoinName,
137140
{
138141
chain,
139142
index,
@@ -160,7 +163,7 @@ export function assertFixedScriptWalletAddress(
160163
throw new Error('missing required param keychains');
161164
}
162165

163-
const expectedAddress = generateAddress(network, {
166+
const expectedAddress = generateAddress(coinName, {
164167
format,
165168
addressType: addressType as ScriptType2Of3,
166169
keychains,

modules/abstract-utxo/src/descriptor/assertDescriptorWalletAddress.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Descriptor } from '@bitgo/wasm-utxo';
55
import { DescriptorMap } from '@bitgo/utxo-core/descriptor';
66

77
import { UtxoCoinSpecific, VerifyAddressOptions } from '../abstractUtxoCoin';
8+
import { getNetworkFromCoinName, UtxoCoinName } from '../names';
89

910
class DescriptorAddressMismatchError extends Error {
1011
constructor(descriptor: Descriptor, index: number, derivedAddress: string, expectedAddress: string) {
@@ -15,7 +16,7 @@ class DescriptorAddressMismatchError extends Error {
1516
}
1617

1718
export function assertDescriptorWalletAddress(
18-
network: utxolib.Network,
19+
coinName: UtxoCoinName,
1920
params: VerifyAddressOptions<UtxoCoinSpecific>,
2021
descriptors: DescriptorMap
2122
): void {
@@ -34,6 +35,7 @@ export function assertDescriptorWalletAddress(
3435
);
3536
}
3637
const derivedScript = Buffer.from(descriptor.atDerivationIndex(params.index).scriptPubkey());
38+
const network = getNetworkFromCoinName(coinName);
3739
const derivedAddress = utxolib.address.fromOutputScript(derivedScript, network);
3840
if (params.address !== derivedAddress) {
3941
throw new DescriptorAddressMismatchError(descriptor, params.index, derivedAddress, params.address);

modules/abstract-utxo/src/names.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,11 @@ export function getFullNameFromCoinName(coinName: UtxoCoinName): string {
193193
export function getFullNameFromNetwork(n: utxolib.Network): string {
194194
return getFullNameFromCoinName(getCoinName(n));
195195
}
196+
197+
export function isTestnetCoin(coinName: UtxoCoinName): boolean {
198+
return isUtxoCoinNameTestnet(coinName);
199+
}
200+
201+
export function isMainnetCoin(coinName: UtxoCoinName): boolean {
202+
return isUtxoCoinNameMainnet(coinName);
203+
}

modules/abstract-utxo/src/offlineVault/OfflineVaultHalfSigned.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { BIP32Interface, bip32 } from '@bitgo/secp256k1';
22
import * as utxolib from '@bitgo/utxo-lib';
33
import { BaseCoin } from '@bitgo/sdk-core';
44

5-
import { getNetworkFromChain } from '../names';
5+
import { UtxoCoinName } from '../names';
66

77
import { OfflineVaultSignable } from './OfflineVaultSignable';
88
import { DescriptorTransaction, getHalfSignedPsbt } from './descriptor';
@@ -16,12 +16,11 @@ function createHalfSignedFromPsbt(psbt: utxolib.Psbt): OfflineVaultHalfSigned {
1616
}
1717

1818
export function createHalfSigned(
19-
coin: string,
19+
coinName: UtxoCoinName,
2020
prv: string | BIP32Interface,
2121
derivationId: string,
2222
tx: unknown
2323
): OfflineVaultHalfSigned {
24-
const network = getNetworkFromChain(coin);
2524
if (typeof prv === 'string') {
2625
prv = bip32.fromBase58(prv);
2726
}
@@ -30,7 +29,7 @@ export function createHalfSigned(
3029
throw new Error('unsupported transaction type');
3130
}
3231
if (DescriptorTransaction.is(tx)) {
33-
return createHalfSignedFromPsbt(getHalfSignedPsbt(tx, prv, network));
32+
return createHalfSignedFromPsbt(getHalfSignedPsbt(tx, prv, coinName));
3433
}
3534
throw new Error('unsupported transaction type');
3635
}

modules/abstract-utxo/src/offlineVault/TransactionExplanation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getNetworkFromChain } from '../names';
1+
import { UtxoCoinName } from '../names';
22

33
import { OfflineVaultSignable } from './OfflineVaultSignable';
44
import { DescriptorTransaction, getTransactionExplanationFromPsbt } from './descriptor';
@@ -19,12 +19,12 @@ export interface TransactionExplanation<TFee> {
1919
};
2020
}
2121

22-
export function getTransactionExplanation(coin: string, tx: unknown): TransactionExplanation<string> {
22+
export function getTransactionExplanation(coinName: UtxoCoinName, tx: unknown): TransactionExplanation<string> {
2323
if (!OfflineVaultSignable.is(tx)) {
2424
throw new Error('not a signable transaction');
2525
}
2626
if (DescriptorTransaction.is(tx)) {
27-
return getTransactionExplanationFromPsbt(tx, getNetworkFromChain(coin));
27+
return getTransactionExplanationFromPsbt(tx, coinName);
2828
}
2929

3030
throw new Error('unsupported transaction type');

modules/abstract-utxo/src/offlineVault/descriptor/transaction.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from '../../descriptor/validatePolicy';
1212
import { explainPsbt, signPsbt } from '../../transaction/descriptor';
1313
import { TransactionExplanation } from '../TransactionExplanation';
14+
import { getNetworkFromCoinName, UtxoCoinName } from '../../names';
1415

1516
export const DescriptorTransaction = t.intersection(
1617
[OfflineVaultSignable, t.type({ descriptors: t.array(NamedDescriptor) })],
@@ -34,8 +35,9 @@ export function getDescriptorsFromDescriptorTransaction(tx: DescriptorTransactio
3435
export function getHalfSignedPsbt(
3536
tx: DescriptorTransaction,
3637
prv: utxolib.BIP32Interface,
37-
network: utxolib.Network
38+
coinName: UtxoCoinName
3839
): utxolib.Psbt {
40+
const network = getNetworkFromCoinName(coinName);
3941
const psbt = utxolib.bitgo.createPsbtDecode(tx.coinSpecific.txHex, network);
4042
const descriptorMap = getDescriptorsFromDescriptorTransaction(tx);
4143
signPsbt(psbt, descriptorMap, prv, { onUnknownInput: 'throw' });
@@ -44,8 +46,9 @@ export function getHalfSignedPsbt(
4446

4547
export function getTransactionExplanationFromPsbt(
4648
tx: DescriptorTransaction,
47-
network: utxolib.Network
49+
coinName: UtxoCoinName
4850
): TransactionExplanation<string> {
51+
const network = getNetworkFromCoinName(coinName);
4952
const psbt = utxolib.bitgo.createPsbtDecode(tx.coinSpecific.txHex, network);
5053
const descriptorMap = getDescriptorsFromDescriptorTransaction(tx);
5154
const { outputs, changeOutputs, fee } = explainPsbt(psbt, descriptorMap);

modules/abstract-utxo/src/recovery/backupKeyRecovery.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { signAndVerifyPsbt } from '../transaction/fixedScript/signTransaction';
1818
import { generateAddressWithChainAndIndex } from '../address';
1919
import { encodeTransaction } from '../transaction/decode';
2020
import { getReplayProtectionPubkeys } from '../transaction/fixedScript/replayProtection';
21+
import { isTestnetCoin, UtxoCoinName } from '../names';
2122

2223
import { forCoin, RecoveryProvider } from './RecoveryProvider';
2324
import { MempoolApi } from './mempoolApi';
@@ -110,21 +111,21 @@ export interface RecoverParams {
110111
/**
111112
* Generate an address and format it for API queries
112113
* @param coin - The coin instance
113-
* @param network - The network to use
114+
* @param coinName - The coin name
114115
* @param walletKeys - The wallet keys
115116
* @param chain - The chain code
116117
* @param addrIndex - The address index
117118
* @returns The formatted address (with cashaddr prefix stripped for BCH/BCHA)
118119
*/
119120
function getFormattedAddress(
120121
coin: AbstractUtxoCoin,
121-
network: utxolib.Network,
122+
coinName: UtxoCoinName,
122123
walletKeys: RootWalletKeys,
123124
chain: ChainCode,
124125
addrIndex: number
125126
): string {
126127
const format = coin.getChain() === 'bch' || coin.getChain() === 'bcha' ? 'cashaddr' : undefined;
127-
const address = generateAddressWithChainAndIndex(network, walletKeys, chain, addrIndex, format);
128+
const address = generateAddressWithChainAndIndex(coinName, walletKeys, chain, addrIndex, format);
128129

129130
// Blockchair uses cashaddr format when querying the API for address information. Strip the prefix for BCH/BCHA.
130131
return format === 'cashaddr' ? address.split(':')[1] : address;
@@ -154,7 +155,7 @@ async function queryBlockchainUnspentsPath(
154155
}
155156

156157
async function gatherUnspents(addrIndex: number) {
157-
const formattedAddress = getFormattedAddress(coin, coin.network, walletKeys, chain, addrIndex);
158+
const formattedAddress = getFormattedAddress(coin, coin.name, walletKeys, chain, addrIndex);
158159
const addrInfo = await recoveryProvider.getAddressInfo(formattedAddress);
159160
// we use txCount here because it implies usage - having tx'es means the addr was generated and used
160161
if (addrInfo.txCount === 0) {
@@ -370,7 +371,7 @@ export async function backupKeyRecovery(
370371
}
371372

372373
// Use wasm-utxo for testnet coins only, utxolib for mainnet
373-
const backend: PsbtBackend = utxolib.isTestnet(coin.network) ? 'wasm-utxo' : 'utxolib';
374+
const backend: PsbtBackend = isTestnetCoin(coin.name) ? 'wasm-utxo' : 'utxolib';
374375
let psbt = createBackupKeyRecoveryPsbt(
375376
coin.getChain(),
376377
walletKeys,
@@ -394,7 +395,7 @@ export async function backupKeyRecovery(
394395
}
395396

396397
const rootWalletKeysWasm = fixedScriptWallet.RootWalletKeys.from(walletKeys);
397-
const replayProtection = { publicKeys: getReplayProtectionPubkeys(coin.network) };
398+
const replayProtection = { publicKeys: getReplayProtectionPubkeys(coin.name) };
398399

399400
// Sign with user key first
400401
psbt = signAndVerifyPsbt(psbt, walletKeys.user, rootWalletKeysWasm, replayProtection);

0 commit comments

Comments
 (0)