@bitpay-labs/bitcore-wallet-service 11.11.1 → 11.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +1 -1
- package/package.json +5 -5
- package/src/lib/chain/btc/index.ts +3 -2
- package/src/lib/chain/eth/index.ts +134 -117
- package/src/lib/chain/sol/index.ts +1 -1
- package/src/lib/chain/xrp/index.ts +1 -1
- package/src/lib/common/constants.ts +1 -1
- package/src/lib/common/utils.ts +18 -1
- package/src/lib/model/tsskeygen.ts +16 -0
- package/src/lib/routes/maintenance.ts +11 -2
- package/src/lib/routes/middleware/authTssRequest.ts +28 -16
- package/src/lib/routes/middleware/verifyTssMessage.ts +4 -1
- package/src/lib/routes/services.ts +7 -1
- package/src/lib/routes/tss.ts +33 -30
- package/src/lib/routes/wallets.ts +3 -1
- package/src/lib/server.ts +198 -105
- package/src/lib/storage.ts +129 -20
- package/src/lib/tss.ts +111 -7
- package/test/chain/eth.test.ts +70 -0
- package/test/chain/sol.test.ts +1 -1
- package/test/chain/xrp.test.ts +1 -1
- package/test/expressapp.test.ts +243 -0
- package/test/integration/addressReads.test.ts +503 -0
- package/test/integration/bcmonitor.test.ts +44 -0
- package/test/integration/server.test.ts +251 -49
- package/test/integration/tss.test.ts +428 -0
- package/test/integration/walletReads.test.ts +488 -0
- package/ts_build/package.json +4 -4
- package/ts_build/src/lib/chain/btc/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/btc/index.js +3 -2
- package/ts_build/src/lib/chain/btc/index.js.map +1 -1
- package/ts_build/src/lib/chain/eth/index.d.ts +8 -1
- package/ts_build/src/lib/chain/eth/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/eth/index.js +114 -102
- package/ts_build/src/lib/chain/eth/index.js.map +1 -1
- package/ts_build/src/lib/chain/sol/index.js +1 -1
- package/ts_build/src/lib/chain/sol/index.js.map +1 -1
- package/ts_build/src/lib/chain/xrp/index.js +1 -1
- package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
- package/ts_build/src/lib/common/constants.d.ts +1 -1
- package/ts_build/src/lib/common/constants.js +1 -1
- package/ts_build/src/lib/common/index.d.ts +2 -1
- package/ts_build/src/lib/common/index.d.ts.map +1 -1
- package/ts_build/src/lib/common/utils.d.ts +1 -0
- package/ts_build/src/lib/common/utils.d.ts.map +1 -1
- package/ts_build/src/lib/common/utils.js +16 -1
- package/ts_build/src/lib/common/utils.js.map +1 -1
- package/ts_build/src/lib/model/tsskeygen.d.ts +10 -0
- package/ts_build/src/lib/model/tsskeygen.d.ts.map +1 -1
- package/ts_build/src/lib/model/tsskeygen.js +3 -0
- package/ts_build/src/lib/model/tsskeygen.js.map +1 -1
- package/ts_build/src/lib/routes/maintenance.d.ts.map +1 -1
- package/ts_build/src/lib/routes/maintenance.js +12 -4
- package/ts_build/src/lib/routes/maintenance.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts +6 -2
- package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.js +21 -14
- package/ts_build/src/lib/routes/middleware/authTssRequest.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.d.ts.map +1 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.js +2 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.js.map +1 -1
- package/ts_build/src/lib/routes/services.d.ts.map +1 -1
- package/ts_build/src/lib/routes/services.js +6 -1
- package/ts_build/src/lib/routes/services.js.map +1 -1
- package/ts_build/src/lib/routes/tss.d.ts.map +1 -1
- package/ts_build/src/lib/routes/tss.js +32 -30
- package/ts_build/src/lib/routes/tss.js.map +1 -1
- package/ts_build/src/lib/routes/wallets.d.ts.map +1 -1
- package/ts_build/src/lib/routes/wallets.js +2 -1
- package/ts_build/src/lib/routes/wallets.js.map +1 -1
- package/ts_build/src/lib/server.d.ts +24 -3
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +148 -97
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/src/lib/storage.d.ts +26 -10
- package/ts_build/src/lib/storage.d.ts.map +1 -1
- package/ts_build/src/lib/storage.js +99 -20
- package/ts_build/src/lib/storage.js.map +1 -1
- package/ts_build/src/lib/tss.d.ts +12 -0
- package/ts_build/src/lib/tss.d.ts.map +1 -1
- package/ts_build/src/lib/tss.js +64 -5
- package/ts_build/src/lib/tss.js.map +1 -1
- package/ts_build/test/chain/eth.test.js +59 -0
- package/ts_build/test/chain/eth.test.js.map +1 -1
- package/ts_build/test/chain/sol.test.js +1 -1
- package/ts_build/test/chain/sol.test.js.map +1 -1
- package/ts_build/test/chain/xrp.test.js +1 -1
- package/ts_build/test/chain/xrp.test.js.map +1 -1
- package/ts_build/test/expressapp.test.js +222 -0
- package/ts_build/test/expressapp.test.js.map +1 -1
- package/ts_build/test/integration/addressReads.test.d.ts +2 -0
- package/ts_build/test/integration/addressReads.test.d.ts.map +1 -0
- package/ts_build/test/integration/addressReads.test.js +465 -0
- package/ts_build/test/integration/addressReads.test.js.map +1 -0
- package/ts_build/test/integration/bcmonitor.test.js +40 -0
- package/ts_build/test/integration/bcmonitor.test.js.map +1 -1
- package/ts_build/test/integration/server.test.js +228 -48
- package/ts_build/test/integration/server.test.js.map +1 -1
- package/ts_build/test/integration/tss.test.d.ts.map +1 -1
- package/ts_build/test/integration/tss.test.js +344 -0
- package/ts_build/test/integration/tss.test.js.map +1 -1
- package/ts_build/test/integration/walletReads.test.d.ts +2 -0
- package/ts_build/test/integration/walletReads.test.d.ts.map +1 -0
- package/ts_build/test/integration/walletReads.test.js +436 -0
- package/ts_build/test/integration/walletReads.test.js.map +1 -0
package/Dockerfile
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@bitpay-labs/bitcore-wallet-service",
|
|
3
3
|
"description": "A service for Mutisig HD Bitcoin Wallets",
|
|
4
4
|
"author": "BitPay Inc",
|
|
5
|
-
"version": "11.11.
|
|
5
|
+
"version": "11.11.3",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "ts_build/src/index.js",
|
|
8
8
|
"types": "./ts_build/src/index.d.ts",
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"cp:templates": "cp -r ./templates ./ts_build/templates"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@bitpay-labs/bitcore-logging": "^11.
|
|
45
|
-
"@bitpay-labs/bitcore-tss": "^11.11.
|
|
46
|
-
"@bitpay-labs/crypto-wallet-core": "^11.11.
|
|
44
|
+
"@bitpay-labs/bitcore-logging": "^11.11.2",
|
|
45
|
+
"@bitpay-labs/bitcore-tss": "^11.11.2",
|
|
46
|
+
"@bitpay-labs/crypto-wallet-core": "^11.11.2",
|
|
47
47
|
"@sendgrid/mail": "6.5.4",
|
|
48
48
|
"abi-decoder": "2.4.0",
|
|
49
49
|
"async": "0.9.2",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
"email": "kjoseph@bitpay.com"
|
|
108
108
|
}
|
|
109
109
|
],
|
|
110
|
-
"gitHead": "
|
|
110
|
+
"gitHead": "154120b032bb0cd42bad2559c0ef81873a8c7e8c"
|
|
111
111
|
}
|
|
@@ -41,7 +41,8 @@ export class BtcChain implements IChain {
|
|
|
41
41
|
server.getUtxosForCurrentWallet(
|
|
42
42
|
{
|
|
43
43
|
coin: opts.coin,
|
|
44
|
-
addresses: opts.addresses
|
|
44
|
+
addresses: opts.addresses,
|
|
45
|
+
wallet
|
|
45
46
|
},
|
|
46
47
|
(err, utxos) => {
|
|
47
48
|
if (err) return cb(err);
|
|
@@ -173,7 +174,7 @@ export class BtcChain implements IChain {
|
|
|
173
174
|
return new Promise((resolve, reject) => {
|
|
174
175
|
const getChangeAddress = (wallet, cb) => {
|
|
175
176
|
if (wallet.singleAddress) {
|
|
176
|
-
server.storage.
|
|
177
|
+
server.storage.fetchAddressesPage(server.walletId, { limit: 1 }, (err, addresses) => {
|
|
177
178
|
if (err) return cb(err);
|
|
178
179
|
if (!addresses?.length) return cb(new ClientError('The wallet has no addresses'));
|
|
179
180
|
return cb(null, addresses[0]);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import util from 'util';
|
|
2
|
+
import { Constants as CWCConstants, Transactions, Utils, Validation, Web3 } from '@bitpay-labs/crypto-wallet-core';
|
|
2
3
|
import _ from 'lodash';
|
|
3
4
|
import { IWallet } from 'src/lib/model';
|
|
4
5
|
import { IAddress } from 'src/lib/model/address';
|
|
@@ -23,6 +24,9 @@ function requireUncached(module) {
|
|
|
23
24
|
return require(module);
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
// the effect type bitcore-node gives ERC20 transfers in tx events
|
|
28
|
+
const ERC20_TRANSFER_EFFECT = 'ERC20:transfer';
|
|
29
|
+
|
|
26
30
|
const Erc20Decoder = requireUncached('abi-decoder');
|
|
27
31
|
Erc20Decoder.addABI(ERC20Abi);
|
|
28
32
|
|
|
@@ -118,7 +122,7 @@ export class EthChain implements IChain {
|
|
|
118
122
|
// TODO support big int
|
|
119
123
|
const lockedSum = Number(amounts + fees) || 0; // previously set to 0 if opts.multisigContractAddress
|
|
120
124
|
const convertedBalance = this.convertBitcoreBalance(balance, lockedSum);
|
|
121
|
-
server.storage.
|
|
125
|
+
server.storage.fetchAddressesPage(server.walletId, { limit: 1 }, (err, addresses: IAddress[]) => {
|
|
122
126
|
if (err) return cb(err);
|
|
123
127
|
if (addresses.length > 0) {
|
|
124
128
|
const byAddress = [
|
|
@@ -170,121 +174,134 @@ export class EthChain implements IChain {
|
|
|
170
174
|
|
|
171
175
|
checkScriptOutput(_output) { }
|
|
172
176
|
|
|
173
|
-
getFee(server, wallet, opts) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const value = opts.tokenAddress || opts.multisigContractAddress ? 0 : output.amount;
|
|
215
|
-
// output.gasLimit used as the gasLimit in getBitcoreTx for non multisend transactions
|
|
216
|
-
const gasLimitEstimate = await server.estimateGas({
|
|
217
|
-
coin,
|
|
218
|
-
chain: this.chain,
|
|
219
|
-
network,
|
|
220
|
-
from,
|
|
221
|
-
to,
|
|
222
|
-
value,
|
|
223
|
-
data: output.data,
|
|
224
|
-
gasPrice
|
|
225
|
-
});
|
|
226
|
-
output.gasLimit = gasLimitEstimate || defaultGasLimit;
|
|
227
|
-
} catch {
|
|
228
|
-
output.gasLimit = defaultGasLimit;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
inGasLimit += output.gasLimit;
|
|
232
|
-
logger.info(`[${from}][${output?.toAddress || opts?.tokenAddress}] Output level gas limit: ${output.gasLimit}`);
|
|
233
|
-
// Add gas Limit buffer to output level gasLimit
|
|
234
|
-
if (gasLimitBuffer) {
|
|
235
|
-
const gasBuffer = Math.ceil(output.gasLimit * (gasLimitBuffer / 100));
|
|
236
|
-
output.gasLimit += gasBuffer;
|
|
237
|
-
inGasLimit += gasBuffer;
|
|
238
|
-
logger.info(`[${from}][${output?.toAddress || opts?.tokenAddress}] Output gas limit with buffer: ${output.gasLimit}`);
|
|
239
|
-
}
|
|
240
|
-
if (_.isNumber(opts.fee)) {
|
|
241
|
-
// This is used for sendmax
|
|
242
|
-
gasPrice = feePerKb = Number((opts.fee / (inGasLimit || defaultGasLimit)).toFixed());
|
|
243
|
-
}
|
|
244
|
-
fee += feePerKb * output.gasLimit;
|
|
245
|
-
}
|
|
246
|
-
// gasLimit == sum of internal gasLimits (for non Multisend)
|
|
247
|
-
gasLimit = inGasLimit;
|
|
248
|
-
logger.info(`[${from}] Current top level gas limit: ${gasLimit}`);
|
|
249
|
-
if (opts.multiSendContractAddress) {
|
|
250
|
-
// Calculate gas limit for top level of txp based on multisend tx
|
|
251
|
-
let _gasLimit;
|
|
252
|
-
try {
|
|
253
|
-
const data = this.encodeContractParameters(
|
|
254
|
-
Constants.BITPAY_CONTRACTS.MULTISEND,
|
|
255
|
-
{ addresses: outputAddresses, amounts: outputAmounts },
|
|
256
|
-
opts
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
_gasLimit = await server.estimateGas({
|
|
260
|
-
coin,
|
|
261
|
-
chain: this.chain,
|
|
262
|
-
network,
|
|
263
|
-
from,
|
|
264
|
-
to: opts.multiSendContractAddress,
|
|
265
|
-
value: totalValue.toString(),
|
|
266
|
-
data,
|
|
267
|
-
gasPrice
|
|
268
|
-
});
|
|
269
|
-
logger.info(`[${from}] Estimated multisend gas limit: ${_gasLimit}`);
|
|
270
|
-
} catch (error) {
|
|
271
|
-
logger.error('Error estimating gas for MultiSend contract: %o', error);
|
|
272
|
-
}
|
|
273
|
-
// Add gas limit buffer to top level gas limit
|
|
274
|
-
const buffer = gasLimitBuffer ? gasLimitBuffer / 100 : Defaults.MS_GAS_LIMIT_BUFFER_PERCENT;
|
|
275
|
-
// If gas estimation fails, fallback to sum of internal gasLimits
|
|
276
|
-
gasLimit = _gasLimit || gasLimit;
|
|
277
|
-
gasLimit += Math.ceil(gasLimit * buffer); // add gas limit buffer
|
|
278
|
-
fee += feePerKb * gasLimit;
|
|
279
|
-
logger.info(`[${from}] Top level gas limit with buffer: ${gasLimit}`);
|
|
177
|
+
async getFee(server, wallet, opts) {
|
|
178
|
+
const hasType2FeeOverride = opts.maxGasFee != null || opts.priorityGasFee != null;
|
|
179
|
+
let { maxGasFee, priorityGasFee } = opts;
|
|
180
|
+
if (hasType2FeeOverride) {
|
|
181
|
+
const chain = wallet.chain || wallet.coin;
|
|
182
|
+
// Keep overrides consistent with CWC's chain-specific minimum priority fee.
|
|
183
|
+
const minimum = CWCConstants.FEE_MINIMUMS[chain.toUpperCase()]?.priority ?? 0;
|
|
184
|
+
if (priorityGasFee < minimum) {
|
|
185
|
+
throw new ClientError(`priorityGasFee must be at least ${minimum} wei for ${chain}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Use the explicit rate for both construction and the fee reserved from the wallet balance.
|
|
189
|
+
const feeOpts = opts.gasPrice != null || hasType2FeeOverride
|
|
190
|
+
? { ...opts, feePerKb: opts.gasPrice ?? maxGasFee }
|
|
191
|
+
: opts;
|
|
192
|
+
// Fixed fees are converted to a gas price below, once the gas limits are known.
|
|
193
|
+
const fixedFee = Common.Utils.isNumber(opts.fee) ? Number(opts.fee) : undefined;
|
|
194
|
+
const inFeePerKb = fixedFee != null
|
|
195
|
+
? undefined
|
|
196
|
+
: await util.promisify(server._getFeePerKb).call(server, wallet, feeOpts);
|
|
197
|
+
let feePerKb = inFeePerKb;
|
|
198
|
+
let gasPrice = inFeePerKb;
|
|
199
|
+
const { from, txType, priorityFeePercentile, gasLimitBuffer } = opts;
|
|
200
|
+
const { coin, network, chain } = wallet;
|
|
201
|
+
let inGasLimit = 0; // Per recepient gas limit
|
|
202
|
+
let gasLimit = 0; // Gas limit for all recepients. used for contract interactions that rollup recepients
|
|
203
|
+
let fee = 0;
|
|
204
|
+
const defaultGasLimit = this.getDefaultGasLimit(opts);
|
|
205
|
+
const outputAddresses = []; // Parameter for MuliSend contract
|
|
206
|
+
const outputAmounts: bigint[] = []; // Parameter for MuliSend contract
|
|
207
|
+
let totalValue = 0n; // Parameter for MuliSend contract
|
|
208
|
+
logger.info(`getFee for address ${from} on network ${network} and chain ${chain}`);
|
|
209
|
+
logger.info('getFee.opts: %o', { from, txType, priorityFeePercentile, gasLimitBuffer });
|
|
210
|
+
logger.info(`[${from}] Add gas limit buffer?: ${!!gasLimitBuffer}`);
|
|
211
|
+
for (const output of opts.outputs) {
|
|
212
|
+
// Multisend txs build contract fn parameters (addresses, amounts) and bypass output level gas estimations
|
|
213
|
+
if (opts.multiSendContractAddress) {
|
|
214
|
+
outputAddresses.push(output.toAddress);
|
|
215
|
+
outputAmounts.push(BigInt(output.amount));
|
|
216
|
+
if (!opts.tokenAddress) {
|
|
217
|
+
totalValue += BigInt(output.amount);
|
|
280
218
|
}
|
|
281
|
-
if
|
|
282
|
-
|
|
283
|
-
|
|
219
|
+
// Used as a fallback value if estimateGas fails for multisend
|
|
220
|
+
inGasLimit += output.gasLimit ? output.gasLimit : defaultGasLimit;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
// Estimate a standard transfer
|
|
224
|
+
if (!output.gasLimit) {
|
|
225
|
+
try {
|
|
226
|
+
const to = opts.payProUrl
|
|
227
|
+
? output.toAddress
|
|
228
|
+
: opts.tokenAddress
|
|
229
|
+
? opts.tokenAddress
|
|
230
|
+
: opts.multisigContractAddress
|
|
231
|
+
? opts.multisigContractAddress
|
|
232
|
+
: output.toAddress;
|
|
233
|
+
const value = opts.tokenAddress || opts.multisigContractAddress ? 0 : output.amount;
|
|
234
|
+
// output.gasLimit used as the gasLimit in getBitcoreTx for non multisend transactions
|
|
235
|
+
const gasLimitEstimate = await server.estimateGas({
|
|
236
|
+
coin,
|
|
237
|
+
chain: this.chain,
|
|
238
|
+
network,
|
|
239
|
+
from,
|
|
240
|
+
to,
|
|
241
|
+
value,
|
|
242
|
+
data: output.data,
|
|
243
|
+
gasPrice
|
|
244
|
+
});
|
|
245
|
+
output.gasLimit = gasLimitEstimate || defaultGasLimit;
|
|
246
|
+
} catch {
|
|
247
|
+
output.gasLimit = defaultGasLimit;
|
|
284
248
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
249
|
+
}
|
|
250
|
+
inGasLimit += output.gasLimit;
|
|
251
|
+
logger.info(`[${from}][${output?.toAddress || opts?.tokenAddress}] Output level gas limit: ${output.gasLimit}`);
|
|
252
|
+
// Add gas Limit buffer to output level gasLimit
|
|
253
|
+
if (gasLimitBuffer) {
|
|
254
|
+
const gasBuffer = Math.ceil(output.gasLimit * (gasLimitBuffer / 100));
|
|
255
|
+
output.gasLimit += gasBuffer;
|
|
256
|
+
inGasLimit += gasBuffer;
|
|
257
|
+
logger.info(`[${from}][${output?.toAddress || opts?.tokenAddress}] Output gas limit with buffer: ${output.gasLimit}`);
|
|
258
|
+
}
|
|
259
|
+
if (fixedFee != null) {
|
|
260
|
+
// This is used for sendmax
|
|
261
|
+
gasPrice = feePerKb = Number((fixedFee / (inGasLimit || defaultGasLimit)).toFixed());
|
|
262
|
+
}
|
|
263
|
+
fee += feePerKb * output.gasLimit;
|
|
264
|
+
}
|
|
265
|
+
// gasLimit == sum of internal gasLimits (for non Multisend)
|
|
266
|
+
gasLimit = inGasLimit;
|
|
267
|
+
logger.info(`[${from}] Current top level gas limit: ${gasLimit}`);
|
|
268
|
+
if (opts.multiSendContractAddress) {
|
|
269
|
+
// Calculate gas limit for top level of txp based on multisend tx
|
|
270
|
+
let _gasLimit;
|
|
271
|
+
try {
|
|
272
|
+
const data = this.encodeContractParameters(
|
|
273
|
+
Constants.BITPAY_CONTRACTS.MULTISEND,
|
|
274
|
+
{ addresses: outputAddresses, amounts: outputAmounts },
|
|
275
|
+
opts
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
_gasLimit = await server.estimateGas({
|
|
279
|
+
coin,
|
|
280
|
+
chain: this.chain,
|
|
281
|
+
network,
|
|
282
|
+
from,
|
|
283
|
+
to: opts.multiSendContractAddress,
|
|
284
|
+
value: totalValue.toString(),
|
|
285
|
+
data,
|
|
286
|
+
gasPrice
|
|
287
|
+
});
|
|
288
|
+
logger.info(`[${from}] Estimated multisend gas limit: ${_gasLimit}`);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
logger.error('Error estimating gas for MultiSend contract: %o', error);
|
|
291
|
+
}
|
|
292
|
+
// Add gas limit buffer to top level gas limit
|
|
293
|
+
const buffer = gasLimitBuffer ? gasLimitBuffer / 100 : Defaults.MS_GAS_LIMIT_BUFFER_PERCENT;
|
|
294
|
+
// If gas estimation fails, fallback to sum of internal gasLimits
|
|
295
|
+
gasLimit = _gasLimit || gasLimit;
|
|
296
|
+
gasLimit += Math.ceil(gasLimit * buffer); // add gas limit buffer
|
|
297
|
+
fee += feePerKb * gasLimit;
|
|
298
|
+
logger.info(`[${from}] Top level gas limit with buffer: ${gasLimit}`);
|
|
299
|
+
}
|
|
300
|
+
if (Number(txType) === 2 && !hasType2FeeOverride) {
|
|
301
|
+
maxGasFee = await server.estimateFee({ network, chain: wallet.chain || coin, txType: 2 });
|
|
302
|
+
priorityGasFee = await server.estimatePriorityFee({ network, chain: wallet.chain || coin, percentile: priorityFeePercentile || 15 });
|
|
303
|
+
}
|
|
304
|
+
return { feePerKb, gasPrice, gasLimit, maxGasFee, priorityGasFee, fee };
|
|
288
305
|
}
|
|
289
306
|
|
|
290
307
|
isPrePublishRawBound(txp: TxProposal) {
|
|
@@ -613,7 +630,7 @@ export class EthChain implements IChain {
|
|
|
613
630
|
if (tx.effects && tx.effects.length) {
|
|
614
631
|
const multisigConfirm = tx.effects.findIndex(e => e.type === 'MULTISIG:confirmTransaction');
|
|
615
632
|
const multisigSubmit = tx.effects.findIndex(e => e.type === 'MULTISIG:submitTransaction');
|
|
616
|
-
const erc20Transfer = tx.effects.findIndex(e => e.type ===
|
|
633
|
+
const erc20Transfer = tx.effects.findIndex(e => e.type === ERC20_TRANSFER_EFFECT);
|
|
617
634
|
const internalTransfer = tx.effects.findIndex(e => !e.type && !e.contractAddress);
|
|
618
635
|
|
|
619
636
|
const exists = [multisigConfirm, multisigSubmit, erc20Transfer, internalTransfer].filter(i => i > -1);
|
|
@@ -625,7 +642,7 @@ export class EthChain implements IChain {
|
|
|
625
642
|
multisigContractAddress = best.contractAddress;
|
|
626
643
|
address = best.to;
|
|
627
644
|
amount = best.amount;
|
|
628
|
-
} else if (best.type ==
|
|
645
|
+
} else if (best.type == ERC20_TRANSFER_EFFECT) {
|
|
629
646
|
tokenAddress = best.contractAddress;
|
|
630
647
|
address = best.to;
|
|
631
648
|
amount = best.amount;
|
|
@@ -73,7 +73,7 @@ export class SolChain implements IChain {
|
|
|
73
73
|
const lockedSum = Number(amounts + fees) || 0; // previously set to 0 if opts.multisigContractAddress
|
|
74
74
|
const reserveAmount = opts.tokenAddress ? 0 : reserve;
|
|
75
75
|
const convertedBalance = this.convertBitcoreBalance(balance, lockedSum, reserveAmount);
|
|
76
|
-
server.storage.
|
|
76
|
+
server.storage.fetchAddressesPage(server.walletId, { limit: 1 }, (err, addresses: IAddress[]) => {
|
|
77
77
|
if (err) return cb(err);
|
|
78
78
|
if (addresses.length > 0) {
|
|
79
79
|
const byAddress = [
|
|
@@ -69,7 +69,7 @@ export class XrpChain implements IChain {
|
|
|
69
69
|
return sum + BigInt(txp.amount || 0) + BigInt(txp.fee || 0);
|
|
70
70
|
}, 0n) || 0n;
|
|
71
71
|
const convertedBalance = this.convertBitcoreBalance(balance, Number(lockedSum), reserve);
|
|
72
|
-
server.storage.
|
|
72
|
+
server.storage.fetchAddressesPage(server.walletId, { limit: 1 }, (err, addresses: IAddress[]) => {
|
|
73
73
|
if (err) return cb(err);
|
|
74
74
|
if (addresses.length > 0) {
|
|
75
75
|
const byAddress = [
|
|
@@ -226,7 +226,7 @@ export const Constants = {
|
|
|
226
226
|
|
|
227
227
|
// May be different from the defaults if we rollback
|
|
228
228
|
// or want to beta test new versions.
|
|
229
|
-
TSS_KEYGEN_SCHEME_VERSION_MAX: 1.
|
|
229
|
+
TSS_KEYGEN_SCHEME_VERSION_MAX: 1.3,
|
|
230
230
|
TSS_SIGGEN_SCHEME_VERSION_MAX: 1.1,
|
|
231
231
|
|
|
232
232
|
TSS_KEYGEN_SCHEME_VERSION_MIN: 1,
|
package/src/lib/common/utils.ts
CHANGED
|
@@ -571,5 +571,22 @@ export const Utils = {
|
|
|
571
571
|
return retval;
|
|
572
572
|
},
|
|
573
573
|
|
|
574
|
-
sleep(ms) {
|
|
574
|
+
sleep(ms) {
|
|
575
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
576
|
+
},
|
|
577
|
+
|
|
578
|
+
convertXPubKeyForNetwork(xPubKey: string, chain: string, network: string) {
|
|
579
|
+
// Convert named network for non-UTXO chains to generic name (e.g. sepolia -> testnet)
|
|
580
|
+
network = !Bitcore_[chain] ? Utils.getGenericName(network) : network;
|
|
581
|
+
const lib = Bitcore_[chain] || BitcoreLib;
|
|
582
|
+
$.checkArgument(xPubKey, 'xPubKey is required at <convertXPubKeyForNetwork()>');
|
|
583
|
+
$.checkArgument(chain, 'chain is required at <convertXPubKeyForNetwork()>');
|
|
584
|
+
$.checkArgument(lib.Networks.get(network), 'invalid network at <convertXPubKeyForNetwork()>: ' + network);
|
|
585
|
+
const hdPubKeyObj = new lib.HDPublicKey(xPubKey).toObject();
|
|
586
|
+
hdPubKeyObj.network = network;
|
|
587
|
+
delete hdPubKeyObj.checksum;
|
|
588
|
+
delete hdPubKeyObj.xpubkey;
|
|
589
|
+
const newHdPubKey = lib.HDPublicKey.fromObject(hdPubKeyObj);
|
|
590
|
+
return newHdPubKey.toString();
|
|
591
|
+
}
|
|
575
592
|
};
|
|
@@ -80,9 +80,18 @@ export interface ITssKeyGenModel {
|
|
|
80
80
|
*/
|
|
81
81
|
timeLimit?: number;
|
|
82
82
|
/**
|
|
83
|
+
* @deprecated Use bwsJoinSecrets instead.
|
|
83
84
|
* The BWC-generated secret for joining a BWS wallet
|
|
84
85
|
*/
|
|
85
86
|
bwsJoinSecret?: string;
|
|
87
|
+
/**
|
|
88
|
+
* The BWC-generated secrets for joining a BWS wallet
|
|
89
|
+
*/
|
|
90
|
+
bwsJoinSecrets?: {
|
|
91
|
+
[chainNetwork: string]: {
|
|
92
|
+
[partyId: number]: string;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
86
95
|
/**
|
|
87
96
|
* The mongo doc version
|
|
88
97
|
*/
|
|
@@ -105,6 +114,11 @@ export class TssKeyGenModel implements ITssKeyGenModel {
|
|
|
105
114
|
createdOn: number;
|
|
106
115
|
timeLimit?: number;
|
|
107
116
|
bwsJoinSecret?: string;
|
|
117
|
+
bwsJoinSecrets?: {
|
|
118
|
+
[chainNetwork: string]: {
|
|
119
|
+
[partyId: number]: string;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
108
122
|
__v: number;
|
|
109
123
|
|
|
110
124
|
|
|
@@ -151,6 +165,7 @@ export class TssKeyGenModel implements ITssKeyGenModel {
|
|
|
151
165
|
x.keyShares = new Array(n);
|
|
152
166
|
x.createdOn = Date.now();
|
|
153
167
|
x.timeLimit = Math.min(timeLimit || Defaults.TSS_KEYGEN_TIME_LIMIT, 60) * ONE_MINUTE; // capped at 60 minutes
|
|
168
|
+
x.bwsJoinSecrets = {};
|
|
154
169
|
x.__v = 0;
|
|
155
170
|
return x;
|
|
156
171
|
}
|
|
@@ -169,6 +184,7 @@ export class TssKeyGenModel implements ITssKeyGenModel {
|
|
|
169
184
|
x.createdOn = obj.createdOn;
|
|
170
185
|
x.timeLimit = obj.timeLimit;
|
|
171
186
|
x.bwsJoinSecret = obj.bwsJoinSecret;
|
|
187
|
+
x.bwsJoinSecrets = obj.bwsJoinSecrets;
|
|
172
188
|
x.__v = obj.__v;
|
|
173
189
|
return x;
|
|
174
190
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
+
import { ClientError } from '../errors/clienterror';
|
|
2
3
|
import { WalletService } from '../server';
|
|
3
4
|
import { Stats } from '../stats';
|
|
4
5
|
import type * as Types from '../../types/expressapp';
|
|
@@ -27,6 +28,14 @@ export function registerMaintenanceRoutes(router: express.Router, context: Route
|
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
router.get('/v1/stats/', (req, res) => {
|
|
31
|
+
const { network, coin } = req.query;
|
|
32
|
+
if (network !== undefined && typeof network !== 'string') {
|
|
33
|
+
return returnError(new ClientError('Invalid network'), res, req);
|
|
34
|
+
}
|
|
35
|
+
if (coin !== undefined && typeof coin !== 'string') {
|
|
36
|
+
return returnError(new ClientError('Invalid coin'), res, req);
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
setPublicCache(res, 1 * ONE_MINUTE);
|
|
31
40
|
const opts: {
|
|
32
41
|
network?: string;
|
|
@@ -35,8 +44,8 @@ export function registerMaintenanceRoutes(router: express.Router, context: Route
|
|
|
35
44
|
to?: string;
|
|
36
45
|
} = {};
|
|
37
46
|
|
|
38
|
-
if (
|
|
39
|
-
if (
|
|
47
|
+
if (network) opts.network = network;
|
|
48
|
+
if (coin) opts.coin = coin;
|
|
40
49
|
if (req.query.from) opts.from = req.query.from as string;
|
|
41
50
|
if (req.query.to) opts.to = req.query.to as string;
|
|
42
51
|
|
|
@@ -11,9 +11,7 @@ import type { ITssSigMessageObject, TssSigGenModel } from '../../model/tsssign';
|
|
|
11
11
|
function verifyMessage(req: express.Request, signature: string, pubKey: string) {
|
|
12
12
|
const message = getMessage(req);
|
|
13
13
|
const isValid = !!Utils.verifyMessage(message, signature, pubKey);
|
|
14
|
-
|
|
15
|
-
throw Errors.NOT_AUTHORIZED.withMessage('Invalid signature');
|
|
16
|
-
}
|
|
14
|
+
return isValid;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
/**
|
|
@@ -21,7 +19,8 @@ function verifyMessage(req: express.Request, signature: string, pubKey: string)
|
|
|
21
19
|
* It checks if the request has valid credentials, verifies the signature,
|
|
22
20
|
* and retrieves the TSS session based on the request path.
|
|
23
21
|
*/
|
|
24
|
-
export function authTssKeyGenRequest(): express.RequestHandler {
|
|
22
|
+
export function authTssKeyGenRequest(params: { bypassExpired?: boolean } = {}): express.RequestHandler {
|
|
23
|
+
const { bypassExpired } = params;
|
|
25
24
|
return async function(req, res, next) {
|
|
26
25
|
try {
|
|
27
26
|
const storage = WalletService.getStorage();
|
|
@@ -46,7 +45,10 @@ export function authTssKeyGenRequest(): express.RequestHandler {
|
|
|
46
45
|
// This is a brand new session. Caller should be party 0 with the initial round message.
|
|
47
46
|
if (body?.message?.round === 0 && body?.message?.partyId === 0) {
|
|
48
47
|
const pubKey = body?.message?.publicKey;
|
|
49
|
-
verifyMessage(req, signature, pubKey)
|
|
48
|
+
if (!verifyMessage(req, signature, pubKey)) {
|
|
49
|
+
// Self verification, but at least ensures that the caller owns the message's private key
|
|
50
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Invalid signature');
|
|
51
|
+
}
|
|
50
52
|
return next();
|
|
51
53
|
} else {
|
|
52
54
|
// Session should have been initialized by the client already in BWC > TssKeyGen.newKey()
|
|
@@ -54,7 +56,7 @@ export function authTssKeyGenRequest(): express.RequestHandler {
|
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
if (session.isExpired()) {
|
|
59
|
+
if (!bypassExpired && session.isExpired()) {
|
|
58
60
|
throw Errors.TSS_SESSION_EXPIRED;
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -103,7 +105,9 @@ export function authTssKeyGenRequest(): express.RequestHandler {
|
|
|
103
105
|
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found in session');
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
verifyMessage(req, signature, pubKey)
|
|
108
|
+
if (!verifyMessage(req, signature, pubKey)) {
|
|
109
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Invalid signature');
|
|
110
|
+
}
|
|
107
111
|
|
|
108
112
|
req.session = session;
|
|
109
113
|
return next();
|
|
@@ -116,7 +120,8 @@ export function authTssKeyGenRequest(): express.RequestHandler {
|
|
|
116
120
|
};
|
|
117
121
|
};
|
|
118
122
|
|
|
119
|
-
export function authTssSignRequest(): express.RequestHandler {
|
|
123
|
+
export function authTssSignRequest(params: { joinable?: boolean } = {}): express.RequestHandler {
|
|
124
|
+
const { joinable } = params;
|
|
120
125
|
return async function(req, res, next) {
|
|
121
126
|
try {
|
|
122
127
|
const storage = WalletService.getStorage();
|
|
@@ -132,16 +137,22 @@ export function authTssSignRequest(): express.RequestHandler {
|
|
|
132
137
|
|
|
133
138
|
const { id } = req.params as { [key: string]: string };
|
|
134
139
|
const body = req.body as { message: ITssSigMessageObject | undefined };
|
|
135
|
-
// Only some requests carry a body.message (e.g. POST /store only sends the final signature).
|
|
136
|
-
// Omitting it is safe since routes that don't receive a message don't consume one.
|
|
137
|
-
const verifyMsg = !!body?.message;
|
|
138
140
|
|
|
139
141
|
const copayer = await storage.fetchCopayerLookup(copayerId);
|
|
140
142
|
if (!copayer) {
|
|
141
143
|
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found');
|
|
142
144
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
// I'm not aware of an instance where copayer.requestPubKeys.length > 1, but we iterate over them to find the key for the given signature.
|
|
146
|
+
const pubKey = copayer.requestPubKeys.find(pubKey => verifyMessage(req, signature, pubKey.key))?.key;
|
|
147
|
+
if (!pubKey) {
|
|
148
|
+
throw Errors.NOT_AUTHORIZED.withMessage('Invalid signature');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Only some requests carry a body.message (e.g. POST /store only sends the final signature).
|
|
152
|
+
// Omitting it is safe since routes that don't receive a message don't change state.
|
|
153
|
+
// `verifyMsgFields` just indicates whether to check the message's fields for expected values (e.g. publicKey, partyId).
|
|
154
|
+
const verifyMsgFields = !!body?.message;
|
|
155
|
+
if (verifyMsgFields && pubKey !== body?.message?.publicKey) {
|
|
145
156
|
throw Errors.NOT_AUTHORIZED.withMessage('Invalid public key');
|
|
146
157
|
}
|
|
147
158
|
|
|
@@ -152,10 +163,12 @@ export function authTssSignRequest(): express.RequestHandler {
|
|
|
152
163
|
throw Errors.TSS_SESSION_EXPIRED;
|
|
153
164
|
}
|
|
154
165
|
const party = session.participants.find(p => p.copayerId === copayerId);
|
|
155
|
-
|
|
166
|
+
// A copayer joins an existing session by sending its round 0 message (participant is added in TssSign.processMessage)
|
|
167
|
+
const isJoining = !party && body?.message?.round === 0;
|
|
168
|
+
if (!party && !(joinable && isJoining)) {
|
|
156
169
|
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found in session');
|
|
157
170
|
}
|
|
158
|
-
if (
|
|
171
|
+
if (verifyMsgFields && party && party.partyId !== body?.message?.partyId) {
|
|
159
172
|
throw Errors.NOT_AUTHORIZED.withMessage('Copayer not found in session');
|
|
160
173
|
}
|
|
161
174
|
|
|
@@ -164,7 +177,6 @@ export function authTssSignRequest(): express.RequestHandler {
|
|
|
164
177
|
throw Errors.TSS_SESSION_NOT_FOUND;
|
|
165
178
|
}
|
|
166
179
|
|
|
167
|
-
verifyMessage(req, signature, pubKey);
|
|
168
180
|
return next();
|
|
169
181
|
} catch (err) {
|
|
170
182
|
if (err instanceof ClientError) {
|
|
@@ -14,7 +14,10 @@ export async function verifyTssMessage(req, res, next) {
|
|
|
14
14
|
throw Errors.TSS_PUBKEY_MISSING;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// 1-of-N signing sessions won't have messages for other recipients.
|
|
18
|
+
// req.session is unset for a new session, and keygen sessions have no `m`.
|
|
19
|
+
const isSoloSigner = req.session?.m === 1;
|
|
20
|
+
if (!isSoloSigner && !message.broadcastMessages?.length && !message.p2pMessages?.length) {
|
|
18
21
|
throw Errors.TSS_INVALID_MESSAGE;
|
|
19
22
|
}
|
|
20
23
|
|
|
@@ -3,6 +3,7 @@ import express from 'express';
|
|
|
3
3
|
import rp from 'request-promise-native';
|
|
4
4
|
import config from '../../config';
|
|
5
5
|
import { Common } from '../common';
|
|
6
|
+
import { ClientError } from '../errors/clienterror';
|
|
6
7
|
import type * as Types from '../../types/expressapp';
|
|
7
8
|
import type { WalletService } from '../server';
|
|
8
9
|
|
|
@@ -125,13 +126,18 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
|
|
|
125
126
|
});
|
|
126
127
|
|
|
127
128
|
router.get('/v1/fiatrates/:code/', (req, res) => {
|
|
129
|
+
const { coin } = req.query;
|
|
130
|
+
if (coin !== undefined && typeof coin !== 'string') {
|
|
131
|
+
return context.returnError(new ClientError('Invalid coin'), res, req);
|
|
132
|
+
}
|
|
133
|
+
|
|
128
134
|
setPublicCache(res, 5 * ONE_MINUTE);
|
|
129
135
|
respondWithPublicServer(req, res, context, server =>
|
|
130
136
|
callbackResult(resolve => {
|
|
131
137
|
server.getFiatRate(
|
|
132
138
|
{
|
|
133
139
|
code: req.params['code'],
|
|
134
|
-
coin:
|
|
140
|
+
coin: coin || 'btc',
|
|
135
141
|
ts: req.query.ts ? +req.query.ts : null
|
|
136
142
|
},
|
|
137
143
|
resolve
|