diff --git a/modules/utxo-lib/src/bitgo/index.ts b/modules/utxo-lib/src/bitgo/index.ts index c7961f1247..f204f595c4 100644 --- a/modules/utxo-lib/src/bitgo/index.ts +++ b/modules/utxo-lib/src/bitgo/index.ts @@ -10,6 +10,7 @@ export * from './dash'; export * from './parseInput'; export * from './signature'; export * from './transaction'; +export * from './transactionAmounts'; export * from './types'; export * from './Unspent'; export * from './UtxoPsbt'; diff --git a/modules/utxo-lib/src/bitgo/transactionAmounts.ts b/modules/utxo-lib/src/bitgo/transactionAmounts.ts new file mode 100644 index 0000000000..9aa370d192 --- /dev/null +++ b/modules/utxo-lib/src/bitgo/transactionAmounts.ts @@ -0,0 +1,37 @@ +import { UtxoPsbt } from './UtxoPsbt'; +import { createTransactionFromBuffer } from './transaction'; + +export function getTransactionAmountsFromPsbt(psbt: UtxoPsbt): { + inputCount: number; + outputCount: number; + inputAmount: bigint; + outputAmount: bigint; + fee: bigint; +} { + const inputCount = psbt.data.inputs.length; + const outputCount = psbt.data.outputs.length; + const txInputs = psbt.txInputs; + const txOutputs = psbt.txOutputs; + const inputAmount = psbt.data.inputs.reduce((acc, input, inputIndex) => { + if (input.witnessUtxo) { + return acc + BigInt(input.witnessUtxo.value); + } else if (input.nonWitnessUtxo) { + const tx = createTransactionFromBuffer(input.nonWitnessUtxo, psbt.network, { amountType: 'bigint' }); + return acc + tx.outs[txInputs[inputIndex].index].value; + } else { + throw new Error('missing witnessUtxo and nonWitnessUtxo'); + } + }, BigInt(0)); + const outputAmount = psbt.data.outputs.reduce( + (acc, output, outputIndex) => acc + txOutputs[outputIndex].value, + BigInt(0) + ); + const fee = inputAmount - outputAmount; + return { + inputCount, + outputCount, + inputAmount, + outputAmount, + fee, + }; +} diff --git a/modules/utxo-lib/test/bitgo/psbt/Psbt.ts b/modules/utxo-lib/test/bitgo/psbt/Psbt.ts index 645f145f9e..639a930390 100644 --- a/modules/utxo-lib/test/bitgo/psbt/Psbt.ts +++ b/modules/utxo-lib/test/bitgo/psbt/Psbt.ts @@ -33,6 +33,8 @@ import { deleteWitnessUtxoForNonSegwitInputs, getPsbtInputScriptType, withUnsafeNonSegwit, + getTransactionAmountsFromPsbt, + WalletUnspent, } from '../../../src/bitgo'; import { createOutputScript2of3, @@ -876,8 +878,9 @@ function testUtxoPsbt(coinNetwork: Network) { describe(`Testing UtxoPsbt (de)serialization for ${getNetworkName(coinNetwork)} network`, function () { let psbt: UtxoPsbt; let psbtHex: string; + let unspents: (WalletUnspent | Unspent)[]; before(async function () { - const unspents = mockUnspents(rootWalletKeys, ['p2sh'], BigInt('10000000000000'), coinNetwork); + unspents = mockUnspents(rootWalletKeys, ['p2sh'], BigInt('10000000000000'), coinNetwork); const txBuilderParams = { signer: 'user', cosigner: 'bitgo', @@ -905,6 +908,15 @@ function testUtxoPsbt(coinNetwork: Network) { assert.deepStrictEqual(createPsbtFromHex(psbtHex, coinNetwork, false).toBuffer(), psbt.toBuffer()); }); + it('should be able to get transaction info from psbt', function () { + const txInfo = getTransactionAmountsFromPsbt(psbt); + assert.strictEqual(txInfo.fee, FEE); + assert.strictEqual(txInfo.inputCount, unspents.length); + assert.strictEqual(txInfo.inputAmount, BigInt('10000000000000') * BigInt(unspents.length)); + assert.strictEqual(txInfo.outputAmount, BigInt('10000000000000') * BigInt(unspents.length) - FEE); + assert.strictEqual(txInfo.outputCount, psbt.data.outputs.length); + }); + function deserializeBip32PathsCorrectly(bip32PathsAbsolute: boolean): void { function checkDerivationPrefix(bip32Derivation: { path: string }): void { const path = bip32Derivation.path.split('/');