Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ export async function handleV2GenerateShareTSS(req: express.Request): Promise<an
}
}

export async function handleV2SignTSSWalletTx(req: express.Request) {
export async function handleV2SignTSSWalletTx(req: ExpressApiRouteRequest<'express.v2.wallet.signtxtss', 'post'>) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const wallet = await coin.wallets().get({ id: req.params.id });
Expand Down Expand Up @@ -1634,12 +1634,7 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
// sign transaction
router.post('express.v2.coin.signtx', [prepareBitGo(config), typedPromiseWrapper(handleV2SignTx)]);
router.post('express.v2.wallet.signtx', [prepareBitGo(config), typedPromiseWrapper(handleV2SignTxWallet)]);
app.post(
'/api/v2/:coin/wallet/:id/signtxtss',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2SignTSSWalletTx)
);
router.post('express.v2.wallet.signtxtss', [prepareBitGo(config), typedPromiseWrapper(handleV2SignTSSWalletTx)]);
router.post('express.v2.wallet.recovertoken', [prepareBitGo(config), typedPromiseWrapper(handleV2RecoverToken)]);

// send transaction
Expand Down
4 changes: 4 additions & 0 deletions modules/express/src/typedRoutes/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { PostOfcSignPayload } from './v2/ofcSignPayload';
import { PostWalletRecoverToken } from './v2/walletRecoverToken';
import { PostCoinSignTx } from './v2/coinSignTx';
import { PostWalletSignTx } from './v2/walletSignTx';
import { PostWalletTxSignTSS } from './v2/walletTxSignTSS';

export const ExpressApi = apiSpec({
'express.ping': {
Expand Down Expand Up @@ -108,6 +109,9 @@ export const ExpressApi = apiSpec({
'express.v2.wallet.signtx': {
post: PostWalletSignTx,
},
'express.v2.wallet.signtxtss': {
post: PostWalletTxSignTSS,
},
});

export type ExpressApi = typeof ExpressApi;
Expand Down
170 changes: 170 additions & 0 deletions modules/express/src/typedRoutes/api/v2/walletTxSignTSS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import * as t from 'io-ts';
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
import { TransactionRequest as TxRequestResponse, TransactionRequestApiVersion } from '@bitgo/public-types';
import { BitgoExpressError } from '../../schemas/error';

/**
* Request path parameters for signing a TSS wallet transaction
*/
export const WalletTxSignTSSParams = {
/** The coin type */
coin: t.string,
/** The wallet ID */
id: t.string,
} as const;

/**
* Transaction prebuild information for TSS wallet signing
*/
export const WalletTxSignTSSTransactionPrebuild = t.partial({
/** Transaction in hex format */
txHex: t.string,
/** Transaction in base64 format (for some coins) */
txBase64: t.string,
/** Transaction in JSON format (for some coins) */
txInfo: t.any,
/** Wallet ID for the transaction */
walletId: t.string,
/** Transaction request ID for TSS wallets */
txRequestId: t.string,
/** Next contract sequence ID (for ETH) */
nextContractSequenceId: t.number,
/** Whether this is a batch transaction (for ETH) */
isBatch: t.boolean,
/** EIP1559 transaction parameters (for ETH) */
eip1559: t.any,
/** Hop transaction data (for ETH) */
hopTransaction: t.any,
/** Backup key nonce (for ETH) */
backupKeyNonce: t.any,
/** Recipients of the transaction */
recipients: t.any,
});

/**
* Request body for signing a TSS wallet transaction
*/
export const WalletTxSignTSSBody = {
/** Transaction prebuild data */
txPrebuild: optional(WalletTxSignTSSTransactionPrebuild),
/** Transaction request ID for TSS wallets */
txRequestId: optional(t.string),
/** Wallet passphrase for TSS wallets */
walletPassphrase: optional(t.string),
/** Public keys for multi-signature transactions */
pubs: optional(t.array(t.string)),
/** Private key for signing (for non-TSS wallets, rarely used with TSS) */
prv: optional(t.string),
/** Cosigner public key */
cosignerPub: optional(t.string),
/** Whether this is the last signature in a multi-sig tx */
isLastSignature: optional(t.boolean),
/** API version: 'lite' or 'full' */
apiVersion: optional(TransactionRequestApiVersion),
/** Multisig type version */
multisigTypeVersion: optional(t.literal('MPCv2')),
/** Gas limit for ETH transactions */
gasLimit: optional(t.union([t.string, t.number])),
/** Gas price for ETH transactions */
gasPrice: optional(t.union([t.string, t.number])),
/** Transaction expiration time */
expireTime: optional(t.number),
/** Sequence ID for transactions */
sequenceId: optional(t.union([t.string, t.number])),
/** Recipients of the transaction */
recipients: optional(t.any),
/** Custodian transaction ID */
custodianTransactionId: optional(t.string),
/** Signing step for MuSig2 */
signingStep: optional(t.union([t.literal('signerNonce'), t.literal('signerSignature'), t.literal('cosignerNonce')])),
/** Allow non-segwit signing without previous transaction */
allowNonSegwitSigningWithoutPrevTx: optional(t.boolean),
/** For EVM cross-chain recovery */
isEvmBasedCrossChainRecovery: optional(t.boolean),
/** Derivation seed for key derivation */
derivationSeed: optional(t.string),
} as const;

/**
* Response for a fully signed transaction
*/
export const FullySignedTransactionResponse = t.type({
/** Transaction in hex format */
txHex: t.string,
});

/**
* Response for a half-signed account transaction
*/
export const HalfSignedAccountTransactionResponse = t.type({
halfSigned: t.partial({
txHex: optional(t.string),
payload: optional(t.string),
txBase64: optional(t.string),
}),
});

/**
* Response for a half-signed UTXO transaction
*/
export const HalfSignedUtxoTransactionResponse = t.type({
txHex: t.string,
});

/**
* Response for a transaction request
*/
export const SignedTransactionRequestResponse = t.type({
txRequestId: t.string,
});

/**
* Response for signing a TSS wallet transaction
*
* Uses TxRequestResponse (TransactionRequest) from @bitgo/public-types for TSS transaction requests
* (supports both Lite and Full versions)
*/
export const WalletTxSignTSSResponse = {
/** Successfully signed transaction */
200: t.union([
FullySignedTransactionResponse,
HalfSignedAccountTransactionResponse,
HalfSignedUtxoTransactionResponse,
SignedTransactionRequestResponse,
TxRequestResponse,
]),
/** Error response */
400: BitgoExpressError,
};

/**
* Sign a transaction for a specific TSS wallet
*
* This endpoint signs a transaction for a specific TSS wallet identified by coin type and wallet ID.
* The request body is passed to wallet.signTransaction() and varies by coin and wallet type.
*
* Common fields for TSS wallets include:
* - txPrebuild: Contains transaction data like txHex, txBase64, or txRequestId
* - walletPassphrase: Passphrase for TSS wallets (required for TSS signing)
* - txRequestId: Transaction request ID for TSS wallets (can be in body or txPrebuild)
* - apiVersion: 'lite' or 'full' for TSS transaction requests
* - multisigTypeVersion: 'MPCv2' for MPCv2 wallets
* - isLastSignature: Whether this is the last signature in a multi-sig tx
* - pubs: Public keys for multi-signature transactions
* - gasLimit: Gas limit for ETH transactions
* - gasPrice: Gas price for ETH transactions
* - expireTime: Transaction expiration time
* - sequenceId: Sequence ID for transactions
* - isEvmBasedCrossChainRecovery: For EVM cross-chain recovery
*
* @operationId express.v2.wallet.signtxtss
*/
export const PostWalletTxSignTSS = httpRoute({
path: '/api/v2/:coin/wallet/:id/signtxtss',
method: 'POST',
request: httpRequest({
params: WalletTxSignTSSParams,
body: WalletTxSignTSSBody,
}),
response: WalletTxSignTSSResponse,
});
Loading