From 9a8a6b58b5486cf07a27b19ffa54a70dcb8db541 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Thu, 6 Aug 2026 23:33:46 -0400 Subject: [PATCH 1/6] make CWC.Transactions create, sign, and getSighash all accept both bitcore-node and bitcore-lib style transactions --- .../src/transactions/btc/index.ts | 102 ++++++++++++------ 1 file changed, 67 insertions(+), 35 deletions(-) diff --git a/packages/crypto-wallet-core/src/transactions/btc/index.ts b/packages/crypto-wallet-core/src/transactions/btc/index.ts index e172e8ad8a0..25432214429 100644 --- a/packages/crypto-wallet-core/src/transactions/btc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/btc/index.ts @@ -16,42 +16,44 @@ export class BTCTxProvider { selectCoins( recipients: Array<{ amount: number }>, - utxos: Array<{ - value: number; - mintHeight: number; - txid?: string; - mintTxid?: string; - mintIndex?: number; - }>, + utxos: UtxoType[], fee: number ) { - utxos = utxos.sort(function(a, b) { - return a.mintHeight - b.mintHeight; - }); + // Only sort by block height if utxos are bitcore-node style + if (utxos[0].mintHeight != undefined) { + utxos = utxos.sort(function(a, b) { + return a.mintHeight - b.mintHeight; + }); + } let index = 0; let utxoSum = 0; const recepientSum = recipients.reduce((sum, cur) => sum + Number(cur.amount), fee || 0); while (utxoSum < recepientSum) { const utxo = utxos[index]; - utxoSum += Number(utxo.value); + utxoSum += Number(utxo.value ?? utxo.satoshis); index += 1; } const filteredUtxos = utxos.slice(0, index); return filteredUtxos; } - create({ recipients, utxos = [], change, feeRate, fee, isSweep, replaceByFee, lockUntilDate, lockUntilBlock }) { + create(params: { + recipients: Array<{ address: string; amount: number }>; + utxos: UtxoType[]; + change: string; + feeRate: number; + fee: number; + isSweep: boolean; + replaceByFee: boolean; + lockUntilDate: number; + lockUntilBlock: number; + }) { + const { recipients, utxos = [], change, feeRate, fee, isSweep, replaceByFee, lockUntilDate, lockUntilBlock } = params; const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(utxo => { - const btcUtxo = Object.assign({}, utxo, { - amount: utxo.value / 1e8, - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex - }); - return new this.lib.Transaction.UnspentOutput(btcUtxo); - }); + const btcUtxos = utxos[0].mintTxid ? this.nodeToLibUtxos(filteredUtxos) : filteredUtxos; const tx = new this.lib.Transaction().from(btcUtxos); + if (fee) { tx.fee(fee); } @@ -62,7 +64,7 @@ export class BTCTxProvider { tx.change(change); } for (const recipient of recipients) { - tx.to(recipient.address, parseInt(recipient.amount)); + tx.to(recipient.address, parseInt(recipient.amount as any)); } if (replaceByFee && typeof tx.enableRBF === 'function') { tx.enableRBF(); @@ -133,7 +135,7 @@ export class BTCTxProvider { return bitcoreTx.hash; } - sign(params: { tx: string; keys: Array; utxos: any[]; pubkeys?: any[]; threshold?: number; opts: any }) { + sign(params: { tx: string; keys: Array; utxos: UtxoType[]; pubkeys?: any[]; threshold?: number; opts: any }) { const { tx, keys, pubkeys, threshold, opts } = params; const utxos = params.utxos || []; const bitcoreTx = new this.lib.Transaction(tx); @@ -152,17 +154,28 @@ export class BTCTxProvider { return signedTx; } - getRelatedUtxos({ outputs, utxos }) { + /** + * Converts the utxos in a bitcore-nodes database to bitcore lib utxos + * + * @param utxos bitcore-node style utxos + * @returns lib style utxos + */ + nodeToLibUtxos(utxos: NodeUtxoType[]): BitcoreLib.Transaction.UnspentOutput[] { + return utxos.map(utxo => new this.lib.Transaction.UnspentOutput({ + satoshis: utxo.value, + // bitcore-node utxos have both mintTxid and spentTxid + txid: utxo.mintTxid, + outputIndex: utxo.mintIndex, + script: utxo.script, + address: utxo.address + })); + } + + getRelatedUtxos(params: { outputs: any[]; utxos: UtxoType[] }) { + const { outputs, utxos } = params; const txids = outputs.map(output => output.toObject().prevTxId); const applicableUtxos = utxos.filter(utxo => txids.includes(utxo.txid || utxo.mintTxid)); - return applicableUtxos.map(utxo => { - const btcUtxo = Object.assign({}, utxo, { - amount: utxo.value / Math.pow(10, 8), - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex - }); - return new this.lib.Transaction.UnspentOutput(btcUtxo); - }); + return utxos[0].mintTxid == undefined ? applicableUtxos : this.nodeToLibUtxos(applicableUtxos); } getOutputsFromTx({ tx }) { @@ -172,7 +185,8 @@ export class BTCTxProvider { }); } - getSigningAddresses({ tx, utxos }): string[] { + getSigningAddresses(params: { tx: string | BitcoreLib.Transaction; utxos: UtxoType[] }): string[] { + const { tx, utxos } = params; const bitcoreTx = new this.lib.Transaction(tx); const applicableUtxos = this.getRelatedUtxos({ outputs: bitcoreTx.inputs, @@ -184,7 +198,7 @@ export class BTCTxProvider { getSighash(params: { tx: string | BitcoreLib.Transaction; index: number; - utxos?: BitcoreLib.Transaction.UnspentOutput[]; + utxos?: UtxoType[]; pubKey?: string | BitcoreLib.PublicKey | BitcoreLib.HDPublicKey; path?: string; sigtype?: number; @@ -204,7 +218,7 @@ export class BTCTxProvider { tx = new this.lib.Transaction(tx); } if (utxos) { - tx.associateInputs(utxos.map(this.lib.Transaction.UnspentOutput), pubKeys, threshold, opts); + tx.associateInputs(utxos[0].mintTxid ? this.nodeToLibUtxos(utxos) : utxos, pubKeys, threshold, opts); } $.checkState(tx.inputs[index].output instanceof this.lib.Transaction.Output, 'Input must have all utxo info'); @@ -224,4 +238,22 @@ export class BTCTxProvider { } } -type SignatureType = BitcoreLib.Transaction.Signature | BitcoreLib.crypto.Signature | TssSig; \ No newline at end of file +type SignatureType = BitcoreLib.Transaction.Signature | BitcoreLib.crypto.Signature | TssSig; +// bitcore-node style utxo minus values that are not used +type NodeUtxoType = { + // network: string; + // chain: string; + mintTxid: string; + mintIndex: number; + mintHeight: number; + // coinbase: boolean; + value: number; + address: string; + script: string; + // spentTxid: string; + // spentHeight?: number; + // confirmations?: number; + // sequenceNumber?: number; +} +// utxo type recieved externaly from this class that could either be from bitcore-node or already a lib utxo +type UtxoType = NodeUtxoType | BitcoreLib.Transaction.UnspentOutput; From 1cb66172cb2627703b1c3e2067ca4f913115ede6 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Fri, 7 Aug 2026 10:29:00 -0400 Subject: [PATCH 2/6] apply node lib utxo conversion to bch, ltc, and doge --- .../crypto-wallet-core/src/transactions/bch/index.ts | 9 +-------- .../crypto-wallet-core/src/transactions/doge/index.ts | 9 +-------- .../crypto-wallet-core/src/transactions/ltc/index.ts | 9 +-------- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/packages/crypto-wallet-core/src/transactions/bch/index.ts b/packages/crypto-wallet-core/src/transactions/bch/index.ts index fdb3314ab3e..f83ebb968fd 100644 --- a/packages/crypto-wallet-core/src/transactions/bch/index.ts +++ b/packages/crypto-wallet-core/src/transactions/bch/index.ts @@ -5,14 +5,7 @@ export class BCHTxProvider extends BTCTxProvider { lib = BitcoreLibCash; create({ recipients, utxos = [], change, fee = 20000, isSweep }) { const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(utxo => { - const btcUtxo = Object.assign({}, utxo, { - amount: utxo.value / 1e8, - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex - }); - return new this.lib.Transaction.UnspentOutput(btcUtxo); - }); + const btcUtxos = filteredUtxos[0].mintTxid == undefined ? filteredUtxos : this.nodeToLibUtxos(filteredUtxos); const tx = new this.lib.Transaction().from(btcUtxos).feePerByte(Number(fee) + 2); if (change) { tx.change(change); diff --git a/packages/crypto-wallet-core/src/transactions/doge/index.ts b/packages/crypto-wallet-core/src/transactions/doge/index.ts index b4e97280056..d6510ea3999 100644 --- a/packages/crypto-wallet-core/src/transactions/doge/index.ts +++ b/packages/crypto-wallet-core/src/transactions/doge/index.ts @@ -5,14 +5,7 @@ export class DOGETxProvider extends BTCTxProvider { lib = BitcoreLibDoge; create({ recipients, utxos = [], change, feeRate, fee = 20000 }) { const filteredUtxos = this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(utxo => { - const btcUtxo = Object.assign({}, utxo, { - amount: utxo.value / 1e8, - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex - }); - return new this.lib.Transaction.UnspentOutput(btcUtxo); - }); + const btcUtxos = filteredUtxos[0].mintTxid == undefined ? filteredUtxos : this.nodeToLibUtxos(filteredUtxos); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); diff --git a/packages/crypto-wallet-core/src/transactions/ltc/index.ts b/packages/crypto-wallet-core/src/transactions/ltc/index.ts index 41cd1a5ef8b..653a0379379 100644 --- a/packages/crypto-wallet-core/src/transactions/ltc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/ltc/index.ts @@ -5,14 +5,7 @@ export class LTCTxProvider extends BTCTxProvider { lib = BitcoreLibLtc; create({ recipients, utxos = [], change, feeRate, fee = 20000 }) { const filteredUtxos = this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(utxo => { - const btcUtxo = Object.assign({}, utxo, { - amount: utxo.value / 1e8, - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex - }); - return new this.lib.Transaction.UnspentOutput(btcUtxo); - }); + const btcUtxos = filteredUtxos[0].mintTxid == undefined ? filteredUtxos : this.nodeToLibUtxos(filteredUtxos); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); From 01c55ff006abdd5cf665bd613f8a6eb170590e18 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Fri, 7 Aug 2026 12:32:57 -0400 Subject: [PATCH 3/6] standardize utxo type differentiation with isNodeUtxo --- .../src/transactions/bch/index.ts | 2 +- .../src/transactions/btc/index.ts | 19 ++++++++++++++----- .../src/transactions/doge/index.ts | 2 +- .../src/transactions/ltc/index.ts | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/crypto-wallet-core/src/transactions/bch/index.ts b/packages/crypto-wallet-core/src/transactions/bch/index.ts index f83ebb968fd..42cae047c51 100644 --- a/packages/crypto-wallet-core/src/transactions/bch/index.ts +++ b/packages/crypto-wallet-core/src/transactions/bch/index.ts @@ -5,7 +5,7 @@ export class BCHTxProvider extends BTCTxProvider { lib = BitcoreLibCash; create({ recipients, utxos = [], change, fee = 20000, isSweep }) { const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos[0].mintTxid == undefined ? filteredUtxos : this.nodeToLibUtxos(filteredUtxos); + const btcUtxos = this.isNodeUtxo(filteredUtxos[0]) ? this.nodeToLibUtxos(filteredUtxos) : filteredUtxos; const tx = new this.lib.Transaction().from(btcUtxos).feePerByte(Number(fee) + 2); if (change) { tx.change(change); diff --git a/packages/crypto-wallet-core/src/transactions/btc/index.ts b/packages/crypto-wallet-core/src/transactions/btc/index.ts index 25432214429..641caaea12f 100644 --- a/packages/crypto-wallet-core/src/transactions/btc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/btc/index.ts @@ -20,7 +20,7 @@ export class BTCTxProvider { fee: number ) { // Only sort by block height if utxos are bitcore-node style - if (utxos[0].mintHeight != undefined) { + if (this.isNodeUtxo(utxos[0])) { utxos = utxos.sort(function(a, b) { return a.mintHeight - b.mintHeight; }); @@ -51,9 +51,8 @@ export class BTCTxProvider { }) { const { recipients, utxos = [], change, feeRate, fee, isSweep, replaceByFee, lockUntilDate, lockUntilBlock } = params; const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, fee); - const btcUtxos = utxos[0].mintTxid ? this.nodeToLibUtxos(filteredUtxos) : filteredUtxos; + const btcUtxos = this.isNodeUtxo(utxos[0]) ? this.nodeToLibUtxos(filteredUtxos) : filteredUtxos; const tx = new this.lib.Transaction().from(btcUtxos); - if (fee) { tx.fee(fee); } @@ -171,11 +170,21 @@ export class BTCTxProvider { })); } + /** + * Return true if utxo is a bitcore-node utxo + * + * @param utxo either a bitcore-lib or bitcore-node utxo + * @returns true if node utxo + */ + isNodeUtxo(utxo: UtxoType): boolean { + return utxo.mintTxid != undefined; + } + getRelatedUtxos(params: { outputs: any[]; utxos: UtxoType[] }) { const { outputs, utxos } = params; const txids = outputs.map(output => output.toObject().prevTxId); const applicableUtxos = utxos.filter(utxo => txids.includes(utxo.txid || utxo.mintTxid)); - return utxos[0].mintTxid == undefined ? applicableUtxos : this.nodeToLibUtxos(applicableUtxos); + return this.isNodeUtxo(utxos[0]) ? this.nodeToLibUtxos(applicableUtxos) : applicableUtxos; } getOutputsFromTx({ tx }) { @@ -218,7 +227,7 @@ export class BTCTxProvider { tx = new this.lib.Transaction(tx); } if (utxos) { - tx.associateInputs(utxos[0].mintTxid ? this.nodeToLibUtxos(utxos) : utxos, pubKeys, threshold, opts); + tx.associateInputs(this.isNodeUtxo(utxos[0]) ? this.nodeToLibUtxos(utxos) : utxos, pubKeys, threshold, opts); } $.checkState(tx.inputs[index].output instanceof this.lib.Transaction.Output, 'Input must have all utxo info'); diff --git a/packages/crypto-wallet-core/src/transactions/doge/index.ts b/packages/crypto-wallet-core/src/transactions/doge/index.ts index d6510ea3999..a53c4043cfa 100644 --- a/packages/crypto-wallet-core/src/transactions/doge/index.ts +++ b/packages/crypto-wallet-core/src/transactions/doge/index.ts @@ -5,7 +5,7 @@ export class DOGETxProvider extends BTCTxProvider { lib = BitcoreLibDoge; create({ recipients, utxos = [], change, feeRate, fee = 20000 }) { const filteredUtxos = this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos[0].mintTxid == undefined ? filteredUtxos : this.nodeToLibUtxos(filteredUtxos); + const btcUtxos = this.isNodeUtxo(filteredUtxos[0]) ? this.nodeToLibUtxos(filteredUtxos) : filteredUtxos; const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); diff --git a/packages/crypto-wallet-core/src/transactions/ltc/index.ts b/packages/crypto-wallet-core/src/transactions/ltc/index.ts index 653a0379379..1c28dd3fdb8 100644 --- a/packages/crypto-wallet-core/src/transactions/ltc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/ltc/index.ts @@ -5,7 +5,7 @@ export class LTCTxProvider extends BTCTxProvider { lib = BitcoreLibLtc; create({ recipients, utxos = [], change, feeRate, fee = 20000 }) { const filteredUtxos = this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos[0].mintTxid == undefined ? filteredUtxos : this.nodeToLibUtxos(filteredUtxos); + const btcUtxos = this.isNodeUtxo(filteredUtxos[0]) ? this.nodeToLibUtxos(filteredUtxos) : filteredUtxos; const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); From b72064e92a14c0234b4993ff8e625bcfece5cf63 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Fri, 7 Aug 2026 15:17:07 -0400 Subject: [PATCH 4/6] refactored CWC.Transactions utxo handling with standard and external utxo types --- .../src/transactions/bch/index.ts | 2 +- .../src/transactions/btc/index.ts | 120 ++++++++++-------- .../src/transactions/doge/index.ts | 2 +- .../src/transactions/ltc/index.ts | 2 +- 4 files changed, 68 insertions(+), 58 deletions(-) diff --git a/packages/crypto-wallet-core/src/transactions/bch/index.ts b/packages/crypto-wallet-core/src/transactions/bch/index.ts index 42cae047c51..d89fca223b4 100644 --- a/packages/crypto-wallet-core/src/transactions/bch/index.ts +++ b/packages/crypto-wallet-core/src/transactions/bch/index.ts @@ -5,7 +5,7 @@ export class BCHTxProvider extends BTCTxProvider { lib = BitcoreLibCash; create({ recipients, utxos = [], change, fee = 20000, isSweep }) { const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, fee); - const btcUtxos = this.isNodeUtxo(filteredUtxos[0]) ? this.nodeToLibUtxos(filteredUtxos) : filteredUtxos; + const btcUtxos = filteredUtxos.map(this.standardizeUtxo); const tx = new this.lib.Transaction().from(btcUtxos).feePerByte(Number(fee) + 2); if (change) { tx.change(change); diff --git a/packages/crypto-wallet-core/src/transactions/btc/index.ts b/packages/crypto-wallet-core/src/transactions/btc/index.ts index 641caaea12f..802e367589a 100644 --- a/packages/crypto-wallet-core/src/transactions/btc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/btc/index.ts @@ -16,11 +16,11 @@ export class BTCTxProvider { selectCoins( recipients: Array<{ amount: number }>, - utxos: UtxoType[], + utxos: UtxoTypeE[], fee: number - ) { + ): UtxoTypeE[] { // Only sort by block height if utxos are bitcore-node style - if (this.isNodeUtxo(utxos[0])) { + if (utxos[0].mintHeight != undefined) { utxos = utxos.sort(function(a, b) { return a.mintHeight - b.mintHeight; }); @@ -38,9 +38,28 @@ export class BTCTxProvider { return filteredUtxos; } + + /** + * Standardize utxo for internal funcionality. + * Accepts either bitcore-node or lib (bitcore-lib, bitcore-lib-cash, etc.). + * Handles both lib style utxos: UnspentOutput properties and UnspentOutput.toObject properties. + * + * @param utxos either a bitcore-node or lib utxo + * @returns utxo in the standard, internaly used format + */ + standardizeUtxo(utxo: UtxoTypeE): UtxoTypeS { + return { + satoshis: utxo.satoshis ?? utxo.value ?? (utxo.amount != undefined ? this.lib.Unit.fromSatoshis(utxo.amount) : undefined), + txId: utxo.txId ?? utxo.mintTxid ?? utxo.txid, + outputIndex: utxo.outputIndex ?? utxo.mintIndex ?? utxo.vout, + script: typeof utxo.script === 'string' ? utxo.script : utxo.script.toString() ?? utxo.scriptPubkey, + address: typeof utxo.address === 'string' ? utxo.address : utxo.address.toString() + }; + } + create(params: { recipients: Array<{ address: string; amount: number }>; - utxos: UtxoType[]; + utxos: UtxoTypeE[]; change: string; feeRate: number; fee: number; @@ -51,7 +70,7 @@ export class BTCTxProvider { }) { const { recipients, utxos = [], change, feeRate, fee, isSweep, replaceByFee, lockUntilDate, lockUntilBlock } = params; const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, fee); - const btcUtxos = this.isNodeUtxo(utxos[0]) ? this.nodeToLibUtxos(filteredUtxos) : filteredUtxos; + const btcUtxos = filteredUtxos.map(this.standardizeUtxo); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); @@ -134,15 +153,23 @@ export class BTCTxProvider { return bitcoreTx.hash; } - sign(params: { tx: string; keys: Array; utxos: UtxoType[]; pubkeys?: any[]; threshold?: number; opts: any }) { + sign(params: { + tx: string; + keys: Array; + utxos: UtxoTypeE[]; + pubkeys?: any[]; + threshold?: number; + opts: any; + }) { const { tx, keys, pubkeys, threshold, opts } = params; const utxos = params.utxos || []; const bitcoreTx = new this.lib.Transaction(tx); + const btcUtxos = utxos.map(this.standardizeUtxo); const applicableUtxos = this.getRelatedUtxos({ outputs: bitcoreTx.inputs, - utxos + utxos: btcUtxos }); - bitcoreTx.associateInputs(applicableUtxos, pubkeys, threshold, opts); + bitcoreTx.associateInputs(applicableUtxos.map(this.lib.Transaction.UnspentOutput), pubkeys, threshold, opts); const uniqePrivKeys = Object.values(keys.reduce((map, key) => { // Need to preserve (un)compressed property, so don't use key.privKey.toString(); const pk = new this.lib.PrivateKey(key.privKey); @@ -153,38 +180,13 @@ export class BTCTxProvider { return signedTx; } - /** - * Converts the utxos in a bitcore-nodes database to bitcore lib utxos - * - * @param utxos bitcore-node style utxos - * @returns lib style utxos - */ - nodeToLibUtxos(utxos: NodeUtxoType[]): BitcoreLib.Transaction.UnspentOutput[] { - return utxos.map(utxo => new this.lib.Transaction.UnspentOutput({ - satoshis: utxo.value, - // bitcore-node utxos have both mintTxid and spentTxid - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex, - script: utxo.script, - address: utxo.address - })); - } - - /** - * Return true if utxo is a bitcore-node utxo - * - * @param utxo either a bitcore-lib or bitcore-node utxo - * @returns true if node utxo - */ - isNodeUtxo(utxo: UtxoType): boolean { - return utxo.mintTxid != undefined; - } - - getRelatedUtxos(params: { outputs: any[]; utxos: UtxoType[] }) { + getRelatedUtxos(params: { + outputs: BitcoreLib.Transaction.Input[]; + utxos: UtxoTypeS[]; + }): UtxoTypeS[] { const { outputs, utxos } = params; const txids = outputs.map(output => output.toObject().prevTxId); - const applicableUtxos = utxos.filter(utxo => txids.includes(utxo.txid || utxo.mintTxid)); - return this.isNodeUtxo(utxos[0]) ? this.nodeToLibUtxos(applicableUtxos) : applicableUtxos; + return utxos.filter(utxo => txids.includes(utxo.txId)); } getOutputsFromTx({ tx }) { @@ -194,12 +196,13 @@ export class BTCTxProvider { }); } - getSigningAddresses(params: { tx: string | BitcoreLib.Transaction; utxos: UtxoType[] }): string[] { + getSigningAddresses(params: { tx: string | BitcoreLib.Transaction; utxos: UtxoTypeE[] }): string[] { const { tx, utxos } = params; const bitcoreTx = new this.lib.Transaction(tx); + const btcUtxos = utxos.map(this.standardizeUtxo); const applicableUtxos = this.getRelatedUtxos({ outputs: bitcoreTx.inputs, - utxos + utxos: btcUtxos }); return applicableUtxos.map(utxo => utxo.address); } @@ -207,7 +210,7 @@ export class BTCTxProvider { getSighash(params: { tx: string | BitcoreLib.Transaction; index: number; - utxos?: UtxoType[]; + utxos?: UtxoTypeE[]; pubKey?: string | BitcoreLib.PublicKey | BitcoreLib.HDPublicKey; path?: string; sigtype?: number; @@ -227,7 +230,8 @@ export class BTCTxProvider { tx = new this.lib.Transaction(tx); } if (utxos) { - tx.associateInputs(this.isNodeUtxo(utxos[0]) ? this.nodeToLibUtxos(utxos) : utxos, pubKeys, threshold, opts); + const btcUtxos = utxos.map(this.standardizeUtxo); + tx.associateInputs(btcUtxos.map(this.lib.Transaction.UnspentOutput), pubKeys, threshold, opts); } $.checkState(tx.inputs[index].output instanceof this.lib.Transaction.Output, 'Input must have all utxo info'); @@ -248,21 +252,27 @@ export class BTCTxProvider { } type SignatureType = BitcoreLib.Transaction.Signature | BitcoreLib.crypto.Signature | TssSig; -// bitcore-node style utxo minus values that are not used -type NodeUtxoType = { - // network: string; - // chain: string; + +// Standard utxo. Used internaly. +type UtxoTypeS = { + txId: string; + outputIndex: number; + satoshis: number; + address: string; + script: string; +} +// Externaly recieved utxo. Could either be node (bitcore-node) or lib (bitcore-lib, bitcore-lib-cash etc.) type. +type UtxoTypeE = UtxoTypeS & { + // node specific properties mintTxid: string; mintIndex: number; mintHeight: number; - // coinbase: boolean; value: number; - address: string; - script: string; - // spentTxid: string; - // spentHeight?: number; - // confirmations?: number; - // sequenceNumber?: number; + script: string | BitcoreLib.Address; + address: string | BitcoreLib.Script; + // UnspentOutput.toObject specific properties + txid: string; + amount: number; + vout: number; + scriptPubkey: string; } -// utxo type recieved externaly from this class that could either be from bitcore-node or already a lib utxo -type UtxoType = NodeUtxoType | BitcoreLib.Transaction.UnspentOutput; diff --git a/packages/crypto-wallet-core/src/transactions/doge/index.ts b/packages/crypto-wallet-core/src/transactions/doge/index.ts index a53c4043cfa..4b5635f3cf1 100644 --- a/packages/crypto-wallet-core/src/transactions/doge/index.ts +++ b/packages/crypto-wallet-core/src/transactions/doge/index.ts @@ -5,7 +5,7 @@ export class DOGETxProvider extends BTCTxProvider { lib = BitcoreLibDoge; create({ recipients, utxos = [], change, feeRate, fee = 20000 }) { const filteredUtxos = this.selectCoins(recipients, utxos, fee); - const btcUtxos = this.isNodeUtxo(filteredUtxos[0]) ? this.nodeToLibUtxos(filteredUtxos) : filteredUtxos; + const btcUtxos = filteredUtxos.map(this.standardizeUtxo); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); diff --git a/packages/crypto-wallet-core/src/transactions/ltc/index.ts b/packages/crypto-wallet-core/src/transactions/ltc/index.ts index 1c28dd3fdb8..9bf55957401 100644 --- a/packages/crypto-wallet-core/src/transactions/ltc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/ltc/index.ts @@ -5,7 +5,7 @@ export class LTCTxProvider extends BTCTxProvider { lib = BitcoreLibLtc; create({ recipients, utxos = [], change, feeRate, fee = 20000 }) { const filteredUtxos = this.selectCoins(recipients, utxos, fee); - const btcUtxos = this.isNodeUtxo(filteredUtxos[0]) ? this.nodeToLibUtxos(filteredUtxos) : filteredUtxos; + const btcUtxos = filteredUtxos.map(this.standardizeUtxo); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); From c4348d79cd909fc2d8093ed4f2d4e7ba615567e0 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Mon, 10 Aug 2026 15:20:50 -0400 Subject: [PATCH 5/6] utxo CWC.Transactions: improved utxo types, multiple bug fixes, and consistent typing --- .../src/transactions/bch/index.ts | 17 ++- .../src/transactions/btc/index.ts | 137 ++++++++++-------- .../src/transactions/doge/index.ts | 17 ++- .../src/transactions/ltc/index.ts | 17 ++- 4 files changed, 116 insertions(+), 72 deletions(-) diff --git a/packages/crypto-wallet-core/src/transactions/bch/index.ts b/packages/crypto-wallet-core/src/transactions/bch/index.ts index d89fca223b4..b1d7740035f 100644 --- a/packages/crypto-wallet-core/src/transactions/bch/index.ts +++ b/packages/crypto-wallet-core/src/transactions/bch/index.ts @@ -1,17 +1,24 @@ import BitcoreLibCash from '@bitpay-labs/bitcore-lib-cash'; -import { BTCTxProvider } from '../btc'; +import { BTCTxProvider, EveryUtxoType } from '../btc'; export class BCHTxProvider extends BTCTxProvider { lib = BitcoreLibCash; - create({ recipients, utxos = [], change, fee = 20000, isSweep }) { - const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(this.standardizeUtxo); + create(params: { + recipients: Array<{ address: string; amount: number }>; + utxos?: EveryUtxoType[]; + change?: string; + fee?: number | string; + isSweep?: boolean; + }): string { + const { recipients, utxos = [], change, fee = 20000, isSweep } = params; + const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, Number(fee)); + const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); const tx = new this.lib.Transaction().from(btcUtxos).feePerByte(Number(fee) + 2); if (change) { tx.change(change); } for (const recipient of recipients) { - tx.to(recipient.address, parseInt(recipient.amount)); + tx.to(recipient.address, Number(recipient.amount)); } return tx.uncheckedSerialize(); } diff --git a/packages/crypto-wallet-core/src/transactions/btc/index.ts b/packages/crypto-wallet-core/src/transactions/btc/index.ts index 802e367589a..f279a200ea0 100644 --- a/packages/crypto-wallet-core/src/transactions/btc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/btc/index.ts @@ -15,12 +15,12 @@ export class BTCTxProvider { lib = BitcoreLib; selectCoins( - recipients: Array<{ amount: number }>, - utxos: UtxoTypeE[], - fee: number - ): UtxoTypeE[] { + recipients: Array<{ amount: number | string }>, + utxos: EveryUtxoType[], + fee?: number + ): EveryUtxoType[] { // Only sort by block height if utxos are bitcore-node style - if (utxos[0].mintHeight != undefined) { + if (utxos.length > 0 && utxos[0].mintHeight != undefined) { utxos = utxos.sort(function(a, b) { return a.mintHeight - b.mintHeight; }); @@ -30,47 +30,47 @@ export class BTCTxProvider { let utxoSum = 0; const recepientSum = recipients.reduce((sum, cur) => sum + Number(cur.amount), fee || 0); while (utxoSum < recepientSum) { + assert(index < utxos.length, 'insufficient funds'); const utxo = utxos[index]; - utxoSum += Number(utxo.value ?? utxo.satoshis); + utxoSum += Number(utxo.value ?? utxo.satoshis ?? this.lib.Unit.fromBTC(utxo.amount).toSatoshis()); index += 1; } const filteredUtxos = utxos.slice(0, index); return filteredUtxos; } - /** * Standardize utxo for internal funcionality. - * Accepts either bitcore-node or lib (bitcore-lib, bitcore-lib-cash, etc.). + * Accepts either a bitcore-node or a lib (bitcore-lib, bitcore-lib-cash, etc.) utxo. * Handles both lib style utxos: UnspentOutput properties and UnspentOutput.toObject properties. - * + * * @param utxos either a bitcore-node or lib utxo * @returns utxo in the standard, internaly used format */ - standardizeUtxo(utxo: UtxoTypeE): UtxoTypeS { + standardizeUtxo(utxo: EveryUtxoType): UtxoType { return { - satoshis: utxo.satoshis ?? utxo.value ?? (utxo.amount != undefined ? this.lib.Unit.fromSatoshis(utxo.amount) : undefined), + satoshis: Number(utxo.satoshis ?? utxo.value ?? this.lib.Unit.fromBTC(utxo.amount ?? 0).toSatoshis()), txId: utxo.txId ?? utxo.mintTxid ?? utxo.txid, - outputIndex: utxo.outputIndex ?? utxo.mintIndex ?? utxo.vout, - script: typeof utxo.script === 'string' ? utxo.script : utxo.script.toString() ?? utxo.scriptPubkey, - address: typeof utxo.address === 'string' ? utxo.address : utxo.address.toString() + outputIndex: Number(utxo.outputIndex ?? utxo.mintIndex ?? utxo.vout ?? 0), + script: utxo.scriptPubKey ?? new this.lib.Script(utxo.script).toHex(), + address: utxo.address != undefined ? new this.lib.Address(utxo.address).toString() : undefined }; } create(params: { - recipients: Array<{ address: string; amount: number }>; - utxos: UtxoTypeE[]; - change: string; - feeRate: number; - fee: number; - isSweep: boolean; - replaceByFee: boolean; - lockUntilDate: number; - lockUntilBlock: number; - }) { + recipients: Array<{ address: string; amount: number | string }>; + utxos?: EveryUtxoType[]; + change?: string; + feeRate?: number | string; + fee?: number | string; + isSweep?: boolean; + replaceByFee?: boolean; + lockUntilDate?: number; + lockUntilBlock?: number; + }): string { const { recipients, utxos = [], change, feeRate, fee, isSweep, replaceByFee, lockUntilDate, lockUntilBlock } = params; - const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(this.standardizeUtxo); + const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, Number(fee)); + const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); @@ -82,7 +82,7 @@ export class BTCTxProvider { tx.change(change); } for (const recipient of recipients) { - tx.to(recipient.address, parseInt(recipient.amount as any)); + tx.to(recipient.address, Number(recipient.amount)); } if (replaceByFee && typeof tx.enableRBF === 'function') { tx.enableRBF(); @@ -99,7 +99,7 @@ export class BTCTxProvider { throw new Error('function getSignature not implemented for UTXO coins'); } - transformSignatureObject(params: { obj: any; sigtype?: number }) { + transformSignatureObject(params: { obj: any; sigtype?: number }): string { const { obj, sigtype } = params; const { v } = obj; let { r, s, i, nhashtype } = obj; @@ -127,7 +127,12 @@ export class BTCTxProvider { return new this.lib.crypto.Signature({ r, s, i, nhashtype }).toString(); } - applySignature(params: { tx: BitcoreLib.Transaction; signature: SignatureType; index: number; sigtype?: number }) { + applySignature(params: { + tx: BitcoreLib.Transaction; + signature: SignatureType; + index: number; + sigtype?: number; + }): BitcoreLib.Transaction { const { index, sigtype, tx } = params; let { signature } = params; assert(tx instanceof this.lib.Transaction, 'tx must be an instance of Transaction'); @@ -148,28 +153,28 @@ export class BTCTxProvider { return tx; } - getHash(params: { tx: string }) { + getHash(params: { tx: TransactionType }): string { const bitcoreTx = new this.lib.Transaction(params.tx); return bitcoreTx.hash; } sign(params: { - tx: string; - keys: Array; - utxos: UtxoTypeE[]; + tx: TransactionType; + keys: Key[]; + utxos: EveryUtxoType[]; pubkeys?: any[]; threshold?: number; opts: any; - }) { + }): string { const { tx, keys, pubkeys, threshold, opts } = params; const utxos = params.utxos || []; const bitcoreTx = new this.lib.Transaction(tx); - const btcUtxos = utxos.map(this.standardizeUtxo); + const btcUtxos = utxos.map(utxo => this.standardizeUtxo(utxo)); const applicableUtxos = this.getRelatedUtxos({ outputs: bitcoreTx.inputs, utxos: btcUtxos }); - bitcoreTx.associateInputs(applicableUtxos.map(this.lib.Transaction.UnspentOutput), pubkeys, threshold, opts); + bitcoreTx.associateInputs(applicableUtxos.map(utxo => new this.lib.Transaction.UnspentOutput(utxo)), pubkeys, threshold, opts); const uniqePrivKeys = Object.values(keys.reduce((map, key) => { // Need to preserve (un)compressed property, so don't use key.privKey.toString(); const pk = new this.lib.PrivateKey(key.privKey); @@ -182,24 +187,29 @@ export class BTCTxProvider { getRelatedUtxos(params: { outputs: BitcoreLib.Transaction.Input[]; - utxos: UtxoTypeS[]; - }): UtxoTypeS[] { + utxos: UtxoType[]; + }): UtxoType[] { const { outputs, utxos } = params; const txids = outputs.map(output => output.toObject().prevTxId); return utxos.filter(utxo => txids.includes(utxo.txId)); } - getOutputsFromTx({ tx }) { - return tx.outputs.map(({ script, satoshis }) => { + getOutputsFromTx(params: { + tx: BitcoreLib.Transaction; + }): Array<{ address: string | BitcoreLib.Script; satoshis: number }> { + return params.tx.outputs.map(({ script, satoshis }) => { const address = script; return { address, satoshis }; }); } - getSigningAddresses(params: { tx: string | BitcoreLib.Transaction; utxos: UtxoTypeE[] }): string[] { + getSigningAddresses(params: { + tx: TransactionType; + utxos: EveryUtxoType[]; + }): (string | undefined)[] { const { tx, utxos } = params; const bitcoreTx = new this.lib.Transaction(tx); - const btcUtxos = utxos.map(this.standardizeUtxo); + const btcUtxos = utxos.map(utxo => this.standardizeUtxo(utxo)); const applicableUtxos = this.getRelatedUtxos({ outputs: bitcoreTx.inputs, utxos: btcUtxos @@ -208,9 +218,9 @@ export class BTCTxProvider { } getSighash(params: { - tx: string | BitcoreLib.Transaction; + tx: TransactionType; index: number; - utxos?: UtxoTypeE[]; + utxos?: EveryUtxoType[]; pubKey?: string | BitcoreLib.PublicKey | BitcoreLib.HDPublicKey; path?: string; sigtype?: number; @@ -230,8 +240,8 @@ export class BTCTxProvider { tx = new this.lib.Transaction(tx); } if (utxos) { - const btcUtxos = utxos.map(this.standardizeUtxo); - tx.associateInputs(btcUtxos.map(this.lib.Transaction.UnspentOutput), pubKeys, threshold, opts); + const btcUtxos = utxos.map(utxo => this.standardizeUtxo(utxo)); + tx.associateInputs(btcUtxos.map(utxo => new this.lib.Transaction.UnspentOutput(utxo)), pubKeys, threshold, opts); } $.checkState(tx.inputs[index].output instanceof this.lib.Transaction.Output, 'Input must have all utxo info'); @@ -253,26 +263,39 @@ export class BTCTxProvider { type SignatureType = BitcoreLib.Transaction.Signature | BitcoreLib.crypto.Signature | TssSig; -// Standard utxo. Used internaly. -type UtxoTypeS = { +/** Transaction data that can be converted into a Transaction via Transaction(tx) */ +type TransactionType = BitcoreLib.Transaction | string | Buffer | object; + +/** + * Standard utxo type use for internal processing. + * Property names are from bitcore-lib's UnspentOutput. + * Note, UnspentOutput addresses and scripts are Address and Script classes respectively, + * here they are both strings. + */ +export type UtxoType = { txId: string; outputIndex: number; satoshis: number; - address: string; script: string; -} -// Externaly recieved utxo. Could either be node (bitcore-node) or lib (bitcore-lib, bitcore-lib-cash etc.) type. -type UtxoTypeE = UtxoTypeS & { - // node specific properties + address?: string; +}; + +/** + * Utxo type for functions were the received utxo type is unknown. + * Could either be in the format of UnspentOutput, UnspentOutput.toObject, or from bitcore-node. + */ +export type EveryUtxoType = Partial; diff --git a/packages/crypto-wallet-core/src/transactions/doge/index.ts b/packages/crypto-wallet-core/src/transactions/doge/index.ts index 4b5635f3cf1..b8e38d9de8a 100644 --- a/packages/crypto-wallet-core/src/transactions/doge/index.ts +++ b/packages/crypto-wallet-core/src/transactions/doge/index.ts @@ -1,11 +1,18 @@ import BitcoreLibDoge from '@bitpay-labs/bitcore-lib-doge'; -import { BTCTxProvider } from '../btc'; +import { BTCTxProvider, EveryUtxoType } from '../btc'; export class DOGETxProvider extends BTCTxProvider { lib = BitcoreLibDoge; - create({ recipients, utxos = [], change, feeRate, fee = 20000 }) { - const filteredUtxos = this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(this.standardizeUtxo); + create(params: { + recipients: Array<{ address: string; amount: number }>; + utxos?: EveryUtxoType[]; + change?: string; + feeRate?: number | string; + fee?: number | string; + }): string { + const { recipients, utxos = [], change, feeRate, fee = 20000 } = params; + const filteredUtxos = this.selectCoins(recipients, utxos, Number(fee)); + const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); @@ -17,7 +24,7 @@ export class DOGETxProvider extends BTCTxProvider { tx.change(change); } for (const recipient of recipients) { - tx.to(recipient.address, parseInt(recipient.amount)); + tx.to(recipient.address, Number(recipient.amount)); } return tx.uncheckedSerialize(); } diff --git a/packages/crypto-wallet-core/src/transactions/ltc/index.ts b/packages/crypto-wallet-core/src/transactions/ltc/index.ts index 9bf55957401..2fdc4840592 100644 --- a/packages/crypto-wallet-core/src/transactions/ltc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/ltc/index.ts @@ -1,11 +1,18 @@ import BitcoreLibLtc from '@bitpay-labs/bitcore-lib-ltc'; -import { BTCTxProvider } from '../btc'; +import { BTCTxProvider, EveryUtxoType } from '../btc'; export class LTCTxProvider extends BTCTxProvider { lib = BitcoreLibLtc; - create({ recipients, utxos = [], change, feeRate, fee = 20000 }) { - const filteredUtxos = this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(this.standardizeUtxo); + create(params: { + recipients: Array<{ address: string; amount: number }>; + utxos?: EveryUtxoType[]; + change?: string; + feeRate?: number | string; + fee?: number | string; + }): string { + const { recipients, utxos = [], change, feeRate, fee = 20000 } = params; + const filteredUtxos = this.selectCoins(recipients, utxos, Number(fee)); + const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); @@ -17,7 +24,7 @@ export class LTCTxProvider extends BTCTxProvider { tx.change(change); } for (const recipient of recipients) { - tx.to(recipient.address, parseInt(recipient.amount)); + tx.to(recipient.address, Number(recipient.amount)); } return tx.uncheckedSerialize(); } From f2e2c6d828259b921c8b55becd5fbac2ddab70b7 Mon Sep 17 00:00:00 2001 From: Micah Maphet Date: Tue, 11 Aug 2026 17:05:00 -0400 Subject: [PATCH 6/6] added full typing to CWC.Transaction --- .../src/transactions/bch/index.ts | 16 +-- .../src/transactions/btc/index.ts | 98 +++++++++++-------- .../src/transactions/doge/index.ts | 16 +-- .../src/transactions/erc20/index.ts | 30 +++--- .../src/transactions/eth/index.ts | 52 ++++++---- .../src/transactions/index.ts | 85 ++++++++++++---- .../src/transactions/ltc/index.ts | 16 +-- .../src/transactions/matic-multisig/index.ts | 22 +++-- .../src/transactions/sol/index.ts | 66 ++++++++----- .../src/transactions/spl/index.ts | 4 +- .../src/transactions/xrp/index.ts | 50 ++++++---- .../src/types/derivation.ts | 6 +- .../test/transactions.test.ts | 65 +++++------- 13 files changed, 307 insertions(+), 219 deletions(-) diff --git a/packages/crypto-wallet-core/src/transactions/bch/index.ts b/packages/crypto-wallet-core/src/transactions/bch/index.ts index b1d7740035f..7da29189550 100644 --- a/packages/crypto-wallet-core/src/transactions/bch/index.ts +++ b/packages/crypto-wallet-core/src/transactions/bch/index.ts @@ -3,13 +3,7 @@ import { BTCTxProvider, EveryUtxoType } from '../btc'; export class BCHTxProvider extends BTCTxProvider { lib = BitcoreLibCash; - create(params: { - recipients: Array<{ address: string; amount: number }>; - utxos?: EveryUtxoType[]; - change?: string; - fee?: number | string; - isSweep?: boolean; - }): string { + create(params: BchCreateParams): string { const { recipients, utxos = [], change, fee = 20000, isSweep } = params; const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, Number(fee)); const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); @@ -23,3 +17,11 @@ export class BCHTxProvider extends BTCTxProvider { return tx.uncheckedSerialize(); } } + +export interface BchCreateParams { + recipients: Array<{ address: string; amount: number }>; + utxos?: EveryUtxoType[]; + change?: string; + fee?: number | string; + isSweep?: boolean; +}; diff --git a/packages/crypto-wallet-core/src/transactions/btc/index.ts b/packages/crypto-wallet-core/src/transactions/btc/index.ts index f279a200ea0..230cac248be 100644 --- a/packages/crypto-wallet-core/src/transactions/btc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/btc/index.ts @@ -57,17 +57,7 @@ export class BTCTxProvider { }; } - create(params: { - recipients: Array<{ address: string; amount: number | string }>; - utxos?: EveryUtxoType[]; - change?: string; - feeRate?: number | string; - fee?: number | string; - isSweep?: boolean; - replaceByFee?: boolean; - lockUntilDate?: number; - lockUntilBlock?: number; - }): string { + create(params: BtcCreateParams): string { const { recipients, utxos = [], change, feeRate, fee, isSweep, replaceByFee, lockUntilDate, lockUntilBlock } = params; const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, Number(fee)); const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); @@ -99,7 +89,7 @@ export class BTCTxProvider { throw new Error('function getSignature not implemented for UTXO coins'); } - transformSignatureObject(params: { obj: any; sigtype?: number }): string { + transformSignatureObject(params: BtcTransformSignatureObjectParams): string { const { obj, sigtype } = params; const { v } = obj; let { r, s, i, nhashtype } = obj; @@ -127,12 +117,7 @@ export class BTCTxProvider { return new this.lib.crypto.Signature({ r, s, i, nhashtype }).toString(); } - applySignature(params: { - tx: BitcoreLib.Transaction; - signature: SignatureType; - index: number; - sigtype?: number; - }): BitcoreLib.Transaction { + applySignature(params: BtcApplySignatureParams): BitcoreLib.Transaction { const { index, sigtype, tx } = params; let { signature } = params; assert(tx instanceof this.lib.Transaction, 'tx must be an instance of Transaction'); @@ -153,19 +138,12 @@ export class BTCTxProvider { return tx; } - getHash(params: { tx: TransactionType }): string { + getHash(params: BtcGetHashParams): string { const bitcoreTx = new this.lib.Transaction(params.tx); return bitcoreTx.hash; } - sign(params: { - tx: TransactionType; - keys: Key[]; - utxos: EveryUtxoType[]; - pubkeys?: any[]; - threshold?: number; - opts: any; - }): string { + sign(params: BtcSignParams): string { const { tx, keys, pubkeys, threshold, opts } = params; const utxos = params.utxos || []; const bitcoreTx = new this.lib.Transaction(tx); @@ -217,22 +195,7 @@ export class BTCTxProvider { return applicableUtxos.map(utxo => utxo.address); } - getSighash(params: { - tx: TransactionType; - index: number; - utxos?: EveryUtxoType[]; - pubKey?: string | BitcoreLib.PublicKey | BitcoreLib.HDPublicKey; - path?: string; - sigtype?: number; - // Multisig params for `associateInputs()` - /** Multisig public keys */ - pubKeys?: string[] | BitcoreLib.PublicKey[]; - /** Threshold for multisig */ - threshold?: number; - /** Options for multisig */ - opts?: any; - // end Multisig params for `associateInputs()` - }): string { + getSighash(params: BtcGetSighashParams): string { const { index, utxos, path, sigtype, pubKeys, threshold, opts } = params; let { tx, pubKey } = params; @@ -299,3 +262,52 @@ export type EveryUtxoType = Partial; + +export interface BtcCreateParams { + recipients: Array<{ address: string; amount: number | string }>; + utxos?: EveryUtxoType[]; + change?: string; + feeRate?: number | string; + fee?: number | string; + isSweep?: boolean; + replaceByFee?: boolean; + lockUntilDate?: number; + lockUntilBlock?: number; +}; + +export interface BtcSignParams { + tx: TransactionType; + keys: Key[]; + utxos: EveryUtxoType[]; + pubkeys?: any[]; + threshold?: number; + opts?: any; +}; + +export interface BtcApplySignatureParams { + tx: BitcoreLib.Transaction; + signature: SignatureType; + index: number; + sigtype?: number; +}; + +export interface BtcGetHashParams { tx: TransactionType }; + +export interface BtcTransformSignatureObjectParams { obj: any; sigtype?: number }; + +export interface BtcGetSighashParams { + tx: TransactionType; + index: number; + utxos?: EveryUtxoType[]; + pubKey?: string | BitcoreLib.PublicKey | BitcoreLib.HDPublicKey; + path?: string; + sigtype?: number; + // Multisig params for `associateInputs()` + /** Multisig public keys */ + pubKeys?: string[] | BitcoreLib.PublicKey[]; + /** Threshold for multisig */ + threshold?: number; + /** Options for multisig */ + opts?: any; + // end Multisig params for `associateInputs()` +}; diff --git a/packages/crypto-wallet-core/src/transactions/doge/index.ts b/packages/crypto-wallet-core/src/transactions/doge/index.ts index b8e38d9de8a..a357e790d08 100644 --- a/packages/crypto-wallet-core/src/transactions/doge/index.ts +++ b/packages/crypto-wallet-core/src/transactions/doge/index.ts @@ -3,13 +3,7 @@ import { BTCTxProvider, EveryUtxoType } from '../btc'; export class DOGETxProvider extends BTCTxProvider { lib = BitcoreLibDoge; - create(params: { - recipients: Array<{ address: string; amount: number }>; - utxos?: EveryUtxoType[]; - change?: string; - feeRate?: number | string; - fee?: number | string; - }): string { + create(params: DogeCreateParams): string { const { recipients, utxos = [], change, feeRate, fee = 20000 } = params; const filteredUtxos = this.selectCoins(recipients, utxos, Number(fee)); const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); @@ -29,3 +23,11 @@ export class DOGETxProvider extends BTCTxProvider { return tx.uncheckedSerialize(); } } + +export interface DogeCreateParams { + recipients: Array<{ address: string; amount: number }>; + utxos?: EveryUtxoType[]; + change?: string; + feeRate?: number | string; + fee?: number | string; +}; diff --git a/packages/crypto-wallet-core/src/transactions/erc20/index.ts b/packages/crypto-wallet-core/src/transactions/erc20/index.ts index 49496ab32b7..1e87b9cd9d3 100644 --- a/packages/crypto-wallet-core/src/transactions/erc20/index.ts +++ b/packages/crypto-wallet-core/src/transactions/erc20/index.ts @@ -10,19 +10,7 @@ export class ERC20TxProvider extends ETHTxProvider { return contract; } - create(params: { - recipients: Array<{ address: string; amount: string }>; - nonce: number; - gasPrice?: number; - data: string; - gasLimit: number; - tokenAddress: string; - network: string; - chainId?: number; - contractAddress?: string; - maxGasFee?: number; - priorityGasFee?: number; - }) { + create(params: Erc20CreateParams) { const { tokenAddress, contractAddress } = params; const data = this.encodeData(params); const recipients = [{ address: contractAddress || tokenAddress, amount: '0' }]; @@ -31,7 +19,7 @@ export class ERC20TxProvider extends ETHTxProvider { } encodeData(params: { - recipients: Array<{ address: string; amount: string }>; + recipients: Array<{ address: string; amount: number | string }>; tokenAddress: string; contractAddress?: string; }) { @@ -55,3 +43,17 @@ export class ERC20TxProvider extends ETHTxProvider { } } } + +export interface Erc20CreateParams { + recipients: Array<{ address: string; amount: number | string }>; + nonce: number; + gasPrice?: number; + gasLimit?: number; + tokenAddress: string; + data?: string; + network: string; + chainId?: number; + contractAddress?: string; + maxGasFee?: number; + priorityGasFee?: number; +}; diff --git a/packages/crypto-wallet-core/src/transactions/eth/index.ts b/packages/crypto-wallet-core/src/transactions/eth/index.ts index c9fa2c49c3e..778b5bdb663 100644 --- a/packages/crypto-wallet-core/src/transactions/eth/index.ts +++ b/packages/crypto-wallet-core/src/transactions/eth/index.ts @@ -16,19 +16,7 @@ export class ETHTxProvider { this.chain = chain; } - create(params: { - recipients: Array<{ address: string; amount: string }>; - nonce: number; - gasPrice?: number; - data: string; - gasLimit: number; - network: string; - chainId?: number; - contractAddress?: string; - maxGasFee?: number; - priorityGasFee?: number; - txType?: number; - }) { + create(params: EthCreateParams) { const { recipients, nonce, gasPrice, gasLimit, network, contractAddress, maxGasFee, priorityGasFee, txType } = params; let { data } = params; let to; @@ -116,18 +104,18 @@ export class ETHTxProvider { return signingKey.sign(ethers.keccak256(tx)); } - getSignature(params: { tx: string; key: Key }) { + getSignature(params: EthGetSignatureParams) { const signatureHex = this.getSignatureObject(params).serialized; return signatureHex; } - getHash(params: { tx: string }) { + getHash(params: EthGetHashParams) { const { tx } = params; // tx must be signed for hash to exist return ethers.Transaction.from(tx).hash; } - applySignature(params: { tx: string; signature: any }) { + applySignature(params: EthApplySignatureParams) { const { tx, signature } = params; const parsedTx = ethers.Transaction.from(tx); const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = parsedTx; @@ -160,7 +148,7 @@ export class ETHTxProvider { return signedTx.serialized; } - sign(params: { tx: string; key: Key }) { + sign(params: EthSignParams) { const { tx, key } = params; const signature = this.getSignatureObject({ tx, key }); return this.applySignature({ tx, signature }); @@ -171,13 +159,39 @@ export class ETHTxProvider { // Web3.utils.toHex('20000') -> '0x323030303030' because it calls utf8ToHex for strings return value != null ? Web3.utils.toHex(BigInt(value)) : undefined; } - transformSignatureObject(params: { obj: any }) { + transformSignatureObject(params: EthTransformSignatureObjectParams) { const { obj } = params; return ethers.Signature.from(obj).serialized; } - getSighash(params: { tx: string }): string { + getSighash(params: EthGetSighashParams): string { const { tx } = params; return ethers.keccak256(tx).slice(2); // remove 0x prefix } } + +export interface EthCreateParams { + recipients: Array<{ address: string; amount: number | bigint | string }>; + nonce: number; + network: string; + data?: string; + gasLimit?: number; + gasPrice?: number; + chainId?: number; + contractAddress?: string; + maxGasFee?: number; + priorityGasFee?: number; + txType?: number; +}; + +export interface EthGetSignatureParams { tx: string; key: Key }; + +export interface EthGetHashParams { tx: string }; + +export interface EthApplySignatureParams { tx: string; signature: any }; + +export interface EthSignParams { tx: string; key: Key }; + +export interface EthTransformSignatureObjectParams { obj: any }; + +export interface EthGetSighashParams { tx: string }; diff --git a/packages/crypto-wallet-core/src/transactions/index.ts b/packages/crypto-wallet-core/src/transactions/index.ts index 47fcbe0076f..e40ebfbe615 100644 --- a/packages/crypto-wallet-core/src/transactions/index.ts +++ b/packages/crypto-wallet-core/src/transactions/index.ts @@ -6,22 +6,22 @@ import { BASEERC20TxProvider, BASETxProvider } from './base'; -import { BCHTxProvider } from './bch'; -import { BTCTxProvider } from './btc'; -import { DOGETxProvider } from './doge'; -import { ERC20TxProvider } from './erc20'; -import { ETHTxProvider } from './eth'; +import { BCHTxProvider, BchCreateParams } from './bch'; +import { BTCTxProvider, BtcApplySignatureParams, BtcCreateParams, BtcGetHashParams, BtcGetSighashParams, BtcSignParams, BtcTransformSignatureObjectParams } from './btc'; +import { DOGETxProvider, DogeCreateParams } from './doge'; +import { ERC20TxProvider, Erc20CreateParams } from './erc20'; +import { ETHTxProvider, EthApplySignatureParams, EthCreateParams, EthGetHashParams, EthGetSignatureParams, EthSignParams, EthTransformSignatureObjectParams } from './eth'; import { ETHMULTISIGTxProvider } from './eth-multisig'; -import { LTCTxProvider } from './ltc'; +import { LTCTxProvider, LtcCreateParams } from './ltc'; import { MATICERC20TxProvider, MATICTxProvider } from './matic'; -import { MATICMULTISIGTxProvider } from './matic-multisig'; +import { MATICMULTISIGTxProvider, MaticMultisigCreateParams } from './matic-multisig'; import { OPERC20TxProvider, OPTxProvider } from './op'; -import { SOLTxProvider } from './sol'; -import { SPLTxProvider } from './spl'; -import { XRPTxProvider } from './xrp'; +import { SOLTxProvider, SolApplySignatureParams, SolCreateParams, SolGetHashParams, SolGetSighashParams, SolGetSignatureParams, SolSignParams } from './sol'; +import { SPLTxProvider, SplCreateParams } from './spl'; +import { XRPTxProvider, XrpApplySignatureParams, XrpCreateParams, XrpGetHashParams, XrpGetSighashParams, XrpSignParams, XrpTransformSignatureObjectParams } from './xrp'; const providers = { BTC: new BTCTxProvider(), @@ -48,38 +48,85 @@ const providers = { }; export class TransactionsProxy { - get({ chain }) { - const normalizedChain = chain.toUpperCase(); + get(params: DefaultParams) { + const normalizedChain = params.chain.toUpperCase(); return providers[normalizedChain]; } - create(params) { + create(params: BtcCreateParams & { chain: 'BTC' }): string; + create(params: BchCreateParams & { chain: 'BCH' }): string; + create(params: LtcCreateParams & { chain: 'LTC' }): string; + create(params: DogeCreateParams & { chain: 'DOGE' }): string; + create(params: MaticMultisigCreateParams & { chain: 'MATICMULTISIG' }): string; + create(params: Erc20CreateParams & { chain: Erc20Chain }): string; + create(params: EthCreateParams & { chain: Exclude }): string; + create(params: SolCreateParams & { chain: 'SOL' }): string; + create(params: SplCreateParams & { chain: 'SOLSPL' }): string; + create(params: XrpCreateParams & { chain: 'XRP' }): string; + create(params: DefaultParams): string { return this.get(params).create(params); } - sign(params): string { + sign(params: BtcSignParams & { chain: UtxoChain }): string; + sign(params: EthSignParams & { chain: EvmChain }): string; + sign(params: SolSignParams & { chain: SolChain }): string; + sign(params: XrpSignParams & { chain: 'XRP' }): string; + sign(params: DefaultParams): string { return this.get(params).sign(params); } - getSignature(params): string { + getSignature(params: Record & { chain: UtxoChain }): any; // not implemented + getSignature(params: EthGetSignatureParams & { chain: EvmChain }): string; + getSignature(params: SolGetSignatureParams & { chain: SolChain }): string; + getSignature(params: XrpGetSighashParams & { chain: 'XRP' }): string; + getSignature(params: DefaultParams): string { return this.get(params).getSignature(params); } - applySignature(params) { + applySignature(params: BtcApplySignatureParams & { chain: UtxoChain }): string; + applySignature(params: EthApplySignatureParams & { chain: EvmChain }): string; + applySignature(params: SolApplySignatureParams & { chain: SolChain }): string; + applySignature(params: XrpApplySignatureParams & { chain: 'XRP' }): string; + applySignature(params: DefaultParams): string { return this.get(params).applySignature(params); } - getHash(params) { + getHash(params: BtcGetHashParams & { chain: UtxoChain }): string; + getHash(params: EthGetHashParams & { chain: EvmChain }): string; + getHash(params: SolGetHashParams & { chain: SolChain }): string; + getHash(params: XrpGetHashParams & { chain: 'XRP' }): string; + getHash(params: DefaultParams): string { return this.get(params).getHash(params); } - transformSignatureObject(params) { + transformSignatureObject(params: BtcTransformSignatureObjectParams & { chain: UtxoChain }): string; + transformSignatureObject(params: EthTransformSignatureObjectParams & { chain: EvmChain }): string; + // not implemented for solana + transformSignatureObject(params: XrpTransformSignatureObjectParams & { chain: 'XRP' }): string; + transformSignatureObject(params: DefaultParams): string { return this.get(params).transformSignatureObject(params); } - getSighash(params): string { + getSighash(params: BtcGetSighashParams & { chain: UtxoChain }): string; + getSighash(params: EthGetSignatureParams & { chain: EvmChain }): string; + getSighash(params: SolGetSighashParams & { chain: SolChain }): string; + getSighash(params: XrpGetSighashParams & { chain: 'XRP' }): string; + getSighash(params: DefaultParams): string { return this.get(params).getSighash(params); } } +type UtxoChain = 'BTC' | 'BCH' | 'LTC' | 'DOGE'; +type Erc20Chain = + 'ETHERC20' | 'MATICERC20' | 'ARBERC20' + | 'BASEERC20' | 'OPERC20' | 'ARCERC20'; +type EvmChain = Erc20Chain | + 'ETH' |'ETHMULTISIG' | 'MATIC' + | 'MATICMULTISIG' | 'ARB' | 'ARC' + | 'OP' | 'OPERC20' | 'BASE'; +type SolChain = 'SOL' | 'SOLSPL'; +type Chain = UtxoChain | EvmChain | SolChain | 'XRP'; + +type DefaultParams = { chain: Chain }; + export default new TransactionsProxy(); diff --git a/packages/crypto-wallet-core/src/transactions/ltc/index.ts b/packages/crypto-wallet-core/src/transactions/ltc/index.ts index 2fdc4840592..a1ec804c2f1 100644 --- a/packages/crypto-wallet-core/src/transactions/ltc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/ltc/index.ts @@ -3,13 +3,7 @@ import { BTCTxProvider, EveryUtxoType } from '../btc'; export class LTCTxProvider extends BTCTxProvider { lib = BitcoreLibLtc; - create(params: { - recipients: Array<{ address: string; amount: number }>; - utxos?: EveryUtxoType[]; - change?: string; - feeRate?: number | string; - fee?: number | string; - }): string { + create(params: LtcCreateParams): string { const { recipients, utxos = [], change, feeRate, fee = 20000 } = params; const filteredUtxos = this.selectCoins(recipients, utxos, Number(fee)); const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); @@ -29,3 +23,11 @@ export class LTCTxProvider extends BTCTxProvider { return tx.uncheckedSerialize(); } } + +export interface LtcCreateParams { + recipients: Array<{ address: string; amount: number }>; + utxos?: EveryUtxoType[]; + change?: string; + feeRate?: number | string; + fee?: number | string; +}; diff --git a/packages/crypto-wallet-core/src/transactions/matic-multisig/index.ts b/packages/crypto-wallet-core/src/transactions/matic-multisig/index.ts index f7be55524b3..eda5566ea67 100644 --- a/packages/crypto-wallet-core/src/transactions/matic-multisig/index.ts +++ b/packages/crypto-wallet-core/src/transactions/matic-multisig/index.ts @@ -10,16 +10,7 @@ export class MATICMULTISIGTxProvider extends MATICTxProvider { return contract; } - create(params: { - recipients: Array<{ address: string; amount: string }>; - nonce: number; - gasPrice: number; - data: string; - gasLimit: number; - multisigContractAddress: string; - network: string; - chainId?: number; - }) { + create(params: MaticMultisigCreateParams) { const { multisigContractAddress } = params; const recipients = [{ address: multisigContractAddress, amount: '0' }]; const newParams = { ...params, recipients }; @@ -170,3 +161,14 @@ export class MATICMULTISIGTxProvider extends MATICTxProvider { return contract.methods.submitTransaction(address, amountStr, data).encodeABI(); } } + +export interface MaticMultisigCreateParams { + recipients: Array<{ address: string; amount: string }>; + nonce: number; + gasPrice: number; + data: string; + gasLimit: number; + multisigContractAddress: string; + network: string; + chainId?: number; +} diff --git a/packages/crypto-wallet-core/src/transactions/sol/index.ts b/packages/crypto-wallet-core/src/transactions/sol/index.ts index 0f6d78386af..2a8a847d0d6 100644 --- a/packages/crypto-wallet-core/src/transactions/sol/index.ts +++ b/packages/crypto-wallet-core/src/transactions/sol/index.ts @@ -11,27 +11,7 @@ export class SOLTxProvider { MAX_TRANSFERS = 12; MINIMUM_PRIORITY_FEE = 1000; - create(params: { - recipients: Array<{ address: string; amount: string; addressKeyPair?: SolKit.KeyPairSigner }>; - from: string; - fee?: number; - feeRate: number; - txType?: 'legacy' | '0'; // legacy, version 0 - category?: 'transfer' | 'createAccount'; // transfer, create account - nonce?: string; // nonce is represented as a transaction id - nonceAddress?: string; - blockHash?: string; - blockHeight?: number; - priorityFee?: number; - computeUnits?: number; - memo?: string; - txInstructions?: Array; - // account creation fields - fromKeyPair?: any; - space?: number; // amount of space to reserve a new account in bytes - mint?: string; // mint address for createATA - ataAddress?: any; // ATA address for createATA - }) { + create(params: SolCreateParams) { const { recipients, from, nonce, nonceAddress, category, space, blockHash, blockHeight, priorityFee, txInstructions, computeUnits, fromKeyPair, memo } = params; const fromAddress = SolKit.address(from); const txType: SolKit.TransactionVersion = ['0', 0].includes(params?.txType) ? 0 : 'legacy'; @@ -180,7 +160,7 @@ export class SOLTxProvider { return SolKit.decompileTransactionMessage(compiledTransactionMessage); } - async sign(params: { tx: string; key: Key }): Promise { + async sign(params: SolSignParams): Promise { const { tx, key } = params; const decodedTx = this.decodeRawTransaction({ rawTx: tx, decodeTransactionMessage: false }); const privKeyBytes = SolKit.getBase58Encoder().encode(key.privKey); @@ -206,7 +186,7 @@ export class SOLTxProvider { return SolKit.getBase58Decoder().decode(signedBytes); } - async getSignature(params: { tx: string; keys: Array }) { + async getSignature(params: SolGetSignatureParams) { const { tx, keys } = params; const key = keys[0]; const signedTx = await this.sign({ tx, key }); @@ -215,7 +195,7 @@ export class SOLTxProvider { return SolKit.getBase58Decoder().decode(sigEncoding); } - applySignature(params: { tx: string; signature: string }): string { + applySignature(params: SolApplySignatureParams): string { const { tx, signature } = params; const signatures = [SolKit.getBase58Encoder().encode(signature)]; const transaction = SolKit.getBase64Encoder().encode(tx); @@ -262,7 +242,7 @@ export class SOLTxProvider { return SolKit.getBase64EncodedWireTransaction(signedTx); } - getHash(params: { tx: string }): string { + getHash(params: SolGetHashParams): string { const { tx } = params; const decodedTx = this.decodeRawTransaction({ rawTx: tx, decodeTransactionMessage: false }); const pubKeys = Object.keys(decodedTx.signatures); @@ -286,7 +266,39 @@ export class SOLTxProvider { return SolKit.getBase58Decoder().decode(signature); } - getSighash(_params: { tx: string }): string { + getSighash(_params: SolGetSighashParams): string { return null; // TODO } -} \ No newline at end of file +} + +export interface SolCreateParams { + recipients: Array<{ address: string; amount: number | string; addressKeyPair?: SolKit.KeyPairSigner }>; + from: string; + fee?: number; + feeRate?: number; + txType?: 'legacy' | '0'; // legacy, version 0 + category?: 'transfer' | 'createAccount'; // transfer, create account + nonce?: string; // nonce is represented as a transaction id + nonceAddress?: string; + blockHash?: string; + blockHeight?: number; + priorityFee?: number; + computeUnits?: number; + memo?: string; + txInstructions?: Array; + // account creation fields + fromKeyPair?: any; + space?: number; // amount of space to reserve a new account in bytes + mint?: string; // mint address for createATA + ataAddress?: any; // ATA address for createATA +}; + +export interface SolSignParams { tx: string; key: Key }; + +export interface SolGetSignatureParams { tx: string; keys: Array }; + +export interface SolApplySignatureParams { tx: string; signature: string }; + +export interface SolGetHashParams { tx: string }; + +export interface SolGetSighashParams { tx: string }; diff --git a/packages/crypto-wallet-core/src/transactions/spl/index.ts b/packages/crypto-wallet-core/src/transactions/spl/index.ts index afa0d6de210..4ceda01af4e 100644 --- a/packages/crypto-wallet-core/src/transactions/spl/index.ts +++ b/packages/crypto-wallet-core/src/transactions/spl/index.ts @@ -4,7 +4,7 @@ import { SOLTxProvider } from '../sol'; export class SPLTxProvider extends SOLTxProvider { - create(params: CreateParams) { + create(params: SplCreateParams) { // Reuse exposed TransactionProxy API (Create) // @ts-expect-error - so public api is minimally changed if (params.category === 'recoverNestedAssociatedToken') { @@ -143,7 +143,7 @@ export class SPLTxProvider extends SOLTxProvider { } } -interface CreateParams { +export interface SplCreateParams { recipients: Array<{ address: string; amount: string; addressKeyPair?: SolKit.KeyPairSigner }>; from: string; fee?: number; diff --git a/packages/crypto-wallet-core/src/transactions/xrp/index.ts b/packages/crypto-wallet-core/src/transactions/xrp/index.ts index 00f4167cb13..a28ea151a30 100644 --- a/packages/crypto-wallet-core/src/transactions/xrp/index.ts +++ b/packages/crypto-wallet-core/src/transactions/xrp/index.ts @@ -8,17 +8,7 @@ import { BTCTxProvider } from '../btc'; import type { Key } from '../../types/derivation'; export class XRPTxProvider { - create(params: { - recipients: Array<{ address: string; amount: string; tag?: number }>; - tag?: number; - from: string; - invoiceID?: string; - fee: number; - feeRate: number; - nonce: number; - txType?: string; - flags?: number | string; - }) { + create(params: XrpCreateParams) { const { recipients, tag, @@ -87,19 +77,19 @@ export class XRPTxProvider { return { signedTransaction: signedTx.tx_blob, hash: signedTx.hash }; } - getSignature(params: { tx: string; key: Key }): string { + getSignature(params: XrpGetSignatureParams): string { const { signedTransaction } = this.getSignatureObject(params); const decoded = (xrpl.decode(signedTransaction) as any) as xrpl.Transaction; return decoded.TxnSignature; } - getHash(params: { tx: string }): string { + getHash(params: XrpGetHashParams): string { const { tx } = params; const prefix = HashPrefix.transactionID.toString('hex').toUpperCase(); return this.sha512Half(prefix + tx); } - applySignature(params: { tx: string; signature: string; pubKey: string }): string { + applySignature(params: XrpApplySignatureParams): string { const { tx, signature, pubKey } = params; const txJSON = (xrpl.decode(tx) as any) as xrpl.Transaction; txJSON.TxnSignature = signature; @@ -108,7 +98,7 @@ export class XRPTxProvider { return signedTx; } - sign(params: { tx: string; key: Key }): string { + sign(params: XrpSignParams): string { const { tx, key } = params; const signature = this.getSignature({ tx, key }); return this.applySignature({ tx, signature, pubKey: key.pubKey }); @@ -122,12 +112,12 @@ export class XRPTxProvider { .slice(0, 64); } - transformSignatureObject(params: { obj: any }) { + transformSignatureObject(params: XrpTransformSignatureObjectParams) { const { obj } = params; return new BTCTxProvider().transformSignatureObject({ obj }); } - getSighash(params: { tx: string; pubKey: string }) { + getSighash(params: XrpGetSighashParams) { const { tx, pubKey } = params; const decoded = RBC.decode(tx); decoded.SigningPubKey = pubKey; @@ -155,4 +145,28 @@ export class XRPTxProvider { return acc; }, {} as T); } -} \ No newline at end of file +} + +export interface XrpCreateParams { + recipients: Array<{ address: string; amount: string; tag?: number }>; + tag?: number; + from: string; + invoiceID?: string; + fee: number; + feeRate?: number; + nonce: number; + txType?: string; + flags?: number | string; +}; + +export interface XrpGetSignatureParams { tx: string; key: Key } + +export interface XrpGetHashParams { tx: string }; + +export interface XrpApplySignatureParams { tx: string; signature: string; pubKey: string }; + +export interface XrpSignParams { tx: string; key: Key }; + +export interface XrpTransformSignatureObjectParams { obj: any }; + +export interface XrpGetSighashParams { tx: string; pubKey: string }; diff --git a/packages/crypto-wallet-core/src/types/derivation.ts b/packages/crypto-wallet-core/src/types/derivation.ts index babb0a87890..dfe53fc2c65 100644 --- a/packages/crypto-wallet-core/src/types/derivation.ts +++ b/packages/crypto-wallet-core/src/types/derivation.ts @@ -1,6 +1,6 @@ export interface Key { - address: string; - privKey?: string; + address?: string; + privKey: string; pubKey?: string; } @@ -31,4 +31,4 @@ export interface IDeriver { * Temporary - converts decrypted private key buffer to lib-specific private key format */ bufferToPrivateKey_TEMP(buf: Buffer, network: string): string; -} \ No newline at end of file +} diff --git a/packages/crypto-wallet-core/test/transactions.test.ts b/packages/crypto-wallet-core/test/transactions.test.ts index 52f12d405d8..3fd74a1c8ee 100644 --- a/packages/crypto-wallet-core/test/transactions.test.ts +++ b/packages/crypto-wallet-core/test/transactions.test.ts @@ -30,7 +30,7 @@ describe('Transaction', function() { } ]; const fee = 7440; - const tx = Transactions.create({ chain: 'BTC', recipients, change, utxos, fee, rbf: true }); + const tx = Transactions.create({ chain: 'BTC', recipients, change, utxos, fee }); const keys = [ { @@ -661,7 +661,7 @@ describe('Transaction', function() { it('should be able to create a XRP tx', () => { const recipients = [{ address: 'rEqj9WKSH7wEkPvWf6b4gCi26Y3F7HbKUF', amount: '123456' }]; const xrpParams = { - chain: 'XRP', + chain: 'XRP' as const, recipients, from: 'rEqj9WKSH7wEkPvWf6b4gCi26Y3F7HbKUF', tag: 123456, @@ -680,7 +680,7 @@ describe('Transaction', function() { try { const recipients = [{ address: 'rEqj9WKSH7wEkPvWf6b4gCi26Y3F7HbKUF', amount: '123456' }]; const xrpParams = { - chain: 'XRP', + chain: 'XRP' as const, recipients, from: 'rEqj9WKSH7wEkPvWf6b4gCi26Y3F7HbKUF', tag: 123456, @@ -699,7 +699,7 @@ describe('Transaction', function() { try { const recipients = [{ address: 'rEqj9WKSH7wEkPvWf6b4gCi26Y3F7HbKUF', amount: '123456' }]; const xrpParams = { - chain: 'XRP', + chain: 'XRP' as const, recipients, from: 'rEqj9WKSH7wEkPvWf6b4gCF', tag: 123456, @@ -715,14 +715,16 @@ describe('Transaction', function() { }); it('should create an AccountSet tx with string flag', () => { + const recipients = [{ address: 'rEqj9WKSH7wEkPvWf6b4gCi26Y3F7HbKUF', amount: '123456' }]; const xrpParams = { - chain: 'XRP', + chain: 'XRP' as const, network: 'testnet', from: 'rMmUqMZRzKKnzrTnN3B6Zcz4qQQvmHowt8', fee: 10, nonce: 11876358, txType: 'accountset', - flags: 'tfRequireDestTag' + flags: 'tfRequireDestTag', + recipients }; const cryptoTx = Transactions.create(xrpParams); const expectedTx = '12000322000100002400B5380668400000000000000A8114E3BEB23E9931CEE681B8CBFDA2F9203EFC18C5BA'; @@ -731,13 +733,14 @@ describe('Transaction', function() { it('should create an AccountSet tx with comma-delimited string flags', () => { const xrpParams = { - chain: 'XRP', + chain: 'XRP' as const, network: 'testnet', from: 'rMmUqMZRzKKnzrTnN3B6Zcz4qQQvmHowt8', fee: 10, nonce: 11876358, txType: 'accountset', - flags: 'tfRequireDestTag,tfDisallowXRP' + flags: 'tfRequireDestTag,tfDisallowXRP', + recipients: [] }; const cryptoTx = Transactions.create(xrpParams); const expectedTx = '12000322001100002400B5380668400000000000000A8114E3BEB23E9931CEE681B8CBFDA2F9203EFC18C5BA'; @@ -746,13 +749,14 @@ describe('Transaction', function() { it('should create an AccountSet tx with number flag', () => { const xrpParams = { - chain: 'XRP', + chain: 'XRP' as const, network: 'testnet', from: 'rMmUqMZRzKKnzrTnN3B6Zcz4qQQvmHowt8', fee: 10, nonce: 11876358, txType: 'accountset', - flags: 65536 // tfRequireDestTag + flags: 65536, // tfRequireDestTag + recipients: [] }; const cryptoTx = Transactions.create(xrpParams); const expectedTx = '12000322000100002400B5380668400000000000000A8114E3BEB23E9931CEE681B8CBFDA2F9203EFC18C5BA'; @@ -761,12 +765,13 @@ describe('Transaction', function() { it('should throw on invalid flag(s)', () => { const xrpParams = { - chain: 'XRP', + chain: 'XRP' as const, network: 'testnet', from: 'rMmUqMZRzKKnzrTnN3B6Zcz4qQQvmHowt8', fee: 10, nonce: 11876358, - txType: 'accountset' + txType: 'accountset', + recipients: [] }; expect(() => Transactions.create({ ...xrpParams, flags: undefined })).to.throw('No XRP flag(s) provided'); expect(() => Transactions.create({ ...xrpParams, flags: null })).to.throw('No XRP flag(s) provided'); @@ -796,7 +801,7 @@ describe('Transaction', function() { } ]; const fee = 7440; - const tx = Transactions.create({ chain: 'DOGE', recipients, change, utxos, fee, rbf: true }); + const tx = Transactions.create({ chain: 'DOGE', recipients, change, utxos, fee }); const keys = [ { @@ -833,7 +838,7 @@ describe('Transaction', function() { } ]; const fee = 7440; - const tx = Transactions.create({ chain: 'LTC', recipients, change, utxos, fee, rbf: true }); + const tx = Transactions.create({ chain: 'LTC', recipients, change, utxos, fee }); const keys = [ { @@ -848,13 +853,14 @@ describe('Transaction', function() { expect(signed).to.eq(expected); }); + // TODO: typing is incorrect it('should be able to create a livenet SOL tx', () => { const rawMaticTx = { network: 'livenet', value: 3896000000000000, to: 'F7FknkRckx4yvA3Gexnx1H3nwPxndMxVt58BwAzEQhcY', from: '8WyoNvKsmfdG6zrbzNBVN8DETyLra3ond61saU9C52YR', - category: 'transfer', + category: 'transfer' as const, blockHash: 'GtV1Hb3FvP3HURHAsj8mGwEqCumvP3pv3i6CVCzYNj3d', blockHeight: 531575, }; @@ -869,8 +875,6 @@ describe('Transaction', function() { 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDb6/gH5XxrVl86CZd+DpqA1jN8YSz91e8yXxOlyeS8tLRnckLdZVIkhi0iAExccvYpTw5tIfPZ8z/OJGQtnvg9QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A+XJrI4siFXUreDo+M94DBeuJwm0Oq5kHqeWuAw7xgBAgIAAQwCAAAAAIALMGTXDQA='; expect(cryptoTx).to.equal(expectedTx); }); - - }); describe('sign', () => { @@ -1740,30 +1744,6 @@ describe('Transaction', function() { expect(hash.length).to.equal(64); expect(hash).to.equal(expectedHash); }); - - it('should get XRP testnet signed tx hash', () => { - const hash = Transactions.getHash({ - chain: 'XRP', - network: 'testnet', - tx: - '120000228000000024000000012E0001E2405011101234567890123456789012345671012345678901234567890156789012345661400000000001E24068400000000000000C732103DBEEC5E9E76DA09C5B502A67136BC2D73423E8902A7C35A8CBC0C5A6AC0469E874473045022100D5C19360E77D691A11CA693F6E8D8472DA6749D16A06E072ED1110EB3FD9E2C80220169F95E55943C3575CEAA46413FE660E4F8F2E7158FAC235DC3CB9C9F26918098114A2C8E8CD9A9133CAD90F2668159AAF572612A5028314A2C8E8CD9A9133CAD90F2668159AAF572612A502' - }); - const expectedHash = '61EA6DF3BD1E435283BA0B06311C7BA683A32A80E465196D9F16A23A439EF6F4'; - expect(hash.length).to.equal(64); - expect(hash).to.equal(expectedHash); - }); - - it('should get XRP livenet signed tx hash', () => { - const hash = Transactions.getHash({ - chain: 'XRP', - network: 'livenet', - tx: - '120000228000000024000000012E0001E2405011101234567890123456789012345671012345678901234567890156789012345661400000000001E24068400000000000000C732103DBEEC5E9E76DA09C5B502A67136BC2D73423E8902A7C35A8CBC0C5A6AC0469E874473045022100D5C19360E77D691A11CA693F6E8D8472DA6749D16A06E072ED1110EB3FD9E2C80220169F95E55943C3575CEAA46413FE660E4F8F2E7158FAC235DC3CB9C9F26918098114A2C8E8CD9A9133CAD90F2668159AAF572612A5028314A2C8E8CD9A9133CAD90F2668159AAF572612A502' - }); - const expectedHash = '61EA6DF3BD1E435283BA0B06311C7BA683A32A80E465196D9F16A23A439EF6F4'; - expect(hash.length).to.equal(64); - expect(hash).to.equal(expectedHash); - }); }); @@ -1885,6 +1865,5 @@ describe('Transaction', function() { const result = ETHTxProvider._toHex('0x200000'); expect(result).to.equal('0x200000'); }); - }); -}); \ No newline at end of file +});