Skip to content

Commit a9d27c7

Browse files
authored
feat(express): migrated fanoutunspents as type route
2 parents ff29330 + bf055ab commit a9d27c7

File tree

4 files changed

+1150
-12
lines changed

4 files changed

+1150
-12
lines changed

modules/express/src/clientRoutes.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -812,10 +812,10 @@ export async function handleV2ConsolidateAccount(req: express.Request) {
812812
* handle wallet fanout unspents
813813
* @param req
814814
*/
815-
async function handleV2FanOutUnspents(req: express.Request) {
815+
async function handleV2FanOutUnspents(req: ExpressApiRouteRequest<'express.v2.wallet.fanoutunspents', 'post'>) {
816816
const bitgo = req.bitgo;
817-
const coin = bitgo.coin(req.params.coin);
818-
const wallet = await coin.wallets().get({ id: req.params.id });
817+
const coin = bitgo.coin(req.decoded.coin);
818+
const wallet = await coin.wallets().get({ id: req.decoded.id });
819819
return wallet.fanoutUnspents(createSendParams(req));
820820
}
821821

@@ -1660,12 +1660,7 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
16601660
prepareBitGo(config),
16611661
promiseWrapper(handleV2ConsolidateUnspents)
16621662
);
1663-
app.post(
1664-
'/api/v2/:coin/wallet/:id/fanoutunspents',
1665-
parseBody,
1666-
prepareBitGo(config),
1667-
promiseWrapper(handleV2FanOutUnspents)
1668-
);
1663+
router.post('express.v2.wallet.fanoutunspents', [prepareBitGo(config), typedPromiseWrapper(handleV2FanOutUnspents)]);
16691664

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

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { PostWalletSignTx } from './v2/walletSignTx';
3232
import { PostWalletTxSignTSS } from './v2/walletTxSignTSS';
3333
import { PostShareWallet } from './v2/shareWallet';
3434
import { PutExpressWalletUpdate } from './v2/expressWalletUpdate';
35+
import { PostFanoutUnspents } from './v2/fanoutUnspents';
3536

3637
// Too large types can cause the following error
3738
//
@@ -135,10 +136,13 @@ export const ExpressV1WalletConsolidateUnspentsApiSpec = apiSpec({
135136
},
136137
});
137138

138-
export const ExpressV1WalletFanoutUnspentsApiSpec = apiSpec({
139+
export const ExpressWalletFanoutUnspentsApiSpec = apiSpec({
139140
'express.v1.wallet.fanoutunspents': {
140141
put: PutFanoutUnspents,
141142
},
143+
'express.v2.wallet.fanoutunspents': {
144+
post: PostFanoutUnspents,
145+
},
142146
});
143147

144148
export const ExpressV2WalletCreateAddressApiSpec = apiSpec({
@@ -217,7 +221,7 @@ export type ExpressApi = typeof ExpressPingApiSpec &
217221
typeof ExpressV1KeychainLocalApiSpec &
218222
typeof ExpressV1PendingApprovalConstructTxApiSpec &
219223
typeof ExpressV1WalletConsolidateUnspentsApiSpec &
220-
typeof ExpressV1WalletFanoutUnspentsApiSpec &
224+
typeof ExpressWalletFanoutUnspentsApiSpec &
221225
typeof ExpressV2WalletCreateAddressApiSpec &
222226
typeof ExpressKeychainLocalApiSpec &
223227
typeof ExpressKeychainChangePasswordApiSpec &
@@ -243,7 +247,7 @@ export const ExpressApi: ExpressApi = {
243247
...ExpressV1KeychainLocalApiSpec,
244248
...ExpressV1PendingApprovalConstructTxApiSpec,
245249
...ExpressV1WalletConsolidateUnspentsApiSpec,
246-
...ExpressV1WalletFanoutUnspentsApiSpec,
250+
...ExpressWalletFanoutUnspentsApiSpec,
247251
...ExpressV2WalletCreateAddressApiSpec,
248252
...ExpressKeychainLocalApiSpec,
249253
...ExpressKeychainChangePasswordApiSpec,
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 fanning out unspents in a wallet (v2)
7+
*/
8+
export const FanoutUnspentsRequestParams = {
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 fanning out 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 fanout operation takes existing unspents and
20+
* creates a larger number of equally-sized outputs for improved transaction parallelization.
21+
*/
22+
export const FanoutUnspentsRequestBody = {
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+
/** The number of new unspents to create */
28+
numUnspentsToMake: optional(t.number),
29+
/** Minimum value of unspents to use (in base units) */
30+
minValue: optional(t.union([t.number, t.string])),
31+
/** Maximum value of unspents to use (in base units) */
32+
maxValue: optional(t.union([t.number, t.string])),
33+
/** Minimum block height of unspents to use */
34+
minHeight: optional(t.number),
35+
/** Minimum number of confirmations needed for an unspent to be included (defaults to 1) */
36+
minConfirms: optional(t.number),
37+
/** If true, minConfirms also applies to change outputs */
38+
enforceMinConfirmsForChange: optional(t.boolean),
39+
/** Maximum number of inputs to use in the transaction */
40+
maxNumInputsToUse: optional(t.number),
41+
/** Array of specific unspent IDs to use */
42+
unspents: optional(t.array(t.string)),
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+
/** Estimate fees to aim for first confirmation within this number of blocks */
50+
feeTxConfirmTarget: optional(t.number),
51+
/** Comment to attach to the transaction */
52+
comment: optional(t.string),
53+
/** One-time password for 2FA */
54+
otp: optional(t.string),
55+
/** Target address for the fanout outputs */
56+
targetAddress: optional(t.string),
57+
} as const;
58+
59+
/**
60+
* Response for fanning out unspents in a wallet (v2)
61+
*
62+
* Returns transaction details after the fanout operation is built, signed, and sent.
63+
*/
64+
export const FanoutUnspentsResponse = t.type({
65+
/** The status of the transaction ('accepted', 'signed', 'pendingApproval', or 'otp') */
66+
status: t.string,
67+
/** The transaction hex/serialized transaction */
68+
tx: t.string,
69+
/** The transaction hash/ID */
70+
hash: optional(t.string),
71+
/** Alternative field for transaction ID (some responses use this instead of hash) */
72+
txid: optional(t.string),
73+
/** The fee amount in base units (satoshis for BTC) */
74+
fee: optional(t.number),
75+
/** The fee rate in base units per kilobyte (satoshis/kB for BTC) */
76+
feeRate: optional(t.number),
77+
/** Whether the transaction is instant */
78+
instant: optional(t.boolean),
79+
/** The instant transaction ID (if applicable) */
80+
instantId: optional(t.string),
81+
/** Travel rule information */
82+
travelInfos: optional(t.unknown),
83+
/** BitGo fee information (if applicable) */
84+
bitgoFee: optional(t.unknown),
85+
/** Travel rule result (if applicable) */
86+
travelResult: optional(t.unknown),
87+
});
88+
89+
/**
90+
* Fan out unspents in a wallet (v2)
91+
*
92+
* This endpoint fans out unspents in a wallet by creating a transaction that spends from
93+
* one or more inputs to create multiple equal-sized outputs. This is useful for increasing
94+
* the number of UTXOs in a wallet, which can improve transaction parallelization and allow
95+
* for concurrent spending operations.
96+
*
97+
* The v2 API differs from v1 by:
98+
* - Requiring a coin parameter in the path
99+
* - Supporting the full set of SDK parameters for advanced UTXO management
100+
* - Using numUnspentsToMake instead of target (though both refer to output count)
101+
* - Supporting additional parameters like maxNumInputsToUse, unspents array, fee controls
102+
*
103+
* @operationId express.v2.wallet.fanoutunspents
104+
* @tag express
105+
*/
106+
export const PostFanoutUnspents = httpRoute({
107+
path: '/api/v2/{coin}/wallet/{id}/fanoutunspents',
108+
method: 'POST',
109+
request: httpRequest({
110+
params: FanoutUnspentsRequestParams,
111+
body: FanoutUnspentsRequestBody,
112+
}),
113+
response: {
114+
/** Successfully fanned out unspents */
115+
200: FanoutUnspentsResponse,
116+
/** Invalid request or fan out operation fails */
117+
400: BitgoExpressError,
118+
},
119+
});

0 commit comments

Comments
 (0)