Skip to content

Commit 80284e0

Browse files
OttoAllmendingerllm-git
andcommitted
feat(abstract-utxo): add custom change wallet parsing to PSBT explainer
Add support for parsing custom change outputs using provided wallet xpubs. This enhances the transaction explanation function to identify outputs that might belong to a different wallet than the main one. Issue: BTC-2732 Co-authored-by: llm-git <llm-git@ttll.de>
1 parent fb12eaf commit 80284e0

File tree

2 files changed

+43
-7
lines changed

2 files changed

+43
-7
lines changed

modules/abstract-utxo/src/transaction/fixedScript/explainPsbtWasm.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,34 +46,45 @@ export function explainPsbtWasm(
4646
checkSignature?: boolean;
4747
outputScripts: Buffer[];
4848
};
49+
customChangeWalletXpubs?: Triple<string>;
4950
}
5051
): TransactionExplanationWasm {
5152
const parsed = psbt.parseTransactionWithWalletKeys(walletXpubs, params.replayProtection);
5253

5354
const changeOutputs: FixedScriptWalletOutput[] = [];
5455
const outputs: Output[] = [];
56+
const parsedCustomChangeOutputs = params.customChangeWalletXpubs
57+
? psbt.parseOutputsWithWalletKeys(params.customChangeWalletXpubs)
58+
: undefined;
5559

56-
parsed.outputs.forEach((output) => {
60+
const customChangeOutputs: FixedScriptWalletOutput[] = [];
61+
62+
parsed.outputs.forEach((output, i) => {
63+
const parseCustomChangeOutput = parsedCustomChangeOutputs?.[i];
5764
if (isParsedWalletOutput(output)) {
5865
// This is a change output
5966
changeOutputs.push(toChangeOutput(output));
67+
} else if (parseCustomChangeOutput && isParsedWalletOutput(parseCustomChangeOutput)) {
68+
customChangeOutputs.push(toChangeOutput(parseCustomChangeOutput));
6069
} else if (isParsedExternalOutput(output)) {
61-
// This is an external output
6270
outputs.push(toExternalOutput(output));
6371
} else {
6472
throw new Error('Invalid output');
6573
}
6674
});
6775

68-
const changeAmount = changeOutputs.reduce((sum, output) => sum + BigInt(output.amount), BigInt(0));
6976
const outputAmount = outputs.reduce((sum, output) => sum + BigInt(output.amount), BigInt(0));
77+
const changeAmount = changeOutputs.reduce((sum, output) => sum + BigInt(output.amount), BigInt(0));
78+
const customChangeAmount = customChangeOutputs.reduce((sum, output) => sum + BigInt(output.amount), BigInt(0));
7079

7180
return {
7281
id: psbt.unsignedTxid(),
7382
outputAmount: outputAmount.toString(),
7483
changeAmount: changeAmount.toString(),
84+
customChangeAmount: customChangeAmount.toString(),
7585
outputs,
7686
changeOutputs,
87+
customChangeOutputs,
7788
fee: parsed.minerFee.toString(),
7889
};
7990
}

modules/abstract-utxo/test/unit/transaction/fixedScript/explainPsbt.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,23 @@ function describeTransactionWith(acidTest: testutil.AcidTest) {
2020
describe(`${acidTest.name}`, function () {
2121
let psbt: utxolib.bitgo.UtxoPsbt;
2222
let psbtBytes: Buffer;
23+
let walletXpubs: Triple<string>;
24+
let customChangeWalletXpubs: Triple<string> | undefined;
25+
let wasmPsbt: fixedScriptWallet.BitGoPsbt;
2326
let refExplanation: TransactionExplanation;
2427
before('prepare', function () {
2528
psbt = acidTest.createPsbt();
2629
refExplanation = explainPsbt(psbt, { pubs: acidTest.rootWalletKeys }, acidTest.network, {
2730
strict: true,
2831
});
2932
psbtBytes = psbt.toBuffer();
33+
const networkName = utxolib.getNetworkName(acidTest.network);
34+
assert(networkName);
35+
walletXpubs = acidTest.rootWalletKeys.triple.map((k) => k.neutered().toBase58()) as Triple<string>;
36+
customChangeWalletXpubs = acidTest.otherWalletKeys.triple.map((k) => k.neutered().toBase58()) as Triple<string>;
37+
if (hasWasmUtxoSupport(acidTest.network)) {
38+
wasmPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(psbtBytes, networkName);
39+
}
3040
});
3141

3242
it('should match the expected values for explainPsbt', function () {
@@ -60,10 +70,6 @@ function describeTransactionWith(acidTest: testutil.AcidTest) {
6070
return this.skip();
6171
}
6272

63-
const networkName = utxolib.getNetworkName(acidTest.network);
64-
assert(networkName);
65-
const wasmPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(psbtBytes, networkName);
66-
const walletXpubs = acidTest.rootWalletKeys.triple.map((k) => k.neutered().toBase58()) as Triple<string>;
6773
const wasmExplanation = explainPsbtWasm(wasmPsbt, walletXpubs, {
6874
replayProtection: {
6975
outputScripts: [acidTest.getReplayProtectionOutputScript()],
@@ -86,6 +92,25 @@ function describeTransactionWith(acidTest: testutil.AcidTest) {
8692
}
8793
}
8894
});
95+
96+
if (acidTest.network !== utxolib.networks.bitcoin) {
97+
return;
98+
}
99+
100+
// extended test suite for bitcoin
101+
102+
it('returns custom change outputs when parameter is set', function () {
103+
const wasmExplanation = explainPsbtWasm(wasmPsbt, walletXpubs, {
104+
replayProtection: {
105+
outputScripts: [acidTest.getReplayProtectionOutputScript()],
106+
},
107+
customChangeWalletXpubs,
108+
});
109+
assert.ok(wasmExplanation.customChangeOutputs);
110+
assert.deepStrictEqual(wasmExplanation.outputs.length, 2);
111+
assert.deepStrictEqual(wasmExplanation.customChangeOutputs.length, 1);
112+
assert.deepStrictEqual(wasmExplanation.customChangeOutputs[0].amount, '900');
113+
});
89114
});
90115
}
91116

0 commit comments

Comments
 (0)