Skip to content

Commit 24a8953

Browse files
committed
feat(express): migrated consolidateunspents as type route
Ticket: WP-5430
1 parent a9d27c7 commit 24a8953

File tree

4 files changed

+1293
-11
lines changed

4 files changed

+1293
-11
lines changed

modules/express/src/clientRoutes.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -750,10 +750,12 @@ async function handleV2RecoverToken(req: ExpressApiRouteRequest<'express.v2.wall
750750
* handle wallet fanout unspents
751751
* @param req
752752
*/
753-
async function handleV2ConsolidateUnspents(req: express.Request) {
753+
async function handleV2ConsolidateUnspents(
754+
req: ExpressApiRouteRequest<'express.v2.wallet.consolidateunspents', 'post'>
755+
) {
754756
const bitgo = req.bitgo;
755-
const coin = bitgo.coin(req.params.coin);
756-
const wallet = await coin.wallets().get({ id: req.params.id });
757+
const coin = bitgo.coin(req.decoded.coin);
758+
const wallet = await coin.wallets().get({ id: req.decoded.id });
757759
return wallet.consolidateUnspents(createSendParams(req));
758760
}
759761

@@ -1654,12 +1656,10 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
16541656
);
16551657

16561658
// unspent changes
1657-
app.post(
1658-
'/api/v2/:coin/wallet/:id/consolidateunspents',
1659-
parseBody,
1659+
router.post('express.v2.wallet.consolidateunspents', [
16601660
prepareBitGo(config),
1661-
promiseWrapper(handleV2ConsolidateUnspents)
1662-
);
1661+
typedPromiseWrapper(handleV2ConsolidateUnspents),
1662+
]);
16631663
router.post('express.v2.wallet.fanoutunspents', [prepareBitGo(config), typedPromiseWrapper(handleV2FanOutUnspents)]);
16641664

16651665
app.post('/api/v2/:coin/wallet/:id/sweep', parseBody, prepareBitGo(config), promiseWrapper(handleV2Sweep));

modules/express/src/typedRoutes/api/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { PostWalletTxSignTSS } from './v2/walletTxSignTSS';
3333
import { PostShareWallet } from './v2/shareWallet';
3434
import { PutExpressWalletUpdate } from './v2/expressWalletUpdate';
3535
import { PostFanoutUnspents } from './v2/fanoutUnspents';
36+
import { PostConsolidateUnspents } from './v2/consolidateunspents';
3637

3738
// Too large types can cause the following error
3839
//
@@ -130,10 +131,13 @@ export const ExpressV1PendingApprovalConstructTxApiSpec = apiSpec({
130131
},
131132
});
132133

133-
export const ExpressV1WalletConsolidateUnspentsApiSpec = apiSpec({
134+
export const ExpressWalletConsolidateUnspentsApiSpec = apiSpec({
134135
'express.v1.wallet.consolidateunspents': {
135136
put: PutConsolidateUnspents,
136137
},
138+
'express.v2.wallet.consolidateunspents': {
139+
post: PostConsolidateUnspents,
140+
},
137141
});
138142

139143
export const ExpressWalletFanoutUnspentsApiSpec = apiSpec({
@@ -220,7 +224,7 @@ export type ExpressApi = typeof ExpressPingApiSpec &
220224
typeof ExpressV1KeychainDeriveApiSpec &
221225
typeof ExpressV1KeychainLocalApiSpec &
222226
typeof ExpressV1PendingApprovalConstructTxApiSpec &
223-
typeof ExpressV1WalletConsolidateUnspentsApiSpec &
227+
typeof ExpressWalletConsolidateUnspentsApiSpec &
224228
typeof ExpressWalletFanoutUnspentsApiSpec &
225229
typeof ExpressV2WalletCreateAddressApiSpec &
226230
typeof ExpressKeychainLocalApiSpec &
@@ -246,7 +250,7 @@ export const ExpressApi: ExpressApi = {
246250
...ExpressV1KeychainDeriveApiSpec,
247251
...ExpressV1KeychainLocalApiSpec,
248252
...ExpressV1PendingApprovalConstructTxApiSpec,
249-
...ExpressV1WalletConsolidateUnspentsApiSpec,
253+
...ExpressWalletConsolidateUnspentsApiSpec,
250254
...ExpressWalletFanoutUnspentsApiSpec,
251255
...ExpressV2WalletCreateAddressApiSpec,
252256
...ExpressKeychainLocalApiSpec,
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import * as t from 'io-ts';
2+
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
3+
import { BitgoExpressError } from '../../schemas/error';
4+
5+
/**
6+
* Request parameters for consolidating unspents in a wallet (v2)
7+
*/
8+
export const ConsolidateUnspentsRequestParams = {
9+
/** The coin identifier (e.g., 'btc', 'tbtc') */
10+
coin: t.string,
11+
/** The ID of the wallet */
12+
id: t.string,
13+
} as const;
14+
15+
/**
16+
* Request body for consolidating unspents in a wallet (v2)
17+
*
18+
* This endpoint supports the full set of parameters available in the BitGo SDK
19+
* for advanced UTXO management. The consolidate operation takes multiple unspents and
20+
* combines them into fewer outputs to reduce the number of UTXOs in a wallet.
21+
*/
22+
export const ConsolidateUnspentsRequestBody = {
23+
/** The wallet passphrase to decrypt the user key */
24+
walletPassphrase: optional(t.string),
25+
/** The extended private key (alternative to walletPassphrase) */
26+
xprv: optional(t.string),
27+
/** Minimum value of unspents to use (in base units) */
28+
minValue: optional(t.union([t.number, t.string])),
29+
/** Maximum value of unspents to use (in base units) */
30+
maxValue: optional(t.union([t.number, t.string])),
31+
/** Minimum block height of unspents to use */
32+
minHeight: optional(t.number),
33+
/** The number of new unspents to make (not applicable for bulk consolidation) */
34+
numUnspentsToMake: optional(t.number),
35+
/** Estimate fees to aim for first confirmation within this number of blocks */
36+
feeTxConfirmTarget: optional(t.number),
37+
/** Maximum number of unspents to use in the transaction */
38+
limit: optional(t.number),
39+
/** Minimum number of confirmations needed for an unspent to be included (defaults to 1) */
40+
minConfirms: optional(t.number),
41+
/** If true, minConfirms also applies to change outputs */
42+
enforceMinConfirmsForChange: optional(t.boolean),
43+
/** The desired fee rate for the transaction in satoshis/kB */
44+
feeRate: optional(t.number),
45+
/** The maximum limit for a fee rate in satoshis/kB */
46+
maxFeeRate: optional(t.number),
47+
/** The maximum proportion of value you're willing to lose to fees (as a decimal, e.g., 0.1 for 10%) */
48+
maxFeePercentage: optional(t.number),
49+
/** Comment to attach to the transaction */
50+
comment: optional(t.string),
51+
/** One-time password for 2FA */
52+
otp: optional(t.string),
53+
/** Target address for the consolidation outputs */
54+
targetAddress: optional(t.string),
55+
/** If true, enables consolidation of large number of unspents by creating multiple transactions (200 unspents per tx) */
56+
bulk: optional(t.boolean),
57+
} as const;
58+
59+
/**
60+
* Single transaction response object
61+
*/
62+
const ConsolidateUnspentsSingleTxResponse = t.type({
63+
/** The status of the transaction ('accepted', 'signed', 'pendingApproval', or 'otp') */
64+
status: t.string,
65+
/** The transaction hex/serialized transaction */
66+
tx: t.string,
67+
/** The transaction hash/ID */
68+
hash: optional(t.string),
69+
/** Alternative field for transaction ID (some responses use this instead of hash) */
70+
txid: optional(t.string),
71+
/** The fee amount in base units (satoshis for BTC) */
72+
fee: optional(t.number),
73+
/** The fee rate in base units per kilobyte (satoshis/kB for BTC) */
74+
feeRate: optional(t.number),
75+
/** Whether the transaction is instant */
76+
instant: optional(t.boolean),
77+
/** The instant transaction ID (if applicable) */
78+
instantId: optional(t.string),
79+
/** Travel rule information */
80+
travelInfos: optional(t.unknown),
81+
/** BitGo fee information (if applicable) */
82+
bitgoFee: optional(t.unknown),
83+
/** Travel rule result (if applicable) */
84+
travelResult: optional(t.unknown),
85+
});
86+
87+
/**
88+
* Response for consolidating unspents in a wallet (v2)
89+
*
90+
* Returns transaction details after the consolidation operation is built, signed, and sent.
91+
* When bulk=true, an array of transaction objects is returned; otherwise, a single transaction object is returned.
92+
*/
93+
export const ConsolidateUnspentsResponse = t.union([
94+
ConsolidateUnspentsSingleTxResponse,
95+
t.array(ConsolidateUnspentsSingleTxResponse),
96+
]);
97+
98+
/**
99+
* Consolidate unspents in a wallet (v2)
100+
*
101+
* This endpoint consolidates unspents in a wallet by creating a transaction that spends from
102+
* multiple inputs to create fewer outputs. This is useful for reducing the number of UTXOs in a wallet,
103+
* which can improve performance and reduce transaction fees for future transactions.
104+
*
105+
* The v2 API differs from v1 by:
106+
* - Requiring a coin parameter in the path
107+
* - Supporting the full set of SDK parameters for advanced UTXO management
108+
* - Using standard parameters like minValue/maxValue instead of minSize/maxSize
109+
* - Supporting bulk consolidation mode that creates multiple transactions
110+
* - Supporting additional parameters like limit, targetAddress, fee controls
111+
*
112+
* @operationId express.v2.wallet.consolidateunspents
113+
* @tag express
114+
*/
115+
export const PostConsolidateUnspents = httpRoute({
116+
path: '/api/v2/{coin}/wallet/{id}/consolidateunspents',
117+
method: 'POST',
118+
request: httpRequest({
119+
params: ConsolidateUnspentsRequestParams,
120+
body: ConsolidateUnspentsRequestBody,
121+
}),
122+
response: {
123+
/** Successfully consolidated unspents */
124+
200: ConsolidateUnspentsResponse,
125+
/** Invalid request or consolidation operation fails */
126+
400: BitgoExpressError,
127+
},
128+
});

0 commit comments

Comments
 (0)