PHP WebShell
Текущая директория: /opt/BitGoJS/modules/express/src
Просмотр файла: clientRoutes.ts
/**
* @prettier
*/
import {
CommitmentShareRecord,
CreateNetworkConnectionParams,
CustomCommitmentGeneratingFunction,
CustomGShareGeneratingFunction,
CustomKShareGeneratingFunction,
CustomMPCv2SigningRound1GeneratingFunction,
CustomMPCv2SigningRound2GeneratingFunction,
CustomMPCv2SigningRound3GeneratingFunction,
CustomMuDeltaShareGeneratingFunction,
CustomPaillierModulusGetterFunction,
CustomRShareGeneratingFunction,
CustomSShareGeneratingFunction,
EcdsaMPCv2Utils,
EcdsaUtils,
EddsaUtils,
EncryptedSignerShareRecord,
encryptRsaWithAesGcm,
GetNetworkPartnersResponse,
GShare,
MPCType,
ShareType,
SignShare,
SShare,
TssEcdsaStep1ReturnMessage,
TssEcdsaStep2ReturnMessage,
UnsupportedCoinError,
Wallet,
} from '@bitgo/sdk-core';
import { BitGo, BitGoOptions, Coin, CustomSigningFunction, SignedTransaction, SignedTransactionRequest } from 'bitgo';
import * as bodyParser from 'body-parser';
import * as debugLib from 'debug';
import * as express from 'express';
import type { ParamsDictionary } from 'express-serve-static-core';
import * as _ from 'lodash';
import * as url from 'url';
import * as superagent from 'superagent';
// RequestTracer should be extracted into a separate npm package (along with
// the rest of the BitGoJS HTTP request machinery)
import { RequestTracer } from 'bitgo/dist/src/v2/internal/util';
import { Config } from './config';
import { ApiResponseError } from './errors';
import { promises as fs } from 'fs';
import { retryPromise } from './retryPromise';
import {
handleCreateSignerMacaroon,
handleGetLightningWalletState,
handleInitLightningWallet,
handleUnlockLightningWallet,
} from './lightning/lightningSignerRoutes';
import { handlePayLightningInvoice } from './lightning/lightningInvoiceRoutes';
import { handleUpdateLightningWalletCoinSpecific } from './lightning/lightningWalletRoutes';
import { ProxyAgent } from 'proxy-agent';
import { isLightningCoinName } from '@bitgo/abstract-lightning';
import { handleLightningWithdraw } from './lightning/lightningWithdrawRoutes';
const { version } = require('bitgo/package.json');
const pjson = require('../package.json');
const debug = debugLib('bitgo:express');
const BITGOEXPRESS_USER_AGENT = `BitGoExpress/${pjson.version} BitGoJS/${version}`;
function handlePing(req: express.Request, res: express.Response, next: express.NextFunction) {
return req.bitgo.ping();
}
function handlePingExpress(req: express.Request) {
return {
status: 'express server is ok!',
};
}
function handleLogin(req: express.Request) {
const username = req.body.username || req.body.email;
const body = req.body;
body.username = username;
return req.bitgo.authenticate(body);
}
function handleDecrypt(req: express.Request) {
return {
decrypted: req.bitgo.decrypt(req.body),
};
}
function handleEncrypt(req: express.Request) {
return {
encrypted: req.bitgo.encrypt(req.body),
};
}
/**
* @deprecated
* @param req
*/
function handleVerifyAddress(req: express.Request) {
return {
verified: req.bitgo.verifyAddress(req.body),
};
}
/**
* @deprecated
* @param req
*/
function handleCreateLocalKeyChain(req: express.Request) {
return req.bitgo.keychains().create(req.body);
}
/**
* @deprecated
* @param req
*/
function handleDeriveLocalKeyChain(req: express.Request) {
return req.bitgo.keychains().deriveLocal(req.body);
}
/**
* @deprecated
* @param req
*/
function handleCreateWalletWithKeychains(req: express.Request) {
return req.bitgo.wallets().createWalletWithKeychains(req.body);
}
/**
* @deprecated
* @param req
*/
function handleSendCoins(req: express.Request) {
return req.bitgo
.wallets()
.get({ id: req.params.id })
.then(function (wallet) {
return wallet.sendCoins(req.body);
})
.catch(function (err) {
err.status = 400;
throw err;
})
.then(function (result) {
if (result.status === 'pendingApproval') {
throw apiResponse(202, result);
}
return result;
});
}
/**
* @deprecated
* @param req
*/
function handleSendMany(req: express.Request) {
return req.bitgo
.wallets()
.get({ id: req.params.id })
.then(function (wallet) {
return wallet.sendMany(req.body);
})
.catch(function (err) {
err.status = 400;
throw err;
})
.then(function (result) {
if (result.status === 'pendingApproval') {
throw apiResponse(202, result);
}
return result;
});
}
/**
* @deprecated
* @param req
*/
function handleCreateTransaction(req: express.Request) {
return req.bitgo
.wallets()
.get({ id: req.params.id })
.then(function (wallet) {
return wallet.createTransaction(req.body);
})
.catch(function (err) {
err.status = 400;
throw err;
});
}
/**
* @deprecated
* @param req
*/
function handleSignTransaction(req: express.Request) {
return req.bitgo
.wallets()
.get({ id: req.params.id })
.then(function (wallet) {
return wallet.signTransaction(req.body);
});
}
/**
* @deprecated
* @param req
*/
function handleShareWallet(req: express.Request) {
return req.bitgo
.wallets()
.get({ id: req.params.id })
.then(function (wallet) {
return wallet.shareWallet(req.body);
});
}
/**
* @deprecated
* @param req
*/
function handleAcceptShare(req: express.Request) {
const params = req.body || {};
params.walletShareId = req.params.shareId;
return req.bitgo.wallets().acceptShare(params);
}
/**
* @deprecated
* @param req
*/
function handleApproveTransaction(req: express.Request) {
const params = req.body || {};
return req.bitgo
.pendingApprovals()
.get({ id: req.params.id })
.then(function (pendingApproval) {
if (params.state === 'approved') {
return pendingApproval.approve(params);
}
return pendingApproval.reject(params);
});
}
/**
* @deprecated
* @param req
*/
function handleConstructApprovalTx(req: express.Request) {
const params = req.body || {};
return req.bitgo
.pendingApprovals()
.get({ id: req.params.id })
.then(function (pendingApproval) {
return pendingApproval.constructApprovalTx(params);
});
}
/**
* @deprecated
* @param req
*/
function handleConsolidateUnspents(req: express.Request) {
return req.bitgo
.wallets()
.get({ id: req.params.id })
.then(function (wallet) {
return wallet.consolidateUnspents(req.body);
});
}
/**
* @deprecated
* @param req
*/
function handleFanOutUnspents(req: express.Request) {
return req.bitgo
.wallets()
.get({ id: req.params.id })
.then(function (wallet) {
return wallet.fanOutUnspents(req.body);
});
}
/**
* @deprecated
* @param req
*/
function handleCalculateMinerFeeInfo(req: express.Request) {
return req.bitgo.calculateMinerFeeInfo({
bitgo: req.bitgo,
feeRate: req.body.feeRate,
nP2shInputs: req.body.nP2shInputs,
nP2pkhInputs: req.body.nP2pkhInputs,
nP2shP2wshInputs: req.body.nP2shP2wshInputs,
nOutputs: req.body.nOutputs,
});
}
/**
* Builds the API's URL string, optionally building the querystring if parameters exist
* @param req
* @return {string}
*/
function createAPIPath(req: express.Request) {
let apiPath = '/' + req.params[0];
if (!_.isEmpty(req.query)) {
// req.params does not contain the querystring, so we manually add them here
const urlDetails = url.parse(req.url);
if (urlDetails.search) {
// "search" is the properly URL encoded query params, prefixed with "?"
apiPath += urlDetails.search;
}
}
return apiPath;
}
/**
* handle any other V1 API call
* @deprecated
* @param req
* @param res
* @param next
*/
function handleREST(req: express.Request, res: express.Response, next: express.NextFunction) {
const method = req.method;
const bitgo = req.bitgo;
const bitgoURL = bitgo.url(createAPIPath(req));
return redirectRequest(bitgo, method, bitgoURL, req, next);
}
/**
* handle any other V2 API call
* @param req
* @param res
* @param next
*/
function handleV2UserREST(req: express.Request, res: express.Response, next: express.NextFunction) {
const method = req.method;
const bitgo = req.bitgo;
const bitgoURL = bitgo.url('/user' + createAPIPath(req), 2);
return redirectRequest(bitgo, method, bitgoURL, req, next);
}
/**
* handle v2 address validation
* @param req
*/
function handleV2VerifyAddress(req: express.Request): { isValid: boolean } {
if (!_.isString(req.body.address)) {
throw new Error('Expected address to be a string');
}
if (req.body.supportOldScriptHashVersion !== undefined && !_.isBoolean(req.body.supportOldScriptHashVersion)) {
throw new Error('Expected supportOldScriptHashVersion to be a boolean.');
}
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
if (coin instanceof Coin.AbstractUtxoCoin) {
return {
isValid: coin.isValidAddress(req.body.address, !!req.body.supportOldScriptHashVersion),
};
}
return {
isValid: coin.isValidAddress(req.body.address),
};
}
/**
* handle address canonicalization
* @param req
*/
function handleCanonicalAddress(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
if (!['ltc', 'bch', 'bsv'].includes(coin.getFamily())) {
throw new Error('only Litecoin/Bitcoin Cash/Bitcoin SV address canonicalization is supported');
}
const address = req.body.address;
const fallbackVersion = req.body.scriptHashVersion; // deprecate
const version = req.body.version;
return (coin as Coin.Bch | Coin.Bsv | Coin.Ltc).canonicalAddress(address, version || fallbackVersion);
}
function getWalletPwFromEnv(walletId: string): string {
const name = `WALLET_${walletId}_PASSPHRASE`;
const walletPw = process.env[name];
if (walletPw === undefined) {
throw new Error(`Could not find wallet passphrase ${name} in environment`);
}
return walletPw;
}
async function getEncryptedPrivKey(path: string, walletId: string): Promise<string> {
const privKeyFile = await fs.readFile(path, { encoding: 'utf8' });
const encryptedPrivKey = JSON.parse(privKeyFile);
if (encryptedPrivKey[walletId] === undefined) {
throw new Error(`Could not find a field for walletId: ${walletId} in ${path}`);
}
return encryptedPrivKey[walletId];
}
function decryptPrivKey(bg: BitGo, encryptedPrivKey: string, walletPw: string): string {
try {
return bg.decrypt({ password: walletPw, input: encryptedPrivKey });
} catch (e) {
throw new Error(`Error when trying to decrypt private key: ${e}`);
}
}
export async function handleV2GenerateShareTSS(req: express.Request): Promise<any> {
const walletId = req.body.txRequest ? req.body.txRequest.walletId : req.body.tssParams.txRequest.walletId;
if (!walletId) {
throw new Error('Missing required field: walletId');
}
const walletPw = getWalletPwFromEnv(walletId);
const { signerFileSystemPath } = req.config;
if (!signerFileSystemPath) {
throw new Error('Missing required configuration: signerFileSystemPath');
}
const encryptedPrivKey = await getEncryptedPrivKey(signerFileSystemPath, walletId);
const bitgo = req.bitgo;
const privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
const coin = bitgo.coin(req.params.coin);
req.body.prv = privKey;
req.body.walletPassphrase = walletPw;
try {
if (coin.getMPCAlgorithm() === MPCType.EDDSA) {
const eddsaUtils = new EddsaUtils(bitgo, coin);
switch (req.params.sharetype) {
case ShareType.Commitment:
return await eddsaUtils.createCommitmentShareFromTxRequest(req.body);
case ShareType.R:
return await eddsaUtils.createRShareFromTxRequest(req.body);
case ShareType.G:
return await eddsaUtils.createGShareFromTxRequest(req.body);
default:
throw new Error(
`Share type ${req.params.sharetype} not supported, only commitment, G and R share generation is supported.`
);
}
} else if (coin.getMPCAlgorithm() === MPCType.ECDSA) {
const isMPCv2 = [
ShareType.MPCv2Round1.toString(),
ShareType.MPCv2Round2.toString(),
ShareType.MPCv2Round3.toString(),
].includes(req.params.sharetype);
if (isMPCv2) {
const ecdsaMPCv2Utils = new EcdsaMPCv2Utils(bitgo, coin);
switch (req.params.sharetype) {
case ShareType.MPCv2Round1:
return await ecdsaMPCv2Utils.createOfflineRound1Share(req.body);
case ShareType.MPCv2Round2:
return await ecdsaMPCv2Utils.createOfflineRound2Share(req.body);
case ShareType.MPCv2Round3:
return await ecdsaMPCv2Utils.createOfflineRound3Share(req.body);
default:
throw new Error(
`Share type ${req.params.sharetype} not supported for MPCv2, only MPCv2Round1, MPCv2Round2 and MPCv2Round3 is supported.`
);
}
} else {
const ecdsaUtils = new EcdsaUtils(bitgo, coin);
switch (req.params.sharetype) {
case ShareType.PaillierModulus:
return ecdsaUtils.getOfflineSignerPaillierModulus(req.body);
case ShareType.K:
return await ecdsaUtils.createOfflineKShare(req.body);
case ShareType.MuDelta:
return await ecdsaUtils.createOfflineMuDeltaShare(req.body);
case ShareType.S:
return await ecdsaUtils.createOfflineSShare(req.body);
default:
throw new Error(
`Share type ${req.params.sharetype} not supported, only PaillierModulus, K, MUDelta, and S share generation is supported.`
);
}
}
} else {
throw new Error(`MPC Algorithm ${coin.getMPCAlgorithm()} is not supported.`);
}
} catch (error) {
console.error('error while signing wallet transaction ', error);
throw error;
}
}
export async function handleV2SignTSSWalletTx(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const wallet = await coin.wallets().get({ id: req.params.id });
try {
return await wallet.signTransaction(createTSSSendParams(req, wallet));
} catch (error) {
console.error('error while signing wallet transaction ', error);
throw error;
}
}
/**
* This route is used to sign while external express signer is enabled
*/
export async function handleV2Sign(req: express.Request) {
const walletId = req.body.txPrebuild?.walletId;
if (!walletId) {
throw new Error('Missing required field: walletId');
}
const walletPw = getWalletPwFromEnv(walletId);
const { signerFileSystemPath } = req.config;
if (!signerFileSystemPath) {
throw new Error('Missing required configuration: signerFileSystemPath');
}
const encryptedPrivKey = await getEncryptedPrivKey(signerFileSystemPath, walletId);
const bitgo = req.bitgo;
let privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
const coin = bitgo.coin(req.params.coin);
if (req.body.derivationSeed) {
privKey = coin.deriveKeyWithSeed({ key: privKey, seed: req.body.derivationSeed }).key;
}
try {
return await coin.signTransaction({ ...req.body, prv: privKey });
} catch (error) {
console.log('error while signing wallet transaction ', error);
throw error;
}
}
export async function handleV2OFCSignPayloadInExtSigningMode(
req: express.Request
): Promise<{ payload: string; signature: string }> {
const walletId = req.body.walletId;
const payload = req.body.payload;
const bodyWalletPassphrase = req.body.walletPassphrase;
const ofcCoinName = 'ofc';
if (!payload) {
throw new ApiResponseError('Missing required field: payload', 400);
}
if (!walletId) {
throw new ApiResponseError('Missing required field: walletId', 400);
}
// fetch the password for the given walletId from the body or the env. This is required for decrypting the private key that belongs to that wallet.
const walletPw = bodyWalletPassphrase || getWalletPwFromEnv(walletId);
const { signerFileSystemPath } = req.config;
if (!signerFileSystemPath) {
throw new ApiResponseError('Missing required configuration: signerFileSystemPath', 500);
}
// get the encrypted private key from the local JSON file (encryptedPrivKeys.json) (populated using fetchEncryptedPrivateKeys.ts)
const encryptedPrivKey = await getEncryptedPrivKey(signerFileSystemPath, walletId);
const bitgo = req.bitgo;
// decrypt the encrypted private key using the wallet pwd
const privKey = decryptPrivKey(bitgo, encryptedPrivKey, walletPw);
// create a BaseCoin instance for 'ofc'
const coin = bitgo.coin(ofcCoinName);
// stringify the payload if not already a string
const stringifiedPayload = typeof payload === 'string' ? payload : JSON.stringify(payload);
try {
// sign the message using the decrypted private key
const signature = (await coin.signMessage({ prv: privKey }, stringifiedPayload)).toString('hex');
return {
payload: stringifiedPayload,
signature,
};
} catch (error) {
console.log('Error while signing message.', error);
throw error;
}
}
export async function handleV2OFCSignPayload(req: express.Request): Promise<{ payload: string; signature: string }> {
const walletId = req.body.walletId;
const payload = req.body.payload;
const bodyWalletPassphrase = req.body.walletPassphrase;
const ofcCoinName = 'ofc';
// If the externalSignerUrl is set, forward the request to the express server hosted on the externalSignerUrl
const externalSignerUrl = req.config?.externalSignerUrl;
if (externalSignerUrl) {
const { body: payloadWithSignature } = await retryPromise(
() =>
superagent
.post(`${externalSignerUrl}/api/v2/ofc/signPayload`)
.type('json')
.send({ walletId: walletId, payload: payload }),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return payloadWithSignature;
}
if (!payload) {
throw new ApiResponseError('Missing required field: payload', 400);
}
if (!walletId) {
throw new ApiResponseError('Missing required field: walletId', 400);
}
const bitgo = req.bitgo;
// This is to set us up for multiple trading accounts per enterprise
const wallet = await bitgo.coin(ofcCoinName).wallets().get({ id: walletId });
if (wallet === undefined) {
throw new ApiResponseError(`Could not find OFC wallet ${walletId}`, 404);
}
const walletPassphrase = bodyWalletPassphrase || getWalletPwFromEnv(wallet.id());
const tradingAccount = wallet.toTradingAccount();
const stringifiedPayload = JSON.stringify(req.body.payload);
const signature = await tradingAccount.signPayload({
payload: stringifiedPayload,
walletPassphrase,
});
return {
payload: stringifiedPayload,
signature,
};
}
/**
* handle new wallet creation
* @param req
*/
export async function handleV2GenerateWallet(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const result = await coin.wallets().generateWallet(req.body);
if (req.query.includeKeychains === 'false') {
return result.wallet.toJSON();
}
return { ...result, wallet: result.wallet.toJSON() };
}
/**
* handle new address creation
* @param req
*/
export async function handleV2CreateAddress(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const wallet = await coin.wallets().get({ id: req.params.id });
return wallet.createAddress(req.body);
}
/**
* handle v2 approve transaction
* @param req
*/
async function handleV2PendingApproval(req: express.Request): Promise<any> {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const params = req.body || {};
const pendingApproval = await coin.pendingApprovals().get({ id: req.params.id });
if (params.state === 'approved') {
return pendingApproval.approve(params);
}
return pendingApproval.reject(params);
}
/**
* create a keychain
* @param req
*/
function handleV2CreateLocalKeyChain(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
return coin.keychains().create(req.body);
}
/**
* handle wallet share
* @param req
*/
async function handleV2ShareWallet(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const wallet = await coin.wallets().get({ id: req.params.id });
return wallet.shareWallet(req.body);
}
/**
* handle accept wallet share
* @param req
*/
async function handleV2AcceptWalletShare(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const params = _.extend({}, req.body, { walletShareId: req.params.id });
return coin.wallets().acceptShare(params);
}
/**
* handle wallet sign transaction
*/
async function handleV2SignTxWallet(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const wallet = await coin.wallets().get({ id: req.params.id });
try {
return await wallet.signTransaction(createSendParams(req));
} catch (error) {
console.log('error while signing wallet transaction ', error);
throw error;
}
}
/**
* handle sign transaction
* @param req
*/
async function handleV2SignTx(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
try {
return await coin.signTransaction(req.body);
} catch (error) {
console.log('error while signing the transaction ', error);
throw error;
}
}
/**
* handle wallet recover token
* @param req
*/
async function handleV2RecoverToken(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const wallet = await coin.wallets().get({ id: req.params.id });
return wallet.recoverToken(req.body);
}
/**
* handle wallet fanout unspents
* @param req
*/
async function handleV2ConsolidateUnspents(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const wallet = await coin.wallets().get({ id: req.params.id });
return wallet.consolidateUnspents(createSendParams(req));
}
/**
* Handle Wallet Account Consolidation.
*
* @param req
*/
export async function handleV2ConsolidateAccount(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
if (req.body.consolidateAddresses && !_.isArray(req.body.consolidateAddresses)) {
throw new Error('consolidate address must be an array of addresses');
}
if (!coin.allowsAccountConsolidations()) {
throw new Error('invalid coin selected');
}
const wallet = await coin.wallets().get({ id: req.params.id });
let result: any;
try {
if (coin.supportsTss()) {
result = await wallet.sendAccountConsolidations(createTSSSendParams(req, wallet));
} else {
result = await wallet.sendAccountConsolidations(createSendParams(req));
}
} catch (err) {
err.status = 400;
throw err;
}
// we had failures to handle
if (result.failure.length && result.failure.length > 0) {
let msg = '';
let status = 202;
if (result.success.length && result.success.length > 0) {
// but we also had successes
msg = `Transactions failed: ${result.failure.length} and succeeded: ${result.success.length}`;
} else {
// or in this case only failures
status = 400;
msg = `All transactions failed`;
}
throw apiResponse(status, result, msg);
}
return result;
}
/**
* handle wallet fanout unspents
* @param req
*/
async function handleV2FanOutUnspents(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const wallet = await coin.wallets().get({ id: req.params.id });
return wallet.fanoutUnspents(createSendParams(req));
}
/**
* handle wallet sweep
* @param req
*/
async function handleV2Sweep(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const wallet = await coin.wallets().get({ id: req.params.id });
return wallet.sweep(createSendParams(req));
}
/**
* handle CPFP accelerate transaction creation
* @param req
*/
async function handleV2AccelerateTransaction(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const wallet = await coin.wallets().get({ id: req.params.id });
return wallet.accelerateTransaction(createSendParams(req));
}
function createSendParams(req: express.Request) {
if (req.config?.externalSignerUrl !== undefined) {
return {
...req.body,
customSigningFunction: createCustomSigningFunction(req.config.externalSignerUrl),
};
} else {
return req.body;
}
}
function createTSSSendParams(req: express.Request, wallet: Wallet) {
if (req.config?.externalSignerUrl !== undefined) {
const coin = req.bitgo.coin(req.params.coin);
if (coin.getMPCAlgorithm() === MPCType.EDDSA) {
return {
...req.body,
customCommitmentGeneratingFunction: createCustomCommitmentGenerator(
req.config.externalSignerUrl,
req.params.coin
),
customRShareGeneratingFunction: createCustomRShareGenerator(req.config.externalSignerUrl, req.params.coin),
customGShareGeneratingFunction: createCustomGShareGenerator(req.config.externalSignerUrl, req.params.coin),
};
} else if (coin.getMPCAlgorithm() === MPCType.ECDSA) {
if (wallet._wallet.multisigTypeVersion === 'MPCv2') {
return {
...req.body,
customMPCv2SigningRound1GenerationFunction: createCustomMPCv2SigningRound1Generator(
req.config.externalSignerUrl,
req.params.coin
),
customMPCv2SigningRound2GenerationFunction: createCustomMPCv2SigningRound2Generator(
req.config.externalSignerUrl,
req.params.coin
),
customMPCv2SigningRound3GenerationFunction: createCustomMPCv2SigningRound3Generator(
req.config.externalSignerUrl,
req.params.coin
),
};
} else {
return {
...req.body,
customPaillierModulusGeneratingFunction: createCustomPaillierModulusGetter(
req.config.externalSignerUrl,
req.params.coin
),
customKShareGeneratingFunction: createCustomKShareGenerator(req.config.externalSignerUrl, req.params.coin),
customMuDeltaShareGeneratingFunction: createCustomMuDeltaShareGenerator(
req.config.externalSignerUrl,
req.params.coin
),
customSShareGeneratingFunction: createCustomSShareGenerator(req.config.externalSignerUrl, req.params.coin),
};
}
} else {
throw new Error(`MPC Algorithm ${coin.getMPCAlgorithm()} is not supported.`);
}
} else {
return req.body;
}
}
/**
* handle send one
* @param req
*/
async function handleV2SendOne(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const reqId = new RequestTracer();
const wallet = await coin.wallets().get({ id: req.params.id, reqId });
req.body.reqId = reqId;
let result;
try {
result = await wallet.send(createSendParams(req));
} catch (err) {
err.status = 400;
throw err;
}
if (result.status === 'pendingApproval') {
throw apiResponse(202, result);
}
return result;
}
/**
* handle send many
* @param req
*/
async function handleV2SendMany(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const reqId = new RequestTracer();
const wallet = await coin.wallets().get({ id: req.params.id, reqId });
req.body.reqId = reqId;
let result;
try {
if (wallet._wallet.multisigType === 'tss') {
result = await wallet.sendMany(createTSSSendParams(req, wallet));
} else {
result = await wallet.sendMany(createSendParams(req));
}
} catch (err) {
err.status = 400;
throw err;
}
if (result.status === 'pendingApproval') {
throw apiResponse(202, result);
}
return result;
}
/**
* payload meant for prebuildAndSignTransaction() in sdk-core which
* validates the payload and makes the appropriate request to WP to
* build, sign, and send a tx.
* - sends request to Platform to build the transaction
* - signs with user key
* - request signature from the second key (BitGo HSM)
* - send/broadcast transaction
* @param req where req.body is {@link PrebuildAndSignTransactionOptions}
*/
export async function handleV2PrebuildAndSignTransaction(req: express.Request): Promise<SignedTransactionRequest> {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const reqId = new RequestTracer();
const wallet = await coin.wallets().get({ id: req.params.id, reqId });
req.body.reqId = reqId;
let result;
try {
result = await wallet.prebuildAndSignTransaction(createSendParams(req));
} catch (err) {
err.status = 400;
throw err;
}
return result;
}
/**
* Enables tokens on a wallet
* @param req
*/
export async function handleV2EnableTokens(req: express.Request) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
const reqId = new RequestTracer();
const wallet = await coin.wallets().get({ id: req.params.id, reqId });
req.body.reqId = reqId;
try {
return wallet.sendTokenEnablements(createSendParams(req));
} catch (err) {
err.status = 400;
throw err;
}
}
/**
* Handle Update Wallet
* @param req
*/
async function handleWalletUpdate(req: express.Request): Promise<unknown> {
// If it's a lightning coin, use the lightning-specific handler
if (isLightningCoinName(req.params.coin)) {
return handleUpdateLightningWalletCoinSpecific(req);
}
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
// For non-lightning coins, directly update the wallet
const wallet = await coin.wallets().get({ id: req.params.id });
return await bitgo.put(wallet.url()).send(req.body).result();
}
/**
* Changes a keychain's passphrase, re-encrypting the key to a new password
* @param req
*/
export async function handleKeychainChangePassword(req: express.Request): Promise<unknown> {
const { oldPassword, newPassword, otp } = req.body;
if (!oldPassword || !newPassword) {
throw new ApiResponseError('Missing 1 or more required fields: [oldPassword, newPassword]', 400);
}
const reqId = new RequestTracer();
const bitgo = req.bitgo;
const coin = bitgo.coin(req.params.coin);
if (otp) {
await bitgo.unlock({ otp });
}
const keychain = await coin.keychains().get({
id: req.params.id,
reqId,
});
if (!keychain) {
throw new ApiResponseError(`Keychain ${req.params.id} not found`, 404);
}
const updatedKeychain = coin.keychains().updateSingleKeychainPassword({
keychain,
oldPassword,
newPassword,
});
return bitgo.put(coin.url(`/key/${updatedKeychain.id}`)).send({
encryptedPrv: updatedKeychain.encryptedPrv,
});
}
/**
* handle any other API call
* @param req
* @param res
* @param next
*/
function handleV2CoinSpecificREST(req: express.Request, res: express.Response, next: express.NextFunction) {
const method = req.method;
const bitgo = req.bitgo;
debug('handling v2 coin specific rest req');
try {
const coin = bitgo.coin(req.params.coin);
const coinURL = coin.url(createAPIPath(req));
return redirectRequest(bitgo, method, coinURL, req, next);
} catch (e) {
if (e instanceof UnsupportedCoinError) {
const queryParams = _.transform(
req.query,
(acc: string[], value, key) => {
for (const val of _.castArray(value)) {
acc.push(`${key}=${val}`);
}
},
[]
);
const baseUrl = bitgo.url(req.baseUrl.replace(/^\/api\/v2/, ''), 2);
const url = _.isEmpty(queryParams) ? baseUrl : `${baseUrl}?${queryParams.join('&')}`;
debug(`coin ${req.params.coin} not supported, attempting to handle as a coinless route with url ${url}`);
return redirectRequest(bitgo, method, url, req, next);
}
throw e;
}
}
/**
* Handle additional option to encrypt on the express route for partners requiring value encryption
* @param req.body.encrypt - boolean to determine if the request should handle encryption on behalf of the submission.
*/
async function handleNetworkV1EnterpriseClientConnections(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
debug('handling network v1 partner connection creation');
const bitgo = req.bitgo;
const params = req.params;
const body = req.body as CreateNetworkConnectionParams & {
encrypt?: boolean;
};
if (body.encrypt === true) {
if (!body.partnerId) {
throw new ApiResponseError('Missing required field: partnerId', 400);
}
const partnersUrl = bitgo.microservicesUrl(`/api/network/v1/enterprises/${params.enterpriseId}/partners`);
const response: GetNetworkPartnersResponse = await bitgo
.get(partnersUrl)
.set('enterprise-id', params.enterpriseId)
.send({ ids: [params.partnerId] })
.result();
const partners = response.partners;
const partner = partners.find((p) => p.id === body.partnerId);
if (!partner) {
throw new ApiResponseError(`Partner not found for partnerId: ${body.partnerId}`, 400);
}
if (!partner.publicKey) {
throw new ApiResponseError('Partner does not require encryption', 400);
}
switch (body.connectionKey.schema) {
case 'token':
req.body.connectionKey.connectionToken = await encryptRsaWithAesGcm(
partner.publicKey,
body.connectionKey.connectionToken
);
break;
case 'tokenAndSignature':
req.body.connectionKey.connectionToken = await encryptRsaWithAesGcm(
partner.publicKey,
body.connectionKey.connectionToken
);
req.body.connectionKey.signature = await encryptRsaWithAesGcm(partner.publicKey, body.connectionKey.signature);
break;
case 'apiKeyAndSecret':
case 'clearloop':
req.body.connectionKey.apiKey = await encryptRsaWithAesGcm(partner.publicKey, body.connectionKey.apiKey);
req.body.connectionKey.apiSecret = await encryptRsaWithAesGcm(partner.publicKey, body.connectionKey.apiSecret);
break;
}
}
return handleProxyReq(req, res, next);
}
/**
* Redirect a request using the bitgo request functions.
* @param bitgo
* @param method
* @param url
* @param req
* @param next
*/
export function redirectRequest(
bitgo: BitGo,
method: string,
url: string,
req: express.Request,
next: express.NextFunction
) {
let request;
switch (method) {
case 'GET':
request = bitgo.get(url);
break;
case 'POST':
request = bitgo.post(url).send(req.body);
break;
case 'PUT':
request = bitgo.put(url).send(req.body);
break;
case 'PATCH':
request = bitgo.patch(url).send(req.body);
break;
case 'OPTIONS':
request = bitgo.options(url).send(req.body);
break;
case 'DELETE':
request = bitgo.del(url).send(req.body);
break;
}
if (request) {
if (req.params.enterpriseId) {
request.set('enterprise-id', req.params.enterpriseId);
}
return request.result().then((result) => {
const status = request.res?.statusCode || 200;
return { status, body: result };
});
}
// something has presumably gone wrong
next();
}
async function handleProxyReq(req: express.Request, res: express.Response, next: express.NextFunction) {
const fullUrl = req.bitgo.microservicesUrl(req.originalUrl);
if (req.url && (/^\/api.*$/.test(req.originalUrl) || /^\/oauth\/token.*$/.test(req.url))) {
req.isProxy = true;
debug('proxying %s request to %s', req.method, fullUrl);
return await redirectRequest(req.bitgo, req.method, fullUrl, req, next);
}
// user tried to access a url which is not an api route, do not proxy
debug('unable to proxy %s request to %s', req.method, fullUrl);
throw new ApiResponseError('bitgo-express can only proxy BitGo API requests', 404);
}
/**
*
* @param status
* @param result
* @param message
*/
function apiResponse(status: number, result: any, message?: string): ApiResponseError {
return new ApiResponseError(message, status, result);
}
const expressJSONParser = bodyParser.json({ limit: '20mb' });
/**
* Perform body parsing here only on routes we want
*/
function parseBody(req: express.Request, res: express.Response, next: express.NextFunction) {
// Set the default Content-Type, in case the client doesn't set it. If
// Content-Type isn't specified, Express silently refuses to parse the
// request body.
req.headers['content-type'] = req.headers['content-type'] || 'application/json';
return expressJSONParser(req, res, next);
}
/**
* Create the bitgo object in the request
* @param config
*/
function prepareBitGo(config: Config) {
const { env, customRootUri, customBitcoinNetwork } = config;
return function prepBitGo(req: express.Request, res: express.Response, next: express.NextFunction) {
// Get access token
let accessToken;
if (req.headers.authorization) {
const authSplit = req.headers.authorization.split(' ');
if (authSplit.length === 2 && authSplit[0].toLowerCase() === 'bearer') {
accessToken = authSplit[1];
}
}
const userAgent = req.headers['user-agent']
? BITGOEXPRESS_USER_AGENT + ' ' + req.headers['user-agent']
: BITGOEXPRESS_USER_AGENT;
const useProxyUrl = process.env.BITGO_USE_PROXY;
const bitgoConstructorParams: BitGoOptions = {
env,
customRootURI: customRootUri,
customBitcoinNetwork,
accessToken,
userAgent,
...(useProxyUrl
? {
customProxyAgent: new ProxyAgent({
getProxyForUrl: () => useProxyUrl,
}),
}
: {}),
};
req.bitgo = new BitGo(bitgoConstructorParams);
req.config = config;
next();
};
}
type RequestHandlerResponse = string | unknown | undefined | { status: number; body: unknown };
interface RequestHandler extends express.RequestHandler<ParamsDictionary, any, RequestHandlerResponse> {
(req: express.Request, res: express.Response, next: express.NextFunction):
| RequestHandlerResponse
| Promise<RequestHandlerResponse>;
}
function handleRequestHandlerError(res: express.Response, error: unknown) {
let err;
if (error instanceof Error) {
err = error;
} else if (typeof error === 'string') {
err = new Error('(string_error) ' + error);
} else {
err = new Error('(object_error) ' + JSON.stringify(error));
}
const message = err.message || 'local error';
// use attached result, or make one
let result = err.result || { error: message };
result = _.extend({}, result, {
message: err.message,
bitgoJsVersion: version,
bitgoExpressVersion: pjson.version,
});
const status = err.status || 500;
if (!(status >= 200 && status < 300)) {
console.log('error %s: %s', status, err.message);
}
if (status >= 500 && status <= 599) {
if (err.response && err.response.request) {
console.log(`failed to make ${err.response.request.method} request to ${err.response.request.url}`);
}
console.log(err.stack);
}
res.status(status).send(result);
}
/**
* Promise handler wrapper to handle sending responses and error cases
* @param promiseRequestHandler
*/
export function promiseWrapper(promiseRequestHandler: RequestHandler) {
return async function promWrapper(req: express.Request, res: express.Response, next: express.NextFunction) {
debug(`handle: ${req.method} ${req.originalUrl}`);
try {
const result = await promiseRequestHandler(req, res, next);
if (typeof result === 'object' && result !== null && 'body' in result && 'status' in result) {
const { status, body } = result as { status: number; body: unknown };
res.status(status).send(body);
} else {
res.status(200).send(result);
}
} catch (e) {
handleRequestHandlerError(res, e);
}
};
}
export function createCustomSigningFunction(externalSignerUrl: string): CustomSigningFunction {
return async function (params): Promise<SignedTransaction> {
const { body: signedTx } = await retryPromise(
() =>
superagent.post(`${externalSignerUrl}/api/v2/${params.coin.getChain()}/sign`).type('json').send({
txPrebuild: params.txPrebuild,
pubs: params.pubs,
derivationSeed: params.derivationSeed,
signingStep: params.signingStep,
}),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return signedTx;
};
}
export function createCustomPaillierModulusGetter(
externalSignerUrl: string,
coin: string
): CustomPaillierModulusGetterFunction {
return async function (params): Promise<{
userPaillierModulus: string;
}> {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/PaillierModulus`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}
export function createCustomKShareGenerator(externalSignerUrl: string, coin: string): CustomKShareGeneratingFunction {
return async function (params): Promise<TssEcdsaStep1ReturnMessage> {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/K`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}
export function createCustomMuDeltaShareGenerator(
externalSignerUrl: string,
coin: string
): CustomMuDeltaShareGeneratingFunction {
return async function (params): Promise<TssEcdsaStep2ReturnMessage> {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/MuDelta`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}
export function createCustomSShareGenerator(externalSignerUrl: string, coin: string): CustomSShareGeneratingFunction {
return async function (params): Promise<SShare> {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/S`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}
export function createCustomCommitmentGenerator(
externalSignerUrl: string,
coin: string
): CustomCommitmentGeneratingFunction {
return async function (params): Promise<{
userToBitgoCommitment: CommitmentShareRecord;
encryptedSignerShare: EncryptedSignerShareRecord;
encryptedUserToBitgoRShare: EncryptedSignerShareRecord;
}> {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/commitment`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}
export function createCustomRShareGenerator(externalSignerUrl: string, coin: string): CustomRShareGeneratingFunction {
return async function (params): Promise<{ rShare: SignShare }> {
const { body: rShare } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/R`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return rShare;
};
}
export function createCustomGShareGenerator(externalSignerUrl: string, coin: string): CustomGShareGeneratingFunction {
return async function (params): Promise<GShare> {
const { body: signedTx } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/G`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return signedTx;
};
}
export function createCustomMPCv2SigningRound1Generator(
externalSignerUrl: string,
coin: string
): CustomMPCv2SigningRound1GeneratingFunction {
return async function (params) {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/MPCv2Round1`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}
export function createCustomMPCv2SigningRound2Generator(
externalSignerUrl: string,
coin: string
): CustomMPCv2SigningRound2GeneratingFunction {
return async function (params) {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/MPCv2Round2`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}
export function createCustomMPCv2SigningRound3Generator(
externalSignerUrl: string,
coin: string
): CustomMPCv2SigningRound3GeneratingFunction {
return async function (params) {
const { body: result } = await retryPromise(
() => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/MPCv2Round3`).type('json').send(params),
(err, tryCount) => {
debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`);
}
);
return result;
};
}
export function setupAPIRoutes(app: express.Application, config: Config): void {
// When adding new routes to BitGo Express make sure that you also add the exact same routes to the server. Since
// some customers were confused when calling a BitGo Express route on the BitGo server, we now handle all BitGo
// Express routes on the BitGo server and return an error message that says that one should call BitGo Express
// instead.
// V1 routes should be added to www/config/routes.js
// V2 routes should be added to www/config/routesV2.js
// ping
// /api/v[12]/pingexpress is the only exception to the rule above, as it explicitly checks the health of the
// express server without running into rate limiting with the BitGo server.
app.get('/api/v[12]/ping', prepareBitGo(config), promiseWrapper(handlePing));
app.get('/api/v[12]/pingexpress', promiseWrapper(handlePingExpress));
// auth
app.post('/api/v[12]/user/login', parseBody, prepareBitGo(config), promiseWrapper(handleLogin));
app.post('/api/v[12]/decrypt', parseBody, prepareBitGo(config), promiseWrapper(handleDecrypt));
app.post('/api/v[12]/encrypt', parseBody, prepareBitGo(config), promiseWrapper(handleEncrypt));
app.post('/api/v[12]/verifyaddress', parseBody, prepareBitGo(config), promiseWrapper(handleVerifyAddress));
app.post(
'/api/v[12]/calculateminerfeeinfo',
parseBody,
prepareBitGo(config),
promiseWrapper(handleCalculateMinerFeeInfo)
);
app.post('/api/v1/keychain/local', parseBody, prepareBitGo(config), promiseWrapper(handleCreateLocalKeyChain));
app.post('/api/v1/keychain/derive', parseBody, prepareBitGo(config), promiseWrapper(handleDeriveLocalKeyChain));
app.post(
'/api/v1/wallets/simplecreate',
parseBody,
prepareBitGo(config),
promiseWrapper(handleCreateWalletWithKeychains)
);
app.post('/api/v1/wallet/:id/sendcoins', parseBody, prepareBitGo(config), promiseWrapper(handleSendCoins));
app.post('/api/v1/wallet/:id/sendmany', parseBody, prepareBitGo(config), promiseWrapper(handleSendMany));
app.post(
'/api/v1/wallet/:id/createtransaction',
parseBody,
prepareBitGo(config),
promiseWrapper(handleCreateTransaction)
);
app.post(
'/api/v1/wallet/:id/signtransaction',
parseBody,
prepareBitGo(config),
promiseWrapper(handleSignTransaction)
);
app.post('/api/v1/wallet/:id/simpleshare', parseBody, prepareBitGo(config), promiseWrapper(handleShareWallet));
app.post(
'/api/v1/walletshare/:shareId/acceptShare',
parseBody,
prepareBitGo(config),
promiseWrapper(handleAcceptShare)
);
app.put(
'/api/v1/pendingapprovals/:id/express',
parseBody,
prepareBitGo(config),
promiseWrapper(handleApproveTransaction)
);
app.put(
'/api/v1/pendingapprovals/:id/constructTx',
parseBody,
prepareBitGo(config),
promiseWrapper(handleConstructApprovalTx)
);
app.put(
'/api/v1/wallet/:id/consolidateunspents',
parseBody,
prepareBitGo(config),
promiseWrapper(handleConsolidateUnspents)
);
app.put('/api/v1/wallet/:id/fanoutunspents', parseBody, prepareBitGo(config), promiseWrapper(handleFanOutUnspents));
// any other API call
app.use('/api/v[1]/*', parseBody, prepareBitGo(config), promiseWrapper(handleREST));
// API v2
// create keychain
app.post(
'/api/v2/:coin/keychain/local',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2CreateLocalKeyChain)
);
// generate wallet
app.post('/api/v2/:coin/wallet/generate', parseBody, prepareBitGo(config), promiseWrapper(handleV2GenerateWallet));
app.put('/express/api/v2/:coin/wallet/:id', parseBody, prepareBitGo(config), promiseWrapper(handleWalletUpdate));
// change wallet passphrase
app.post(
'/api/v2/:coin/keychain/:id/changepassword',
parseBody,
prepareBitGo(config),
promiseWrapper(handleKeychainChangePassword)
);
// create address
app.post('/api/v2/:coin/wallet/:id/address', parseBody, prepareBitGo(config), promiseWrapper(handleV2CreateAddress));
// share wallet
app.post('/api/v2/:coin/wallet/:id/share', parseBody, prepareBitGo(config), promiseWrapper(handleV2ShareWallet));
app.post(
'/api/v2/:coin/walletshare/:id/acceptshare',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2AcceptWalletShare)
);
// sign arbitrary payloads w/ trading account key
app.post(`/api/v2/ofc/signPayload`, parseBody, prepareBitGo(config), promiseWrapper(handleV2OFCSignPayload));
// sign transaction
app.post('/api/v2/:coin/signtx', parseBody, prepareBitGo(config), promiseWrapper(handleV2SignTx));
app.post('/api/v2/:coin/wallet/:id/signtx', parseBody, prepareBitGo(config), promiseWrapper(handleV2SignTxWallet));
app.post(
'/api/v2/:coin/wallet/:id/signtxtss',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2SignTSSWalletTx)
);
app.post(
'/api/v2/:coin/wallet/:id/recovertoken',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2RecoverToken)
);
// send transaction
app.post('/api/v2/:coin/wallet/:id/sendcoins', parseBody, prepareBitGo(config), promiseWrapper(handleV2SendOne));
app.post('/api/v2/:coin/wallet/:id/sendmany', parseBody, prepareBitGo(config), promiseWrapper(handleV2SendMany));
app.post(
'/api/v2/:coin/wallet/:id/prebuildAndSignTransaction',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2PrebuildAndSignTransaction)
);
// token enablement
app.post(
'/api/v2/:coin/wallet/:id/enableTokens',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2EnableTokens)
);
// unspent changes
app.post(
'/api/v2/:coin/wallet/:id/consolidateunspents',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2ConsolidateUnspents)
);
app.post(
'/api/v2/:coin/wallet/:id/fanoutunspents',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2FanOutUnspents)
);
app.post('/api/v2/:coin/wallet/:id/sweep', parseBody, prepareBitGo(config), promiseWrapper(handleV2Sweep));
// CPFP
app.post(
'/api/v2/:coin/wallet/:id/acceleratetx',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2AccelerateTransaction)
);
// account-based
app.post(
'/api/v2/:coin/wallet/:id/consolidateAccount',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2ConsolidateAccount)
);
// Miscellaneous
app.post('/api/v2/:coin/canonicaladdress', parseBody, prepareBitGo(config), promiseWrapper(handleCanonicalAddress));
app.post('/api/v2/:coin/verifyaddress', parseBody, prepareBitGo(config), promiseWrapper(handleV2VerifyAddress));
app.put(
'/api/v2/:coin/pendingapprovals/:id',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2PendingApproval)
);
// lightning - pay invoice
app.post(
'/api/v2/:coin/wallet/:id/lightning/payment',
parseBody,
prepareBitGo(config),
promiseWrapper(handlePayLightningInvoice)
);
// lightning - onchain withdrawal
app.post(
'/api/v2/:coin/wallet/:id/lightning/withdraw',
parseBody,
prepareBitGo(config),
promiseWrapper(handleLightningWithdraw)
);
// any other API v2 call
app.use('/api/v2/user/*', parseBody, prepareBitGo(config), promiseWrapper(handleV2UserREST));
app.use('/api/v2/:coin/*', parseBody, prepareBitGo(config), promiseWrapper(handleV2CoinSpecificREST));
app.post(
'/api/network/v1/enterprises/:enterpriseId/clients/connections',
parseBody,
prepareBitGo(config),
promiseWrapper(handleNetworkV1EnterpriseClientConnections)
);
// everything else should use the proxy handler
if (config.disableProxy !== true) {
app.use(
'/api/:namespace/v[12]/enterprises/:enterpriseId/*',
parseBody,
prepareBitGo(config),
promiseWrapper(handleProxyReq)
);
app.use(parseBody, prepareBitGo(config), promiseWrapper(handleProxyReq));
}
}
export function setupSigningRoutes(app: express.Application, config: Config): void {
app.post('/api/v2/:coin/sign', parseBody, prepareBitGo(config), promiseWrapper(handleV2Sign));
app.post(
'/api/v2/:coin/tssshare/:sharetype',
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2GenerateShareTSS)
);
app.post(
`/api/v2/ofc/signPayload`,
parseBody,
prepareBitGo(config),
promiseWrapper(handleV2OFCSignPayloadInExtSigningMode)
);
}
export function setupLightningSignerNodeRoutes(app: express.Application, config: Config): void {
app.post(
'/api/v2/:coin/wallet/:id/initwallet',
parseBody,
prepareBitGo(config),
promiseWrapper(handleInitLightningWallet)
);
app.post(
'/api/v2/:coin/wallet/:id/signermacaroon',
parseBody,
prepareBitGo(config),
promiseWrapper(handleCreateSignerMacaroon)
);
app.post(
'/api/v2/:coin/wallet/:id/unlockwallet',
parseBody,
prepareBitGo(config),
promiseWrapper(handleUnlockLightningWallet)
);
app.get('/api/v2/:coin/wallet/:id/state', prepareBitGo(config), promiseWrapper(handleGetLightningWalletState));
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!