@@ -2,6 +2,14 @@ import * as t from 'io-ts';
22import { httpRoute , httpRequest , optional } from '@api-ts/io-ts-http' ;
33import { TransactionRequest as TxRequestResponse } from '@bitgo/public-types' ;
44import { BitgoExpressError } from '../../schemas/error' ;
5+ import {
6+ TransactionPrebuild ,
7+ Recipient ,
8+ FullySignedTransactionResponse ,
9+ HalfSignedAccountTransactionResponse ,
10+ HalfSignedUtxoTransactionResponse ,
11+ SignedTransactionRequestResponse ,
12+ } from './coinSignTx' ;
513
614/**
715 * Request parameters for signing a transaction (external signer mode)
@@ -13,33 +21,21 @@ export const CoinSignParams = {
1321
1422/**
1523 * Transaction prebuild information for external signing
16- * Requires walletId to retrieve encrypted private key from filesystem
24+ *
25+ * Same as TransactionPrebuild from coinSignTx, but with walletId as REQUIRED field.
26+ * The walletId is required for retrieving the encrypted private key from the filesystem.
27+ *
28+ * This is enforced by the handler at runtime (clientRoutes.ts:513-517).
29+ *
30+ * Reference: modules/express/src/typedRoutes/api/v2/coinSignTx.ts:102-191 (TransactionPrebuild)
31+ * Handler validation: modules/express/src/clientRoutes.ts:513-517 (handleV2Sign)
1732 */
1833export const TransactionPrebuildForExternalSigning = t . intersection ( [
1934 t . type ( {
20- /** Wallet ID - required for retrieving encrypted private key */
35+ /** Wallet ID - REQUIRED for retrieving encrypted private key from filesystem */
2136 walletId : t . string ,
2237 } ) ,
23- t . partial ( {
24- /** Transaction in hex format */
25- txHex : t . string ,
26- /** Transaction in base64 format (for some coins) */
27- txBase64 : t . string ,
28- /** Transaction in JSON format (for some coins) */
29- txInfo : t . any ,
30- /** Next contract sequence ID (for ETH) */
31- nextContractSequenceId : t . number ,
32- /** Whether this is a batch transaction (for ETH) */
33- isBatch : t . boolean ,
34- /** EIP1559 transaction parameters (for ETH) */
35- eip1559 : t . any ,
36- /** Hop transaction data (for ETH) */
37- hopTransaction : t . any ,
38- /** Backup key nonce (for ETH) */
39- backupKeyNonce : t . any ,
40- /** Recipients of the transaction */
41- recipients : t . any ,
42- } ) ,
38+ TransactionPrebuild ,
4339] ) ;
4440
4541/**
@@ -48,17 +44,31 @@ export const TransactionPrebuildForExternalSigning = t.intersection([
4844 * This route is used when BitGo Express is configured with external signing.
4945 * The private key is retrieved from the filesystem and decrypted using
5046 * a wallet passphrase stored in the environment variable WALLET_{walletId}_PASSPHRASE.
47+ *
48+ * Fields are similar to CoinSignTxBody except:
49+ * - NO `prv` field (added automatically by handler from filesystem)
50+ * - HAS `derivationSeed` field (unique to external signing)
51+ * - `txPrebuild` has required `walletId` field
52+ *
53+ * Reference: modules/express/src/typedRoutes/api/v2/coinSignTx.ts:250-293 (CoinSignTxBody)
54+ * Handler: modules/express/src/clientRoutes.ts:512-539 (handleV2Sign)
5155 */
5256export const CoinSignBody = {
53- /** Transaction prebuild data - must contain walletId */
57+ /** Transaction prebuild data - must contain walletId (REQUIRED) */
5458 txPrebuild : TransactionPrebuildForExternalSigning ,
59+
5560 /**
5661 * Derivation seed for deriving a child key from the main private key.
5762 * If provided, the key will be derived using coin.deriveKeyWithSeed()
63+ * UNIQUE TO EXTERNAL SIGNING - not present in CoinSignTxBody
5864 */
5965 derivationSeed : optional ( t . string ) ,
66+
67+ // ============ Universal fields ============
6068 /** Whether this is the last signature in a multi-sig tx */
6169 isLastSignature : optional ( t . boolean ) ,
70+
71+ // ============ EVM-specific fields ============
6272 /** Gas limit for ETH transactions */
6373 gasLimit : optional ( t . union ( [ t . string , t . number ] ) ) ,
6474 /** Gas price for ETH transactions */
@@ -67,52 +77,46 @@ export const CoinSignBody = {
6777 expireTime : optional ( t . number ) ,
6878 /** Sequence ID for transactions */
6979 sequenceId : optional ( t . number ) ,
70- /** Public keys for multi-signature transactions */
71- pubKeys : optional ( t . array ( t . string ) ) ,
72- /** For EVM cross-chain recovery */
73- isEvmBasedCrossChainRecovery : optional ( t . boolean ) ,
7480 /** Recipients of the transaction */
75- recipients : optional ( t . any ) ,
81+ recipients : optional ( t . array ( Recipient ) ) ,
7682 /** Custodian transaction ID */
7783 custodianTransactionId : optional ( t . string ) ,
84+ /** For EVM cross-chain recovery */
85+ isEvmBasedCrossChainRecovery : optional ( t . boolean ) ,
86+ /** Wallet version (for EVM) */
87+ walletVersion : optional ( t . number ) ,
88+ /** Signing key nonce for EVM final signing */
89+ signingKeyNonce : optional ( t . number ) ,
90+ /** Wallet contract address for EVM final signing */
91+ walletContractAddress : optional ( t . string ) ,
92+
93+ // ============ UTXO-specific fields ============
94+ /** Public keys for multi-signature transactions (xpub triple: user, backup, bitgo) */
95+ pubs : optional ( t . array ( t . string ) ) ,
96+ /** Cosigner public key (defaults to bitgo) */
97+ cosignerPub : optional ( t . string ) ,
7898 /** Signing step for MuSig2 */
7999 signingStep : optional ( t . union ( [ t . literal ( 'signerNonce' ) , t . literal ( 'signerSignature' ) , t . literal ( 'cosignerNonce' ) ] ) ) ,
80- /** Allow non-segwit signing without previous transaction */
100+ /** Allow non-segwit signing without previous transaction (deprecated) */
81101 allowNonSegwitSigningWithoutPrevTx : optional ( t . boolean ) ,
82- } as const ;
83102
84- /**
85- * Response for a fully signed transaction
86- */
87- export const FullySignedTransactionResponse = t . type ( {
88- /** Transaction in hex format */
89- txHex : t . string ,
90- } ) ;
91-
92- /**
93- * Response for a half-signed account transaction
94- */
95- export const HalfSignedAccountTransactionResponse = t . partial ( {
96- halfSigned : t . partial ( {
97- txHex : t . string ,
98- payload : t . string ,
99- txBase64 : t . string ,
100- } ) ,
101- } ) ;
102-
103- /**
104- * Response for a half-signed UTXO transaction
105- */
106- export const HalfSignedUtxoTransactionResponse = t . type ( {
107- txHex : t . string ,
108- } ) ;
103+ // ============ Solana-specific fields ============
104+ /** Public keys for Solana transactions */
105+ pubKeys : optional ( t . array ( t . string ) ) ,
106+ } as const ;
109107
110108/**
111- * Response for a transaction request
109+ * Response codecs are imported from coinSignTx.ts since both endpoints call the same
110+ * coin.signTransaction() method and return identical response formats:
111+ *
112+ * - FullySignedTransactionResponse: For fully signed transactions (all signatures collected)
113+ * - HalfSignedAccountTransactionResponse: For half-signed account-based transactions (EVM, Algorand, etc.)
114+ * - HalfSignedUtxoTransactionResponse: For half-signed UTXO transactions (BTC, LTC, etc.)
115+ * - SignedTransactionRequestResponse: For TSS transaction requests
116+ * - TxRequestResponse: For TSS transaction requests (from @bitgo/public-types)
117+ *
118+ * Reference: modules/express/src/typedRoutes/api/v2/coinSignTx.ts:267-418 (Response codecs)
112119 */
113- export const SignedTransactionRequestResponse = t . type ( {
114- txRequestId : t . string ,
115- } ) ;
116120
117121/**
118122 * Response for signing a transaction in external signer mode
0 commit comments