@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.
Files changed (109) hide show
  1. package/package.json +4 -4
  2. package/src/config.ts +8 -0
  3. package/src/externalservices/coingecko.ts +6 -1
  4. package/src/externalservices/transak.ts +394 -70
  5. package/src/lib/blockchainexplorer.ts +4 -0
  6. package/src/lib/blockchainexplorers/v8.ts +1 -0
  7. package/src/lib/blockchainmonitor.ts +1 -0
  8. package/src/lib/chain/arc/index.ts +95 -0
  9. package/src/lib/chain/eth/index.ts +41 -23
  10. package/src/lib/chain/index.ts +2 -0
  11. package/src/lib/common/constants.ts +12 -1
  12. package/src/lib/common/defaults.ts +31 -0
  13. package/src/lib/emailservice.ts +3 -1
  14. package/src/lib/errors/errordefinitions.ts +4 -0
  15. package/src/lib/model/preferences.ts +4 -0
  16. package/src/lib/model/wallet.ts +1 -0
  17. package/src/lib/pushnotificationsservice.ts +2 -0
  18. package/src/lib/routes/services.ts +1 -1
  19. package/src/lib/routes/walletdata.ts +2 -0
  20. package/src/lib/server.ts +20 -2
  21. package/test/blockchainexplorer.test.ts +7 -0
  22. package/test/expressapp.test.ts +31 -0
  23. package/test/integration/externalservices/coinGecko.test.ts +42 -2
  24. package/test/integration/externalservices/transak.test.ts +545 -135
  25. package/test/integration/fiatrateservice.test.ts +13 -0
  26. package/test/integration/helpers.ts +1 -0
  27. package/test/integration/server.test.ts +139 -3
  28. package/ts_build/package.json +1 -1
  29. package/ts_build/src/config.d.ts.map +1 -1
  30. package/ts_build/src/config.js +8 -0
  31. package/ts_build/src/config.js.map +1 -1
  32. package/ts_build/src/externalservices/coingecko.d.ts.map +1 -1
  33. package/ts_build/src/externalservices/coingecko.js +6 -1
  34. package/ts_build/src/externalservices/coingecko.js.map +1 -1
  35. package/ts_build/src/externalservices/transak.d.ts +28 -2
  36. package/ts_build/src/externalservices/transak.d.ts.map +1 -1
  37. package/ts_build/src/externalservices/transak.js +274 -57
  38. package/ts_build/src/externalservices/transak.js.map +1 -1
  39. package/ts_build/src/lib/blockchainexplorer.d.ts.map +1 -1
  40. package/ts_build/src/lib/blockchainexplorer.js +4 -0
  41. package/ts_build/src/lib/blockchainexplorer.js.map +1 -1
  42. package/ts_build/src/lib/blockchainexplorers/v8.d.ts.map +1 -1
  43. package/ts_build/src/lib/blockchainexplorers/v8.js +1 -0
  44. package/ts_build/src/lib/blockchainexplorers/v8.js.map +1 -1
  45. package/ts_build/src/lib/blockchainmonitor.d.ts.map +1 -1
  46. package/ts_build/src/lib/blockchainmonitor.js +1 -0
  47. package/ts_build/src/lib/blockchainmonitor.js.map +1 -1
  48. package/ts_build/src/lib/chain/arc/index.d.ts +20 -0
  49. package/ts_build/src/lib/chain/arc/index.d.ts.map +1 -0
  50. package/ts_build/src/lib/chain/arc/index.js +95 -0
  51. package/ts_build/src/lib/chain/arc/index.js.map +1 -0
  52. package/ts_build/src/lib/chain/eth/index.d.ts +1 -0
  53. package/ts_build/src/lib/chain/eth/index.d.ts.map +1 -1
  54. package/ts_build/src/lib/chain/eth/index.js +38 -19
  55. package/ts_build/src/lib/chain/eth/index.js.map +1 -1
  56. package/ts_build/src/lib/chain/index.d.ts.map +1 -1
  57. package/ts_build/src/lib/chain/index.js +2 -0
  58. package/ts_build/src/lib/chain/index.js.map +1 -1
  59. package/ts_build/src/lib/common/constants.d.ts +12 -0
  60. package/ts_build/src/lib/common/constants.d.ts.map +1 -1
  61. package/ts_build/src/lib/common/constants.js +12 -1
  62. package/ts_build/src/lib/common/constants.js.map +1 -1
  63. package/ts_build/src/lib/common/defaults.d.ts +24 -0
  64. package/ts_build/src/lib/common/defaults.d.ts.map +1 -1
  65. package/ts_build/src/lib/common/defaults.js +30 -0
  66. package/ts_build/src/lib/common/defaults.js.map +1 -1
  67. package/ts_build/src/lib/common/index.d.ts +36 -0
  68. package/ts_build/src/lib/common/index.d.ts.map +1 -1
  69. package/ts_build/src/lib/emailservice.d.ts.map +1 -1
  70. package/ts_build/src/lib/emailservice.js +2 -0
  71. package/ts_build/src/lib/emailservice.js.map +1 -1
  72. package/ts_build/src/lib/errors/errordefinitions.d.ts +2 -0
  73. package/ts_build/src/lib/errors/errordefinitions.d.ts.map +1 -1
  74. package/ts_build/src/lib/errors/errordefinitions.js +2 -0
  75. package/ts_build/src/lib/errors/errordefinitions.js.map +1 -1
  76. package/ts_build/src/lib/model/preferences.d.ts +2 -0
  77. package/ts_build/src/lib/model/preferences.d.ts.map +1 -1
  78. package/ts_build/src/lib/model/preferences.js +3 -0
  79. package/ts_build/src/lib/model/preferences.js.map +1 -1
  80. package/ts_build/src/lib/model/wallet.d.ts.map +1 -1
  81. package/ts_build/src/lib/model/wallet.js +1 -0
  82. package/ts_build/src/lib/model/wallet.js.map +1 -1
  83. package/ts_build/src/lib/pushnotificationsservice.d.ts.map +1 -1
  84. package/ts_build/src/lib/pushnotificationsservice.js +2 -0
  85. package/ts_build/src/lib/pushnotificationsservice.js.map +1 -1
  86. package/ts_build/src/lib/routes/services.js +1 -1
  87. package/ts_build/src/lib/routes/services.js.map +1 -1
  88. package/ts_build/src/lib/routes/walletdata.d.ts.map +1 -1
  89. package/ts_build/src/lib/routes/walletdata.js +2 -0
  90. package/ts_build/src/lib/routes/walletdata.js.map +1 -1
  91. package/ts_build/src/lib/server.d.ts.map +1 -1
  92. package/ts_build/src/lib/server.js +17 -2
  93. package/ts_build/src/lib/server.js.map +1 -1
  94. package/ts_build/test/blockchainexplorer.test.js +6 -0
  95. package/ts_build/test/blockchainexplorer.test.js.map +1 -1
  96. package/ts_build/test/expressapp.test.js +30 -0
  97. package/ts_build/test/expressapp.test.js.map +1 -1
  98. package/ts_build/test/integration/externalservices/coinGecko.test.js +38 -2
  99. package/ts_build/test/integration/externalservices/coinGecko.test.js.map +1 -1
  100. package/ts_build/test/integration/externalservices/transak.test.d.ts.map +1 -1
  101. package/ts_build/test/integration/externalservices/transak.test.js +498 -128
  102. package/ts_build/test/integration/externalservices/transak.test.js.map +1 -1
  103. package/ts_build/test/integration/fiatrateservice.test.js +11 -0
  104. package/ts_build/test/integration/fiatrateservice.test.js.map +1 -1
  105. package/ts_build/test/integration/helpers.d.ts.map +1 -1
  106. package/ts_build/test/integration/helpers.js +1 -0
  107. package/ts_build/test/integration/helpers.js.map +1 -1
  108. package/ts_build/test/integration/server.test.js +135 -3
  109. 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
- txps.filter(txp => {
110
- // Add gas used for tokens when getting native balance
111
- if (!opts.tokenAddress) {
112
- fees += txp.fee ? BigInt(txp.fee) : 0n;
113
- }
114
- // Filter tokens when getting native balance
115
- if (txp.tokenAddress && !opts.tokenAddress) {
116
- return false;
117
- }
118
- amounts += txp.amount ? BigInt(txp.amount) : 0n;
119
- return true;
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
- // Use the explicit rate for both construction and the fee reserved from the wallet balance.
189
- const feeOpts = opts.gasPrice != null || hasType2FeeOverride
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
- 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 });
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
- const txpTotalAmount =
486
- (opts.multisigContractAddress || opts.tokenAddress) && txp.payProUrl
487
- ? getInvoiceValue(txp)
488
- : txp.getTotalAmountBigInt();
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);
@@ -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
@@ -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;
@@ -24,6 +24,7 @@ const Bitcore = {
24
24
  arb: BitcoreLib,
25
25
  base: BitcoreLib,
26
26
  op: BitcoreLib,
27
+ arc: BitcoreLib,
27
28
  xrp: BitcoreLib,
28
29
  doge: BitcoreLibDoge,
29
30
  ltc: BitcoreLibLtc,
@@ -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 {
@@ -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 },