Skip to content

Commit 13b86c3

Browse files
authored
Merge pull request #223 from BitGo/WCN-764/consolidateUnspents-integ-test
test: add integration test for consolidate unspents
2 parents 471644c + 0c9a460 commit 13b86c3

5 files changed

Lines changed: 286 additions & 11 deletions

File tree

src/__tests__/integration/accelerate.integ.test.ts

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { SigningMode } from '../../shared/types';
55

66
/**
77
* Deterministic test keypair derived from Buffer.alloc(64, 0x42) — a public, reproducible seed.
8-
* Not a secret. Never funded. Matches getKeychain.user.json and prebuildTx.tbtc.json.
8+
* Not a secret. Never funded. Matches getKeychain.user.json and prebuildTx.accelerate.tbtc.json.
99
*/
1010
const USER_XPUB =
1111
'xpub661MyMwAqRbcEvJQx6spkkHLRgtjxmVdyDSvbDt2m9NFpbkHdcu5WJsHHHqFxNATbNHnhMWJiwckoMqF75EpcNhU9xeVM4oDS7urM3os4BH';
@@ -23,6 +23,22 @@ const accelerateRequestBody = {
2323
maxFee: 10000,
2424
};
2525

26+
interface TransferEntry {
27+
address: string;
28+
value: number;
29+
isChange?: boolean;
30+
}
31+
32+
interface AccelerateResponse {
33+
txid: string;
34+
tx: string;
35+
status: string;
36+
transfer: {
37+
txid: string;
38+
entries: TransferEntry[];
39+
};
40+
}
41+
2642
describe('Accelerate: EXTERNAL signing', () => {
2743
let services: IntegServices;
2844

@@ -50,19 +66,29 @@ describe('Accelerate: EXTERNAL signing', () => {
5066
);
5167

5268
res.status.should.equal(200);
53-
const body = (await res.json()) as { txid: string; tx: string; status: string };
69+
const body = (await res.json()) as AccelerateResponse;
5470
body.should.have.property('txid', 'test-tx-id');
5571
body.should.have.property('tx', '01000000000101030a0000');
5672
body.should.have.property('status', 'signed');
73+
body.transfer.should.have.property('txid', 'test-tx-id');
74+
body.transfer.entries.should.be.Array().and.have.length(2);
5775

5876
/**
5977
* In external mode, AWM delegates signing to the key provider.
6078
* POST /sign must be called — not POST /key (no local key retrieval for signing).
79+
* The signablePayload for BTC is a PSBT hex.
6180
*/
62-
services.keyProvider.calls.filter((c) => c.path === '/sign').should.have.length(1);
81+
const signCalls = services.keyProvider.calls.filter((c) => c.path === '/sign');
82+
signCalls.should.have.length(1);
83+
const signBody = signCalls[0].body as { signablePayload: string };
84+
signBody.signablePayload.should.startWith('70736274ff');
85+
6386
services.keyProvider.calls.filter((c) => c.path === '/key').should.have.length(0);
6487

65-
/** BitGo must receive tx/build with the correct cpfpTxIds, block/latest, and tx/send */
88+
/**
89+
* BitGo must receive tx/build with the correct cpfpTxIds, block/latest, and tx/send.
90+
* cpfpTxIds and txHex both survive the TxSendBody whitelist and appear in tx/send.
91+
*/
6692
const buildCalls = services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build'));
6793
buildCalls.should.have.length(1);
6894
const buildBody = buildCalls[0].body as { cpfpTxIds?: string[] };
@@ -71,7 +97,15 @@ describe('Accelerate: EXTERNAL signing', () => {
7197
services.bitgo.calls
7298
.filter((c) => c.path.endsWith('/public/block/latest'))
7399
.should.have.length(1);
74-
services.bitgo.calls.filter((c) => c.path.endsWith('/tx/send')).should.have.length(1);
100+
101+
const sendCalls = services.bitgo.calls.filter((c) => c.path.endsWith('/tx/send'));
102+
sendCalls.should.have.length(1);
103+
const sendBody = sendCalls[0].body as {
104+
cpfpTxIds?: string[];
105+
txHex?: string;
106+
};
107+
sendBody.should.have.property('cpfpTxIds').which.deepEqual([CPFP_TX_ID]);
108+
sendBody.txHex!.should.startWith('70736274ff');
75109
});
76110
});
77111

@@ -119,10 +153,12 @@ describe('Accelerate: LOCAL signing', () => {
119153
);
120154

121155
res.status.should.equal(200);
122-
const body = (await res.json()) as { txid: string; tx: string; status: string };
156+
const body = (await res.json()) as AccelerateResponse;
123157
body.should.have.property('txid', 'test-tx-id');
124158
body.should.have.property('tx', '01000000000101030a0000');
125159
body.should.have.property('status', 'signed');
160+
body.transfer.should.have.property('txid', 'test-tx-id');
161+
body.transfer.entries.should.be.Array().and.have.length(2);
126162

127163
/**
128164
* In local mode, AWM retrieves the xprv via GET /key/:pub and signs internally.
@@ -131,7 +167,10 @@ describe('Accelerate: LOCAL signing', () => {
131167
services.keyProvider.calls.filter((c) => c.path === '/sign').should.have.length(0);
132168
services.keyProvider.calls.filter((c) => c.path.startsWith('/key/')).length.should.be.above(0);
133169

134-
/** BitGo must receive tx/build with the correct cpfpTxIds, block/latest, and tx/send */
170+
/**
171+
* BitGo must receive tx/build with the correct cpfpTxIds, block/latest, and tx/send.
172+
* cpfpTxIds and txHex both survive the TxSendBody whitelist and appear in tx/send.
173+
*/
135174
const buildCalls = services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build'));
136175
buildCalls.should.have.length(1);
137176
const buildBody = buildCalls[0].body as { cpfpTxIds?: string[] };
@@ -140,6 +179,14 @@ describe('Accelerate: LOCAL signing', () => {
140179
services.bitgo.calls
141180
.filter((c) => c.path.endsWith('/public/block/latest'))
142181
.should.have.length(1);
143-
services.bitgo.calls.filter((c) => c.path.endsWith('/tx/send')).should.have.length(1);
182+
183+
const sendCalls = services.bitgo.calls.filter((c) => c.path.endsWith('/tx/send'));
184+
sendCalls.should.have.length(1);
185+
const sendBody = sendCalls[0].body as {
186+
cpfpTxIds?: string[];
187+
txHex?: string;
188+
};
189+
sendBody.should.have.property('cpfpTxIds').which.deepEqual([CPFP_TX_ID]);
190+
sendBody.txHex!.should.startWith('70736274ff');
144191
});
145192
});
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import 'should';
2+
import { startServices, IntegServices } from './helpers/setup';
3+
import { LOCALHOST } from './helpers/servers';
4+
import { SigningMode } from '../../shared/types';
5+
6+
/**
7+
* Deterministic test keypair derived from Buffer.alloc(64, 0x42) — a public, reproducible seed.
8+
* Not a secret. Never funded. Matches getKeychain.user.json and prebuildTx.consolidate.tbtc.json.
9+
*/
10+
const USER_XPUB =
11+
'xpub661MyMwAqRbcEvJQx6spkkHLRgtjxmVdyDSvbDt2m9NFpbkHdcu5WJsHHHqFxNATbNHnhMWJiwckoMqF75EpcNhU9xeVM4oDS7urM3os4BH';
12+
const USER_XPRV =
13+
'xprv9s21ZrQH143K2SDwr5LpPcLbsf4FZJmnbzXKnqURCoqGwoR965apxWYoS2DKu2ivcMTB9uTK6XhZDEPfTeNXGf7mmACuMN6cFS5ttmrpZ3i';
14+
15+
const WALLET_ID = 'test-wallet-id';
16+
17+
const consolidateRequestBody = {
18+
pubkey: USER_XPUB,
19+
source: 'user' as const,
20+
feeRate: 1000,
21+
maxFeeRate: 2000,
22+
minValue: 1000,
23+
};
24+
25+
interface TransferEntry {
26+
address: string;
27+
value: number;
28+
isChange?: boolean;
29+
}
30+
31+
interface ConsolidateResponse {
32+
txid: string;
33+
tx: string;
34+
status: string;
35+
transfer: {
36+
txid: string;
37+
status: string;
38+
entries: TransferEntry[];
39+
};
40+
}
41+
42+
describe('Consolidate unspents: EXTERNAL signing', () => {
43+
let services: IntegServices;
44+
45+
before(async () => {
46+
services = await startServices({ signingMode: SigningMode.EXTERNAL });
47+
});
48+
49+
after(async () => {
50+
await services.teardown();
51+
});
52+
53+
beforeEach(() => {
54+
services.keyProvider.calls.length = 0;
55+
services.bitgo.calls.length = 0;
56+
});
57+
58+
it('consolidates tbtc unspents via external key provider', async () => {
59+
const res = await fetch(
60+
`http://${LOCALHOST}:${services.mbePort}/api/v1/tbtc/advancedwallet/${WALLET_ID}/consolidateunspents`,
61+
{
62+
method: 'POST',
63+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-token' },
64+
body: JSON.stringify(consolidateRequestBody),
65+
},
66+
);
67+
68+
res.status.should.equal(200);
69+
const body = (await res.json()) as ConsolidateResponse;
70+
body.should.have.property('txid', 'test-tx-id');
71+
body.should.have.property('tx', '01000000000101030a0000');
72+
body.should.have.property('status', 'signed');
73+
body.transfer.should.have.property('txid', 'test-tx-id');
74+
body.transfer.entries.should.be.Array().and.have.length(2);
75+
76+
/**
77+
* In external mode, AWM delegates signing to the key provider.
78+
* POST /sign must be called — not POST /key (no local key retrieval for signing).
79+
* The signablePayload for BTC is a PSBT hex (starts with PSBT magic bytes 70736274ff).
80+
*/
81+
const signCalls = services.keyProvider.calls.filter((c) => c.path === '/sign');
82+
signCalls.should.have.length(1);
83+
const signBody = signCalls[0].body as { signablePayload: string };
84+
signBody.signablePayload.should.startWith('70736274ff');
85+
86+
services.keyProvider.calls.filter((c) => c.path === '/key').should.have.length(0);
87+
88+
/**
89+
* BitGo must receive consolidateUnspents (not tx/build) with the consolidation params,
90+
* then tx/send with type: 'consolidate'. tx/build is only used by sendMany and accelerate.
91+
*/
92+
const consolidateCalls = services.bitgo.calls.filter((c) =>
93+
c.path.endsWith('/consolidateUnspents'),
94+
);
95+
consolidateCalls.should.have.length(1);
96+
const consolidateBody = consolidateCalls[0].body as {
97+
feeRate?: number;
98+
maxFeeRate?: number;
99+
minValue?: number;
100+
txFormat?: string;
101+
};
102+
consolidateBody.should.have.property('feeRate', 1000);
103+
consolidateBody.should.have.property('maxFeeRate', 2000);
104+
consolidateBody.should.have.property('minValue', 1000);
105+
consolidateBody.should.have.property('txFormat', 'psbt-lite');
106+
107+
const sendCalls = services.bitgo.calls.filter((c) => c.path.endsWith('/tx/send'));
108+
sendCalls.should.have.length(1);
109+
const sendBody = sendCalls[0].body as { type?: string };
110+
sendBody.should.have.property('type', 'consolidate');
111+
112+
services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build')).should.have.length(0);
113+
});
114+
});
115+
116+
describe('Consolidate unspents: LOCAL signing', () => {
117+
let services: IntegServices;
118+
119+
before(async () => {
120+
services = await startServices({ signingMode: SigningMode.LOCAL });
121+
122+
/**
123+
* Seed the mock key provider with a known xprv so AWM can retrieve it
124+
* via GET /key/:pub and sign the PSBT locally. The xpub must match
125+
* getKeychain.user.json and the bip32Derivation in prebuildTx.consolidate.tbtc.json.
126+
*/
127+
await fetch(`http://127.0.0.1:${services.keyProvider.port}/key`, {
128+
method: 'POST',
129+
headers: { 'Content-Type': 'application/json' },
130+
body: JSON.stringify({
131+
pub: USER_XPUB,
132+
prv: USER_XPRV,
133+
coin: 'tbtc',
134+
source: 'user',
135+
type: 'independent',
136+
}),
137+
});
138+
});
139+
140+
after(async () => {
141+
await services.teardown();
142+
});
143+
144+
beforeEach(() => {
145+
services.keyProvider.calls.length = 0;
146+
services.bitgo.calls.length = 0;
147+
});
148+
149+
it('consolidates tbtc unspents using locally stored xprv', async () => {
150+
const res = await fetch(
151+
`http://${LOCALHOST}:${services.mbePort}/api/v1/tbtc/advancedwallet/${WALLET_ID}/consolidateunspents`,
152+
{
153+
method: 'POST',
154+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-token' },
155+
body: JSON.stringify(consolidateRequestBody),
156+
},
157+
);
158+
159+
res.status.should.equal(200);
160+
const body = (await res.json()) as ConsolidateResponse;
161+
body.should.have.property('txid', 'test-tx-id');
162+
body.should.have.property('tx', '01000000000101030a0000');
163+
body.should.have.property('status', 'signed');
164+
body.transfer.should.have.property('txid', 'test-tx-id');
165+
body.transfer.entries.should.be.Array().and.have.length(2);
166+
167+
/**
168+
* In local mode, AWM retrieves the xprv via GET /key/:pub and signs internally.
169+
* POST /sign must NOT be called — signing happens inside AWM, not in the key provider.
170+
*/
171+
services.keyProvider.calls.filter((c) => c.path === '/sign').should.have.length(0);
172+
services.keyProvider.calls.filter((c) => c.path.startsWith('/key/')).length.should.be.above(0);
173+
174+
/**
175+
* BitGo must receive consolidateUnspents (not tx/build) with the consolidation params,
176+
* then tx/send with type: 'consolidate'.
177+
*/
178+
const consolidateCalls = services.bitgo.calls.filter((c) =>
179+
c.path.endsWith('/consolidateUnspents'),
180+
);
181+
consolidateCalls.should.have.length(1);
182+
const consolidateBody = consolidateCalls[0].body as {
183+
feeRate?: number;
184+
maxFeeRate?: number;
185+
minValue?: number;
186+
txFormat?: string;
187+
};
188+
consolidateBody.should.have.property('feeRate', 1000);
189+
consolidateBody.should.have.property('maxFeeRate', 2000);
190+
consolidateBody.should.have.property('minValue', 1000);
191+
consolidateBody.should.have.property('txFormat', 'psbt-lite');
192+
193+
const sendCalls = services.bitgo.calls.filter((c) => c.path.endsWith('/tx/send'));
194+
sendCalls.should.have.length(1);
195+
const sendBody = sendCalls[0].body as { type?: string };
196+
sendBody.should.have.property('type', 'consolidate');
197+
198+
services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build')).should.have.length(0);
199+
});
200+
});
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
{
2-
"txHex": "70736274ff01000a01000000000000000000000000",
3-
"txInfo": { "nP2SHInputs": 0, "nSegwitInputs": 0, "nOutputs": 0 }
2+
"txHex": "70736274ff01005e0100000001015736beea8d00e1145b39e6e16505877e84961ffbb64071b4ed2556c53c27e20000000000ffffffff01905f010000000000220020b41d3a1e7ae51e81e1f5ac6518d6a4c449f1e1744461b9a298452913c7c14a9e000000004f010488b21e0000000000000000001860687e74faf798e258d66855e725343e64978b899441c761e05f321eece137025a0fef9917548521cc62dc96f7d55da6392e001fe6f3087f201310eb83f7bb8a04460c65944f010488b21e00000000000000000025de30d2eda67a684e7290fb06abb26e29488db6d6522a081c2e1c09d125a4c20335929e0c0d34bfec5318221142dbfa31dda3915480909706272e196974d76283046bf51aa84f010488b21e00000000000000000035ed9ee074c71886a1a6fa216a6fde3f285e7dea25044ed1d96fba451081690f0338681e310aeea9e95ee4d629655a77b72c022ec5c8b673647cea4b2f82ef67c604d3b12b170001012ba08601000000000022002008dbf93c4d3788ce02d95d4b5c6932a93df1fab66c6522c27d27bd5366e0a64a0103040100000001056952210293bdede5bdeeb0e2c43143a84468167bb0b6666db59015da11d65d7f5ddc974a210316e1bc7c3de9b4705b7cf81cab17006e9db0f89a4b6f94bd1962a36cb3dd3a742102fbed0170983333c42d809a3cfef91c59a37ae35f6b9351f320f798d19a82572353ae22060293bdede5bdeeb0e2c43143a84468167bb0b6666db59015da11d65d7f5ddc974a146bf51aa800000000000000001500000000000000220602fbed0170983333c42d809a3cfef91c59a37ae35f6b9351f320f798d19a82572314d3b12b170000000000000000150000000000000022060316e1bc7c3de9b4705b7cf81cab17006e9db0f89a4b6f94bd1962a36cb3dd3a7414460c6594000000000000000015000000000000000001016952210295bc96ecdb5ceae2716051cd835ec0e635ff3ab464a992c0b2d7dbdf969d682021038c69636106774394c8369affe354e33b63a0deb196278320be2eaa273a5dcf542102020e1b0585251821432d92d3d716774aee750cb0758b52d4e51a480a952d410153ae220202020e1b0585251821432d92d3d716774aee750cb0758b52d4e51a480a952d410114d3b12b170000000000000000140000000000000022020295bc96ecdb5ceae2716051cd835ec0e635ff3ab464a992c0b2d7dbdf969d6820146bf51aa8000000000000000014000000000000002202038c69636106774394c8369affe354e33b63a0deb196278320be2eaa273a5dcf5414460c65940000000000000000140000000000000000",
3+
"txInfo": {
4+
"nP2SHInputs": 0,
5+
"nSegwitInputs": 1,
6+
"nOutputs": 1,
7+
"changeAddresses": [
8+
"tb1qkswn58n6u50grc0443j3344yc3ylrct5g3smng5cg553837pf20qgcd3u9"
9+
]
10+
}
411
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"txHex": "70736274ff0100870100000002a6758b462440d328f24027763756b0e0bc094741a5ceb0416a199dac2acc1a180000000000ffffffffe0538936ed9a8e57700a33dd4881174c625eb9189abdae3bbfa21aed9c60c7ed0100000000ffffffff01d07e010000000000220020b41d3a1e7ae51e81e1f5ac6518d6a4c449f1e1744461b9a298452913c7c14a9e000000004f010488b21e0000000000000000001860687e74faf798e258d66855e725343e64978b899441c761e05f321eece137025a0fef9917548521cc62dc96f7d55da6392e001fe6f3087f201310eb83f7bb8a04460c65944f010488b21e00000000000000000025de30d2eda67a684e7290fb06abb26e29488db6d6522a081c2e1c09d125a4c20335929e0c0d34bfec5318221142dbfa31dda3915480909706272e196974d76283046bf51aa84f010488b21e00000000000000000035ed9ee074c71886a1a6fa216a6fde3f285e7dea25044ed1d96fba451081690f0338681e310aeea9e95ee4d629655a77b72c022ec5c8b673647cea4b2f82ef67c604d3b12b170001012b50c300000000000022002008dbf93c4d3788ce02d95d4b5c6932a93df1fab66c6522c27d27bd5366e0a64a0103040100000001056952210293bdede5bdeeb0e2c43143a84468167bb0b6666db59015da11d65d7f5ddc974a210316e1bc7c3de9b4705b7cf81cab17006e9db0f89a4b6f94bd1962a36cb3dd3a742102fbed0170983333c42d809a3cfef91c59a37ae35f6b9351f320f798d19a82572353ae22060293bdede5bdeeb0e2c43143a84468167bb0b6666db59015da11d65d7f5ddc974a146bf51aa800000000000000001500000000000000220602fbed0170983333c42d809a3cfef91c59a37ae35f6b9351f320f798d19a82572314d3b12b170000000000000000150000000000000022060316e1bc7c3de9b4705b7cf81cab17006e9db0f89a4b6f94bd1962a36cb3dd3a7414460c6594000000000000000015000000000000000001012b50c3000000000000220020b180ecd6ef26da91ec5f739c2e7d631a525f914d3839a3d0e04a9f15a2798346010304010000000105695221024d7e8c3413c9410e9685f0b4aaeb24ef8933b7de4156a8cf07da9c7bd879151d21033b5c5699c1ae79ce9b3c6e52069b96a5c8814f6124e13655be3637006d599f4f21028e334ee478c3650865833c96e83b93579277be05a2e89d2a50e53cbdf21c732753ae2206024d7e8c3413c9410e9685f0b4aaeb24ef8933b7de4156a8cf07da9c7bd879151d146bf51aa8000000000000000015000000010000002206028e334ee478c3650865833c96e83b93579277be05a2e89d2a50e53cbdf21c732714d3b12b17000000000000000015000000010000002206033b5c5699c1ae79ce9b3c6e52069b96a5c8814f6124e13655be3637006d599f4f14460c6594000000000000000015000000010000000001016952210295bc96ecdb5ceae2716051cd835ec0e635ff3ab464a992c0b2d7dbdf969d682021038c69636106774394c8369affe354e33b63a0deb196278320be2eaa273a5dcf542102020e1b0585251821432d92d3d716774aee750cb0758b52d4e51a480a952d410153ae220202020e1b0585251821432d92d3d716774aee750cb0758b52d4e51a480a952d410114d3b12b170000000000000000140000000000000022020295bc96ecdb5ceae2716051cd835ec0e635ff3ab464a992c0b2d7dbdf969d6820146bf51aa8000000000000000014000000000000002202038c69636106774394c8369affe354e33b63a0deb196278320be2eaa273a5dcf5414460c65940000000000000000140000000000000000",
3+
"txInfo": {
4+
"nP2SHInputs": 0,
5+
"nSegwitInputs": 2,
6+
"nOutputs": 1,
7+
"changeAddresses": [
8+
"tb1qkswn58n6u50grc0443j3344yc3ylrct5g3smng5cg553837pf20qgcd3u9"
9+
]
10+
}
11+
}

src/__tests__/integration/helpers/mockBitgoServer.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ type SendManyFixtureMethod = 'getWallet' | 'prebuildTx' | 'sendTx';
2525
type SupportedCoin = 'hteth' | 'tbtc';
2626
type CoinToFixtures<C extends SupportedCoin> = {
2727
[K in SendManyFixtureMethod]: `${K}.${C}`;
28-
} & { acceleratePrebuildTx: `prebuildTx.accelerate.${C}` | `prebuildTx.${C}` };
28+
} & {
29+
acceleratePrebuildTx: `prebuildTx.accelerate.${C}` | `prebuildTx.${C}`;
30+
consolidatePrebuildTx: `prebuildTx.consolidate.${C}` | `prebuildTx.${C}`;
31+
};
2932

3033
/** Registry — add a new coin here to support it across all sendMany integ test routes */
3134
const COIN_FIXTURES: { [C in SupportedCoin]: CoinToFixtures<C> } = {
@@ -34,12 +37,14 @@ const COIN_FIXTURES: { [C in SupportedCoin]: CoinToFixtures<C> } = {
3437
prebuildTx: 'prebuildTx.hteth',
3538
sendTx: 'sendTx.hteth',
3639
acceleratePrebuildTx: 'prebuildTx.hteth', // CPFP/RBF not applicable to EVM; reuses standard prebuild
40+
consolidatePrebuildTx: 'prebuildTx.hteth', // consolidateUnspents not applicable to EVM; reuses standard prebuild
3741
},
3842
tbtc: {
3943
getWallet: 'getWallet.tbtc',
4044
prebuildTx: 'prebuildTx.tbtc',
4145
sendTx: 'sendTx.tbtc',
4246
acceleratePrebuildTx: 'prebuildTx.accelerate.tbtc',
47+
consolidatePrebuildTx: 'prebuildTx.consolidate.tbtc',
4348
},
4449
};
4550

@@ -117,6 +122,11 @@ export async function startMockBitgoServer(): Promise<MockBitgoServer> {
117122
res.json(loadFixture(fixtureName));
118123
});
119124

125+
/** Consolidate unspents prebuild — coin-specific PSBT-lite fixture */
126+
app.post('/api/v2/:coin/wallet/:walletId/consolidateUnspents', (req, res) => {
127+
res.json(loadFixture(coinFixtures(req.params.coin).consolidatePrebuildTx));
128+
});
129+
120130
/** Transaction submit — coin-specific fixture */
121131
app.post('/api/v2/:coin/wallet/:walletId/tx/send', (req, res) => {
122132
res.json(loadFixture(coinFixtures(req.params.coin).sendTx));

0 commit comments

Comments
 (0)