Skip to content

Commit 9769ded

Browse files
committed
feat(express): migrated pendingApprovalV2 to type route
Ticket: WP-5437
1 parent 4965cd5 commit 9769ded

File tree

4 files changed

+763
-12
lines changed

4 files changed

+763
-12
lines changed

modules/express/src/clientRoutes.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -664,11 +664,13 @@ export async function handleV2CreateAddress(req: ExpressApiRouteRequest<'express
664664
* handle v2 approve transaction
665665
* @param req
666666
*/
667-
async function handleV2PendingApproval(req: express.Request): Promise<any> {
667+
async function handleV2PendingApproval(
668+
req: ExpressApiRouteRequest<'express.v2.pendingapprovals', 'put'>
669+
): Promise<any> {
668670
const bitgo = req.bitgo;
669-
const coin = bitgo.coin(req.params.coin);
671+
const coin = bitgo.coin(req.decoded.coin);
670672
const params = req.body || {};
671-
const pendingApproval = await coin.pendingApprovals().get({ id: req.params.id });
673+
const pendingApproval = await coin.pendingApprovals().get({ id: req.decoded.id });
672674
if (params.state === 'approved') {
673675
return pendingApproval.approve(params);
674676
}
@@ -1686,12 +1688,7 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
16861688
// Miscellaneous
16871689
app.post('/api/v2/:coin/canonicaladdress', parseBody, prepareBitGo(config), promiseWrapper(handleCanonicalAddress));
16881690
router.post('express.verifycoinaddress', [prepareBitGo(config), typedPromiseWrapper(handleV2VerifyAddress)]);
1689-
app.put(
1690-
'/api/v2/:coin/pendingapprovals/:id',
1691-
parseBody,
1692-
prepareBitGo(config),
1693-
promiseWrapper(handleV2PendingApproval)
1694-
);
1691+
router.put('express.v2.pendingapprovals', [prepareBitGo(config), typedPromiseWrapper(handleV2PendingApproval)]);
16951692

16961693
// lightning - pay invoice
16971694
app.post(

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { PostSendCoins } from './v2/sendCoins';
4242
import { PostGenerateShareTSS } from './v2/generateShareTSS';
4343
import { PostOfcExtSignPayload } from './v2/ofcExtSignPayload';
4444
import { PostLightningWalletWithdraw } from './v2/lightningWithdraw';
45+
import { PutV2PendingApproval } from './v2/pendingApproval';
4546

4647
// Too large types can cause the following error
4748
//
@@ -109,10 +110,13 @@ export const ExpressV1WalletSimpleCreateApiSpec = apiSpec({
109110
},
110111
});
111112

112-
export const ExpressV1PendingApprovalsApiSpec = apiSpec({
113+
export const ExpressPendingApprovalsApiSpec = apiSpec({
113114
'express.v1.pendingapprovals': {
114115
put: PutPendingApproval,
115116
},
117+
'express.v2.pendingapprovals': {
118+
put: PutV2PendingApproval,
119+
},
116120
});
117121

118122
export const ExpressWalletSignTransactionApiSpec = apiSpec({
@@ -275,7 +279,7 @@ export type ExpressApi = typeof ExpressPingApiSpec &
275279
typeof ExpressCalculateMinerFeeInfoApiSpec &
276280
typeof ExpressV1WalletAcceptShareApiSpec &
277281
typeof ExpressV1WalletSimpleCreateApiSpec &
278-
typeof ExpressV1PendingApprovalsApiSpec &
282+
typeof ExpressPendingApprovalsApiSpec &
279283
typeof ExpressWalletSignTransactionApiSpec &
280284
typeof ExpressV1KeychainDeriveApiSpec &
281285
typeof ExpressV1KeychainLocalApiSpec &
@@ -309,7 +313,7 @@ export const ExpressApi: ExpressApi = {
309313
...ExpressCalculateMinerFeeInfoApiSpec,
310314
...ExpressV1WalletAcceptShareApiSpec,
311315
...ExpressV1WalletSimpleCreateApiSpec,
312-
...ExpressV1PendingApprovalsApiSpec,
316+
...ExpressPendingApprovalsApiSpec,
313317
...ExpressWalletSignTransactionApiSpec,
314318
...ExpressV1KeychainDeriveApiSpec,
315319
...ExpressV1KeychainLocalApiSpec,
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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+
* Path parameters for pending approval endpoint
7+
*/
8+
export const PendingApprovalParams = {
9+
/** Coin identifier (e.g., 'btc', 'eth', 'tbtc') */
10+
coin: t.string,
11+
/** Pending approval ID */
12+
id: t.string,
13+
} as const;
14+
15+
/**
16+
* Request body for approving or rejecting a pending approval
17+
*/
18+
export const PendingApprovalRequestBody = {
19+
/** State of the approval: 'approved' to approve, omit or 'rejected' to reject */
20+
state: optional(t.string),
21+
/** Wallet passphrase for decrypting user keys during transaction signing */
22+
walletPassphrase: optional(t.string),
23+
/** One-time password for 2FA verification */
24+
otp: optional(t.string),
25+
/** Transaction hex to use instead of the original transaction */
26+
tx: optional(t.string),
27+
/** Private key in string form as an alternative to wallet passphrase */
28+
xprv: optional(t.string),
29+
/** If true, returns information about pending transactions without approving */
30+
previewPendingTxs: optional(t.boolean),
31+
/** Alternative ID for the pending approval */
32+
pendingApprovalId: optional(t.string),
33+
} as const;
34+
35+
/**
36+
* Pending approval state enum
37+
*/
38+
export const PendingApprovalState = t.union([
39+
t.literal('pending'),
40+
t.literal('awaitingSignature'),
41+
t.literal('pendingBitGoAdminApproval'),
42+
t.literal('pendingIdVerification'),
43+
t.literal('pendingCustodianApproval'),
44+
t.literal('pendingFinalApproval'),
45+
t.literal('approved'),
46+
t.literal('processing'),
47+
t.literal('rejected'),
48+
]);
49+
50+
/**
51+
* Pending approval type enum
52+
*/
53+
export const PendingApprovalType = t.union([
54+
t.literal('userChangeRequest'),
55+
t.literal('transactionRequest'),
56+
t.literal('policyRuleRequest'),
57+
t.literal('updateApprovalsRequiredRequest'),
58+
t.literal('transactionRequestFull'),
59+
]);
60+
61+
/**
62+
* Build parameters for transaction request
63+
* Allows any additional properties beyond the known 'type' field
64+
*/
65+
export const BuildParams = t.intersection([
66+
t.partial({
67+
/** Transaction type (e.g., fanout, consolidate) */
68+
type: t.union([t.literal('fanout'), t.literal('consolidate')]),
69+
}),
70+
t.UnknownRecord,
71+
]);
72+
73+
/**
74+
* Transaction request info within pending approval
75+
*/
76+
export const TransactionRequestInfo = t.intersection([
77+
t.type({
78+
/** Coin-specific transaction parameters */
79+
coinSpecific: t.UnknownRecord,
80+
/** Transaction recipients */
81+
recipients: t.unknown,
82+
/** Build parameters for the transaction */
83+
buildParams: BuildParams,
84+
}),
85+
t.partial({
86+
/** Source wallet ID for the transaction */
87+
sourceWallet: t.string,
88+
}),
89+
]);
90+
91+
/**
92+
* Pending approval information structure
93+
*/
94+
export const PendingApprovalInfo = t.intersection([
95+
t.type({
96+
/** Type of pending approval */
97+
type: PendingApprovalType,
98+
}),
99+
t.partial({
100+
/** Transaction request details (if type is transactionRequest) */
101+
transactionRequest: TransactionRequestInfo,
102+
}),
103+
]);
104+
105+
/**
106+
* Pending approval data response
107+
* Both approve and reject return the same structure
108+
*/
109+
export const PendingApprovalResponse = t.intersection([
110+
t.type({
111+
/** Pending approval unique identifier */
112+
id: t.string,
113+
/** Current state of the pending approval */
114+
state: PendingApprovalState,
115+
/** User ID of the pending approval creator */
116+
creator: t.string,
117+
/** Pending approval information and details */
118+
info: PendingApprovalInfo,
119+
}),
120+
t.partial({
121+
/** Wallet ID if this is a wallet-level approval */
122+
wallet: t.string,
123+
/** Enterprise ID if this is an enterprise-level approval */
124+
enterprise: t.string,
125+
/** Number of approvals required for this pending approval */
126+
approvalsRequired: t.number,
127+
/** Transaction request ID associated with this pending approval */
128+
txRequestId: t.string,
129+
}),
130+
]);
131+
132+
/**
133+
* Update Pending Approval
134+
* Approve or reject a pending approval by its ID.
135+
* Supports transaction approvals, policy rule changes, and user change requests.
136+
*
137+
* @operationId express.v2.pendingapprovals
138+
* @tag express
139+
*/
140+
export const PutV2PendingApproval = httpRoute({
141+
path: '/api/v2/{coin}/pendingapprovals/{id}',
142+
method: 'PUT',
143+
request: httpRequest({
144+
params: PendingApprovalParams,
145+
body: PendingApprovalRequestBody,
146+
}),
147+
response: {
148+
/** Successfully updated pending approval */
149+
200: PendingApprovalResponse,
150+
/** Bad request or validation error */
151+
400: BitgoExpressError,
152+
},
153+
});

0 commit comments

Comments
 (0)