PHP WebShell
Текущая директория: /opt/BitGoJS/modules/bitgo/test/v2/unit/coins/utxo
Просмотр файла: transaction.ts
/**
* @prettier
*/
import 'mocha';
import * as _ from 'lodash';
import * as assert from 'assert';
import * as utxolib from '@bitgo/utxo-lib';
import * as nock from 'nock';
import { BIP32Interface, bitgo, testutil } from '@bitgo/utxo-lib';
import { AbstractUtxoCoin, getReplayProtectionAddresses } from '@bitgo/abstract-utxo';
import {
utxoCoins,
shouldEqualJSON,
getFixture,
getUtxoWallet,
mockUnspent,
InputScriptType,
TransactionObj,
transactionToObj,
transactionHexToObj,
createPrebuildTransaction,
getDefaultWalletKeys,
getUtxoCoin,
keychainsBase58,
getWalletKeys,
} from './util';
import {
common,
FullySignedTransaction,
HalfSignedUtxoTransaction,
Triple,
WalletSignTransactionOptions,
} from '@bitgo/sdk-core';
import { TestBitGo } from '@bitgo/sdk-test';
import { BitGo } from '../../../../../src';
type Unspent<TNumber extends number | bigint = number> = bitgo.Unspent<TNumber>;
type WalletUnspent<TNumber extends number | bigint = number> = bitgo.WalletUnspent<TNumber>;
function getScriptTypes2Of3() {
return [...bitgo.outputScripts.scriptTypes2Of3, 'taprootKeyPathSpend'] as const;
}
describe(`UTXO coin signTransaction`, async function () {
const bgUrl = common.Environments[TestBitGo.decorate(BitGo, { env: 'mock' }).getEnv()].uri;
const coin = getUtxoCoin('btc');
const wallet = getUtxoWallet(coin, { id: '5b34252f1bf349930e34020a00000000', coin: coin.getChain() });
const rootWalletKeys = getDefaultWalletKeys();
const userPrv = rootWalletKeys.user.toBase58();
const pubs = keychainsBase58.map((v) => v.pub) as Triple<string>;
function validatePsbt(txHex: string, targetSigCount: 0 | 1, targetNonceCount?: 1 | 2) {
const psbt = utxolib.bitgo.createPsbtFromHex(txHex, coin.network);
psbt.data.inputs.forEach((input, index) => {
const parsed = utxolib.bitgo.parsePsbtInput(input);
if (parsed.scriptType === 'taprootKeyPathSpend') {
assert.ok(targetNonceCount);
const nonce = psbt.getProprietaryKeyVals(index, {
identifier: utxolib.bitgo.PSBT_PROPRIETARY_IDENTIFIER,
subtype: utxolib.bitgo.ProprietaryKeySubtype.MUSIG2_PUB_NONCE,
});
assert.strictEqual(nonce.length, targetNonceCount);
}
const expectedSigCount = parsed.scriptType === 'p2shP2pk' || targetSigCount === 0 ? undefined : 1;
assert.strictEqual(parsed.signatures?.length, expectedSigCount);
});
}
function validateTx(txHex: string, unspents: Unspent<bigint>[], targetSigCount: 0 | 1) {
const tx = utxolib.bitgo.createTransactionFromHex(txHex, coin.network);
unspents.forEach((u, i) => {
const sigCount = utxolib.bitgo.getStrictSignatureCount(tx.ins[i]);
const expectedSigCount = utxolib.bitgo.isWalletUnspent(u) && !!targetSigCount ? 1 : 0;
assert.strictEqual(sigCount, expectedSigCount);
});
}
async function signTransaction(
tx: utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction<bigint>,
useSigningSteps: boolean,
unspents?: Unspent<bigint>[]
) {
const isPsbt = tx instanceof utxolib.bitgo.UtxoPsbt;
const isTxWithTaprootKeyPathSpend = isPsbt && utxolib.bitgo.isTransactionWithKeyPathSpendInput(tx);
const txHex = tx.toHex();
function nockSignPsbt(psbtHex: string): nock.Scope {
const psbt = utxolib.bitgo.createPsbtFromHex(psbtHex, coin.network);
return nock(bgUrl)
.post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/signpsbt`, (body) => body.psbt)
.reply(200, { psbt: psbt.setAllInputsMusig2NonceHD(rootWalletKeys.bitgo).toHex() });
}
if (!useSigningSteps) {
let scope: nock.Scope | undefined;
if (tx instanceof utxolib.bitgo.UtxoPsbt && isTxWithTaprootKeyPathSpend) {
scope = nockSignPsbt(tx.clone().setAllInputsMusig2NonceHD(rootWalletKeys.bitgo).toHex());
}
const psbt = await coin.signTransaction({
txPrebuild: {
txHex,
txInfo: isPsbt ? undefined : { unspents },
walletId: isTxWithTaprootKeyPathSpend ? wallet.id() : undefined,
},
prv: userPrv,
pubs: isPsbt ? undefined : pubs,
});
assert.ok('txHex' in psbt);
if (isPsbt) {
validatePsbt(psbt.txHex, 1, 2);
} else {
assert(unspents);
validateTx(psbt.txHex, unspents, 1);
}
if (scope) {
assert.strictEqual(scope.isDone(), true);
}
return;
}
const signerNoncePsbt = await coin.signTransaction({
txPrebuild: { txHex },
prv: userPrv,
signingStep: 'signerNonce',
});
assert.ok('txHex' in signerNoncePsbt);
if (isPsbt) {
validatePsbt(signerNoncePsbt.txHex, 0, isTxWithTaprootKeyPathSpend ? 1 : undefined);
} else {
assert(unspents);
validateTx(signerNoncePsbt.txHex, unspents, 0);
}
let scope: nock.Scope | undefined;
if (isTxWithTaprootKeyPathSpend) {
scope = nockSignPsbt(signerNoncePsbt.txHex);
}
const cosignerNoncePsbt = await coin.signTransaction({
txPrebuild: { ...signerNoncePsbt, walletId: wallet.id() },
signingStep: 'cosignerNonce',
});
assert.ok('txHex' in cosignerNoncePsbt);
if (isPsbt) {
validatePsbt(cosignerNoncePsbt.txHex, 0, isTxWithTaprootKeyPathSpend ? 2 : undefined);
} else {
assert(unspents);
validateTx(cosignerNoncePsbt.txHex, unspents, 0);
}
if (scope) {
assert.strictEqual(scope.isDone(), true);
}
const signerSigPsbt = await coin.signTransaction({
txPrebuild: { ...cosignerNoncePsbt, txInfo: isPsbt ? undefined : { unspents } },
prv: userPrv,
pubs: isPsbt ? undefined : pubs,
signingStep: 'signerSignature',
});
assert.ok('txHex' in signerSigPsbt);
if (isPsbt) {
validatePsbt(signerSigPsbt.txHex, 1, isTxWithTaprootKeyPathSpend ? 2 : undefined);
} else {
assert(unspents);
validateTx(signerSigPsbt.txHex, unspents, 1);
}
}
it('success when called like customSigningFunction flow - PSBT with taprootKeyPathSpend inputs', async function () {
const inputs: testutil.Input[] = testutil.inputScriptTypes.map((scriptType) => ({
scriptType,
value: BigInt(1000),
}));
const unspentSum = inputs.reduce((prev: bigint, curr) => prev + curr.value, BigInt(0));
const outputs: testutil.Output[] = [{ scriptType: 'p2sh', value: unspentSum - BigInt(1000) }];
const psbt = testutil.constructPsbt(inputs, outputs, coin.network, rootWalletKeys, 'unsigned');
for (const v of [false, true]) {
await signTransaction(psbt, v);
}
});
it('success when called like customSigningFunction flow - PSBT without taprootKeyPathSpend inputs', async function () {
const inputs: testutil.Input[] = testutil.inputScriptTypes
.filter((v) => v !== 'taprootKeyPathSpend')
.map((scriptType) => ({
scriptType,
value: BigInt(1000),
}));
const unspentSum = inputs.reduce((prev: bigint, cur) => prev + cur.value, BigInt(0));
const outputs: testutil.Output[] = [{ scriptType: 'p2sh', value: unspentSum - BigInt(1000) }];
const psbt = testutil.constructPsbt(inputs, outputs, coin.network, rootWalletKeys, 'unsigned');
for (const v of [false, true]) {
await signTransaction(psbt, v);
}
});
it('success when called like customSigningFunction flow - Network Tx', async function () {
const inputs: testutil.TxnInput<bigint>[] = testutil.txnInputScriptTypes
.filter((v) => v !== 'p2shP2pk')
.map((scriptType) => ({
scriptType,
value: BigInt(1000),
}));
const unspentSum = inputs.reduce((prev: bigint, curr) => prev + curr.value, BigInt(0));
const outputs: testutil.TxnOutput<bigint>[] = [{ scriptType: 'p2sh', value: unspentSum - BigInt(1000) }];
const txBuilder = testutil.constructTxnBuilder(inputs, outputs, coin.network, rootWalletKeys, 'unsigned');
const unspents = inputs.map((v, i) => testutil.toTxnUnspent(v, i, coin.network, rootWalletKeys));
for (const v of [false, true]) {
await signTransaction(txBuilder.buildIncomplete(), v, unspents);
}
});
it('fails when called like customSigningFunction flow - PSBT cache miss', async function () {
const inputs: testutil.Input[] = [{ scriptType: 'taprootKeyPathSpend', value: BigInt(1000) }];
const unspentSum = inputs.reduce((prev: bigint, curr) => prev + curr.value, BigInt(0));
const outputs: testutil.Output[] = [{ scriptType: 'p2sh', value: unspentSum - BigInt(1000) }];
const psbt = testutil.constructPsbt(inputs, outputs, coin.network, rootWalletKeys, 'unsigned');
await assert.rejects(
async () => {
await coin.signTransaction({
txPrebuild: { txHex: psbt.toHex() },
prv: userPrv,
signingStep: 'signerSignature',
});
},
{
message: `Psbt is missing from txCache (cache size 0).
This may be due to the request being routed to a different BitGo-Express instance that for signing step 'signerNonce'.`,
}
);
});
it('fails when unsupported locking script is used', async function () {
const inputs: testutil.Input[] = [
{ scriptType: 'p2wsh', value: BigInt(1000) },
{ scriptType: 'p2trMusig2', value: BigInt(1000) },
];
const unspentSum = inputs.reduce((prev: bigint, curr) => prev + curr.value, BigInt(0));
const outputs: testutil.Output[] = [{ scriptType: 'p2sh', value: unspentSum - BigInt(500) }];
const psbt = testutil.constructPsbt(inputs, outputs, coin.network, rootWalletKeys, 'unsigned');
// override the 1st PSBT input with unsupported 2 of 2 multi-sig locking script.
const unspent = testutil.toUnspent(inputs[0], 0, coin.network, rootWalletKeys);
if (!utxolib.bitgo.isWalletUnspent(unspent)) {
throw new Error('invalid unspent');
}
const { publicKeys } = rootWalletKeys.deriveForChainAndIndex(unspent.chain, unspent.index);
const script2Of2 = utxolib.payments.p2ms({ m: 2, pubkeys: [publicKeys[0], publicKeys[1]] });
psbt.data.inputs[0].witnessScript = script2Of2.output;
await assert.rejects(
async () => {
await coin.signTransaction({
txPrebuild: { txHex: psbt.toHex() },
prv: userPrv,
});
},
{
message: `length mismatch`,
}
);
});
});
function run<TNumber extends number | bigint = number>(
coin: AbstractUtxoCoin,
inputScripts: testutil.InputScriptType[],
txFormat: 'legacy' | 'psbt',
amountType: 'number' | 'bigint' = 'number'
) {
describe(`Transaction Stages ${coin.getChain()} (${amountType}) scripts=${inputScripts.join(
','
)} txFormat=${txFormat}`, function () {
const bgUrl = common.Environments[TestBitGo.decorate(BitGo, { env: 'mock' }).getEnv()].uri;
const isTransactionWithKeyPathSpend = inputScripts.some((s) => s === 'taprootKeyPathSpend');
const isTransactionWithReplayProtection = inputScripts.some((s) => s === 'p2shP2pk');
const isTransactionWithP2tr = inputScripts.some((s) => s === 'p2tr');
const isTransactionWithP2trMusig2 = inputScripts.some((s) => s === 'p2trMusig2');
const value = (amountType === 'bigint' ? BigInt('10999999800000001') : 1e8) as TNumber;
const wallet = getUtxoWallet(coin, { id: '5b34252f1bf349930e34020a00000000', coin: coin.getChain() });
const walletKeys = getDefaultWalletKeys();
const fullSign = !(isTransactionWithReplayProtection || isTransactionWithKeyPathSpend);
function getUnspentsForPsbt(): Unspent<bigint>[] {
return inputScripts.map((t, index) => {
return testutil.toUnspent(
{ scriptType: t, value: t === 'p2shP2pk' ? BigInt(1000) : BigInt(value) },
index,
coin.network,
walletKeys
);
});
}
function toTxnInputScriptType(type: testutil.InputScriptType): InputScriptType {
return type === 'p2shP2pk' ? 'replayProtection' : type === 'taprootKeyPathSpend' ? 'p2trMusig2' : type;
}
function getUnspents(): Unspent<TNumber>[] {
return inputScripts.map((type, i) =>
mockUnspent<TNumber>(coin.network, walletKeys, toTxnInputScriptType(type), i, value)
);
}
function getOutputAddress(rootWalletKeys: utxolib.bitgo.RootWalletKeys): string {
return coin.generateAddress({
keychains: rootWalletKeys.triple.map((k) => ({ pub: k.neutered().toBase58() })),
}).address;
}
function getSignParams(
prebuildHex: string,
signer: BIP32Interface,
cosigner: BIP32Interface
): WalletSignTransactionOptions {
const txInfo = {
unspents: txFormat === 'psbt' ? undefined : getUnspents(),
};
return {
txPrebuild: {
walletId: isTransactionWithKeyPathSpend ? wallet.id() : undefined,
txHex: prebuildHex,
txInfo,
},
prv: signer.toBase58(),
pubs: walletKeys.triple.map((k) => k.neutered().toBase58()),
cosignerPub: cosigner.neutered().toBase58(),
} as WalletSignTransactionOptions;
}
async function createHalfSignedTransaction(
prebuild: utxolib.bitgo.UtxoTransaction<TNumber> | utxolib.bitgo.UtxoPsbt,
signer: BIP32Interface,
cosigner: BIP32Interface
): Promise<HalfSignedUtxoTransaction> {
let scope: nock.Scope | undefined;
if (prebuild instanceof utxolib.bitgo.UtxoPsbt && isTransactionWithKeyPathSpend) {
const psbt = prebuild.clone().setAllInputsMusig2NonceHD(cosigner);
scope = nock(bgUrl)
.post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/signpsbt`, (body) => body.psbt)
.reply(200, { psbt: psbt.toHex() });
}
// half-sign with the user key
const result = (await wallet.signTransaction(
getSignParams(prebuild.toBuffer().toString('hex'), signer, cosigner)
)) as Promise<HalfSignedUtxoTransaction>;
if (scope) {
assert.strictEqual(scope.isDone(), true);
}
return result;
}
async function createFullSignedTransaction(
halfSigned: HalfSignedUtxoTransaction,
signer: BIP32Interface,
cosigner: BIP32Interface
): Promise<FullySignedTransaction> {
return (await wallet.signTransaction({
...getSignParams(halfSigned.txHex, signer, cosigner),
isLastSignature: true,
})) as FullySignedTransaction;
}
type TransactionStages = {
prebuild: utxolib.bitgo.UtxoTransaction<TNumber> | utxolib.bitgo.UtxoPsbt;
halfSignedUserBackup?: HalfSignedUtxoTransaction;
halfSignedUserBitGo: HalfSignedUtxoTransaction;
fullSignedUserBackup?: FullySignedTransaction;
fullSignedUserBitGo?: FullySignedTransaction;
};
type TransactionObjStages = Record<keyof TransactionStages, TransactionObj>;
function createPrebuildPsbt() {
const inputs = inputScripts.map(
(t): testutil.Input => ({
scriptType: t,
value: t === 'p2shP2pk' ? BigInt(1000) : BigInt(value),
})
);
const unspentSum = inputs.reduce((prev: bigint, curr) => prev + curr.value, BigInt(0));
const outputs: testutil.Output[] = [
{ address: getOutputAddress(getWalletKeys('test')), value: unspentSum - BigInt(1000) },
];
const psbt = testutil.constructPsbt(inputs, outputs, coin.network, walletKeys, 'unsigned');
utxolib.bitgo.addXpubsToPsbt(psbt, walletKeys);
return psbt;
}
async function getTransactionStages(): Promise<TransactionStages> {
const prebuild =
txFormat === 'psbt'
? createPrebuildPsbt()
: createPrebuildTransaction<TNumber>(coin.network, getUnspents(), getOutputAddress(walletKeys));
const halfSignedUserBitGo = await createHalfSignedTransaction(prebuild, walletKeys.user, walletKeys.bitgo);
const fullSignedUserBitGo =
fullSign && !isTransactionWithP2trMusig2
? await createFullSignedTransaction(halfSignedUserBitGo, walletKeys.bitgo, walletKeys.user)
: undefined;
const halfSignedUserBackup =
!isTransactionWithKeyPathSpend && !(txFormat === 'psbt' && isTransactionWithP2tr)
? await createHalfSignedTransaction(prebuild, walletKeys.user, walletKeys.backup)
: undefined;
const fullSignedUserBackup =
fullSign && halfSignedUserBackup
? await createFullSignedTransaction(halfSignedUserBackup, walletKeys.backup, walletKeys.user)
: undefined;
return {
prebuild,
halfSignedUserBackup,
halfSignedUserBitGo,
fullSignedUserBackup,
fullSignedUserBitGo,
};
}
let transactionStages: TransactionStages;
before('prepare', async function () {
transactionStages = await getTransactionStages();
});
afterEach(nock.cleanAll);
it('match fixtures', async function (this: Mocha.Context) {
if (txFormat === 'psbt') {
// TODO (maybe) - once full PSBT support is added to abstract-utxo module, custom JSON representation of PSBT can be created and tested here.
// signatures of taprootKeyPathSpends are random since random nature of MuSig2 nonce, so psbt hex comparison also wont work.
return this.skip();
}
function toTransactionStagesObj(stages: TransactionStages): TransactionObjStages {
return _.mapValues(stages, (v) =>
v === undefined || v instanceof utxolib.bitgo.UtxoPsbt
? undefined
: v instanceof utxolib.bitgo.UtxoTransaction
? transactionToObj<TNumber>(v)
: transactionHexToObj(v.txHex, coin.network, amountType)
) as TransactionObjStages;
}
shouldEqualJSON(
toTransactionStagesObj(transactionStages),
await getFixture(
coin,
`transactions-${inputScripts.map((t) => toTxnInputScriptType(t)).join('-')}`,
toTransactionStagesObj(transactionStages)
)
);
});
function testPsbtValidSignatures(tx: HalfSignedUtxoTransaction, signedBy: BIP32Interface[]) {
const psbt = utxolib.bitgo.createPsbtFromHex(tx.txHex, coin.network);
const unspents = getUnspentsForPsbt();
psbt.data.inputs.forEach((input, index) => {
const unspent = unspents[index];
if (!utxolib.bitgo.isWalletUnspent(unspent)) {
assert.ok(utxolib.bitgo.getPsbtInputScriptType(input), 'p2shP2pk');
return;
}
const pubkeys = walletKeys.deriveForChainAndIndex(unspent.chain, unspent.index).publicKeys;
pubkeys.forEach((pk, pkIndex) => {
psbt.validateSignaturesOfInputCommon(index, pk).should.eql(signedBy.includes(walletKeys.triple[pkIndex]));
});
});
}
function testValidSignatures(
tx: HalfSignedUtxoTransaction | FullySignedTransaction,
signedBy: BIP32Interface[],
sign: 'halfsigned' | 'fullsigned'
) {
if (txFormat === 'psbt' && sign === 'halfsigned') {
testPsbtValidSignatures(tx, signedBy);
return;
}
const unspents =
txFormat === 'psbt'
? getUnspentsForPsbt().map((u) => ({ ...u, value: bitgo.toTNumber(u.value, amountType) as TNumber }))
: getUnspents();
const prevOutputs = unspents.map(
(u): utxolib.TxOutput<TNumber> => ({
script: utxolib.address.toOutputScript(u.address, coin.network),
value: u.value,
})
);
const transaction = utxolib.bitgo.createTransactionFromBuffer<TNumber>(
Buffer.from(tx.txHex, 'hex'),
coin.network,
{ amountType }
);
transaction.ins.forEach((input, index) => {
if (inputScripts[index] === 'p2shP2pk') {
assert(coin.isBitGoTaintedUnspent(unspents[index]));
return;
}
const unspent = unspents[index] as WalletUnspent<TNumber>;
const pubkeys = walletKeys.deriveForChainAndIndex(unspent.chain, unspent.index).publicKeys;
pubkeys.forEach((pk, pkIndex) => {
utxolib.bitgo
.verifySignature<TNumber>(
transaction,
index,
prevOutputs[index].value,
{
publicKey: pk,
},
prevOutputs
)
.should.eql(signedBy.includes(walletKeys.triple[pkIndex]));
});
});
}
async function testExplainTx(
stageName: string,
txHex: string,
unspents?: utxolib.bitgo.Unspent<TNumber>[],
pubs?: Triple<string>
): Promise<void> {
const explanation = await coin.explainTransaction<TNumber>({
txHex,
txInfo: {
unspents,
},
pubs,
});
explanation.should.have.properties(
'displayOrder',
'id',
'outputs',
'changeOutputs',
'changeAmount',
'outputAmount',
'inputSignatures',
'signatures'
);
const expectedSignatureCount =
stageName === 'prebuild' || pubs === undefined
? 0
: stageName.startsWith('halfSigned')
? 1
: stageName.startsWith('fullSigned')
? 2
: undefined;
explanation.inputSignatures.should.eql(
// FIXME(BG-35154): implement signature verification for replay protection inputs
inputScripts.map((type) => (type === 'p2shP2pk' ? 0 : expectedSignatureCount))
);
explanation.signatures.should.eql(expectedSignatureCount);
explanation.changeAmount.should.eql('0'); // no change addresses given
let expectedOutputAmount =
BigInt((txFormat === 'psbt' ? getUnspentsForPsbt() : getUnspents()).length) * BigInt(value);
inputScripts.forEach((type) => {
if (type === 'p2shP2pk') {
// replayProtection unspents have value 1000
expectedOutputAmount -= BigInt(value);
expectedOutputAmount += BigInt(1000);
}
});
expectedOutputAmount -= BigInt(1000); // fee of 1000
explanation.outputAmount.should.eql(expectedOutputAmount.toString());
}
it('have valid signature for half-signed transaction', function () {
if (transactionStages.halfSignedUserBackup) {
testValidSignatures(transactionStages.halfSignedUserBackup, [walletKeys.user], 'halfsigned');
}
testValidSignatures(transactionStages.halfSignedUserBitGo, [walletKeys.user], 'halfsigned');
});
it('have valid signatures for full-signed transaction', function () {
if (!fullSign) {
return this.skip();
}
if (transactionStages.fullSignedUserBackup) {
testValidSignatures(transactionStages.fullSignedUserBackup, [walletKeys.user, walletKeys.backup], 'fullsigned');
}
if (transactionStages.fullSignedUserBitGo) {
testValidSignatures(transactionStages.fullSignedUserBitGo, [walletKeys.user, walletKeys.bitgo], 'fullsigned');
}
});
it('have correct results for explainTransaction', async function () {
for (const [stageName, stageTx] of Object.entries(transactionStages)) {
if (!stageTx) {
continue;
}
const txHex =
stageTx instanceof utxolib.bitgo.UtxoPsbt || stageTx instanceof utxolib.bitgo.UtxoTransaction
? stageTx.toBuffer().toString('hex')
: stageTx.txHex;
const pubs = walletKeys.triple.map((k) => k.neutered().toBase58()) as Triple<string>;
const unspents =
txFormat === 'psbt'
? getUnspentsForPsbt().map((u) => ({ ...u, value: bitgo.toTNumber(u.value, amountType) as TNumber }))
: getUnspents();
await testExplainTx(stageName, txHex, unspents, pubs);
await testExplainTx(stageName, txHex, unspents);
}
});
});
}
function runWithAmountType(
coin: AbstractUtxoCoin,
inputScripts: testutil.InputScriptType[],
txFormat: 'legacy' | 'psbt'
) {
const amountType = coin.amountType;
if (amountType === 'bigint') {
run<bigint>(coin, inputScripts, txFormat, amountType);
} else {
run(coin, inputScripts, txFormat, amountType);
}
}
utxoCoins.forEach((coin) =>
getScriptTypes2Of3().forEach((type) => {
(['legacy', 'psbt'] as const).forEach((txFormat) => {
if ((type === 'taprootKeyPathSpend' || type === 'p2trMusig2') && txFormat !== 'psbt') {
return;
}
if (coin.supportsAddressType(type === 'taprootKeyPathSpend' ? 'p2trMusig2' : type)) {
runWithAmountType(coin, [type, type], txFormat);
if (getReplayProtectionAddresses(coin.network).length) {
runWithAmountType(coin, ['p2shP2pk', type], txFormat);
}
}
});
})
);
Выполнить команду
Для локальной разработки. Не используйте в интернете!