diff --git a/modules/abstract-utxo/src/abstractUtxoCoin.ts b/modules/abstract-utxo/src/abstractUtxoCoin.ts index 7ae4b5f961..ff0227e422 100644 --- a/modules/abstract-utxo/src/abstractUtxoCoin.ts +++ b/modules/abstract-utxo/src/abstractUtxoCoin.ts @@ -42,6 +42,7 @@ import { isValidPrv, isValidXprv, } from '@bitgo/sdk-core'; +import { fixedScriptWallet } from '@bitgo/wasm-utxo'; import { backupKeyRecovery, @@ -68,6 +69,7 @@ import { verifyTransaction, } from './transaction'; import type { TransactionExplanation } from './transaction/fixedScript/explainTransaction'; +import { Musig2Participant } from './transaction/fixedScript/musig2'; import { AggregateValidationError, ErrorMissingOutputs, @@ -76,8 +78,8 @@ import { import { assertDescriptorWalletAddress, getDescriptorMapFromWallet, isDescriptorWallet } from './descriptor'; import { getChainFromNetwork, getFamilyFromNetwork, getFullNameFromNetwork } from './names'; import { assertFixedScriptWalletAddress } from './address/fixedScript'; -import { ParsedTransaction } from './transaction/types'; -import { decodePsbtWith, stringToBufferTryFormats } from './transaction/decode'; +import { isSdkBackend, ParsedTransaction, SdkBackend } from './transaction/types'; +import { decodePsbtWith, encodeTransaction, stringToBufferTryFormats } from './transaction/decode'; import { toBip32Triple, UtxoKeychain } from './keychains'; import { verifyKeySignature, verifyUserPublicKey } from './verifyKey'; import { getPolicyForEnv } from './descriptor/validatePolicy'; @@ -213,6 +215,7 @@ export interface ExplainTransactionOptions; feeInfo?: string; pubs?: Triple; + decodeWith?: SdkBackend; } export interface DecoratedExplainTransactionOptions @@ -225,6 +228,7 @@ export type UtxoNetwork = utxolib.Network; export interface TransactionPrebuild extends BaseTransactionPrebuild { txInfo?: TransactionInfo; blockHeight?: number; + decodeWith?: SdkBackend; } export interface TransactionParams extends BaseTransactionParams { @@ -284,6 +288,7 @@ type UtxoBaseSignTransactionOptions = walletId?: string; txHex: string; txInfo?: TransactionInfo; + decodeWith?: SdkBackend; }; /** xpubs triple for wallet (user, backup, bitgo). Required only when txPrebuild.txHex is not a PSBT */ pubs?: Triple; @@ -360,7 +365,10 @@ export interface SignPsbtResponse { psbt: string; } -export abstract class AbstractUtxoCoin extends BaseCoin { +export abstract class AbstractUtxoCoin + extends BaseCoin + implements Musig2Participant, Musig2Participant +{ public altScriptHash?: number; public supportAltScriptDestination?: boolean; public readonly amountType: 'number' | 'bigint'; @@ -509,7 +517,7 @@ export abstract class AbstractUtxoCoin extends BaseCoin { if (_.isUndefined(prebuild.blockHeight)) { prebuild.blockHeight = (await this.getLatestBlockHeight()) as number; } - return _.extend({}, prebuild, { txHex: tx.toHex() }); + return _.extend({}, prebuild, { txHex: encodeTransaction(tx).toString('hex') }); } /** @@ -526,38 +534,52 @@ export abstract class AbstractUtxoCoin extends BaseCoin { return utxolib.bitgo.createTransactionFromHex(hex, this.network, this.amountType); } - decodeTransaction(input: Buffer | string): DecodedTransaction { + decodeTransaction( + input: Buffer | string, + decodeWith?: SdkBackend + ): DecodedTransaction { if (typeof input === 'string') { const buffer = stringToBufferTryFormats(input, ['hex', 'base64']); - return this.decodeTransaction(buffer); + return this.decodeTransaction(buffer, decodeWith); } if (utxolib.bitgo.isPsbt(input)) { - return decodePsbtWith(input, this.network, 'utxolib'); + return decodePsbtWith(input, this.network, decodeWith ?? 'utxolib'); } else { + if (decodeWith ?? 'utxolib' !== 'utxolib') { + console.error('received decodeWith hint %s, ignoring for legacy transaction', decodeWith); + } return utxolib.bitgo.createTransactionFromBuffer(input, this.network, { amountType: this.amountType, }); } } - decodeTransactionAsPsbt(input: Buffer | string): utxolib.bitgo.UtxoPsbt { + decodeTransactionAsPsbt(input: Buffer | string): utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt { const decoded = this.decodeTransaction(input); - if (!(decoded instanceof utxolib.bitgo.UtxoPsbt)) { - throw new Error('expected psbt but got transaction'); + if (decoded instanceof fixedScriptWallet.BitGoPsbt || decoded instanceof utxolib.bitgo.UtxoPsbt) { + return decoded; } - return decoded; + throw new Error('expected psbt but got transaction'); } decodeTransactionFromPrebuild(prebuild: { txHex?: string; txBase64?: string; + decodeWith?: string; }): DecodedTransaction { const string = prebuild.txHex ?? prebuild.txBase64; if (!string) { throw new Error('missing required txHex or txBase64 property'); } - return this.decodeTransaction(string); + let { decodeWith } = prebuild; + if (decodeWith !== undefined) { + if (typeof decodeWith !== 'string' || !isSdkBackend(decodeWith)) { + console.error('decodeWith %s is not a valid value, using default', decodeWith); + decodeWith = undefined; + } + } + return this.decodeTransaction(string, decodeWith); } toCanonicalTransactionRecipient(output: { valueString: string; address?: string }): { @@ -720,16 +742,29 @@ export abstract class AbstractUtxoCoin extends BaseCoin { /** * @returns input psbt added with deterministic MuSig2 nonce for bitgo key for each MuSig2 inputs. - * @param psbtHex all MuSig2 inputs should contain user MuSig2 nonce + * @param psbt all MuSig2 inputs should contain user MuSig2 nonce * @param walletId */ - async getMusig2Nonces(psbt: utxolib.bitgo.UtxoPsbt, walletId: string): Promise { - const params: SignPsbtRequest = { psbt: psbt.toHex() }; + async getMusig2Nonces(psbt: utxolib.bitgo.UtxoPsbt, walletId: string): Promise; + async getMusig2Nonces(psbt: fixedScriptWallet.BitGoPsbt, walletId: string): Promise; + async getMusig2Nonces( + psbt: T, + walletId: string + ): Promise; + async getMusig2Nonces( + psbt: T, + walletId: string + ): Promise { + const buffer = encodeTransaction(psbt); const response = await this.bitgo .post(this.url('/wallet/' + walletId + '/tx/signpsbt')) - .send(params) + .send({ psbt: buffer.toString('hex') }) .result(); - return this.decodeTransactionAsPsbt(response.psbt); + if (psbt instanceof utxolib.bitgo.UtxoPsbt) { + return decodePsbtWith(response.psbt, this.network, 'utxolib') as T; + } else { + return decodePsbtWith(response.psbt, this.network, 'wasm-utxo') as T; + } } /** @@ -739,7 +774,8 @@ export abstract class AbstractUtxoCoin extends BaseCoin { * @param walletId */ async signPsbt(psbtHex: string, walletId: string): Promise { - return { psbt: (await this.getMusig2Nonces(this.decodeTransactionAsPsbt(psbtHex), walletId)).toHex() }; + const psbt = await this.getMusig2Nonces(this.decodeTransactionAsPsbt(psbtHex), walletId); + return { psbt: encodeTransaction(psbt).toString('hex') }; } /** @@ -749,11 +785,10 @@ export abstract class AbstractUtxoCoin extends BaseCoin { async signPsbtFromOVC(ovcJson: Record): Promise> { assert(ovcJson['psbtHex'], 'ovcJson must contain psbtHex'); assert(ovcJson['walletId'], 'ovcJson must contain walletId'); - const psbt = await this.getMusig2Nonces( - this.decodeTransactionAsPsbt(ovcJson['psbtHex'] as string), - ovcJson['walletId'] as string - ); - return _.extend(ovcJson, { txHex: psbt.toHex() }); + const hex = ovcJson['psbtHex'] as string; + const walletId = ovcJson['walletId'] as string; + const psbt = await this.getMusig2Nonces(this.decodeTransactionAsPsbt(hex), walletId); + return _.extend(ovcJson, { txHex: encodeTransaction(psbt).toString('hex') }); } /** diff --git a/modules/abstract-utxo/src/transaction/decode.ts b/modules/abstract-utxo/src/transaction/decode.ts index e4077d0224..feccd9de4d 100644 --- a/modules/abstract-utxo/src/transaction/decode.ts +++ b/modules/abstract-utxo/src/transaction/decode.ts @@ -57,3 +57,15 @@ export function decodePsbtWith( return fixedScriptWallet.BitGoPsbt.fromBytes(psbt, toNetworkName(network)); } } + +export function encodeTransaction( + transaction: utxolib.bitgo.UtxoTransaction | utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt +): Buffer { + if (transaction instanceof utxolib.bitgo.UtxoTransaction) { + return transaction.toBuffer(); + } else if (transaction instanceof utxolib.bitgo.UtxoPsbt) { + return transaction.toBuffer(); + } else { + return Buffer.from(transaction.serialize()); + } +} diff --git a/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts b/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts index 4d240e705f..cdcb35a4d9 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts @@ -20,7 +20,7 @@ const PSBT_CACHE_WASM = new Map(); function hasKeyPathSpendInput( tx: fixedScriptWallet.BitGoPsbt, - rootWalletKeys: fixedScriptWallet.IWalletKeys, + rootWalletKeys: fixedScriptWallet.RootWalletKeys, replayProtection: ReplayProtectionKeys ): boolean { const parsed = tx.parseTransactionWithWalletKeys(rootWalletKeys, replayProtection); @@ -36,10 +36,10 @@ function hasKeyPathSpendInput( export function signAndVerifyPsbtWasm( tx: fixedScriptWallet.BitGoPsbt, signerKeychain: BIP32Interface, - rootWalletKeys: fixedScriptWallet.IWalletKeys, + rootWalletKeys: fixedScriptWallet.RootWalletKeys, replayProtection: ReplayProtectionKeys, { isLastSignature }: { isLastSignature: boolean } -): fixedScriptWallet.BitGoPsbt | Uint8Array { +): fixedScriptWallet.BitGoPsbt | Buffer { const wasmSigner = toWasmBIP32(signerKeychain); const parsed = tx.parseTransactionWithWalletKeys(rootWalletKeys, replayProtection); @@ -85,7 +85,7 @@ export function signAndVerifyPsbtWasm( if (isLastSignature) { tx.finalizeAllInputs(); - return tx.extractTransaction(); + return Buffer.from(tx.extractTransaction()); } return tx; @@ -100,17 +100,17 @@ export async function signPsbtWithMusig2ParticipantWasm( coin: Musig2Participant, tx: fixedScriptWallet.BitGoPsbt, signerKeychain: BIP32Interface | undefined, - rootWalletKeys: fixedScriptWallet.IWalletKeys, - replayProtection: ReplayProtectionKeys, + rootWalletKeys: fixedScriptWallet.RootWalletKeys, params: { + replayProtection: ReplayProtectionKeys; isLastSignature: boolean; signingStep: 'signerNonce' | 'cosignerNonce' | 'signerSignature' | undefined; walletId: string | undefined; } -): Promise { +): Promise { const wasmSigner = signerKeychain ? toWasmBIP32(signerKeychain) : undefined; - if (hasKeyPathSpendInput(tx, rootWalletKeys, replayProtection)) { + if (hasKeyPathSpendInput(tx, rootWalletKeys, params.replayProtection)) { // We can only be the first signature on a transaction with taproot key path spend inputs because // we require the secret nonce in the cache of the first signer, which is impossible to retrieve if // deserialized from a hex. @@ -162,7 +162,7 @@ export async function signPsbtWithMusig2ParticipantWasm( } assert(signerKeychain); - return signAndVerifyPsbtWasm(tx, signerKeychain, rootWalletKeys, replayProtection, { + return signAndVerifyPsbtWasm(tx, signerKeychain, rootWalletKeys, params.replayProtection, { isLastSignature: params.isLastSignature, }); } diff --git a/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts b/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts index 10c20561a0..773df4869c 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts @@ -1,17 +1,23 @@ +import assert from 'assert'; + +import { isTriple } from '@bitgo/sdk-core'; import _ from 'lodash'; import { BIP32Interface } from '@bitgo/secp256k1'; import { bitgo } from '@bitgo/utxo-lib'; import * as utxolib from '@bitgo/utxo-lib'; - -import { DecodedTransaction } from '../types'; +import { fixedScriptWallet } from '@bitgo/wasm-utxo'; import { Musig2Participant } from './musig2'; import { signLegacyTransaction } from './signLegacyTransaction'; import { signPsbtWithMusig2Participant } from './signPsbt'; +import { signPsbtWithMusig2ParticipantWasm } from './signPsbtWasm'; +import { getReplayProtectionPubkeys } from './replayProtection'; -export async function signTransaction( - coin: Musig2Participant, - tx: DecodedTransaction, +export async function signTransaction< + T extends utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction | fixedScriptWallet.BitGoPsbt +>( + coin: Musig2Participant | Musig2Participant, + tx: T, signerKeychain: BIP32Interface | undefined, network: utxolib.Network, params: { @@ -24,7 +30,9 @@ export async function signTransaction( pubs: string[] | undefined; cosignerPub: string | undefined; } -): Promise> { +): Promise< + utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction | fixedScriptWallet.BitGoPsbt | Buffer +> { let isLastSignature = false; if (_.isBoolean(params.isLastSignature)) { // if build is called instead of buildIncomplete, no signature placeholders are left in the sig script @@ -32,11 +40,29 @@ export async function signTransaction( } if (tx instanceof bitgo.UtxoPsbt) { - return signPsbtWithMusig2Participant(coin, tx, signerKeychain, { + return signPsbtWithMusig2Participant(coin as Musig2Participant, tx, signerKeychain, { isLastSignature, signingStep: params.signingStep, walletId: params.walletId, }); + } else if (tx instanceof fixedScriptWallet.BitGoPsbt) { + assert(params.pubs, 'pubs are required for fixed script signing'); + assert(isTriple(params.pubs), 'pubs must be a triple'); + const rootWalletKeys = fixedScriptWallet.RootWalletKeys.fromXpubs(params.pubs); + return signPsbtWithMusig2ParticipantWasm( + coin as Musig2Participant, + tx, + signerKeychain, + rootWalletKeys, + { + replayProtection: { + publicKeys: getReplayProtectionPubkeys(network), + }, + isLastSignature, + signingStep: params.signingStep, + walletId: params.walletId, + } + ); } return signLegacyTransaction(tx, signerKeychain, { diff --git a/modules/abstract-utxo/src/transaction/signTransaction.ts b/modules/abstract-utxo/src/transaction/signTransaction.ts index f99b4fcb1f..15c2d69949 100644 --- a/modules/abstract-utxo/src/transaction/signTransaction.ts +++ b/modules/abstract-utxo/src/transaction/signTransaction.ts @@ -10,6 +10,7 @@ import { fetchKeychains, toBip32Triple } from '../keychains'; import * as fixedScript from './fixedScript'; import * as descriptor from './descriptor'; +import { encodeTransaction } from './decode'; const debug = buildDebug('bitgo:abstract-utxo:transaction:signTransaction'); @@ -72,6 +73,7 @@ export async function signTransaction( pubs: params.pubs, cosignerPub: params.cosignerPub, }); - return { txHex: signedTx.toBuffer().toString('hex') }; + const buffer = Buffer.isBuffer(signedTx) ? signedTx : encodeTransaction(signedTx); + return { txHex: buffer.toString('hex') }; } } diff --git a/modules/abstract-utxo/src/transaction/types.ts b/modules/abstract-utxo/src/transaction/types.ts index 06484aadbe..635f226506 100644 --- a/modules/abstract-utxo/src/transaction/types.ts +++ b/modules/abstract-utxo/src/transaction/types.ts @@ -1,4 +1,5 @@ import * as utxolib from '@bitgo/utxo-lib'; +import { fixedScriptWallet } from '@bitgo/wasm-utxo'; import type { UtxoNamedKeychains } from '../keychains'; @@ -6,9 +7,14 @@ import type { CustomChangeOptions } from './fixedScript'; export type SdkBackend = 'utxolib' | 'wasm-utxo'; +export function isSdkBackend(backend: string): backend is SdkBackend { + return backend === 'utxolib' || backend === 'wasm-utxo'; +} + export type DecodedTransaction = | utxolib.bitgo.UtxoTransaction - | utxolib.bitgo.UtxoPsbt; + | utxolib.bitgo.UtxoPsbt + | fixedScriptWallet.BitGoPsbt; export interface BaseOutput { address: string; diff --git a/modules/abstract-utxo/test/unit/transaction.ts b/modules/abstract-utxo/test/unit/transaction.ts index b5ee481aa0..6d99bd60b9 100644 --- a/modules/abstract-utxo/test/unit/transaction.ts +++ b/modules/abstract-utxo/test/unit/transaction.ts @@ -14,7 +14,9 @@ import { } from '@bitgo/sdk-core'; import { AbstractUtxoCoin, getReplayProtectionAddresses, generateAddress } from '../../src'; +import { SdkBackend } from '../../src/transaction/types'; +import { hasWasmUtxoSupport } from './transaction/fixedScript/util'; import { utxoCoins, shouldEqualJSON, @@ -271,11 +273,16 @@ function run( coin: AbstractUtxoCoin, inputScripts: testutil.InputScriptType[], txFormat: 'legacy' | 'psbt', - amountType: 'number' | 'bigint' = 'number' + { decodeWith }: { decodeWith?: SdkBackend } = {} ) { - describe(`Transaction Stages ${coin.getChain()} (${amountType}) scripts=${inputScripts.join( - ',' - )} txFormat=${txFormat}`, function () { + const amountType = coin.amountType; + const title = [ + inputScripts.join(','), + `txFormat=${txFormat}`, + `amountType=${amountType}`, + decodeWith ? `decodeWith=${decodeWith}` : '', + ]; + describe(`${title.join(' ')}`, function () { const bgUrl = common.Environments[defaultBitGo.getEnv()].uri; const isTransactionWithKeyPathSpend = inputScripts.some((s) => s === 'taprootKeyPathSpend'); @@ -319,7 +326,8 @@ function run( function getSignParams( prebuildHex: string, signer: BIP32Interface, - cosigner: BIP32Interface + cosigner: BIP32Interface, + decodeWith: SdkBackend | undefined ): WalletSignTransactionOptions { const txInfo = { unspents: txFormat === 'psbt' ? undefined : getUnspents(), @@ -329,6 +337,7 @@ function run( walletId: isTransactionWithKeyPathSpend ? wallet.id() : undefined, txHex: prebuildHex, txInfo, + decodeWith, }, prv: signer.toBase58(), pubs: walletKeys.triple.map((k) => k.neutered().toBase58()), @@ -351,7 +360,7 @@ function run( // half-sign with the user key const result = (await wallet.signTransaction( - getSignParams(prebuild.toBuffer().toString('hex'), signer, cosigner) + getSignParams(prebuild.toBuffer().toString('hex'), signer, cosigner, decodeWith) )) as Promise; if (scope) { @@ -367,7 +376,7 @@ function run( cosigner: BIP32Interface ): Promise { return (await wallet.signTransaction({ - ...getSignParams(halfSigned.txHex, signer, cosigner), + ...getSignParams(halfSigned.txHex, signer, cosigner, decodeWith), isLastSignature: true, })) as FullySignedTransaction; } @@ -533,8 +542,8 @@ function run( async function testExplainTx( stageName: string, txHex: string, - unspents?: utxolib.bitgo.Unspent[], - pubs?: Triple + unspents: utxolib.bitgo.Unspent[], + pubs: Triple | undefined ): Promise { const explanation = await coin.explainTransaction({ txHex, @@ -542,18 +551,16 @@ function run( unspents, }, pubs, + decodeWith, }); - explanation.should.have.properties( - 'displayOrder', - 'id', - 'outputs', - 'changeOutputs', - 'changeAmount', - 'outputAmount', - 'inputSignatures', - 'signatures' - ); + const expectedProperties = ['id', 'outputs', 'changeOutputs', 'changeAmount', 'outputAmount']; + + if (decodeWith !== 'wasm-utxo') { + expectedProperties.push('displayOrder', 'inputSignatures', 'signatures'); + } + + explanation.should.have.properties(...expectedProperties); const expectedSignatureCount = stageName === 'prebuild' || pubs === undefined @@ -623,38 +630,44 @@ function run( ? getUnspentsForPsbt().map((u) => ({ ...u, value: bitgo.toTNumber(u.value, amountType) as TNumber })) : getUnspents(); await testExplainTx(stageName, txHex, unspents, pubs); - await testExplainTx(stageName, txHex, unspents); + if (decodeWith !== 'wasm-utxo') { + await testExplainTx(stageName, txHex, unspents, undefined); + } } }); }); } -function runWithAmountType( - coin: AbstractUtxoCoin, - inputScripts: testutil.InputScriptType[], - txFormat: 'legacy' | 'psbt' -) { - const amountType = coin.amountType; - if (amountType === 'bigint') { - run(coin, inputScripts, txFormat, amountType); - } else { - run(coin, inputScripts, txFormat, amountType); - } -} - -utxoCoins.forEach((coin) => +function runTestForCoin(coin: AbstractUtxoCoin) { getScriptTypes2Of3().forEach((type) => { (['legacy', 'psbt'] as const).forEach((txFormat) => { + if (!coin.supportsAddressType(type === 'taprootKeyPathSpend' ? 'p2trMusig2' : type)) { + return; + } + if ((type === 'taprootKeyPathSpend' || type === 'p2trMusig2') && txFormat !== 'psbt') { return; } - if (coin.supportsAddressType(type === 'taprootKeyPathSpend' ? 'p2trMusig2' : type)) { - runWithAmountType(coin, [type, type], txFormat); + run(coin, [type, type], txFormat); + if (getReplayProtectionAddresses(coin.network).length) { + run(coin, ['p2shP2pk', type], txFormat); + } + + if (txFormat === 'psbt' && hasWasmUtxoSupport(coin.network)) { + run(coin, [type, type], txFormat, { decodeWith: 'wasm-utxo' }); if (getReplayProtectionAddresses(coin.network).length) { - runWithAmountType(coin, ['p2shP2pk', type], txFormat); + run(coin, ['p2shP2pk', type], txFormat, { decodeWith: 'wasm-utxo' }); } } }); - }) -); + }); +} + +describe('Transaction Suite', function () { + utxoCoins.forEach((coin) => { + describe(`${coin.getChain()}`, function () { + runTestForCoin(coin); + }); + }); +}); diff --git a/modules/abstract-utxo/test/unit/transaction/fixedScript/signPsbt.ts b/modules/abstract-utxo/test/unit/transaction/fixedScript/signPsbt.ts index 47c08d717f..00d1025517 100644 --- a/modules/abstract-utxo/test/unit/transaction/fixedScript/signPsbt.ts +++ b/modules/abstract-utxo/test/unit/transaction/fixedScript/signPsbt.ts @@ -130,9 +130,9 @@ function describeSignPsbtWithMusig2Participant( getMockCoinWasm(acidTest.rootWalletKeys, acidTest.network), psbt, acidTest.rootWalletKeys.user, - wasmWalletKeys, - replayProtection, + fixedScriptWallet.RootWalletKeys.from(acidTest.rootWalletKeys), { + replayProtection, isLastSignature: false, signingStep: undefined, walletId: 'test-wallet-id', diff --git a/modules/abstract-utxo/test/unit/transaction/fixedScript/util.ts b/modules/abstract-utxo/test/unit/transaction/fixedScript/util.ts index 3b62f89179..813fe24d9e 100644 --- a/modules/abstract-utxo/test/unit/transaction/fixedScript/util.ts +++ b/modules/abstract-utxo/test/unit/transaction/fixedScript/util.ts @@ -4,6 +4,7 @@ export function hasWasmUtxoSupport(network: utxolib.Network): boolean { return ![ utxolib.networks.bitcoincash, utxolib.networks.bitcoingold, + utxolib.networks.bitcoinsv, utxolib.networks.ecash, utxolib.networks.zcash, ].includes(utxolib.getMainnet(network));