Skip to content

Commit 6bf1d6f

Browse files
authored
fix(express): fixed type codec for coinSignTx
2 parents 2bb4c3d + dfd5583 commit 6bf1d6f

File tree

2 files changed

+298
-63
lines changed

2 files changed

+298
-63
lines changed

modules/express/src/typedRoutes/api/v2/coinSignTx.ts

Lines changed: 195 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -13,97 +13,233 @@ export const CoinSignTxParams = {
1313

1414
/**
1515
* EIP1559 transaction parameters for Ethereum
16-
* Reference: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:1106
16+
* Reference: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:116-119
17+
* Note: Both fields are REQUIRED when EIP1559 object is provided
1718
*/
18-
export const EIP1559 = t.partial({
19-
/** Maximum fee per gas */
20-
maxFeePerGas: t.union([t.string, t.number]),
21-
/** Maximum priority fee per gas */
19+
export const EIP1559 = t.type({
20+
/** Maximum priority fee per gas (REQUIRED) */
2221
maxPriorityFeePerGas: t.union([t.string, t.number]),
22+
/** Maximum fee per gas (REQUIRED) */
23+
maxFeePerGas: t.union([t.string, t.number]),
2324
});
2425

2526
/**
26-
* Recipient information for a transaction
27-
* Reference: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:1100-1102
27+
* Replay protection options for EVM transactions
28+
* Reference: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:121-124
29+
* Note: Both fields are REQUIRED when ReplayProtectionOptions object is provided
2830
*/
29-
export const Recipient = t.partial({
30-
/** Recipient address */
31-
address: t.string,
32-
/** Amount to send */
33-
amount: t.union([t.string, t.number]),
34-
/** Token name (for token transfers) */
35-
tokenName: t.string,
36-
/** Additional data */
37-
data: t.string,
31+
export const ReplayProtectionOptions = t.type({
32+
/** Chain ID (REQUIRED) */
33+
chain: t.union([t.string, t.number]),
34+
/** Hardfork name (REQUIRED) */
35+
hardfork: t.string,
3836
});
3937

38+
/**
39+
* Recipient information for a transaction
40+
* Reference: modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts:468-472 (Recipient)
41+
* Reference: modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts:79-84 (ITransactionRecipient)
42+
* Validation: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:622-642
43+
*
44+
* Note: address and amount are REQUIRED (accessed without null checks in SDK validation)
45+
* tokenName, data, and memo are OPTIONAL
46+
*/
47+
export const Recipient = t.intersection([
48+
t.type({
49+
/** Recipient address (REQUIRED) */
50+
address: t.string,
51+
/** Amount to send (REQUIRED) */
52+
amount: t.union([t.string, t.number]),
53+
}),
54+
t.partial({
55+
/** Token name (for token transfers) (OPTIONAL) */
56+
tokenName: t.string,
57+
/** Additional data (EVM) (OPTIONAL) */
58+
data: t.string,
59+
/** Memo field (used in ITransactionRecipient for various coins) (OPTIONAL) */
60+
memo: t.string,
61+
}),
62+
]);
63+
4064
/**
4165
* Hop transaction data for Ethereum
42-
* Reference: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:1110
66+
* Reference: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:90-102 (HopPrebuild - full interface)
67+
* Note: All fields are REQUIRED when HopPrebuild object is provided
4368
*/
44-
export const HopTransaction = t.partial({
45-
/** Transaction hex */
46-
txHex: t.string,
47-
/** User request signature */
69+
export const HopTransaction = t.type({
70+
/** Transaction hex (REQUIRED) */
71+
tx: t.string,
72+
/** Transaction ID (REQUIRED) */
73+
id: t.string,
74+
/** Signature (REQUIRED) */
75+
signature: t.string,
76+
/** Payment ID (REQUIRED) */
77+
paymentId: t.string,
78+
/** Gas price (REQUIRED) */
79+
gasPrice: t.number,
80+
/** Gas limit (REQUIRED) */
81+
gasLimit: t.number,
82+
/** Amount to send (REQUIRED) */
83+
amount: t.number,
84+
/** Recipient address (REQUIRED) */
85+
recipient: t.string,
86+
/** Transaction nonce (REQUIRED) */
87+
nonce: t.number,
88+
/** User request signature (REQUIRED) */
4889
userReqSig: t.string,
49-
/** Maximum gas price */
50-
gasPriceMax: t.union([t.string, t.number]),
51-
/** Gas limit */
52-
gasLimit: t.union([t.string, t.number]),
90+
/** Maximum gas price (REQUIRED) */
91+
gasPriceMax: t.number,
5392
});
5493

5594
/**
5695
* Half-signed transaction data
96+
*
97+
* This covers two use cases:
98+
* 1. Response halfSigned data (txHex, payload, txBase64, txHash) - general coins
99+
* 2. Request txPrebuild.halfSigned for EVM final signing (expireTime, contractSequenceId, backupKeyNonce, signature, txHex)
100+
*
101+
* Reference:
102+
* - Response: modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts:408-414
103+
* - Request: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:147-153 (SignFinalOptions.txPrebuild.halfSigned)
57104
*/
58105
export const HalfSignedData = t.partial({
59-
/** Transaction hash */
60-
txHash: t.string,
106+
// From response/general usage (HalfSignedAccountTransaction)
107+
/** Transaction in hex format */
108+
txHex: t.string,
61109
/** Transaction payload */
62110
payload: t.string,
63111
/** Transaction in base64 format */
64112
txBase64: t.string,
113+
/** Transaction hash */
114+
txHash: t.string,
115+
116+
// From SignFinalOptions.txPrebuild.halfSigned (EVM final signing request)
117+
/** Expiration time (EVM final signing) */
118+
expireTime: t.number,
119+
/** Contract sequence ID (EVM final signing) */
120+
contractSequenceId: t.number,
121+
/** Backup key nonce (EVM final signing) */
122+
backupKeyNonce: t.number,
123+
/** Signature (EVM final signing) */
124+
signature: t.string,
125+
});
126+
127+
/**
128+
* Build parameters structure
129+
* Reference: modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts:315-318
130+
*/
131+
export const BuildParams = t.partial({
132+
/** Preview mode flag */
133+
preview: t.boolean,
134+
/** Recipients for the transaction */
135+
recipients: t.array(Recipient),
136+
});
137+
138+
/**
139+
* Address information for transaction signing (used by Tron, Tezos, etc.)
140+
* Reference: modules/sdk-coin-trx/src/trx.ts:55-59
141+
* Note: All fields are REQUIRED when AddressInfo object is provided
142+
*/
143+
export const AddressInfo = t.type({
144+
/** Address string (REQUIRED) */
145+
address: t.string,
146+
/** Chain index for address derivation (REQUIRED) */
147+
chain: t.number,
148+
/** Address index for derivation (REQUIRED) */
149+
index: t.number,
65150
});
66151

67152
/**
68153
* Transaction prebuild information
69-
* Reference: modules/abstract-utxo/src/abstractUtxoCoin.ts:336-346
70-
* Reference: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:1088-1116
154+
*
155+
* Base interface: modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts:327-331
156+
* EVM extension: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:126-138
157+
* UTXO extension: modules/abstract-utxo/src/abstractUtxoCoin.ts:248-251
158+
*
159+
* This codec covers all fields from:
160+
* - SDK Core Base (txBase64, txHex, txInfo, buildParams, consolidateId, txRequestId)
161+
* - EVM-specific (coin, token, nextContractSequenceId, isBatch, eip1559, hopTransaction, etc.)
162+
* - UTXO-specific (blockHeight)
163+
* - Account-based coins (addressInfo, source, feeInfo, keys, addressVersion, etc.)
71164
*/
72165
export const TransactionPrebuild = t.partial({
166+
// ============ Base SDK Core fields ============
73167
/** Transaction in hex format */
74168
txHex: t.string,
75-
/** Transaction in base64 format (for some coins like Solana) */
169+
/** Transaction in base64 format (Solana, Stellar, etc.) */
76170
txBase64: t.string,
77-
/** Transaction info with unspents (for UTXO coins) - coin-specific structure, varies by coin type */
171+
/** Transaction info with unspents (UTXO coins) - coin-specific structure */
78172
txInfo: t.any,
173+
/** Build parameters including recipients (from BaseSignable) */
174+
buildParams: BuildParams,
175+
/** Consolidate ID (from BaseSignable) */
176+
consolidateId: t.string,
177+
/** Transaction request ID for TSS transactions (from BaseSignable) */
178+
txRequestId: t.string,
179+
180+
// ============ Universal fields ============
79181
/** Wallet ID for the transaction */
80182
walletId: t.string,
81-
/** Transaction request ID (for TSS transactions) */
82-
txRequestId: t.string,
83-
/** Consolidate ID */
84-
consolidateId: t.string,
85-
/** Next contract sequence ID (for ETH) */
183+
/** Transaction expiration time */
184+
expireTime: t.number,
185+
/** Half-signed transaction data */
186+
halfSigned: HalfSignedData,
187+
/** Payload string */
188+
payload: t.string,
189+
190+
// ============ EVM-specific fields ============
191+
/** Coin identifier (EVM - required in EVM interface) */
192+
coin: t.string,
193+
/** Token identifier (EVM - optional in EVM interface) */
194+
token: t.string,
195+
/** Next contract sequence ID (EVM) */
86196
nextContractSequenceId: t.number,
87-
/** Whether this is a batch transaction (for ETH) */
197+
/** Whether this is a batch transaction (EVM) */
88198
isBatch: t.boolean,
89-
/** EIP1559 transaction parameters (for ETH) */
199+
/** EIP1559 transaction parameters (EVM) */
90200
eip1559: EIP1559,
91-
/** Hop transaction data (for ETH) */
92-
hopTransaction: HopTransaction,
93-
/** Backup key nonce (for ETH) */
201+
/** Replay protection options (EVM) */
202+
replayProtectionOptions: ReplayProtectionOptions,
203+
/** Hop transaction data (EVM) - can be string (in SignFinalOptions) or HopPrebuild object (in TransactionPrebuild) */
204+
hopTransaction: t.union([t.string, HopTransaction]),
205+
/** Backup key nonce (EVM) */
94206
backupKeyNonce: t.union([t.number, t.string]),
95207
/** Recipients of the transaction */
96208
recipients: t.array(Recipient),
97-
/** Gas limit (for EVM chains) */
209+
/** Gas limit (EVM chains) */
98210
gasLimit: t.union([t.string, t.number]),
99-
/** Gas price (for EVM chains) */
211+
/** Gas price (EVM chains) */
100212
gasPrice: t.union([t.string, t.number]),
101-
/** Transaction expiration time */
102-
expireTime: t.number,
103-
/** Half-signed transaction data */
104-
halfSigned: HalfSignedData,
105-
/** Payload string */
106-
payload: t.string,
213+
214+
// ============ UTXO-specific fields ============
215+
/** Block height (UTXO coins) */
216+
blockHeight: t.number,
217+
218+
// ============ Account-based coin specific fields ============
219+
/** Address information for derivation (Tron, Tezos) - USED in Tron signTransaction */
220+
addressInfo: AddressInfo,
221+
/** Source address (Solana, Tezos, Hedera, Flare) */
222+
source: t.string,
223+
/** Fee information (Tron, Tezos, Hedera) - coin-specific structure */
224+
feeInfo: t.any,
225+
/** Data to sign (Tezos) */
226+
dataToSign: t.string,
227+
/** Keys array (Algorand) */
228+
keys: t.array(t.string),
229+
/** Address version (Algorand) */
230+
addressVersion: t.number,
231+
232+
// ============ Near-specific fields ============
233+
/** Key for Near transactions */
234+
key: t.string,
235+
/** Block hash for Near transactions */
236+
blockHash: t.string,
237+
/** Nonce for Near transactions (bigint in SDK, but JSON uses number/string) */
238+
nonce: t.any,
239+
240+
// ============ Polkadot-specific fields ============
241+
/** Transaction data for Polkadot */
242+
transaction: t.any,
107243
});
108244

109245
/**
@@ -136,6 +272,10 @@ export const CoinSignTxBody = {
136272
isEvmBasedCrossChainRecovery: optional(t.boolean),
137273
/** Wallet version (for EVM) */
138274
walletVersion: optional(t.number),
275+
/** Signing key nonce for EVM final signing */
276+
signingKeyNonce: optional(t.number),
277+
/** Wallet contract address for EVM final signing */
278+
walletContractAddress: optional(t.string),
139279

140280
// UTXO-specific fields
141281
/** Public keys for multi-signature transactions (xpub triple: user, backup, bitgo) */
@@ -146,6 +286,10 @@ export const CoinSignTxBody = {
146286
signingStep: optional(t.union([t.literal('signerNonce'), t.literal('signerSignature'), t.literal('cosignerNonce')])),
147287
/** Allow non-segwit signing without previous transaction (deprecated) */
148288
allowNonSegwitSigningWithoutPrevTx: optional(t.boolean),
289+
290+
// Solana-specific fields
291+
/** Public keys for Solana transactions */
292+
pubKeys: optional(t.array(t.string)),
149293
} as const;
150294

151295
/**
@@ -193,8 +337,8 @@ export const HalfSignedAccountTransactionResponse = t.type({
193337
sequenceId: t.number,
194338
/** EIP1559 parameters (EVM) */
195339
eip1559: EIP1559,
196-
/** Hop transaction data (EVM) */
197-
hopTransaction: HopTransaction,
340+
/** Hop transaction data (EVM) - can be string or object */
341+
hopTransaction: t.union([t.string, HopTransaction]),
198342
/** Custodian transaction ID (EVM) */
199343
custodianTransactionId: t.string,
200344
/** Whether this is a batch transaction (EVM) */

0 commit comments

Comments
 (0)