|
| 1 | +import { bip32, BIP32Interface, bitgo } from '@bitgo/utxo-lib'; |
1 | 2 | import * as utxolib from '@bitgo/utxo-lib'; |
2 | | -import { BitGoBase, IRequestTracer, Triple } from '@bitgo/sdk-core'; |
| 3 | +import { Triple } from '@bitgo/sdk-core'; |
| 4 | + |
3 | 5 | import { |
4 | | - AbstractUtxoCoin, |
5 | 6 | DecoratedExplainTransactionOptions, |
6 | | - WalletOutput, |
7 | 7 | ExplainTransactionOptions, |
8 | | - TransactionExplanation, |
9 | | - TransactionPrebuild, |
10 | 8 | Output, |
11 | | -} from './abstractUtxoCoin'; |
12 | | -import { bip32, BIP32Interface, bitgo } from '@bitgo/utxo-lib'; |
13 | | - |
14 | | -const ScriptRecipientPrefix = 'scriptPubKey:'; |
15 | | - |
16 | | -/** |
17 | | - * Check if the address is a script recipient (starts with `scriptPubKey:`). |
18 | | - * @param address |
19 | | - */ |
20 | | -export function isScriptRecipient(address: string): boolean { |
21 | | - return address.toLowerCase().startsWith(ScriptRecipientPrefix.toLowerCase()); |
22 | | -} |
23 | | - |
24 | | -/** |
25 | | - * An extended address is one that encodes either a regular address or a hex encoded script with the prefix `scriptPubKey:`. |
26 | | - * This function converts the extended address format to either a script or an address. |
27 | | - * @param extendedAddress |
28 | | - */ |
29 | | -export function fromExtendedAddressFormat(extendedAddress: string): { address: string } | { script: string } { |
30 | | - if (isScriptRecipient(extendedAddress)) { |
31 | | - return { script: extendedAddress.slice(ScriptRecipientPrefix.length) }; |
32 | | - } |
33 | | - return { address: extendedAddress }; |
34 | | -} |
35 | | - |
36 | | -/** |
37 | | - * Convert a script or address to the extended address format. |
38 | | - * @param script |
39 | | - * @param network |
40 | | - * @returns if the script is an OP_RETURN script, then it will be prefixed with `scriptPubKey:`, otherwise it will be converted to an address. |
41 | | - */ |
42 | | -export function toExtendedAddressFormat(script: Buffer, network: utxolib.Network): string { |
43 | | - return script[0] === utxolib.opcodes.OP_RETURN |
44 | | - ? `${ScriptRecipientPrefix}${script.toString('hex')}` |
45 | | - : utxolib.address.fromOutputScript(script, network); |
46 | | -} |
47 | | - |
48 | | -export function assertValidTransactionRecipient(output: { amount: bigint | number | string; address?: string }): void { |
49 | | - // In the case that this is an OP_RETURN output or another non-encodable scriptPubkey, we dont have an address. |
50 | | - // We will verify that the amount is zero, and if it isnt then we will throw an error. |
51 | | - if (!output.address || isScriptRecipient(output.address)) { |
52 | | - if (output.amount.toString() !== '0') { |
53 | | - throw new Error(`Only zero amounts allowed for non-encodeable scriptPubkeys: ${JSON.stringify(output)}`); |
54 | | - } |
55 | | - } |
56 | | -} |
57 | | - |
58 | | -/** |
59 | | - * Get the inputs for a psbt from a prebuild. |
60 | | - */ |
61 | | -export function getPsbtTxInputs( |
62 | | - psbtArg: string | utxolib.bitgo.UtxoPsbt, |
63 | | - network: utxolib.Network |
64 | | -): { address: string; value: bigint; valueString: string }[] { |
65 | | - const psbt = psbtArg instanceof utxolib.bitgo.UtxoPsbt ? psbtArg : utxolib.bitgo.createPsbtFromHex(psbtArg, network); |
66 | | - const txInputs = psbt.txInputs; |
67 | | - return psbt.data.inputs.map((input, index) => { |
68 | | - let address: string; |
69 | | - let value: bigint; |
70 | | - if (input.witnessUtxo) { |
71 | | - address = utxolib.address.fromOutputScript(input.witnessUtxo.script, network); |
72 | | - value = input.witnessUtxo.value; |
73 | | - } else if (input.nonWitnessUtxo) { |
74 | | - const tx = utxolib.bitgo.createTransactionFromBuffer<bigint>(input.nonWitnessUtxo, network, { |
75 | | - amountType: 'bigint', |
76 | | - }); |
77 | | - const txId = (Buffer.from(txInputs[index].hash).reverse() as Buffer).toString('hex'); |
78 | | - if (tx.getId() !== txId) { |
79 | | - throw new Error('input transaction hex does not match id'); |
80 | | - } |
81 | | - const prevTxOutputIndex = txInputs[index].index; |
82 | | - address = utxolib.address.fromOutputScript(tx.outs[prevTxOutputIndex].script, network); |
83 | | - value = tx.outs[prevTxOutputIndex].value; |
84 | | - } else { |
85 | | - throw new Error('psbt input is missing both witnessUtxo and nonWitnessUtxo'); |
86 | | - } |
87 | | - return { address, value, valueString: value.toString() }; |
88 | | - }); |
89 | | -} |
| 9 | + TransactionExplanation, |
| 10 | + WalletOutput, |
| 11 | +} from '../abstractUtxoCoin'; |
90 | 12 |
|
91 | | -/** |
92 | | - * Get the inputs for a transaction from a prebuild. |
93 | | - */ |
94 | | -export async function getTxInputs<TNumber extends number | bigint>(params: { |
95 | | - txPrebuild: TransactionPrebuild<TNumber>; |
96 | | - bitgo: BitGoBase; |
97 | | - coin: AbstractUtxoCoin; |
98 | | - disableNetworking: boolean; |
99 | | - reqId?: IRequestTracer; |
100 | | -}): Promise<{ address: string; value: TNumber; valueString: string }[]> { |
101 | | - const { txPrebuild, bitgo, coin, disableNetworking, reqId } = params; |
102 | | - if (!txPrebuild.txHex) { |
103 | | - throw new Error(`txPrebuild.txHex not set`); |
104 | | - } |
105 | | - const transaction = coin.createTransactionFromHex<TNumber>(txPrebuild.txHex); |
106 | | - const transactionCache = {}; |
107 | | - return await Promise.all( |
108 | | - transaction.ins.map(async (currentInput): Promise<{ address: string; value: TNumber; valueString: string }> => { |
109 | | - const transactionId = (Buffer.from(currentInput.hash).reverse() as Buffer).toString('hex'); |
110 | | - const txHex = txPrebuild.txInfo?.txHexes?.[transactionId]; |
111 | | - if (txHex) { |
112 | | - const localTx = coin.createTransactionFromHex<TNumber>(txHex); |
113 | | - if (localTx.getId() !== transactionId) { |
114 | | - throw new Error('input transaction hex does not match id'); |
115 | | - } |
116 | | - const currentOutput = localTx.outs[currentInput.index]; |
117 | | - const address = utxolib.address.fromOutputScript(currentOutput.script, coin.network); |
118 | | - return { |
119 | | - address, |
120 | | - value: currentOutput.value, |
121 | | - valueString: currentOutput.value.toString(), |
122 | | - }; |
123 | | - } else if (!transactionCache[transactionId]) { |
124 | | - if (disableNetworking) { |
125 | | - throw new Error('attempting to retrieve transaction details externally with networking disabled'); |
126 | | - } |
127 | | - if (reqId) { |
128 | | - bitgo.setRequestTracer(reqId); |
129 | | - } |
130 | | - transactionCache[transactionId] = await bitgo.get(coin.url(`/public/tx/${transactionId}`)).result(); |
131 | | - } |
132 | | - const transactionDetails = transactionCache[transactionId]; |
133 | | - return transactionDetails.outputs[currentInput.index]; |
134 | | - }) |
135 | | - ); |
136 | | -} |
| 13 | +import { toExtendedAddressFormat } from './recipient'; |
137 | 14 |
|
138 | 15 | function explainCommon<TNumber extends number | bigint>( |
139 | 16 | tx: bitgo.UtxoTransaction<TNumber>, |
@@ -258,17 +135,11 @@ function getTxInputSignaturesCount<TNumber extends number | bigint>( |
258 | 135 | * Decompose a raw psbt into useful information, such as the total amounts, |
259 | 136 | * change amounts, and transaction outputs. |
260 | 137 | */ |
261 | | -export function explainPsbt<TNumber extends number | bigint>( |
| 138 | +export function explainPsbt<TNumber extends number | bigint, Tx extends bitgo.UtxoTransaction<bigint>>( |
| 139 | + psbt: bitgo.UtxoPsbt<Tx>, |
262 | 140 | params: ExplainTransactionOptions<TNumber>, |
263 | 141 | network: utxolib.Network |
264 | 142 | ): TransactionExplanation { |
265 | | - const { txHex } = params; |
266 | | - let psbt: bitgo.UtxoPsbt; |
267 | | - try { |
268 | | - psbt = bitgo.createPsbtFromHex(txHex, network); |
269 | | - } catch (e) { |
270 | | - throw new Error('failed to parse psbt hex'); |
271 | | - } |
272 | 143 | const txOutputs = psbt.txOutputs; |
273 | 144 |
|
274 | 145 | function getChainAndIndexFromBip32Derivations(output: bitgo.PsbtOutput) { |
@@ -346,18 +217,12 @@ export function explainPsbt<TNumber extends number | bigint>( |
346 | 217 | * change amounts, and transaction outputs. |
347 | 218 | */ |
348 | 219 | export function explainTx<TNumber extends number | bigint>( |
| 220 | + tx: bitgo.UtxoTransaction<TNumber>, |
349 | 221 | params: ExplainTransactionOptions<TNumber>, |
350 | | - coin: AbstractUtxoCoin |
| 222 | + network: utxolib.Network |
351 | 223 | ): TransactionExplanation { |
352 | | - const { txHex } = params; |
353 | | - let tx; |
354 | | - try { |
355 | | - tx = coin.createTransactionFromHex(txHex); |
356 | | - } catch (e) { |
357 | | - throw new Error('failed to parse transaction hex'); |
358 | | - } |
359 | | - const common = explainCommon(tx, params, coin.network); |
360 | | - const inputSignaturesCount = getTxInputSignaturesCount(tx, params, coin.network); |
| 224 | + const common = explainCommon(tx, params, network); |
| 225 | + const inputSignaturesCount = getTxInputSignaturesCount(tx, params, network); |
361 | 226 | return { |
362 | 227 | ...common, |
363 | 228 | inputSignatures: inputSignaturesCount, |
|
0 commit comments