Skip to content

Commit cb6df0f

Browse files
authored
feat(express): migrated pendingApprovalV2 to type route
2 parents fa5006e + 9769ded commit cb6df0f

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
router.post('express.v2.wallet.lightningPayment', [

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { PostGenerateShareTSS } from './v2/generateShareTSS';
4343
import { PostOfcExtSignPayload } from './v2/ofcExtSignPayload';
4444
import { PostLightningWalletPayment } from './v2/lightningPayment';
4545
import { PostLightningWalletWithdraw } from './v2/lightningWithdraw';
46+
import { PutV2PendingApproval } from './v2/pendingApproval';
4647

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

113-
export const ExpressV1PendingApprovalsApiSpec = apiSpec({
114+
export const ExpressPendingApprovalsApiSpec = apiSpec({
114115
'express.v1.pendingapprovals': {
115116
put: PutPendingApproval,
116117
},
118+
'express.v2.pendingapprovals': {
119+
put: PutV2PendingApproval,
120+
},
117121
});
118122

119123
export const ExpressWalletSignTransactionApiSpec = apiSpec({
@@ -282,7 +286,7 @@ export type ExpressApi = typeof ExpressPingApiSpec &
282286
typeof ExpressCalculateMinerFeeInfoApiSpec &
283287
typeof ExpressV1WalletAcceptShareApiSpec &
284288
typeof ExpressV1WalletSimpleCreateApiSpec &
285-
typeof ExpressV1PendingApprovalsApiSpec &
289+
typeof ExpressPendingApprovalsApiSpec &
286290
typeof ExpressWalletSignTransactionApiSpec &
287291
typeof ExpressV1KeychainDeriveApiSpec &
288292
typeof ExpressV1KeychainLocalApiSpec &
@@ -317,7 +321,7 @@ export const ExpressApi: ExpressApi = {
317321
...ExpressCalculateMinerFeeInfoApiSpec,
318322
...ExpressV1WalletAcceptShareApiSpec,
319323
...ExpressV1WalletSimpleCreateApiSpec,
320-
...ExpressV1PendingApprovalsApiSpec,
324+
...ExpressPendingApprovalsApiSpec,
321325
...ExpressWalletSignTransactionApiSpec,
322326
...ExpressV1KeychainDeriveApiSpec,
323327
...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)