@bitpay-labs/bitcore-wallet-service 11.11.3 → 11.11.4
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/package.json +4 -4
- package/src/config.ts +8 -0
- package/src/externalservices/coingecko.ts +6 -1
- package/src/externalservices/transak.ts +394 -70
- package/src/lib/blockchainexplorer.ts +4 -0
- package/src/lib/blockchainexplorers/v8.ts +1 -0
- package/src/lib/blockchainmonitor.ts +1 -0
- package/src/lib/chain/arc/index.ts +95 -0
- package/src/lib/chain/eth/index.ts +41 -23
- package/src/lib/chain/index.ts +2 -0
- package/src/lib/common/constants.ts +12 -1
- package/src/lib/common/defaults.ts +31 -0
- package/src/lib/emailservice.ts +3 -1
- package/src/lib/errors/errordefinitions.ts +4 -0
- package/src/lib/model/preferences.ts +4 -0
- package/src/lib/model/wallet.ts +1 -0
- package/src/lib/pushnotificationsservice.ts +2 -0
- package/src/lib/routes/services.ts +1 -1
- package/src/lib/routes/walletdata.ts +2 -0
- package/src/lib/server.ts +20 -2
- package/test/blockchainexplorer.test.ts +7 -0
- package/test/expressapp.test.ts +31 -0
- package/test/integration/externalservices/coinGecko.test.ts +42 -2
- package/test/integration/externalservices/transak.test.ts +545 -135
- package/test/integration/fiatrateservice.test.ts +13 -0
- package/test/integration/helpers.ts +1 -0
- package/test/integration/server.test.ts +139 -3
- package/ts_build/package.json +1 -1
- package/ts_build/src/config.d.ts.map +1 -1
- package/ts_build/src/config.js +8 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/externalservices/coingecko.d.ts.map +1 -1
- package/ts_build/src/externalservices/coingecko.js +6 -1
- package/ts_build/src/externalservices/coingecko.js.map +1 -1
- package/ts_build/src/externalservices/transak.d.ts +28 -2
- package/ts_build/src/externalservices/transak.d.ts.map +1 -1
- package/ts_build/src/externalservices/transak.js +274 -57
- package/ts_build/src/externalservices/transak.js.map +1 -1
- package/ts_build/src/lib/blockchainexplorer.d.ts.map +1 -1
- package/ts_build/src/lib/blockchainexplorer.js +4 -0
- package/ts_build/src/lib/blockchainexplorer.js.map +1 -1
- package/ts_build/src/lib/blockchainexplorers/v8.d.ts.map +1 -1
- package/ts_build/src/lib/blockchainexplorers/v8.js +1 -0
- package/ts_build/src/lib/blockchainexplorers/v8.js.map +1 -1
- package/ts_build/src/lib/blockchainmonitor.d.ts.map +1 -1
- package/ts_build/src/lib/blockchainmonitor.js +1 -0
- package/ts_build/src/lib/blockchainmonitor.js.map +1 -1
- package/ts_build/src/lib/chain/arc/index.d.ts +20 -0
- package/ts_build/src/lib/chain/arc/index.d.ts.map +1 -0
- package/ts_build/src/lib/chain/arc/index.js +95 -0
- package/ts_build/src/lib/chain/arc/index.js.map +1 -0
- package/ts_build/src/lib/chain/eth/index.d.ts +1 -0
- package/ts_build/src/lib/chain/eth/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/eth/index.js +38 -19
- package/ts_build/src/lib/chain/eth/index.js.map +1 -1
- package/ts_build/src/lib/chain/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/index.js +2 -0
- package/ts_build/src/lib/chain/index.js.map +1 -1
- package/ts_build/src/lib/common/constants.d.ts +12 -0
- package/ts_build/src/lib/common/constants.d.ts.map +1 -1
- package/ts_build/src/lib/common/constants.js +12 -1
- package/ts_build/src/lib/common/constants.js.map +1 -1
- package/ts_build/src/lib/common/defaults.d.ts +24 -0
- package/ts_build/src/lib/common/defaults.d.ts.map +1 -1
- package/ts_build/src/lib/common/defaults.js +30 -0
- package/ts_build/src/lib/common/defaults.js.map +1 -1
- package/ts_build/src/lib/common/index.d.ts +36 -0
- package/ts_build/src/lib/common/index.d.ts.map +1 -1
- package/ts_build/src/lib/emailservice.d.ts.map +1 -1
- package/ts_build/src/lib/emailservice.js +2 -0
- package/ts_build/src/lib/emailservice.js.map +1 -1
- package/ts_build/src/lib/errors/errordefinitions.d.ts +2 -0
- package/ts_build/src/lib/errors/errordefinitions.d.ts.map +1 -1
- package/ts_build/src/lib/errors/errordefinitions.js +2 -0
- package/ts_build/src/lib/errors/errordefinitions.js.map +1 -1
- package/ts_build/src/lib/model/preferences.d.ts +2 -0
- package/ts_build/src/lib/model/preferences.d.ts.map +1 -1
- package/ts_build/src/lib/model/preferences.js +3 -0
- package/ts_build/src/lib/model/preferences.js.map +1 -1
- package/ts_build/src/lib/model/wallet.d.ts.map +1 -1
- package/ts_build/src/lib/model/wallet.js +1 -0
- package/ts_build/src/lib/model/wallet.js.map +1 -1
- package/ts_build/src/lib/pushnotificationsservice.d.ts.map +1 -1
- package/ts_build/src/lib/pushnotificationsservice.js +2 -0
- package/ts_build/src/lib/pushnotificationsservice.js.map +1 -1
- package/ts_build/src/lib/routes/services.js +1 -1
- package/ts_build/src/lib/routes/services.js.map +1 -1
- package/ts_build/src/lib/routes/walletdata.d.ts.map +1 -1
- package/ts_build/src/lib/routes/walletdata.js +2 -0
- package/ts_build/src/lib/routes/walletdata.js.map +1 -1
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +17 -2
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/test/blockchainexplorer.test.js +6 -0
- package/ts_build/test/blockchainexplorer.test.js.map +1 -1
- package/ts_build/test/expressapp.test.js +30 -0
- package/ts_build/test/expressapp.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/coinGecko.test.js +38 -2
- package/ts_build/test/integration/externalservices/coinGecko.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/transak.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/transak.test.js +498 -128
- package/ts_build/test/integration/externalservices/transak.test.js.map +1 -1
- package/ts_build/test/integration/fiatrateservice.test.js +11 -0
- package/ts_build/test/integration/fiatrateservice.test.js.map +1 -1
- package/ts_build/test/integration/helpers.d.ts.map +1 -1
- package/ts_build/test/integration/helpers.js +1 -0
- package/ts_build/test/integration/helpers.js.map +1 -1
- package/ts_build/test/integration/server.test.js +135 -3
- package/ts_build/test/integration/server.test.js.map +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import util from 'util';
|
|
2
|
+
import { Constants, ethers } from '@bitpay-labs/crypto-wallet-core';
|
|
3
|
+
import { ClientError } from '../../errors/clienterror';
|
|
4
|
+
import { Errors } from '../../errors/errordefinitions';
|
|
5
|
+
import { EthChain } from '../eth';
|
|
6
|
+
import { ERC20Abi } from '../eth/abi-erc20';
|
|
7
|
+
import type { TxProposal } from '../../model/txproposal';
|
|
8
|
+
|
|
9
|
+
const usdcInterface = new ethers.Interface(ERC20Abi);
|
|
10
|
+
|
|
11
|
+
export class ArcChain extends EthChain {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this.chain = 'ARC';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private isUsdcTokenView(opts) {
|
|
18
|
+
return opts.tokenAddress?.toLowerCase() === Constants.ARC.USDC_CONTRACT_ADDRESS;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected getAdditionalNativeAmount(txp: TxProposal): bigint {
|
|
22
|
+
if (txp.tokenAddress || txp.multisigContractAddress || txp.multiSendContractAddress) return 0n;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
return (txp.outputs || []).reduce((total, output, index) => {
|
|
26
|
+
if (output.toAddress?.toLowerCase() !== Constants.ARC.USDC_CONTRACT_ADDRESS) return total;
|
|
27
|
+
const data = index === 0 && txp.data ? txp.data : output.data;
|
|
28
|
+
if (!data || data === '0x') return total;
|
|
29
|
+
const call = usdcInterface.parseTransaction({ data });
|
|
30
|
+
let amount = 0n;
|
|
31
|
+
if (call?.name === 'transfer') {
|
|
32
|
+
amount = call.args[1];
|
|
33
|
+
} else if (call?.name === 'transferFrom' && call.args[0].toLowerCase() === txp.from?.toLowerCase()) {
|
|
34
|
+
amount = call.args[2];
|
|
35
|
+
}
|
|
36
|
+
// The ERC20 interface uses 6 decimals; the same native balance uses 18.
|
|
37
|
+
return total + amount * 10n ** 12n;
|
|
38
|
+
}, 0n);
|
|
39
|
+
} catch {
|
|
40
|
+
throw new ClientError('Invalid Arc USDC contract call data');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getWalletBalance(server, wallet, opts, cb) {
|
|
45
|
+
// Native USDC and its ERC-20 view share funds. A separate token balance would
|
|
46
|
+
// use different units and omit the native spend locked by pending proposals.
|
|
47
|
+
if (this.isUsdcTokenView(opts)) {
|
|
48
|
+
return cb(new ClientError('USDC is the native Arc balance; omit tokenAddress'));
|
|
49
|
+
}
|
|
50
|
+
return super.getWalletBalance(server, wallet, opts, cb);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getWalletSendMaxInfo(server, wallet, opts, cb) {
|
|
54
|
+
const onFee = (err, feePerKb?) => {
|
|
55
|
+
if (err) return cb(err);
|
|
56
|
+
feePerKb = Number(feePerKb);
|
|
57
|
+
if (!Number.isFinite(feePerKb) || feePerKb < Constants.ARC.MIN_GAS_PRICE) {
|
|
58
|
+
return cb(new ClientError('Arc gas price must be at least 20 gwei'));
|
|
59
|
+
}
|
|
60
|
+
return super.getWalletSendMaxInfo(server, wallet, { ...opts, feePerKb }, cb);
|
|
61
|
+
};
|
|
62
|
+
// Match the type-2 cap reserved when creating the proposal.
|
|
63
|
+
if (Number(opts.txType) === 2) {
|
|
64
|
+
return server.estimateFee({ network: wallet.network, chain: wallet.chain, txType: 2 })
|
|
65
|
+
.then(feePerKb => onFee(null, feePerKb), onFee);
|
|
66
|
+
}
|
|
67
|
+
return server._getFeePerKb(wallet, opts, onFee);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getFee(server, wallet, opts) {
|
|
71
|
+
if (this.isUsdcTokenView(opts)) {
|
|
72
|
+
throw new ClientError('USDC is the native Arc asset; omit tokenAddress');
|
|
73
|
+
}
|
|
74
|
+
const fees = await super.getFee(server, wallet, opts);
|
|
75
|
+
const gasPrice = Number(Number(opts.txType) === 2 ? fees.maxGasFee : fees.gasPrice);
|
|
76
|
+
if (!Number.isFinite(gasPrice) || gasPrice < Constants.ARC.MIN_GAS_PRICE) {
|
|
77
|
+
throw new ClientError('Arc gas price must be at least 20 gwei');
|
|
78
|
+
}
|
|
79
|
+
// Reserve the fee actually encoded in the transaction, including type-2 caps.
|
|
80
|
+
const fee = gasPrice * fees.gasLimit;
|
|
81
|
+
if (opts.sendMax && Number(opts.txType) === 2) {
|
|
82
|
+
const balance = await util.promisify(server.getBalance).call(server, {});
|
|
83
|
+
if (balance.availableAmount <= fee) throw Errors.INSUFFICIENT_FUNDS_FOR_FEE;
|
|
84
|
+
opts.outputs[0].amount = balance.availableAmount - fee;
|
|
85
|
+
}
|
|
86
|
+
return { ...fees, fee };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
checkDust(output, opts) {
|
|
90
|
+
if (!opts.tokenAddress && output.amount > 0 &&
|
|
91
|
+
output.toAddress.toLowerCase() === '0x0000000000000000000000000000000000000000') {
|
|
92
|
+
return new ClientError('Native USDC cannot be sent to the zero address on Arc');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -84,6 +84,10 @@ export class EthChain implements IChain {
|
|
|
84
84
|
return true;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
protected getAdditionalNativeAmount(_txp: TxProposal): bigint {
|
|
88
|
+
return 0n;
|
|
89
|
+
}
|
|
90
|
+
|
|
87
91
|
getWalletBalance(server, wallet, opts, cb) {
|
|
88
92
|
const bc = server._getBlockchainExplorer(wallet.chain || wallet.coin, wallet.network);
|
|
89
93
|
|
|
@@ -106,18 +110,22 @@ export class EthChain implements IChain {
|
|
|
106
110
|
let fees = 0n;
|
|
107
111
|
let amounts = 0n;
|
|
108
112
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
113
|
+
try {
|
|
114
|
+
txps.filter(txp => {
|
|
115
|
+
// Add gas used for tokens when getting native balance
|
|
116
|
+
if (!opts.tokenAddress) {
|
|
117
|
+
fees += txp.fee ? BigInt(txp.fee) : 0n;
|
|
118
|
+
}
|
|
119
|
+
// Filter tokens when getting native balance
|
|
120
|
+
if (txp.tokenAddress && !opts.tokenAddress) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
amounts += BigInt(txp.amount || 0) + this.getAdditionalNativeAmount(txp);
|
|
124
|
+
return true;
|
|
125
|
+
});
|
|
126
|
+
} catch (err) {
|
|
127
|
+
return cb(err);
|
|
128
|
+
}
|
|
121
129
|
|
|
122
130
|
// TODO support big int
|
|
123
131
|
const lockedSum = Number(amounts + fees) || 0; // previously set to 0 if opts.multisigContractAddress
|
|
@@ -175,8 +183,15 @@ export class EthChain implements IChain {
|
|
|
175
183
|
checkScriptOutput(_output) { }
|
|
176
184
|
|
|
177
185
|
async getFee(server, wallet, opts) {
|
|
186
|
+
const { from, txType, priorityFeePercentile, gasLimitBuffer } = opts;
|
|
187
|
+
const { coin, network, chain } = wallet;
|
|
178
188
|
const hasType2FeeOverride = opts.maxGasFee != null || opts.priorityGasFee != null;
|
|
179
189
|
let { maxGasFee, priorityGasFee } = opts;
|
|
190
|
+
if (Number(txType) === 2 && !hasType2FeeOverride) {
|
|
191
|
+
maxGasFee = await server.estimateFee({ network, chain: chain || coin, txType: 2 });
|
|
192
|
+
priorityGasFee = await server.estimatePriorityFee({ network, chain: chain || coin, percentile: priorityFeePercentile || 15 });
|
|
193
|
+
maxGasFee = Math.max(maxGasFee, priorityGasFee);
|
|
194
|
+
}
|
|
180
195
|
if (hasType2FeeOverride) {
|
|
181
196
|
const chain = wallet.chain || wallet.coin;
|
|
182
197
|
// Keep overrides consistent with CWC's chain-specific minimum priority fee.
|
|
@@ -185,8 +200,8 @@ export class EthChain implements IChain {
|
|
|
185
200
|
throw new ClientError(`priorityGasFee must be at least ${minimum} wei for ${chain}`);
|
|
186
201
|
}
|
|
187
202
|
}
|
|
188
|
-
//
|
|
189
|
-
const feeOpts = opts.gasPrice != null ||
|
|
203
|
+
// Reserve the same rate used for construction, including the automatic type-2 fee cap.
|
|
204
|
+
const feeOpts = opts.gasPrice != null || maxGasFee != null
|
|
190
205
|
? { ...opts, feePerKb: opts.gasPrice ?? maxGasFee }
|
|
191
206
|
: opts;
|
|
192
207
|
// Fixed fees are converted to a gas price below, once the gas limits are known.
|
|
@@ -196,8 +211,6 @@ export class EthChain implements IChain {
|
|
|
196
211
|
: await util.promisify(server._getFeePerKb).call(server, wallet, feeOpts);
|
|
197
212
|
let feePerKb = inFeePerKb;
|
|
198
213
|
let gasPrice = inFeePerKb;
|
|
199
|
-
const { from, txType, priorityFeePercentile, gasLimitBuffer } = opts;
|
|
200
|
-
const { coin, network, chain } = wallet;
|
|
201
214
|
let inGasLimit = 0; // Per recepient gas limit
|
|
202
215
|
let gasLimit = 0; // Gas limit for all recepients. used for contract interactions that rollup recepients
|
|
203
216
|
let fee = 0;
|
|
@@ -297,9 +310,9 @@ export class EthChain implements IChain {
|
|
|
297
310
|
fee += feePerKb * gasLimit;
|
|
298
311
|
logger.info(`[${from}] Top level gas limit with buffer: ${gasLimit}`);
|
|
299
312
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
313
|
+
// BWC verifies an explicit feePerKb against the request, separately from the type-2 fee cap.
|
|
314
|
+
if (Number(txType) === 2 && Common.Utils.isNumber(opts.feePerKb)) {
|
|
315
|
+
feePerKb = opts.feePerKb;
|
|
303
316
|
}
|
|
304
317
|
return { feePerKb, gasPrice, gasLimit, maxGasFee, priorityGasFee, fee };
|
|
305
318
|
}
|
|
@@ -482,10 +495,15 @@ export class EthChain implements IChain {
|
|
|
482
495
|
"gasLimit":200000
|
|
483
496
|
}]
|
|
484
497
|
*/
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
498
|
+
let txpTotalAmount;
|
|
499
|
+
try {
|
|
500
|
+
txpTotalAmount =
|
|
501
|
+
(opts.multisigContractAddress || opts.tokenAddress) && txp.payProUrl
|
|
502
|
+
? getInvoiceValue(txp)
|
|
503
|
+
: txp.getTotalAmountBigInt() + this.getAdditionalNativeAmount(txp);
|
|
504
|
+
} catch (err) {
|
|
505
|
+
return cb(err);
|
|
506
|
+
}
|
|
489
507
|
|
|
490
508
|
if (totalAmount < txpTotalAmount) {
|
|
491
509
|
return cb(Errors.INSUFFICIENT_FUNDS);
|
package/src/lib/chain/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { IWallet, TxProposal } from '../model';
|
|
|
4
4
|
import { WalletService } from '../server';
|
|
5
5
|
import logger from './../logger';
|
|
6
6
|
import { ArbChain } from './arb';
|
|
7
|
+
import { ArcChain } from './arc';
|
|
7
8
|
import { BaseChain } from './base';
|
|
8
9
|
import { BchChain } from './bch';
|
|
9
10
|
import { BtcChain } from './btc';
|
|
@@ -24,6 +25,7 @@ const chains: { [chain: string]: IChain } = {
|
|
|
24
25
|
ETH: new EthChain(),
|
|
25
26
|
MATIC: new MaticChain(),
|
|
26
27
|
ARB: new ArbChain(),
|
|
28
|
+
ARC: new ArcChain(),
|
|
27
29
|
BASE: new BaseChain(),
|
|
28
30
|
OP: new OpChain(),
|
|
29
31
|
XRP: new XrpChain(),
|
|
@@ -10,6 +10,7 @@ export const Constants = {
|
|
|
10
10
|
ARB: 'arb',
|
|
11
11
|
BASE: 'base',
|
|
12
12
|
OP: 'op',
|
|
13
|
+
ARC: 'arc',
|
|
13
14
|
XRP: 'xrp',
|
|
14
15
|
DOGE: 'doge',
|
|
15
16
|
LTC: 'ltc',
|
|
@@ -38,7 +39,8 @@ export const Constants = {
|
|
|
38
39
|
EUROC: 'euroc',
|
|
39
40
|
USDT: 'usdt',
|
|
40
41
|
WETH: 'weth',
|
|
41
|
-
SOL: 'sol'
|
|
42
|
+
SOL: 'sol',
|
|
43
|
+
ARC: 'arc'
|
|
42
44
|
},
|
|
43
45
|
|
|
44
46
|
BITPAY_SUPPORTED_ETH_ERC20: {
|
|
@@ -60,6 +62,7 @@ export const Constants = {
|
|
|
60
62
|
|
|
61
63
|
BITPAY_USD_STABLECOINS: {
|
|
62
64
|
// used for rates
|
|
65
|
+
ARC: 'arc',
|
|
63
66
|
USDC: 'usdc',
|
|
64
67
|
PYUSD: 'pyusd',
|
|
65
68
|
USDP: 'usdp',
|
|
@@ -88,6 +91,7 @@ export const Constants = {
|
|
|
88
91
|
ARB: 'arb',
|
|
89
92
|
BASE: 'base',
|
|
90
93
|
OP: 'op',
|
|
94
|
+
ARC: 'arc',
|
|
91
95
|
},
|
|
92
96
|
|
|
93
97
|
EVM_CHAINS_WITH_ETH_GAS: {
|
|
@@ -112,6 +116,7 @@ export const Constants = {
|
|
|
112
116
|
arb: ['livenet', 'sepolia', 'holesky', 'regtest'],
|
|
113
117
|
base: ['livenet', 'sepolia', 'holesky', 'regtest'],
|
|
114
118
|
op: ['livenet', 'sepolia', 'holesky', 'regtest'],
|
|
119
|
+
arc: ['livenet', 'testnet'],
|
|
115
120
|
sol: ['livenet', 'testnet', 'devnet', 'regtest']
|
|
116
121
|
} as { [chain: string]: Array<string> },
|
|
117
122
|
|
|
@@ -156,6 +161,9 @@ export const Constants = {
|
|
|
156
161
|
mainnet: 'livenet',
|
|
157
162
|
testnet: 'sepolia'
|
|
158
163
|
},
|
|
164
|
+
arc: {
|
|
165
|
+
mainnet: 'livenet'
|
|
166
|
+
},
|
|
159
167
|
sol: {
|
|
160
168
|
mainnet: 'livenet',
|
|
161
169
|
testnet: 'devnet'
|
|
@@ -200,6 +208,8 @@ export const Constants = {
|
|
|
200
208
|
ARB_TOKEN_OPTS: CWC.Constants.ARB_TOKEN_OPTS,
|
|
201
209
|
OP_TOKEN_OPTS: CWC.Constants.OP_TOKEN_OPTS,
|
|
202
210
|
BASE_TOKEN_OPTS: CWC.Constants.BASE_TOKEN_OPTS,
|
|
211
|
+
ARC_TOKEN_OPTS: CWC.Constants.ARC_TOKEN_OPTS,
|
|
212
|
+
ARC: CWC.Constants.ARC,
|
|
203
213
|
SOL_TOKEN_OPTS: CWC.Constants.SOL_TOKEN_OPTS,
|
|
204
214
|
|
|
205
215
|
BITPAY_CONTRACTS: {
|
|
@@ -219,6 +229,7 @@ export const Constants = {
|
|
|
219
229
|
bch: { testnet: 300, livenet: 0 },
|
|
220
230
|
eth: { testnet: 300, livenet: 0 },
|
|
221
231
|
matic: { testnet: 300, livenet: 12 }, // MATIC set to 12 because blocks normally occur every 1-2 seconds
|
|
232
|
+
arc: { testnet: 0, livenet: 0 },
|
|
222
233
|
xrp: { testnet: 300, livenet: 0 },
|
|
223
234
|
doge: { testnet: 300, livenet: 0 },
|
|
224
235
|
ltc: { testnet: 300, livenet: 0 }
|
|
@@ -194,6 +194,34 @@ export const Defaults = {
|
|
|
194
194
|
defaultValue: 2000000000
|
|
195
195
|
}
|
|
196
196
|
],
|
|
197
|
+
// Fallbacks must meet Arc's testnet gas-price floor. Live estimates still take precedence.
|
|
198
|
+
arc: [
|
|
199
|
+
{
|
|
200
|
+
name: 'urgent',
|
|
201
|
+
nbBlocks: 1,
|
|
202
|
+
defaultValue: 20000000000
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'priority',
|
|
206
|
+
nbBlocks: 2,
|
|
207
|
+
defaultValue: 20000000000
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'normal',
|
|
211
|
+
nbBlocks: 3,
|
|
212
|
+
defaultValue: 20000000000
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'economy',
|
|
216
|
+
nbBlocks: 4,
|
|
217
|
+
defaultValue: 20000000000
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'superEconomy',
|
|
221
|
+
nbBlocks: 4,
|
|
222
|
+
defaultValue: 20000000000
|
|
223
|
+
}
|
|
224
|
+
],
|
|
197
225
|
xrp: [
|
|
198
226
|
{
|
|
199
227
|
name: 'normal',
|
|
@@ -362,6 +390,7 @@ export const Defaults = {
|
|
|
362
390
|
arb: 1000000000000, // 50 Gwei,
|
|
363
391
|
base: 1000000000000, // 50 Gwei,
|
|
364
392
|
op: 1000000000000, // 50 Gwei,
|
|
393
|
+
arc: 1000000000000,
|
|
365
394
|
xrp: 1000000000000,
|
|
366
395
|
doge: 100000000 * 100,
|
|
367
396
|
ltc: 10000 * 1000, // 10k sat/b
|
|
@@ -376,6 +405,7 @@ export const Defaults = {
|
|
|
376
405
|
arb: 0,
|
|
377
406
|
base: 0,
|
|
378
407
|
op: 0,
|
|
408
|
+
arc: 0,
|
|
379
409
|
xrp: 0,
|
|
380
410
|
doge: 0,
|
|
381
411
|
ltc: 0,
|
|
@@ -390,6 +420,7 @@ export const Defaults = {
|
|
|
390
420
|
arb: 1 * 1e18, // 1 eth
|
|
391
421
|
base: 1 * 1e18, // 1 eth
|
|
392
422
|
op: 1 * 1e18, // 1 eth
|
|
423
|
+
arc: 1 * 1e18, // 1 USDC
|
|
393
424
|
xrp: 1 * 1e6, // 1 xrp
|
|
394
425
|
doge: 400 * 1e8,
|
|
395
426
|
ltc: 0.05 * 1e8
|
package/src/lib/emailservice.ts
CHANGED
|
@@ -337,6 +337,7 @@ export class EmailService {
|
|
|
337
337
|
bch: 'BCH',
|
|
338
338
|
eth: 'ETH',
|
|
339
339
|
matic: 'MATIC',
|
|
340
|
+
arc: 'USDC',
|
|
340
341
|
xrp: 'XRP',
|
|
341
342
|
doge: 'DOGE',
|
|
342
343
|
ltc: 'LTC',
|
|
@@ -685,6 +686,7 @@ export class EmailService {
|
|
|
685
686
|
arb: 'arbitrum-one',
|
|
686
687
|
base: 'base',
|
|
687
688
|
op: 'optimistic-ethereum',
|
|
689
|
+
arc: 'arc',
|
|
688
690
|
sol: 'solana',
|
|
689
691
|
};
|
|
690
692
|
|
|
@@ -739,4 +741,4 @@ export class EmailService {
|
|
|
739
741
|
});
|
|
740
742
|
});
|
|
741
743
|
}
|
|
742
|
-
}
|
|
744
|
+
}
|
|
@@ -37,6 +37,8 @@ interface Errors<T> {
|
|
|
37
37
|
// Solana Errors
|
|
38
38
|
INSUFFICIENT_SOL_FEE: T;
|
|
39
39
|
LOCKED_SOL_FEE: T;
|
|
40
|
+
INSUFFICIENT_ARC_FEE: T;
|
|
41
|
+
LOCKED_ARC_FEE: T;
|
|
40
42
|
HISTORY_LIMIT_EXCEEDED: T;
|
|
41
43
|
MAIN_ADDRESS_GAP_REACHED: T;
|
|
42
44
|
MULTI_TX_UNSUPPORTED: T;
|
|
@@ -114,6 +116,8 @@ const errors: Errors<string> = {
|
|
|
114
116
|
LOCKED_OP_FEE: 'Your linked OP wallet does not have enough ETH for fee',
|
|
115
117
|
INSUFFICIENT_SOL_FEE: 'Your linked SOL wallet does not have enough SOL for fee',
|
|
116
118
|
LOCKED_SOL_FEE: 'Your linked SOL wallet does not have enough SOL for fee',
|
|
119
|
+
INSUFFICIENT_ARC_FEE: 'Your linked ARC wallet does not have enough USDC for fee',
|
|
120
|
+
LOCKED_ARC_FEE: 'Your linked ARC wallet USDC is locked by pending spend proposals',
|
|
117
121
|
INVALID_ADDRESS: 'Invalid address',
|
|
118
122
|
INVALID_CHANGE_ADDRESS: 'Invalid change address',
|
|
119
123
|
KEY_IN_COPAYER: 'Key already registered',
|
|
@@ -12,6 +12,7 @@ export interface IPreferences {
|
|
|
12
12
|
opTokenAddresses?: string[];
|
|
13
13
|
baseTokenAddresses?: string[];
|
|
14
14
|
arbTokenAddresses?: string[];
|
|
15
|
+
arcTokenAddresses?: string[];
|
|
15
16
|
solTokenAddresses?: string[];
|
|
16
17
|
multisigMaticInfo: object[];
|
|
17
18
|
}
|
|
@@ -29,6 +30,7 @@ export class Preferences {
|
|
|
29
30
|
opTokenAddresses: string[];
|
|
30
31
|
baseTokenAddresses: string[];
|
|
31
32
|
arbTokenAddresses: string[];
|
|
33
|
+
arcTokenAddresses: string[];
|
|
32
34
|
solTokenAddresses: string[];
|
|
33
35
|
multisigMaticInfo: object[];
|
|
34
36
|
|
|
@@ -50,6 +52,7 @@ export class Preferences {
|
|
|
50
52
|
x.opTokenAddresses = opts.opTokenAddresses;
|
|
51
53
|
x.baseTokenAddresses = opts.baseTokenAddresses;
|
|
52
54
|
x.arbTokenAddresses = opts.arbTokenAddresses;
|
|
55
|
+
x.arcTokenAddresses = opts.arcTokenAddresses;
|
|
53
56
|
x.solTokenAddresses = opts.solTokenAddresses;
|
|
54
57
|
x.multisigMaticInfo = opts.multisigMaticInfo;
|
|
55
58
|
// you can't put useDust here since this is copayer's specific.
|
|
@@ -72,6 +75,7 @@ export class Preferences {
|
|
|
72
75
|
x.opTokenAddresses = obj.opTokenAddresses;
|
|
73
76
|
x.baseTokenAddresses = obj.baseTokenAddresses;
|
|
74
77
|
x.arbTokenAddresses = obj.arbTokenAddresses;
|
|
78
|
+
x.arcTokenAddresses = obj.arcTokenAddresses;
|
|
75
79
|
x.solTokenAddresses = obj.solTokenAddresses;
|
|
76
80
|
x.multisigMaticInfo = obj.multisigMaticInfo;
|
|
77
81
|
return x;
|
package/src/lib/model/wallet.ts
CHANGED
|
@@ -474,6 +474,7 @@ export class PushNotificationsService {
|
|
|
474
474
|
bch: 'BCH',
|
|
475
475
|
eth: 'ETH',
|
|
476
476
|
matic: 'MATIC',
|
|
477
|
+
arc: 'USDC',
|
|
477
478
|
xrp: 'XRP',
|
|
478
479
|
doge: 'DOGE',
|
|
479
480
|
ltc: 'LTC',
|
|
@@ -748,6 +749,7 @@ export class PushNotificationsService {
|
|
|
748
749
|
arb: 'arbitrum-one',
|
|
749
750
|
base: 'base',
|
|
750
751
|
op: 'optimistic-ethereum',
|
|
752
|
+
arc: 'arc',
|
|
751
753
|
sol: 'solana',
|
|
752
754
|
};
|
|
753
755
|
|
|
@@ -461,7 +461,7 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
|
|
|
461
461
|
|
|
462
462
|
router.post('/v1/service/transak/orderDetails', cors(transakCorsOptions), (req, res) => {
|
|
463
463
|
respondWithAuthServer(req, res, context, server => {
|
|
464
|
-
return server.externalServices.transak.transakGetOrderDetails(req);
|
|
464
|
+
return server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
465
465
|
});
|
|
466
466
|
});
|
|
467
467
|
|
|
@@ -125,11 +125,13 @@ export function registerWalletDataRoutes(router: express.Router, context: RouteC
|
|
|
125
125
|
const opts: {
|
|
126
126
|
feePerKb?: number;
|
|
127
127
|
feeLevel?: number;
|
|
128
|
+
txType?: number;
|
|
128
129
|
returnInputs?: boolean;
|
|
129
130
|
excludeUnconfirmedUtxos?: boolean;
|
|
130
131
|
} = {};
|
|
131
132
|
if (query.feePerKb) opts.feePerKb = +query.feePerKb;
|
|
132
133
|
if (query.feeLevel) opts.feeLevel = Number(query.feeLevel);
|
|
134
|
+
if (query.txType != null) opts.txType = Number(query.txType);
|
|
133
135
|
if (query.excludeUnconfirmedUtxos == '1') opts.excludeUnconfirmedUtxos = true;
|
|
134
136
|
if (query.returnInputs == '1') opts.returnInputs = true;
|
|
135
137
|
|
package/src/lib/server.ts
CHANGED
|
@@ -71,6 +71,7 @@ const Bitcore_ = {
|
|
|
71
71
|
arb: Bitcore,
|
|
72
72
|
base: Bitcore,
|
|
73
73
|
op: Bitcore,
|
|
74
|
+
arc: Bitcore,
|
|
74
75
|
xrp: Bitcore,
|
|
75
76
|
doge: BitcoreDoge,
|
|
76
77
|
ltc: BitcoreLtc,
|
|
@@ -187,7 +188,7 @@ export class WalletService implements IWalletService {
|
|
|
187
188
|
sardine: new SardineService(),
|
|
188
189
|
simplex: new SimplexService(),
|
|
189
190
|
thorswap: new ThorswapService(),
|
|
190
|
-
transak: new TransakService(),
|
|
191
|
+
transak: new TransakService(this.storage, this.lock),
|
|
191
192
|
coinGecko: new CoinGeckoService(this.storage),
|
|
192
193
|
};
|
|
193
194
|
}
|
|
@@ -1330,6 +1331,7 @@ export class WalletService implements IWalletService {
|
|
|
1330
1331
|
* @param {string} opts.opTokenAddresses - Linked token addresses
|
|
1331
1332
|
* @param {string} opts.baseTokenAddresses - Linked token addresses
|
|
1332
1333
|
* @param {string} opts.arbTokenAddresses - Linked token addresses
|
|
1334
|
+
* @param {string[]} opts.arcTokenAddresses - Linked Arc ERC20 token addresses
|
|
1333
1335
|
* @param {string} opts.solTokenAddresses - Linked token addresses
|
|
1334
1336
|
* @param {string} opts.multisigMaticInfo - Linked multisig eth wallet info
|
|
1335
1337
|
*
|
|
@@ -1404,6 +1406,14 @@ export class WalletService implements IWalletService {
|
|
|
1404
1406
|
return Array.isArray(value) && value.every(x => Validation.validateAddress('arb', 'mainnet', x));
|
|
1405
1407
|
}
|
|
1406
1408
|
},
|
|
1409
|
+
{
|
|
1410
|
+
name: 'arcTokenAddresses',
|
|
1411
|
+
isValid(value) {
|
|
1412
|
+
return Array.isArray(value) && value.every(address =>
|
|
1413
|
+
Validation.validateAddress('arc', 'mainnet', address) &&
|
|
1414
|
+
address.toLowerCase() !== Constants.ARC.USDC_CONTRACT_ADDRESS);
|
|
1415
|
+
}
|
|
1416
|
+
},
|
|
1407
1417
|
{
|
|
1408
1418
|
name: 'solTokenAddresses',
|
|
1409
1419
|
isValid(value) {
|
|
@@ -1505,6 +1515,13 @@ export class WalletService implements IWalletService {
|
|
|
1505
1515
|
preferences.arbTokenAddresses = _.uniq(oldPref.arbTokenAddresses.concat(opts.arbTokenAddresses));
|
|
1506
1516
|
}
|
|
1507
1517
|
|
|
1518
|
+
if (opts.arcTokenAddresses) {
|
|
1519
|
+
oldPref = oldPref || {} as Preferences;
|
|
1520
|
+
oldPref.arcTokenAddresses = oldPref.arcTokenAddresses || [];
|
|
1521
|
+
preferences.arcTokenAddresses = _.uniq(oldPref.arcTokenAddresses.concat(opts.arcTokenAddresses)
|
|
1522
|
+
.map(address => address.toLowerCase()));
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1508
1525
|
if (opts.solTokenAddresses) {
|
|
1509
1526
|
oldPref = oldPref || {} as Preferences;
|
|
1510
1527
|
oldPref.solTokenAddresses = oldPref.solTokenAddresses || [];
|
|
@@ -2502,7 +2519,8 @@ export class WalletService implements IWalletService {
|
|
|
2502
2519
|
feePerKb: opts.feePerKb,
|
|
2503
2520
|
excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos,
|
|
2504
2521
|
returnInputs: true,
|
|
2505
|
-
numSignatures: opts.numSignatures
|
|
2522
|
+
numSignatures: opts.numSignatures,
|
|
2523
|
+
txType: opts.txType
|
|
2506
2524
|
},
|
|
2507
2525
|
(err, info) => {
|
|
2508
2526
|
if (err) return next(err);
|
|
@@ -39,6 +39,13 @@ describe('BlockChain explorer', function() {
|
|
|
39
39
|
exp2.should.respondTo('initSocket');
|
|
40
40
|
exp2.should.respondTo('register');
|
|
41
41
|
exp2.should.respondTo('addAddresses');
|
|
42
|
+
|
|
43
|
+
for (const [network, apiNetwork] of [['livenet', 'mainnet'], ['testnet', 'testnet']]) {
|
|
44
|
+
const opts = { coin: 'arc', network };
|
|
45
|
+
BlockChainExplorer(opts).baseUrl.should.equal(`https://api-eth.bitcore.io/api/ARC/${apiNetwork}`);
|
|
46
|
+
BlockChainExplorer({ ...opts, url: 'http://localhost:3000' })
|
|
47
|
+
.baseUrl.should.equal(`http://localhost:3000/api/ARC/${apiNetwork}`);
|
|
48
|
+
}
|
|
42
49
|
});
|
|
43
50
|
it('should fail on unsupported provider', function() {
|
|
44
51
|
try {
|
package/test/expressapp.test.ts
CHANGED
|
@@ -737,6 +737,37 @@ describe('ExpressApp', function() {
|
|
|
737
737
|
});
|
|
738
738
|
});
|
|
739
739
|
|
|
740
|
+
describe('Transak routes', function() {
|
|
741
|
+
it('/v1/service/transak/orderDetails should check the order against the authenticated wallet', function(done) {
|
|
742
|
+
const transakGetOrderDetails = sinon.stub().resolves({ data: { status: 'COMPLETED' } });
|
|
743
|
+
const server = {
|
|
744
|
+
walletId: 'authenticatedWalletId',
|
|
745
|
+
externalServices: { transak: { transakGetOrderDetails } },
|
|
746
|
+
};
|
|
747
|
+
sandbox.stub(WalletService, 'initialize').callsArg(1);
|
|
748
|
+
sandbox.stub(WalletService, 'getInstanceWithAuth').callsArgWith(1, null, server);
|
|
749
|
+
start(ExpressApp, function() {
|
|
750
|
+
const requestOptions = {
|
|
751
|
+
url: testHost + ':' + testPort + config.basePath + '/v1/service/transak/orderDetails',
|
|
752
|
+
method: 'post',
|
|
753
|
+
json: { env: 'sandbox', orderId: 'order1', walletId: 'someOtherWalletId' },
|
|
754
|
+
headers: {
|
|
755
|
+
'x-identity': 'identity',
|
|
756
|
+
'x-signature': 'signature'
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
request(requestOptions, function(err, res, body) {
|
|
760
|
+
should.not.exist(err);
|
|
761
|
+
res.statusCode.should.equal(200);
|
|
762
|
+
body.data.status.should.equal('COMPLETED');
|
|
763
|
+
transakGetOrderDetails.calledOnce.should.equal(true);
|
|
764
|
+
transakGetOrderDetails.firstCall.args[1].should.equal('authenticatedWalletId');
|
|
765
|
+
done();
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
|
|
740
771
|
describe('Moralis routes (wallet-authenticated or public)', function() {
|
|
741
772
|
const whitelistedOrigin = 'https://whitelisted-dapp.example';
|
|
742
773
|
let originalMoralisConfig;
|
|
@@ -224,6 +224,37 @@ describe('CoinGecko integration', function() {
|
|
|
224
224
|
should.exist(data);
|
|
225
225
|
});
|
|
226
226
|
|
|
227
|
+
it('should use the Arc platform for token lists and contract lookups', async () => {
|
|
228
|
+
forceGlobalCacheMisses();
|
|
229
|
+
const tokenAddress = '0x3600000000000000000000000000000000000000';
|
|
230
|
+
const otherToken = { chainId: 5042, address: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
231
|
+
name: 'Arc Test Token', symbol: 'TEST', decimals: 6, logoURI: '' };
|
|
232
|
+
const tokenList = [{ chainId: 5042, address: tokenAddress, symbol: 'USDC', decimals: 6 }, otherToken];
|
|
233
|
+
fakeRequest = createCoinGeckoRequestStub({
|
|
234
|
+
onTokenList: platformId => {
|
|
235
|
+
platformId.should.equal('arc');
|
|
236
|
+
return { tokens: tokenList };
|
|
237
|
+
},
|
|
238
|
+
onCoinContract: (platformId, address) => {
|
|
239
|
+
platformId.should.equal('arc');
|
|
240
|
+
address.should.equal(tokenAddress);
|
|
241
|
+
return { id: 'usd-coin', symbol: 'usdc', asset_platform_id: 'arc' };
|
|
242
|
+
},
|
|
243
|
+
onMarketChart: id => {
|
|
244
|
+
id.should.equal('usd-coin');
|
|
245
|
+
return { prices: [[1, 1]] };
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
cg().request = fakeRequest;
|
|
249
|
+
|
|
250
|
+
const tokens = await getTokens({ params: { chain: 'arc' } });
|
|
251
|
+
tokens.should.deep.equal([otherToken]);
|
|
252
|
+
cg().storage.checkAndUseGlobalCache.onSecondCall().callsFake((_key, _duration, cb) => cb(null, tokenList, null));
|
|
253
|
+
(await getTokens({ params: { chain: 'arc' } })).should.deep.equal([otherToken]);
|
|
254
|
+
const rates = await getFiatRates({ coin: 'USDC', chain: 'arc', tokenAddress });
|
|
255
|
+
rates.should.deep.equal([{ ts: 1, rate: 1 }]);
|
|
256
|
+
});
|
|
257
|
+
|
|
227
258
|
it('should map chain aliases to the correct token list platform', async () => {
|
|
228
259
|
forceGlobalCacheMisses();
|
|
229
260
|
|
|
@@ -315,6 +346,15 @@ describe('CoinGecko integration', function() {
|
|
|
315
346
|
data[0].low52w.should.equal(90);
|
|
316
347
|
});
|
|
317
348
|
|
|
349
|
+
it('should resolve native Arc market stats and fiat rates to USDC', async () => {
|
|
350
|
+
const getSpy = sandbox.spy(fakeRequest, 'get');
|
|
351
|
+
await getMarketStats({ coin: 'arc' });
|
|
352
|
+
parseRequestedIdsFromMarketsUrl(getSpy.firstCall.args[0]).should.deep.equal(['usd-coin']);
|
|
353
|
+
|
|
354
|
+
await getFiatRates({ coin: 'arc' });
|
|
355
|
+
matchCoinIdFromMarketChartUrl(getSpy.lastCall.args[0]).should.equal('usd-coin');
|
|
356
|
+
});
|
|
357
|
+
|
|
318
358
|
it('should reject non-default coin symbols without tokenAddress', async () => {
|
|
319
359
|
try {
|
|
320
360
|
await getMarketStats({ coin: 'WBTC' });
|
|
@@ -829,7 +869,7 @@ describe('CoinGecko integration', function() {
|
|
|
829
869
|
should.exist(data);
|
|
830
870
|
Object.keys(data)
|
|
831
871
|
.sort()
|
|
832
|
-
.should.deep.equal(['bch', 'btc', 'doge', 'eth', 'ltc', 'pol', 'sol', 'xrp']);
|
|
872
|
+
.should.deep.equal(['arc', 'bch', 'btc', 'doge', 'eth', 'ltc', 'pol', 'sol', 'xrp']);
|
|
833
873
|
});
|
|
834
874
|
|
|
835
875
|
it('should reject overly long coin symbols', async () => {
|
|
@@ -922,7 +962,7 @@ describe('CoinGecko integration', function() {
|
|
|
922
962
|
should.exist(data);
|
|
923
963
|
Object.keys(data)
|
|
924
964
|
.sort()
|
|
925
|
-
.should.deep.equal(['bch', 'btc', 'doge', 'eth', 'ltc', 'pol', 'sol', 'xrp']);
|
|
965
|
+
.should.deep.equal(['arc', 'bch', 'btc', 'doge', 'eth', 'ltc', 'pol', 'sol', 'xrp']);
|
|
926
966
|
|
|
927
967
|
data.btc.should.deep.equal([
|
|
928
968
|
{ ts: 1, rate: 100 },
|