@bitpay-labs/bitcore-wallet-service 11.10.1 → 11.10.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/bws.example.config.js +4 -3
- package/package.json +5 -5
- package/src/config.ts +4 -3
- package/src/externalservices/moonpay.ts +10 -0
- package/src/externalservices/transak.ts +20 -28
- package/src/lib/blockchainexplorers/v8.ts +19 -0
- package/src/lib/chain/xrp/index.ts +13 -8
- package/src/lib/common/defaults.ts +3 -0
- package/src/lib/common/utils.ts +3 -3
- package/src/lib/emailservice.ts +2 -1
- package/src/lib/model/txproposal.ts +4 -0
- package/src/lib/pushnotificationsservice.ts +2 -1
- package/src/lib/routes/services.ts +22 -6
- package/src/lib/routes/tss.ts +1 -1
- package/src/lib/routes/wallets.ts +16 -1
- package/src/lib/server.ts +47 -2
- package/test/chain/xrp.test.ts +21 -0
- package/test/integration/externalservices/moonpay.test.ts +14 -2
- package/test/integration/server.test.ts +138 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/config.d.ts.map +1 -1
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
- package/ts_build/src/externalservices/moonpay.js +6 -0
- package/ts_build/src/externalservices/moonpay.js.map +1 -1
- package/ts_build/src/externalservices/transak.d.ts.map +1 -1
- package/ts_build/src/externalservices/transak.js +20 -30
- package/ts_build/src/externalservices/transak.js.map +1 -1
- package/ts_build/src/lib/blockchainexplorers/v8.d.ts +3 -0
- package/ts_build/src/lib/blockchainexplorers/v8.d.ts.map +1 -1
- package/ts_build/src/lib/blockchainexplorers/v8.js +18 -0
- package/ts_build/src/lib/blockchainexplorers/v8.js.map +1 -1
- package/ts_build/src/lib/chain/xrp/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/xrp/index.js +12 -8
- package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
- package/ts_build/src/lib/common/defaults.d.ts +1 -0
- package/ts_build/src/lib/common/defaults.d.ts.map +1 -1
- package/ts_build/src/lib/common/defaults.js +1 -0
- package/ts_build/src/lib/common/defaults.js.map +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 -1
- package/ts_build/src/lib/common/utils.d.ts.map +1 -1
- package/ts_build/src/lib/common/utils.js.map +1 -1
- package/ts_build/src/lib/emailservice.d.ts.map +1 -1
- package/ts_build/src/lib/emailservice.js +1 -1
- package/ts_build/src/lib/emailservice.js.map +1 -1
- package/ts_build/src/lib/model/txproposal.d.ts +2 -0
- package/ts_build/src/lib/model/txproposal.d.ts.map +1 -1
- package/ts_build/src/lib/model/txproposal.js +3 -0
- package/ts_build/src/lib/model/txproposal.js.map +1 -1
- package/ts_build/src/lib/pushnotificationsservice.d.ts.map +1 -1
- package/ts_build/src/lib/pushnotificationsservice.js +1 -1
- package/ts_build/src/lib/pushnotificationsservice.js.map +1 -1
- package/ts_build/src/lib/routes/services.d.ts.map +1 -1
- package/ts_build/src/lib/routes/services.js +22 -6
- package/ts_build/src/lib/routes/services.js.map +1 -1
- package/ts_build/src/lib/routes/tss.js +0 -1
- 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 +15 -0
- package/ts_build/src/lib/routes/wallets.js.map +1 -1
- package/ts_build/src/lib/server.d.ts +3 -0
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +41 -2
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/test/chain/xrp.test.js +19 -0
- package/ts_build/test/chain/xrp.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/moonpay.test.js +14 -2
- package/ts_build/test/integration/externalservices/moonpay.test.js.map +1 -1
- package/ts_build/test/integration/server.test.js +115 -0
- package/ts_build/test/integration/server.test.js.map +1 -1
package/bws.example.config.js
CHANGED
|
@@ -325,7 +325,7 @@ module.exports = {
|
|
|
325
325
|
// publicKey: 'simplex_web_public_key_here',
|
|
326
326
|
// }
|
|
327
327
|
// },
|
|
328
|
-
// thorswap
|
|
328
|
+
// thorswap: {
|
|
329
329
|
// sandbox: {
|
|
330
330
|
// api: 'https://dev-api.thorswap.net',
|
|
331
331
|
// apiKey: 'thorswap_sandbox_api_key_here',
|
|
@@ -339,7 +339,7 @@ module.exports = {
|
|
|
339
339
|
// referer: 'thorswap_production_referer_here'
|
|
340
340
|
// },
|
|
341
341
|
// },
|
|
342
|
-
// transak
|
|
342
|
+
// transak: {
|
|
343
343
|
// sandbox: {
|
|
344
344
|
// api: 'https://api-stg.transak.com',
|
|
345
345
|
// widgetApi: 'https://api-gateway-stg.transak.com',
|
|
@@ -363,7 +363,8 @@ module.exports = {
|
|
|
363
363
|
// widgetApi: 'https://api-gateway.transak.com',
|
|
364
364
|
// apiKey: 'transak_production_web_api_key_here',
|
|
365
365
|
// secretKey: 'transak_production_web_secret_key_here',
|
|
366
|
-
// }
|
|
366
|
+
// },
|
|
367
|
+
// whitelist: []
|
|
367
368
|
// },
|
|
368
369
|
// wyre: {
|
|
369
370
|
// sandbox: {
|
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.10.
|
|
5
|
+
"version": "11.10.3",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "ts_build/src/index.js",
|
|
8
8
|
"types": "./ts_build/src/index.d.ts",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"cp:templates": "cp -r ./templates ./ts_build/templates"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@bitpay-labs/bitcore-logging": "^11.
|
|
47
|
-
"@bitpay-labs/bitcore-tss": "^11.10.
|
|
48
|
-
"@bitpay-labs/crypto-wallet-core": "^11.10.
|
|
46
|
+
"@bitpay-labs/bitcore-logging": "^11.10.3",
|
|
47
|
+
"@bitpay-labs/bitcore-tss": "^11.10.3",
|
|
48
|
+
"@bitpay-labs/crypto-wallet-core": "^11.10.3",
|
|
49
49
|
"@sendgrid/mail": "6.5.4",
|
|
50
50
|
"abi-decoder": "2.4.0",
|
|
51
51
|
"async": "0.9.2",
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"email": "kjoseph@bitpay.com"
|
|
127
127
|
}
|
|
128
128
|
],
|
|
129
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "70298eedd1e458b23139e275125a07c577219bac"
|
|
130
130
|
}
|
package/src/config.ts
CHANGED
|
@@ -400,7 +400,7 @@ const Config = (): any => {
|
|
|
400
400
|
// publicKey: 'simplex_web_public_key_here',
|
|
401
401
|
// }
|
|
402
402
|
// },
|
|
403
|
-
// thorswap
|
|
403
|
+
// thorswap: {
|
|
404
404
|
// sandbox: {
|
|
405
405
|
// api: 'https://dev-api.thorswap.net',
|
|
406
406
|
// apiKey: 'thorswap_sandbox_api_key_here',
|
|
@@ -414,7 +414,7 @@ const Config = (): any => {
|
|
|
414
414
|
// referer: 'thorswap_production_referer_here'
|
|
415
415
|
// },
|
|
416
416
|
// },
|
|
417
|
-
// transak
|
|
417
|
+
// transak: {
|
|
418
418
|
// sandbox: {
|
|
419
419
|
// api: 'https://api-stg.transak.com',
|
|
420
420
|
// widgetApi: 'https://global-stg.transak.com',
|
|
@@ -438,7 +438,8 @@ const Config = (): any => {
|
|
|
438
438
|
// widgetApi: 'https://global.transak.com',
|
|
439
439
|
// apiKey: 'transak_production_web_api_key_here',
|
|
440
440
|
// secretKey: 'transak_production_web_secret_key_here',
|
|
441
|
-
// }
|
|
441
|
+
// },
|
|
442
|
+
// whitelist: []
|
|
442
443
|
// },
|
|
443
444
|
// wyre: {
|
|
444
445
|
// sandbox: {
|
|
@@ -225,6 +225,16 @@ export class MoonpayService {
|
|
|
225
225
|
if (req.body.paymentMethod) qs.push('paymentMethod=' + encodeURIComponent(req.body.paymentMethod));
|
|
226
226
|
if (req.body.areFeesIncluded) qs.push('areFeesIncluded=' + encodeURIComponent(req.body.areFeesIncluded));
|
|
227
227
|
|
|
228
|
+
const deviceIp = Utils.getIpFromReq(req);
|
|
229
|
+
if (!deviceIp) {
|
|
230
|
+
throw new ClientError('Could not determine device IP address');
|
|
231
|
+
}
|
|
232
|
+
const allowedIpAddress: string = Bitcore.crypto.Hash.sha256hmac(
|
|
233
|
+
Buffer.from(deviceIp),
|
|
234
|
+
Buffer.from(SECRET_KEY)
|
|
235
|
+
).toString('base64');
|
|
236
|
+
qs.push('allowedIpAddress=' + encodeURIComponent(allowedIpAddress));
|
|
237
|
+
|
|
228
238
|
const URL_SEARCH: string = `?${qs.join('&')}`;
|
|
229
239
|
|
|
230
240
|
const URLSignatureHash: string = Bitcore.crypto.Hash.sha256hmac(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as request from 'request';
|
|
2
2
|
import config from '../config';
|
|
3
|
+
import { Utils } from '../lib/common/utils';
|
|
3
4
|
import { ClientError } from '../lib/errors/clienterror';
|
|
4
5
|
import { checkRequired } from '../lib/server';
|
|
5
6
|
|
|
@@ -50,6 +51,8 @@ export class TransakService {
|
|
|
50
51
|
const headers = {
|
|
51
52
|
'Content-Type': 'application/json',
|
|
52
53
|
'api-secret': SECRET_KEY,
|
|
54
|
+
'x-api-key': API_KEY,
|
|
55
|
+
'x-user-ip': Utils.getIpFromReq(req),
|
|
53
56
|
};
|
|
54
57
|
|
|
55
58
|
const body = {
|
|
@@ -85,10 +88,13 @@ export class TransakService {
|
|
|
85
88
|
return reject(err);
|
|
86
89
|
}
|
|
87
90
|
const API = keys.API;
|
|
91
|
+
const API_KEY = keys.API_KEY;
|
|
88
92
|
|
|
89
93
|
const headers = {
|
|
90
94
|
'Accept': 'application/json',
|
|
91
95
|
'Content-Type': 'application/json',
|
|
96
|
+
'x-api-key': API_KEY,
|
|
97
|
+
'x-user-ip': Utils.getIpFromReq(req),
|
|
92
98
|
};
|
|
93
99
|
|
|
94
100
|
const URL: string = API + '/api/v2/currencies/crypto-currencies';
|
|
@@ -124,6 +130,8 @@ export class TransakService {
|
|
|
124
130
|
const headers = {
|
|
125
131
|
'Accept': 'application/json',
|
|
126
132
|
'Content-Type': 'application/json',
|
|
133
|
+
'x-api-key': API_KEY,
|
|
134
|
+
'x-user-ip': Utils.getIpFromReq(req),
|
|
127
135
|
};
|
|
128
136
|
|
|
129
137
|
const URL: string = API + `/api/v2/currencies/fiat-currencies?apiKey=${API_KEY}`;
|
|
@@ -162,6 +170,8 @@ export class TransakService {
|
|
|
162
170
|
|
|
163
171
|
const headers = {
|
|
164
172
|
Accept: 'application/json',
|
|
173
|
+
'x-api-key': API_KEY,
|
|
174
|
+
'x-user-ip': Utils.getIpFromReq(req),
|
|
165
175
|
};
|
|
166
176
|
|
|
167
177
|
const qs: string[] = [];
|
|
@@ -197,6 +207,7 @@ export class TransakService {
|
|
|
197
207
|
transakGetSignedPaymentUrl(req): Promise<{ urlWithSignature: string }> {
|
|
198
208
|
return new Promise(async (resolve, reject) => {
|
|
199
209
|
const appRequiredParams = [
|
|
210
|
+
'accessToken',
|
|
200
211
|
'walletAddress',
|
|
201
212
|
'redirectURL',
|
|
202
213
|
'fiatAmount',
|
|
@@ -207,7 +218,7 @@ export class TransakService {
|
|
|
207
218
|
'partnerCustomerId',
|
|
208
219
|
];
|
|
209
220
|
|
|
210
|
-
const requiredParams = req.body.context === 'web' ? [] : appRequiredParams;
|
|
221
|
+
const requiredParams = req.body.context === 'web' ? ['accessToken'] : appRequiredParams;
|
|
211
222
|
const referrerDomain = req.body.referrerDomain ?? req.body.context === 'web' ? 'bitpay.com' : 'bitpay';
|
|
212
223
|
let keys;
|
|
213
224
|
try {
|
|
@@ -222,22 +233,12 @@ export class TransakService {
|
|
|
222
233
|
return reject(new ClientError("Transak's request missing arguments"));
|
|
223
234
|
}
|
|
224
235
|
|
|
225
|
-
let accessToken;
|
|
226
|
-
if (req.body.accessToken) {
|
|
227
|
-
accessToken = req.body.accessToken;
|
|
228
|
-
} else {
|
|
229
|
-
try {
|
|
230
|
-
const accessTokenData = await this.transakGetAccessToken(req);
|
|
231
|
-
accessToken = accessTokenData?.data?.accessToken;
|
|
232
|
-
} catch (err) {
|
|
233
|
-
return reject(err?.body ? err.body : err);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
236
|
const headers = {
|
|
238
237
|
Accept: 'application/json',
|
|
239
238
|
'Content-Type': 'application/json',
|
|
240
|
-
'access-token': accessToken,
|
|
239
|
+
'access-token': req.body.accessToken,
|
|
240
|
+
'x-api-key': API_KEY,
|
|
241
|
+
'x-user-ip': Utils.getIpFromReq(req),
|
|
241
242
|
};
|
|
242
243
|
|
|
243
244
|
const body = {
|
|
@@ -277,26 +278,17 @@ export class TransakService {
|
|
|
277
278
|
return reject(err);
|
|
278
279
|
}
|
|
279
280
|
const API = keys.API;
|
|
281
|
+
const API_KEY = keys.API_KEY;
|
|
280
282
|
|
|
281
|
-
if (!checkRequired(req.body, ['orderId'])) {
|
|
283
|
+
if (!checkRequired(req.body, ['orderId', 'accessToken'])) {
|
|
282
284
|
return reject(new ClientError("Transak's request missing arguments"));
|
|
283
285
|
}
|
|
284
286
|
|
|
285
|
-
let accessToken;
|
|
286
|
-
if (req.body.accessToken) {
|
|
287
|
-
accessToken = req.body.accessToken;
|
|
288
|
-
} else {
|
|
289
|
-
try {
|
|
290
|
-
const accessTokenData = await this.transakGetAccessToken(req);
|
|
291
|
-
accessToken = accessTokenData?.data?.accessToken;
|
|
292
|
-
} catch (err) {
|
|
293
|
-
return reject(err?.body ? err.body : err);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
287
|
const headers = {
|
|
298
288
|
Accept: 'application/json',
|
|
299
|
-
'access-token': accessToken,
|
|
289
|
+
'access-token': req.body.accessToken,
|
|
290
|
+
'x-api-key': API_KEY,
|
|
291
|
+
'x-user-ip': Utils.getIpFromReq(req),
|
|
300
292
|
};
|
|
301
293
|
|
|
302
294
|
const URL: string = API + `/partners/api/v2/order/${req.body.orderId}`;
|
|
@@ -43,6 +43,13 @@ function v8network(bwsNetwork, chain = 'btc') {
|
|
|
43
43
|
return bwsNetwork;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function scrubResponseError(err) {
|
|
47
|
+
if (err.response) {
|
|
48
|
+
return new Error(`HTTP ${err.response.statusCode} - ${err.response.statusMessage}: ${err.response.body}\n${err.response.request.method} ${err.response.request.href}`);
|
|
49
|
+
}
|
|
50
|
+
return new Error(err);
|
|
51
|
+
}
|
|
52
|
+
|
|
46
53
|
export type WalletWithOpts = IWallet & { tokenAddress?: string; multisigContractAddress?: string };
|
|
47
54
|
|
|
48
55
|
export class V8 {
|
|
@@ -765,6 +772,18 @@ export class V8 {
|
|
|
765
772
|
return callbacks.onIncomingPayments(notification);
|
|
766
773
|
});
|
|
767
774
|
}
|
|
775
|
+
|
|
776
|
+
async getFlags(opts: { address: string }) {
|
|
777
|
+
try {
|
|
778
|
+
const url = `${this.baseUrl}/address/${opts.address}/flags`;
|
|
779
|
+
const ret = await this.request.get(url);
|
|
780
|
+
return JSON.parse(ret).flags;
|
|
781
|
+
} catch (err) {
|
|
782
|
+
const e = scrubResponseError(err);
|
|
783
|
+
logger.error(`Error getting flags for address ${opts.address}: %o`, e);
|
|
784
|
+
throw e;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
768
787
|
}
|
|
769
788
|
|
|
770
789
|
const getPerformanceKey = (name: string) => {
|
|
@@ -90,14 +90,19 @@ export class XrpChain implements IChain {
|
|
|
90
90
|
getWalletSendMaxInfo(server, wallet, opts, cb) {
|
|
91
91
|
server.getBalance({}, (err, balance) => {
|
|
92
92
|
if (err) return cb(err);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
|
|
94
|
+
server._getFeePerKb(wallet, opts, (err, feePerKb) => {
|
|
95
|
+
if (err) return cb(err);
|
|
96
|
+
|
|
97
|
+
const { availableAmount } = balance;
|
|
98
|
+
const fee = feePerKb;
|
|
99
|
+
return cb(null, {
|
|
100
|
+
utxosBelowFee: 0,
|
|
101
|
+
amountBelowFee: 0,
|
|
102
|
+
amount: availableAmount - fee,
|
|
103
|
+
feePerKb,
|
|
104
|
+
fee
|
|
105
|
+
});
|
|
101
106
|
});
|
|
102
107
|
});
|
|
103
108
|
}
|
|
@@ -19,6 +19,9 @@ export const Defaults = {
|
|
|
19
19
|
// TODO: should allow different gap sizes for external/internal chains
|
|
20
20
|
SCAN_ADDRESS_GAP: 30,
|
|
21
21
|
|
|
22
|
+
// Lookback window (in seconds) for scanning broadcasted txps during nonce assignment
|
|
23
|
+
BROADCASTED_NONCE_SCAN_WINDOW: 24 * 3600,
|
|
24
|
+
|
|
22
25
|
FEE_LEVELS: {
|
|
23
26
|
btc: [
|
|
24
27
|
{
|
package/src/lib/common/utils.ts
CHANGED
|
@@ -81,11 +81,11 @@ export const Utils = {
|
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
*
|
|
84
|
-
* @desc rounds a
|
|
85
|
-
* @param number
|
|
84
|
+
* @desc rounds a Javascript number
|
|
85
|
+
* @param {number} number
|
|
86
86
|
* @return {number}
|
|
87
87
|
*/
|
|
88
|
-
strip(number) {
|
|
88
|
+
strip(number: number): number {
|
|
89
89
|
return parseFloat(number.toPrecision(12));
|
|
90
90
|
},
|
|
91
91
|
|
package/src/lib/emailservice.ts
CHANGED
|
@@ -561,7 +561,8 @@ export class EmailService {
|
|
|
561
561
|
_checkShouldSendEmail(notification: Notification, cb) {
|
|
562
562
|
if (notification.type != 'NewTxProposal') return cb(null, true);
|
|
563
563
|
this.storage.fetchWallet(notification.walletId, (err, wallet) => {
|
|
564
|
-
|
|
564
|
+
// TSS wallets are stored as m=1 but still need other signers
|
|
565
|
+
return cb(err, wallet && (wallet.m > 1 || !!wallet.tssKeyId));
|
|
565
566
|
});
|
|
566
567
|
}
|
|
567
568
|
|
|
@@ -81,6 +81,7 @@ export interface ITxProposal<NumberType = number> {
|
|
|
81
81
|
multisigTxId?: string;
|
|
82
82
|
destinationTag?: string;
|
|
83
83
|
invoiceID?: string;
|
|
84
|
+
flags?: string; // Comma-delimited list of XRP account flags to set (e.g. "tfRequireDestTag,tfDisallowXRP")
|
|
84
85
|
lockUntilBlockHeight?: NumberType;
|
|
85
86
|
instantAcceptanceEscrow?: NumberType;
|
|
86
87
|
isTokenSwap?: boolean;
|
|
@@ -175,6 +176,7 @@ export class TxProposal<NumberType = number> implements ITxProposal<NumberType>
|
|
|
175
176
|
multisigTxId?: string;
|
|
176
177
|
destinationTag?: string;
|
|
177
178
|
invoiceID?: string;
|
|
179
|
+
flags?: string; // Comma-delimited list of XRP account flags to set (e.g. "tfRequireDestTag,tfDisallowXRP")
|
|
178
180
|
lockUntilBlockHeight?: NumberType;
|
|
179
181
|
instantAcceptanceEscrow?: NumberType;
|
|
180
182
|
isTokenSwap?: boolean;
|
|
@@ -305,6 +307,7 @@ export class TxProposal<NumberType = number> implements ITxProposal<NumberType>
|
|
|
305
307
|
x.destinationTag = opts.destinationTag;
|
|
306
308
|
x.invoiceID = opts.invoiceID;
|
|
307
309
|
x.multiTx = opts.multiTx; // proposal contains multiple transactions
|
|
310
|
+
x.flags = opts.flags; // XRP account flags to set
|
|
308
311
|
|
|
309
312
|
// SOL
|
|
310
313
|
x.space = opts.space; // space to allocate for account creation
|
|
@@ -400,6 +403,7 @@ export class TxProposal<NumberType = number> implements ITxProposal<NumberType>
|
|
|
400
403
|
x.destinationTag = obj.destinationTag;
|
|
401
404
|
x.invoiceID = obj.invoiceID;
|
|
402
405
|
x.multiTx = obj.multiTx;
|
|
406
|
+
x.flags = obj.flags; // XRP account flags to set
|
|
403
407
|
|
|
404
408
|
// SOL
|
|
405
409
|
x.space = obj.space; // space to allocate for account creation
|
|
@@ -358,7 +358,8 @@ export class PushNotificationsService {
|
|
|
358
358
|
_checkShouldSendNotif(notification, cb) {
|
|
359
359
|
if (notification.type != 'NewTxProposal') return cb(null, true);
|
|
360
360
|
this.storage.fetchWallet(notification.walletId, (err, wallet) => {
|
|
361
|
-
|
|
361
|
+
// TSS wallets are stored as m=1 but still need other signers
|
|
362
|
+
return cb(err, wallet && (wallet.m > 1 || !!wallet.tssKeyId));
|
|
362
363
|
});
|
|
363
364
|
}
|
|
364
365
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import cors from 'cors';
|
|
1
2
|
import express from 'express';
|
|
2
3
|
import rp from 'request-promise-native';
|
|
4
|
+
import config from '../../config';
|
|
3
5
|
import { Common } from '../common';
|
|
4
6
|
import type * as Types from '../../types/expressapp';
|
|
5
7
|
import type { WalletService } from '../server';
|
|
@@ -65,6 +67,20 @@ function respondWithAuthServer(req, res, context: RouteContext, handler: RouteHa
|
|
|
65
67
|
export function registerServiceRoutes(router: express.Router, context: RouteContext) {
|
|
66
68
|
const { getServerWithAuth, setPublicCache, returnError } = context;
|
|
67
69
|
|
|
70
|
+
const transakCorsOptions = {
|
|
71
|
+
origin: (origin, cb) => {
|
|
72
|
+
if (!origin) {
|
|
73
|
+
return cb(null, true);
|
|
74
|
+
}
|
|
75
|
+
const transakWhiteList = config.transak?.whitelist ?? [];
|
|
76
|
+
if (transakWhiteList.indexOf(origin) !== -1) {
|
|
77
|
+
cb(null, true);
|
|
78
|
+
} else {
|
|
79
|
+
cb(new Error('Not allowed by CORS'));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
68
84
|
router.get('/latest-version', async (req, res) => {
|
|
69
85
|
setPublicCache(res, 10 * ONE_MINUTE);
|
|
70
86
|
try {
|
|
@@ -420,37 +436,37 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
|
|
|
420
436
|
});
|
|
421
437
|
});
|
|
422
438
|
|
|
423
|
-
router.post('/v1/service/transak/getAccessToken', (req, res) => {
|
|
439
|
+
router.post('/v1/service/transak/getAccessToken', cors(transakCorsOptions), (req, res) => {
|
|
424
440
|
respondWithAuthServer(req, res, context, server => {
|
|
425
441
|
return server.externalServices.transak.transakGetAccessToken(req);
|
|
426
442
|
});
|
|
427
443
|
});
|
|
428
444
|
|
|
429
|
-
router.post('/v1/service/transak/cryptoCurrencies', (req, res) => {
|
|
445
|
+
router.post('/v1/service/transak/cryptoCurrencies', cors(transakCorsOptions), (req, res) => {
|
|
430
446
|
respondWithPublicServer(req, res, context, server => {
|
|
431
447
|
return server.externalServices.transak.transakGetCryptoCurrencies(req);
|
|
432
448
|
});
|
|
433
449
|
});
|
|
434
450
|
|
|
435
|
-
router.post('/v1/service/transak/fiatCurrencies', (req, res) => {
|
|
451
|
+
router.post('/v1/service/transak/fiatCurrencies', cors(transakCorsOptions), (req, res) => {
|
|
436
452
|
respondWithPublicServer(req, res, context, server => {
|
|
437
453
|
return server.externalServices.transak.transakGetFiatCurrencies(req);
|
|
438
454
|
});
|
|
439
455
|
});
|
|
440
456
|
|
|
441
|
-
router.post('/v1/service/transak/quote', (req, res) => {
|
|
457
|
+
router.post('/v1/service/transak/quote', cors(transakCorsOptions), (req, res) => {
|
|
442
458
|
respondWithAuthServer(req, res, context, server => {
|
|
443
459
|
return server.externalServices.transak.transakGetQuote(req);
|
|
444
460
|
});
|
|
445
461
|
});
|
|
446
462
|
|
|
447
|
-
router.post('/v1/service/transak/signedPaymentUrl', (req, res) => {
|
|
463
|
+
router.post('/v1/service/transak/signedPaymentUrl', cors(transakCorsOptions), (req, res) => {
|
|
448
464
|
respondWithAuthServer(req, res, context, server => {
|
|
449
465
|
return server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
450
466
|
});
|
|
451
467
|
});
|
|
452
468
|
|
|
453
|
-
router.post('/v1/service/transak/orderDetails', (req, res) => {
|
|
469
|
+
router.post('/v1/service/transak/orderDetails', cors(transakCorsOptions), (req, res) => {
|
|
454
470
|
respondWithPublicServer(req, res, context, server => {
|
|
455
471
|
return server.externalServices.transak.transakGetOrderDetails(req);
|
|
456
472
|
});
|
package/src/lib/routes/tss.ts
CHANGED
|
@@ -291,4 +291,19 @@ export function registerWalletRoutes(router: express.Router, context: RouteConte
|
|
|
291
291
|
});
|
|
292
292
|
});
|
|
293
293
|
});
|
|
294
|
-
|
|
294
|
+
|
|
295
|
+
router.get('/v1/flags', (req, res) => {
|
|
296
|
+
getServerWithAuth(req, res, async server => {
|
|
297
|
+
try {
|
|
298
|
+
const opts = { account: 0 };
|
|
299
|
+
if (req.query.account) {
|
|
300
|
+
opts.account = parseInt(req.query.account);
|
|
301
|
+
}
|
|
302
|
+
const flags = await server.getFlags(opts);
|
|
303
|
+
res.json(flags);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
returnError(err, res, req);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
}
|
package/src/lib/server.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import util from 'util';
|
|
1
2
|
import {
|
|
2
3
|
BitcoreLib as Bitcore,
|
|
3
4
|
BitcoreLibCash as BitcoreCash,
|
|
@@ -2924,7 +2925,8 @@ export class WalletService implements IWalletService {
|
|
|
2924
2925
|
fromAta: opts.fromAta,
|
|
2925
2926
|
decimals: opts.decimals,
|
|
2926
2927
|
refreshOnPublish: opts.refreshOnPublish,
|
|
2927
|
-
deferNonce: opts.deferNonce
|
|
2928
|
+
deferNonce: opts.deferNonce,
|
|
2929
|
+
flags: opts.flags
|
|
2928
2930
|
};
|
|
2929
2931
|
txp = TxProposal.create(txOpts);
|
|
2930
2932
|
next();
|
|
@@ -3358,6 +3360,19 @@ export class WalletService implements IWalletService {
|
|
|
3358
3360
|
return cb(Errors.TX_NONCE_CONFLICT);
|
|
3359
3361
|
}
|
|
3360
3362
|
}
|
|
3363
|
+
// A deferred txp matching a broadcasted txp's nonce means its JIT-assigned
|
|
3364
|
+
// value went stale. Non-deferred txps may reuse an in-flight nonce
|
|
3365
|
+
// intentionally (e.g. speed-up).
|
|
3366
|
+
if (txp.nonce != null && txp.deferNonce) {
|
|
3367
|
+
const broadcastedTxps = await util.promisify(this.storage.fetchBroadcastedTxs).call(this.storage, this.walletId, {
|
|
3368
|
+
minTs: Math.floor(Date.now() / 1000) - Defaults.BROADCASTED_NONCE_SCAN_WINDOW
|
|
3369
|
+
});
|
|
3370
|
+
for (const t of broadcastedTxps || []) {
|
|
3371
|
+
if (t.id !== txp.id && t.nonce != null && Number(t.nonce) === Number(txp.nonce)) {
|
|
3372
|
+
return cb(Errors.TX_NONCE_CONFLICT);
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3361
3376
|
} catch (err) {
|
|
3362
3377
|
return cb(err);
|
|
3363
3378
|
}
|
|
@@ -3486,9 +3501,18 @@ export class WalletService implements IWalletService {
|
|
|
3486
3501
|
.filter(t => t.id !== txp.id && t.nonce != null && t.status !== 'rejected')
|
|
3487
3502
|
.map(t => Number(t.nonce));
|
|
3488
3503
|
|
|
3504
|
+
// 2b. Broadcasted-but-unmined txps still occupy their nonces but appear in
|
|
3505
|
+
// neither the confirmed count nor the pending set
|
|
3506
|
+
const broadcastedTxps = await util.promisify(this.storage.fetchBroadcastedTxs).call(this.storage, this.walletId, {
|
|
3507
|
+
minTs: Math.floor(Date.now() / 1000) - Defaults.BROADCASTED_NONCE_SCAN_WINDOW
|
|
3508
|
+
});
|
|
3509
|
+
const inFlightNonces = (broadcastedTxps || [])
|
|
3510
|
+
.filter(t => t.nonce != null && Number(t.nonce) >= Number(confirmedNonce))
|
|
3511
|
+
.map(t => Number(t.nonce));
|
|
3512
|
+
|
|
3489
3513
|
// 3. Calculate gap-free nonce
|
|
3490
3514
|
let suggestedNonce = Number(confirmedNonce);
|
|
3491
|
-
const allNonces = pendingNonces.sort((a, b) => a - b);
|
|
3515
|
+
const allNonces = [...pendingNonces, ...inFlightNonces].sort((a, b) => a - b);
|
|
3492
3516
|
for (const n of allNonces) {
|
|
3493
3517
|
if (n === suggestedNonce) {
|
|
3494
3518
|
suggestedNonce++;
|
|
@@ -5303,6 +5327,27 @@ export class WalletService implements IWalletService {
|
|
|
5303
5327
|
});
|
|
5304
5328
|
}
|
|
5305
5329
|
|
|
5330
|
+
async getFlags(opts: { account: number }) {
|
|
5331
|
+
try {
|
|
5332
|
+
const wallet = await util.promisify(this.getWallet).call(this, {});
|
|
5333
|
+
if (wallet.chain !== 'xrp') throw new Error('Flags are only supported for XRP wallets');
|
|
5334
|
+
|
|
5335
|
+
const addresses = await util.promisify(this.storage.fetchAddresses).call(this.storage, this.walletId);
|
|
5336
|
+
const addressObj = addresses.find(a => a.path === `m/0/${opts.account || 0}`);
|
|
5337
|
+
if (!addressObj) throw new Error('Could not find address for account ' + opts.account);
|
|
5338
|
+
|
|
5339
|
+
const bc = this._getBlockchainExplorer(wallet.chain, wallet.network);
|
|
5340
|
+
if (!bc) throw new Error('Could not get blockchain explorer instance');
|
|
5341
|
+
|
|
5342
|
+
const address = addressObj.address.split(':')[0]; // Remove testnet suffix
|
|
5343
|
+
const flags = await bc.getFlags({ address });
|
|
5344
|
+
return flags;
|
|
5345
|
+
} catch (err) {
|
|
5346
|
+
this.logw('Error getting flags: %o', err);
|
|
5347
|
+
throw err;
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
|
|
5306
5351
|
static upgradeNeeded(
|
|
5307
5352
|
paths: Upgrade | Upgrade[],
|
|
5308
5353
|
opts: UpgradeCheckOpts & { clientVersion: string; userAgent: string }
|
package/test/chain/xrp.test.ts
CHANGED
|
@@ -10,6 +10,27 @@ const should = chai.should();
|
|
|
10
10
|
const xpub = 'tpubDD7tYYerLNNm65Ez7pRjxQ2NpRHDoyRWoLWudnQ8agXjr7qs9BsPsRXk8Z6spPPJodnaY158YqeCKT5oXuZvbuNLfm1R4kXGJ2vPd9pUxDT';
|
|
11
11
|
|
|
12
12
|
describe('Chain XRP', function() {
|
|
13
|
+
describe('#getWalletSendMaxInfo', function() {
|
|
14
|
+
it('should resolve fee level before calculating sendMax amount', function(done) {
|
|
15
|
+
const server = {
|
|
16
|
+
getBalance: (_opts, cb) => cb(null, { availableAmount: 1000000 }),
|
|
17
|
+
_getFeePerKb: (_wallet, opts, cb) => {
|
|
18
|
+
opts.feeLevel.should.equal('normal');
|
|
19
|
+
return cb(null, 12);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const wallet = { chain: 'xrp' };
|
|
23
|
+
|
|
24
|
+
ChainService.get('xrp').getWalletSendMaxInfo(server as any, wallet as any, { feeLevel: 'normal' }, (err, info) => {
|
|
25
|
+
should.not.exist(err);
|
|
26
|
+
info.amount.should.equal(999988);
|
|
27
|
+
info.fee.should.equal(12);
|
|
28
|
+
info.feePerKb.should.equal(12);
|
|
29
|
+
done();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
13
34
|
describe('#getBitcoreTx', function() {
|
|
14
35
|
it('should create a valid bitcore TX', function() {
|
|
15
36
|
const txp = TxProposal.fromObj(aTXP()) as TxProposal;
|
|
@@ -260,7 +260,9 @@ describe('Moonpay integration', () => {
|
|
|
260
260
|
describe('#moonpayGetSignedPaymentUrl', () => {
|
|
261
261
|
beforeEach(() => {
|
|
262
262
|
req = {
|
|
263
|
-
headers: {
|
|
263
|
+
headers: {
|
|
264
|
+
'x-forwarded-for': '1.2.3.4'
|
|
265
|
+
},
|
|
264
266
|
body: {
|
|
265
267
|
env: 'production',
|
|
266
268
|
currencyCode: 'btc',
|
|
@@ -277,7 +279,17 @@ describe('Moonpay integration', () => {
|
|
|
277
279
|
it('should get the paymentUrl properly if req is OK', () => {
|
|
278
280
|
const data = server.externalServices.moonpay.moonpayGetSignedPaymentUrl(req);
|
|
279
281
|
should.exist(data.urlWithSignature);
|
|
280
|
-
data.urlWithSignature.should.equal('widgetApi2?apiKey=apiKey2¤cyCode=btc&walletAddress=bitcoin%3A123123&baseCurrencyCode=usd&baseCurrencyAmount=500&externalTransactionId=123123&redirectURL=bitpay%3A%2F%2Fmoonpay&signature
|
|
282
|
+
data.urlWithSignature.should.equal('widgetApi2?apiKey=apiKey2¤cyCode=btc&walletAddress=bitcoin%3A123123&baseCurrencyCode=usd&baseCurrencyAmount=500&externalTransactionId=123123&redirectURL=bitpay%3A%2F%2Fmoonpay&allowedIpAddress=CN35SFB5PKS4vkiZ4CglTxRgTAaUHBLGZcenAw6gHEY%3D&signature=3XxjRX3EMj2RNaoAwgOwFBOiVTXsgAS7C50uJf9SsvM%3D');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should return error if request does not have IP', () => {
|
|
286
|
+
delete req.headers['x-forwarded-for'];
|
|
287
|
+
try {
|
|
288
|
+
server.externalServices.moonpay.moonpayGetSignedPaymentUrl(req);
|
|
289
|
+
should.fail('should have thrown');
|
|
290
|
+
} catch (err) {
|
|
291
|
+
err.message.should.equal('Could not determine device IP address');
|
|
292
|
+
}
|
|
281
293
|
});
|
|
282
294
|
|
|
283
295
|
it('should return error if there is some missing arguments', () => {
|