Skip to content

Consistent UTXO Handling for CWC Transactions - #4213

Open
MicahMaphet wants to merge 6 commits into
bitpay:masterfrom
MicahMaphet:uni-tx-create
Open

Consistent UTXO Handling for CWC Transactions#4213
MicahMaphet wants to merge 6 commits into
bitpay:masterfrom
MicahMaphet:uni-tx-create

Conversation

@MicahMaphet

@MicahMaphet MicahMaphet commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Description 🗒️

Previously crypto-wallet-core's CWC.Transactions had inconsistent handling of utxos. .create and .sign required bitcore-node utxos. By bitcore-node utxos I mean how bitcore-node stores utxos in its database. These have mintTxid, spentTxid, mintIndex, and value; while bitcore-lib style utxos have txid, outputIndex, and satoshis. .getSighash required bitcore-lib utxos.

Now CWC.Transactions accepts both kinds of utxos.

Motivation

I need these changes for bitcore-hardware in order to sign transactions. I need to construct a transaction with .create and pass those utxos into a [a given hardware wallet object].sign. To sign with hardware wallets, I need to give the the sighash via .getSighash. The current version of crypto-wallet-core does not allow this because .create and .getSighash take different utxo types. This problem could be most easily solved be simply making .getSighash convert bitcore-node utxos into bitcore-lib utxos. However, we want consistent handling of utxos. This PR instead solves the core problem by making all transaction methods in the cryto-wallet-core utxo chains accept both bitcore-node and bitcore-lib utxos.

Changelog 🪵

functionality 🔨

  • Defined two utxo types for utxo chains (bitcoin, bitcoin cash, litecoin, and doge):
    EveryUtxoType where the properties are unknown due to it being received outside the Transactions class. Can contain any type of utxo data: UnspentOutput, UnspentOutput.toObject, and utxos from the bitcore-node database. Hard to work with because all the properties need to be checked individually.
    UtxoType for internal usage. Nice to work with because the properties don't need to be checked. Uses property naming from UnspentOutput.
  • Transactions.create, Transactions.sign, Transactions.getSighash, and Transactions.getSigningAddresses accept EveryUtxoType
  • Transactions.create silently ignores utxo mintHeight sorting when supplied with bitcore-lib utxos

organization 🎶

  • All functions in utxo Transaction classes have parameter and return types
  • Most functions use 'const { ... } = params;'
  • When the type is 'number | string', Number() is used instead of parseInt()
  • getRelatedUtxos accepts UtxoType rather than bitcore-node utxo types

Testing Notes 🥼 🧪

crypto-wallet-core tests all work, but I can add more to test the new functionality.

The following cases would previously break, but they now work. .create and .getSighash previously use different utxo types but now accept either.

bitcore-lib utxos
const CWC = require('@bitpay-labs/crypto-wallet-core');
const { UnspentOutput } = CWC.BitcoreLib.Transaction;

const utxos = [
  new UnspentOutput({
    outputIndex: 1,
    txid: '6bcb6a3695e24b90d14b8dcbdbb3280a9f5fadb408cd76093a078db6fe4a6f24',
    script: '76a91403b6029fe9863d8c2e4e42ca2c08e55c69dd060188ac',
    amount: 0.002000000,
  }),
  new UnspentOutput({
    outputIndex: 1,
    txid: '5b9f304f363c98ac9270773b9b567b0d5b2f2b0d522120abe7e551584e3c3244',
    script: '76a914b38845dcfc6911d96a43b4c6ec27bc741bdc005588ac',
    amount: 2.000000000,
  })
];

const tx = CWC.Transactions.create({
  chain: 'BTC',
  recipients: [{ address: 'bcrt1qp8eln5e22s4qhyrkcrzeef9l68mfds0fwn82tc', amount: 200_100_000 }],
  utxos,
  isSweep: true
});

const sighash = CWC.Transactions.getSighash({ chain: 'BTC', tx, utxos, index: 0 });
console.log(sighash);
bitcore-node utxos
const CWC = require('@bitpay-labs/crypto-wallet-core');

const utxos = [
  {
    mintIndex: 1,
    mintTxid: '6bcb6a3695e24b90d14b8dcbdbb3280a9f5fadb408cd76093a078db6fe4a6f24',
    script: '76a91403b6029fe9863d8c2e4e42ca2c08e55c69dd060188ac',
    value: 200_000,
  },
  {
    mintIndex: 1,
    mintTxid: '5b9f304f363c98ac9270773b9b567b0d5b2f2b0d522120abe7e551584e3c3244',
    script: '76a914b38845dcfc6911d96a43b4c6ec27bc741bdc005588ac',
    value: 200_000_000,
  }
];

const tx = CWC.Transactions.create({
  chain: 'BTC',
  recipients: [{ address: 'bcrt1qp8eln5e22s4qhyrkcrzeef9l68mfds0fwn82tc', amount: 200_100_000 }],
  utxos,
  isSweep: true
});

const sighash = CWC.Transactions.getSighash({ chain: 'BTC', tx, utxos, index: 0 });
console.log(sighash);

Checklist ✅ 🗒️

  • I have read CONTRIBUTING.md and verified that this PR follows the guidelines and requirements outlined in it.
  • Add tests for new functionality unless not needed.
  • More changes to typing organization if there are any to be made

@MicahMaphet MicahMaphet changed the title Consistent UTXO Handling for Transaction create, sign, getSighash, and getSigningAddresses Consistent UTXO Handling for CWC Transactions Aug 7, 2026

@kajoseph kajoseph left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @MicahMaphet , thanks for the PR and nice catch. I think a better way to approach this is to have a standardizeUtxo function that looks something like this:

standardizeUtxo(utxo) {
  return {
    txid: utxo.txid || utxo.mintTxid,
    vout: utxo.vout || utxo.mintIndex,
    ...etc...
  };
}

then, we can just do

utxos = utxos.map(this.standardizeUtxo);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants