Skip to content

Commit 1086c31

Browse files
authored
feat(express): migrate wallet signTx to typed routes
2 parents b8e441f + 335fe30 commit 1086c31

File tree

4 files changed

+857
-2
lines changed

4 files changed

+857
-2
lines changed

modules/express/src/clientRoutes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,7 @@ async function handleV2AcceptWalletShare(req: express.Request) {
708708
/**
709709
* handle wallet sign transaction
710710
*/
711-
async function handleV2SignTxWallet(req: express.Request) {
711+
async function handleV2SignTxWallet(req: ExpressApiRouteRequest<'express.v2.wallet.signtx', 'post'>) {
712712
const bitgo = req.bitgo;
713713
const coin = bitgo.coin(req.params.coin);
714714
const wallet = await coin.wallets().get({ id: req.params.id });
@@ -1633,7 +1633,7 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
16331633

16341634
// sign transaction
16351635
router.post('express.v2.coin.signtx', [prepareBitGo(config), typedPromiseWrapper(handleV2SignTx)]);
1636-
app.post('/api/v2/:coin/wallet/:id/signtx', parseBody, prepareBitGo(config), promiseWrapper(handleV2SignTxWallet));
1636+
router.post('express.v2.wallet.signtx', [prepareBitGo(config), typedPromiseWrapper(handleV2SignTxWallet)]);
16371637
app.post(
16381638
'/api/v2/:coin/wallet/:id/signtxtss',
16391639
parseBody,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { PutFanoutUnspents } from './v1/fanoutUnspents';
2727
import { PostOfcSignPayload } from './v2/ofcSignPayload';
2828
import { PostWalletRecoverToken } from './v2/walletRecoverToken';
2929
import { PostCoinSignTx } from './v2/coinSignTx';
30+
import { PostWalletSignTx } from './v2/walletSignTx';
3031

3132
export const ExpressApi = apiSpec({
3233
'express.ping': {
@@ -104,6 +105,9 @@ export const ExpressApi = apiSpec({
104105
'express.v2.coin.signtx': {
105106
post: PostCoinSignTx,
106107
},
108+
'express.v2.wallet.signtx': {
109+
post: PostWalletSignTx,
110+
},
107111
});
108112

109113
export type ExpressApi = typeof ExpressApi;
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import * as t from 'io-ts';
2+
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
3+
import { TransactionRequest as TxRequestResponse } from '@bitgo/public-types';
4+
import { BitgoExpressError } from '../../schemas/error';
5+
6+
/**
7+
* Request path parameters for signing a wallet transaction
8+
*/
9+
export const WalletSignTxParams = {
10+
/** The coin type */
11+
coin: t.string,
12+
/** The wallet ID */
13+
id: t.string,
14+
} as const;
15+
16+
/**
17+
* Transaction prebuild information for wallet signing
18+
*/
19+
export const WalletTransactionPrebuild = t.partial({
20+
/** Transaction in hex format */
21+
txHex: t.string,
22+
/** Transaction in base64 format (for some coins) */
23+
txBase64: t.string,
24+
/** Transaction in JSON format (for some coins) */
25+
txInfo: t.any,
26+
/** Wallet ID for the transaction */
27+
walletId: t.string,
28+
/** Next contract sequence ID (for ETH) */
29+
nextContractSequenceId: t.number,
30+
/** Whether this is a batch transaction (for ETH) */
31+
isBatch: t.boolean,
32+
/** EIP1559 transaction parameters (for ETH) */
33+
eip1559: t.any,
34+
/** Hop transaction data (for ETH) */
35+
hopTransaction: t.any,
36+
/** Backup key nonce (for ETH) */
37+
backupKeyNonce: t.any,
38+
/** Recipients of the transaction */
39+
recipients: t.any,
40+
});
41+
42+
/**
43+
* Request body for signing a wallet transaction
44+
*/
45+
export const WalletSignTxBody = {
46+
/** Private key for signing */
47+
prv: optional(t.string),
48+
/** Transaction prebuild data */
49+
txPrebuild: optional(WalletTransactionPrebuild),
50+
/** Public keys for multi-signature transactions */
51+
pubs: optional(t.array(t.string)),
52+
/** Transaction request ID for TSS wallets */
53+
txRequestId: optional(t.string),
54+
/** Cosigner public key */
55+
cosignerPub: optional(t.string),
56+
/** Whether this is the last signature in a multi-sig tx */
57+
isLastSignature: optional(t.boolean),
58+
/** Wallet passphrase for TSS wallets */
59+
walletPassphrase: optional(t.string),
60+
/** API version: 'lite' or 'full' */
61+
apiVersion: optional(t.union([t.literal('lite'), t.literal('full')])),
62+
/** Multisig type version */
63+
multisigTypeVersion: optional(t.literal('MPCv2')),
64+
/** Gas limit for ETH transactions */
65+
gasLimit: optional(t.union([t.string, t.number])),
66+
/** Gas price for ETH transactions */
67+
gasPrice: optional(t.union([t.string, t.number])),
68+
/** Transaction expiration time */
69+
expireTime: optional(t.number),
70+
/** Sequence ID for transactions */
71+
sequenceId: optional(t.union([t.string, t.number])),
72+
/** Recipients of the transaction */
73+
recipients: optional(t.any),
74+
/** Custodian transaction ID */
75+
custodianTransactionId: optional(t.string),
76+
/** Signing step for MuSig2 */
77+
signingStep: optional(t.union([t.literal('signerNonce'), t.literal('signerSignature'), t.literal('cosignerNonce')])),
78+
/** Allow non-segwit signing without previous transaction */
79+
allowNonSegwitSigningWithoutPrevTx: optional(t.boolean),
80+
/** For EVM cross-chain recovery */
81+
isEvmBasedCrossChainRecovery: optional(t.boolean),
82+
/** Derivation seed for key derivation */
83+
derivationSeed: optional(t.string),
84+
} as const;
85+
86+
/**
87+
* Response for a fully signed transaction
88+
*/
89+
export const FullySignedTransactionResponse = t.type({
90+
/** Transaction in hex format */
91+
txHex: t.string,
92+
});
93+
94+
/**
95+
* Response for a half-signed account transaction
96+
*/
97+
export const HalfSignedAccountTransactionResponse = t.type({
98+
halfSigned: t.partial({
99+
txHex: optional(t.string),
100+
payload: optional(t.string),
101+
txBase64: optional(t.string),
102+
}),
103+
});
104+
105+
/**
106+
* Response for a half-signed UTXO transaction
107+
*/
108+
export const HalfSignedUtxoTransactionResponse = t.type({
109+
txHex: t.string,
110+
});
111+
112+
/**
113+
* Response for a transaction request
114+
*/
115+
export const SignedTransactionRequestResponse = t.type({
116+
txRequestId: t.string,
117+
});
118+
119+
/**
120+
* Response for signing a wallet transaction
121+
*
122+
* Uses TxRequestResponse (TransactionRequest) from @bitgo/public-types for TSS transaction requests
123+
* (supports both Lite and Full versions)
124+
*/
125+
export const WalletSignTxResponse = {
126+
/** Successfully signed transaction */
127+
200: t.union([
128+
FullySignedTransactionResponse,
129+
HalfSignedAccountTransactionResponse,
130+
HalfSignedUtxoTransactionResponse,
131+
SignedTransactionRequestResponse,
132+
TxRequestResponse,
133+
]),
134+
/** Error response */
135+
400: BitgoExpressError,
136+
};
137+
138+
/**
139+
* Sign a transaction for a specific wallet
140+
*
141+
* This endpoint signs a transaction for a specific wallet identified by coin type and wallet ID.
142+
* The request body is passed to wallet.signTransaction() and varies by coin and wallet type.
143+
*
144+
* Common fields include:
145+
* - txPrebuild: Contains transaction data like txHex or txBase64
146+
* - prv: Private key for signing (for non-TSS wallets)
147+
* - walletPassphrase: Passphrase for TSS wallets
148+
* - txRequestId: Transaction request ID for TSS wallets
149+
* - isLastSignature: Whether this is the last signature in a multi-sig tx
150+
* - pubs: Public keys for multi-signature transactions
151+
* - apiVersion: 'lite' or 'full' for TSS transaction requests
152+
* - gasLimit: Gas limit for ETH transactions
153+
* - gasPrice: Gas price for ETH transactions
154+
* - expireTime: Transaction expiration time
155+
* - sequenceId: Sequence ID for transactions
156+
* - isEvmBasedCrossChainRecovery: For EVM cross-chain recovery
157+
*
158+
* @operationId express.v2.wallet.signtx
159+
*/
160+
export const PostWalletSignTx = httpRoute({
161+
path: '/api/v2/:coin/wallet/:id/signtx',
162+
method: 'POST',
163+
request: httpRequest({
164+
params: WalletSignTxParams,
165+
body: WalletSignTxBody,
166+
}),
167+
response: WalletSignTxResponse,
168+
});

0 commit comments

Comments
 (0)