@bitpay-labs/bitcore-wallet-service 11.10.10 → 11.10.12
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/externalservices/simplex.ts +15 -33
- package/src/lib/blockchainexplorers/v8/client.ts +14 -0
- package/src/lib/blockchainexplorers/v8.ts +12 -1
- package/src/lib/chain/btc/index.ts +5 -0
- package/src/lib/chain/eth/index.ts +4 -0
- package/src/lib/chain/index.ts +4 -0
- package/src/lib/chain/sol/index.ts +6 -0
- package/src/lib/chain/xrp/index.ts +5 -0
- package/src/lib/common/utils.ts +52 -1
- package/src/lib/routes/middleware/authTssRequest.ts +1 -1
- package/src/lib/routes/services.ts +1 -14
- package/src/lib/routes/walletdata.ts +11 -0
- package/src/lib/server.ts +44 -2
- package/src/types/chain.d.ts +1 -0
- package/test/chain/sol.test.ts +48 -0
- package/test/chain/xrp.test.ts +30 -0
- package/test/expressapp.test.ts +26 -0
- package/test/integration/externalservices/simplex.test.ts +141 -19
- package/test/integration/server.test.ts +94 -0
- package/test/integration/tss.test.ts +67 -0
- package/test/v8.test.ts +32 -0
- package/ts_build/package.json +3 -3
- package/ts_build/src/externalservices/simplex.d.ts +1 -1
- package/ts_build/src/externalservices/simplex.d.ts.map +1 -1
- package/ts_build/src/externalservices/simplex.js +11 -27
- package/ts_build/src/externalservices/simplex.js.map +1 -1
- package/ts_build/src/lib/blockchainexplorers/v8/client.d.ts +1 -0
- package/ts_build/src/lib/blockchainexplorers/v8/client.d.ts.map +1 -1
- package/ts_build/src/lib/blockchainexplorers/v8/client.js +13 -0
- package/ts_build/src/lib/blockchainexplorers/v8/client.js.map +1 -1
- package/ts_build/src/lib/blockchainexplorers/v8.d.ts +1 -0
- package/ts_build/src/lib/blockchainexplorers/v8.d.ts.map +1 -1
- package/ts_build/src/lib/blockchainexplorers/v8.js +10 -0
- package/ts_build/src/lib/blockchainexplorers/v8.js.map +1 -1
- package/ts_build/src/lib/chain/btc/index.d.ts +2 -1
- package/ts_build/src/lib/chain/btc/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/btc/index.js +3 -0
- package/ts_build/src/lib/chain/btc/index.js.map +1 -1
- 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 +3 -0
- package/ts_build/src/lib/chain/eth/index.js.map +1 -1
- package/ts_build/src/lib/chain/index.d.ts +1 -0
- package/ts_build/src/lib/chain/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/index.js +3 -0
- package/ts_build/src/lib/chain/index.js.map +1 -1
- package/ts_build/src/lib/chain/sol/index.d.ts +1 -0
- package/ts_build/src/lib/chain/sol/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/sol/index.js +5 -0
- package/ts_build/src/lib/chain/sol/index.js.map +1 -1
- package/ts_build/src/lib/chain/xrp/index.d.ts +1 -0
- package/ts_build/src/lib/chain/xrp/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/xrp/index.js +4 -0
- package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
- package/ts_build/src/lib/common/index.d.ts +2 -0
- package/ts_build/src/lib/common/index.d.ts.map +1 -1
- package/ts_build/src/lib/common/utils.d.ts +4 -0
- package/ts_build/src/lib/common/utils.d.ts.map +1 -1
- package/ts_build/src/lib/common/utils.js +33 -0
- package/ts_build/src/lib/common/utils.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.js +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.js.map +1 -1
- package/ts_build/src/lib/routes/services.d.ts.map +1 -1
- package/ts_build/src/lib/routes/services.js +1 -13
- 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 +12 -0
- package/ts_build/src/lib/routes/walletdata.js.map +1 -1
- package/ts_build/src/lib/server.d.ts +1 -0
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +32 -1
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/test/chain/sol.test.d.ts +2 -0
- package/ts_build/test/chain/sol.test.d.ts.map +1 -0
- package/ts_build/test/chain/sol.test.js +78 -0
- package/ts_build/test/chain/sol.test.js.map +1 -0
- package/ts_build/test/chain/xrp.test.js +28 -0
- package/ts_build/test/chain/xrp.test.js.map +1 -1
- package/ts_build/test/expressapp.test.js +25 -0
- package/ts_build/test/expressapp.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/simplex.test.js +130 -15
- package/ts_build/test/integration/externalservices/simplex.test.js.map +1 -1
- package/ts_build/test/integration/server.test.js +80 -0
- package/ts_build/test/integration/server.test.js.map +1 -1
- package/ts_build/test/integration/tss.test.js +57 -0
- package/ts_build/test/integration/tss.test.js.map +1 -1
- package/ts_build/test/v8.test.js +30 -0
- package/ts_build/test/v8.test.js.map +1 -1
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.12",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "ts_build/src/index.js",
|
|
8
8
|
"types": "./ts_build/src/index.d.ts",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@bitpay-labs/bitcore-logging": "^11.10.9",
|
|
45
|
-
"@bitpay-labs/bitcore-tss": "^11.10.
|
|
46
|
-
"@bitpay-labs/crypto-wallet-core": "^11.10.
|
|
45
|
+
"@bitpay-labs/bitcore-tss": "^11.10.12",
|
|
46
|
+
"@bitpay-labs/crypto-wallet-core": "^11.10.12",
|
|
47
47
|
"@sendgrid/mail": "6.5.4",
|
|
48
48
|
"abi-decoder": "2.4.0",
|
|
49
49
|
"async": "0.9.2",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
"email": "kjoseph@bitpay.com"
|
|
108
108
|
}
|
|
109
109
|
],
|
|
110
|
-
"gitHead": "
|
|
110
|
+
"gitHead": "d1992fd744501a1b69b8cb97f07b0896df7223fd"
|
|
111
111
|
}
|
|
@@ -32,6 +32,18 @@ export class SimplexService {
|
|
|
32
32
|
return keys;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
private simplexGetUserIp(req): string {
|
|
36
|
+
const isWebContext = req.body?.context === 'web';
|
|
37
|
+
let userIp = (isWebContext && req.body.deviceIp) || Utils.getIpFromReq(req);
|
|
38
|
+
delete req.body.deviceIp;
|
|
39
|
+
if (userIp) {
|
|
40
|
+
// Canonicalize IPv4-mapped IPv6 (dual-stack sockets report IPv4 clients as
|
|
41
|
+
// ::ffff:1.2.3.4) so Simplex sees a plain IPv4 address.
|
|
42
|
+
userIp = String(userIp).trim().replace(/^::ffff:/i, '');
|
|
43
|
+
}
|
|
44
|
+
return userIp || '';
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
simplexGetCurrencies(req): Promise<any> {
|
|
36
48
|
return new Promise((resolve, reject) => {
|
|
37
49
|
const keys = this.simplexGetKeys(req);
|
|
@@ -63,11 +75,11 @@ export class SimplexService {
|
|
|
63
75
|
|
|
64
76
|
simplexGetQuote(req): Promise<any> {
|
|
65
77
|
return new Promise((resolve, reject) => {
|
|
78
|
+
const ip = this.simplexGetUserIp(req);
|
|
66
79
|
const keys = this.simplexGetKeys(req);
|
|
67
80
|
|
|
68
81
|
const API = keys.API;
|
|
69
82
|
const API_KEY = keys.API_KEY;
|
|
70
|
-
const ip = Utils.getIpFromReq(req);
|
|
71
83
|
|
|
72
84
|
req.body.client_ip = ip;
|
|
73
85
|
req.body.wallet_id = keys.APP_PROVIDER_ID;
|
|
@@ -147,6 +159,7 @@ export class SimplexService {
|
|
|
147
159
|
|
|
148
160
|
simplexPaymentRequest(req): Promise<any> {
|
|
149
161
|
return new Promise((resolve, reject) => {
|
|
162
|
+
const ip = this.simplexGetUserIp(req);
|
|
150
163
|
const keys = this.simplexGetKeys(req);
|
|
151
164
|
|
|
152
165
|
const API = keys.API;
|
|
@@ -155,7 +168,6 @@ export class SimplexService {
|
|
|
155
168
|
const paymentId = Uuid.v4();
|
|
156
169
|
const orderId = Uuid.v4();
|
|
157
170
|
const apiHost = keys.API;
|
|
158
|
-
const ip = Utils.getIpFromReq(req);
|
|
159
171
|
|
|
160
172
|
if (
|
|
161
173
|
!checkRequired(req.body, ['account_details', 'transaction_details']) &&
|
|
@@ -248,34 +260,4 @@ export class SimplexService {
|
|
|
248
260
|
);
|
|
249
261
|
});
|
|
250
262
|
}
|
|
251
|
-
|
|
252
|
-
simplexGetEvents(req): Promise<any> {
|
|
253
|
-
return new Promise((resolve, reject) => {
|
|
254
|
-
if (!config.simplex) return reject(new Error('Simplex missing credentials'));
|
|
255
|
-
if (!req.env || (req.env != 'sandbox' && req.env != 'production'))
|
|
256
|
-
return reject(new Error("Simplex's request wrong environment"));
|
|
257
|
-
|
|
258
|
-
const API = config.simplex[req.env].api;
|
|
259
|
-
const API_KEY = config.simplex[req.env].apiKey;
|
|
260
|
-
const headers = {
|
|
261
|
-
'Content-Type': 'application/json',
|
|
262
|
-
Authorization: 'ApiKey ' + API_KEY
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
this.request.get(
|
|
266
|
-
API + '/wallet/merchant/v2/events',
|
|
267
|
-
{
|
|
268
|
-
headers,
|
|
269
|
-
json: true
|
|
270
|
-
},
|
|
271
|
-
(err, data) => {
|
|
272
|
-
if (err) {
|
|
273
|
-
return reject(err.body ? err.body : null);
|
|
274
|
-
} else {
|
|
275
|
-
return resolve(data.body ? data.body : null);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
);
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
}
|
|
263
|
+
}
|
|
@@ -61,6 +61,20 @@ export class Client {
|
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
async getBalanceAtTime(params) {
|
|
65
|
+
const { payload, pubKey, time, tokenAddress } = params;
|
|
66
|
+
let url = `${this.baseUrl}/wallet/${pubKey}/balance/${encodeURIComponent(time)}`;
|
|
67
|
+
if (tokenAddress) {
|
|
68
|
+
url += `?tokenAddress=${encodeURIComponent(tokenAddress)}`;
|
|
69
|
+
}
|
|
70
|
+
const signature = this.sign({ method: 'GET', url, payload });
|
|
71
|
+
return request.get(url, {
|
|
72
|
+
headers: { 'x-signature': signature },
|
|
73
|
+
body: payload,
|
|
74
|
+
json: true
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
async getCheckData(params) {
|
|
65
79
|
const { payload, pubKey } = params;
|
|
66
80
|
const url = `${this.baseUrl}/wallet/${pubKey}/check`;
|
|
@@ -178,6 +178,17 @@ export class V8 {
|
|
|
178
178
|
.catch(cb);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
async getBalanceAtTime(wallet: WalletWithOpts, time: string, cb) {
|
|
182
|
+
const client = this._getAuthClient(wallet);
|
|
183
|
+
const { tokenAddress } = wallet;
|
|
184
|
+
client
|
|
185
|
+
.getBalanceAtTime({ pubKey: wallet.beAuthPublicKey2, payload: {}, time, tokenAddress })
|
|
186
|
+
.then(ret => {
|
|
187
|
+
return cb(null, ret);
|
|
188
|
+
})
|
|
189
|
+
.catch(cb);
|
|
190
|
+
}
|
|
191
|
+
|
|
181
192
|
getConnectionInfo() {
|
|
182
193
|
return 'V8 (' + this.chain + '/' + this.v8network + ') @ ' + this.host;
|
|
183
194
|
}
|
|
@@ -788,4 +799,4 @@ export class V8 {
|
|
|
788
799
|
|
|
789
800
|
const getPerformanceKey = (name: string) => {
|
|
790
801
|
return name + '-' + crypto.randomBytes(5).toString('hex');
|
|
791
|
-
};
|
|
802
|
+
};
|
|
@@ -349,6 +349,11 @@ export class BtcChain implements IChain {
|
|
|
349
349
|
});
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
isPrePublishRawBound(_txp: TxProposal) {
|
|
353
|
+
// UTXO chains never mutate tx data at publish, so they never legitimately carry prePublishRaw.
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
|
|
352
357
|
getBitcoreTx(txp, opts = { signed: true }) {
|
|
353
358
|
const t = new this.bitcoreLib.Transaction();
|
|
354
359
|
|
|
@@ -287,6 +287,10 @@ export class EthChain implements IChain {
|
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
isPrePublishRawBound(txp: TxProposal) {
|
|
291
|
+
return Common.Utils.isPrePublishRawBound(this, txp);
|
|
292
|
+
}
|
|
293
|
+
|
|
290
294
|
getBitcoreTx(txp: TxProposal, opts = { signed: true }) {
|
|
291
295
|
const {
|
|
292
296
|
data,
|
package/src/lib/chain/index.ts
CHANGED
|
@@ -98,6 +98,10 @@ class ChainProxy {
|
|
|
98
98
|
return this.get(txp.chain).getBitcoreTx(txp, { signed: opts.signed });
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
isPrePublishRawBound(txp: TxProposal<any>) {
|
|
102
|
+
return this.get(txp.chain).isPrePublishRawBound(txp);
|
|
103
|
+
}
|
|
104
|
+
|
|
101
105
|
convertFeePerKb(chain, p, feePerKb) {
|
|
102
106
|
return this.get(chain).convertFeePerKb(p, feePerKb);
|
|
103
107
|
}
|
|
@@ -2,6 +2,7 @@ import { Transactions, Utils, Validation } from '@bitpay-labs/crypto-wallet-core
|
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import { IChain } from '../../../types/chain';
|
|
4
4
|
import { WalletWithOpts } from '../../blockchainexplorers/v8';
|
|
5
|
+
import { Common } from '../../common';
|
|
5
6
|
import { Defaults } from '../../common/defaults';
|
|
6
7
|
import { Errors } from '../../errors/errordefinitions';
|
|
7
8
|
import logger from '../../logger';
|
|
@@ -28,6 +29,7 @@ export class SolChain implements IChain {
|
|
|
28
29
|
totalConfirmedAmount: confirmed,
|
|
29
30
|
lockedAmount: activatedLocked,
|
|
30
31
|
lockedConfirmedAmount: activatedLocked,
|
|
32
|
+
reserve: balance > 0 ? minRent : 0,
|
|
31
33
|
availableAmount: balance - activatedLocked,
|
|
32
34
|
availableConfirmedAmount: confirmed - activatedLocked,
|
|
33
35
|
byAddress: []
|
|
@@ -98,6 +100,10 @@ export class SolChain implements IChain {
|
|
|
98
100
|
});
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
isPrePublishRawBound(txp) {
|
|
104
|
+
return Common.Utils.isPrePublishRawBound(this, txp);
|
|
105
|
+
}
|
|
106
|
+
|
|
101
107
|
getBitcoreTx(txp, opts = { signed: true }) {
|
|
102
108
|
const {
|
|
103
109
|
data,
|
|
@@ -29,6 +29,7 @@ export class XrpChain implements IChain {
|
|
|
29
29
|
totalConfirmedAmount: confirmed,
|
|
30
30
|
lockedAmount: activatedLocked,
|
|
31
31
|
lockedConfirmedAmount: activatedLocked,
|
|
32
|
+
reserve: balance > 0 ? reserve : 0,
|
|
32
33
|
availableAmount: balance - activatedLocked,
|
|
33
34
|
availableConfirmedAmount: confirmed - activatedLocked,
|
|
34
35
|
byAddress: []
|
|
@@ -143,6 +144,10 @@ export class XrpChain implements IChain {
|
|
|
143
144
|
});
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
isPrePublishRawBound(txp) {
|
|
148
|
+
return Common.Utils.isPrePublishRawBound(this, txp);
|
|
149
|
+
}
|
|
150
|
+
|
|
146
151
|
getBitcoreTx(txp, opts = { signed: true }) {
|
|
147
152
|
const { destinationTag, outputs, outputOrder, multiTx } = txp;
|
|
148
153
|
const chain = 'XRP';
|
package/src/lib/common/utils.ts
CHANGED
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
BitcoreLibCash,
|
|
4
4
|
BitcoreLibDoge,
|
|
5
5
|
BitcoreLibLtc,
|
|
6
|
-
Constants as CWConstants
|
|
6
|
+
Constants as CWConstants,
|
|
7
|
+
Transactions
|
|
7
8
|
} from '@bitpay-labs/crypto-wallet-core';
|
|
8
9
|
import _ from 'lodash';
|
|
9
10
|
import { singleton } from 'preconditions';
|
|
@@ -12,6 +13,8 @@ import Config from '../../config';
|
|
|
12
13
|
import { logger } from '../logger';
|
|
13
14
|
import { Constants } from './constants';
|
|
14
15
|
import { Defaults } from './defaults';
|
|
16
|
+
import type { IChain } from '../../types/chain';
|
|
17
|
+
import type { TxProposal } from '../model/txproposal';
|
|
15
18
|
|
|
16
19
|
const $ = singleton();
|
|
17
20
|
const Bitcore_ = {
|
|
@@ -23,6 +26,54 @@ const Bitcore_ = {
|
|
|
23
26
|
|
|
24
27
|
export const Utils = {
|
|
25
28
|
|
|
29
|
+
/**
|
|
30
|
+
* txp.prePublishRaw as a list of raw transactions, or null when it isn't one. An empty list is truthy and
|
|
31
|
+
* hashes to nothing, so without this the value reaches the tx provider and fails as a thrown error rather
|
|
32
|
+
* than a refused check. Mirrors the client-side Verifier helper so both ends agree on a usable fallback.
|
|
33
|
+
*
|
|
34
|
+
* @param txp - the transaction proposal
|
|
35
|
+
*/
|
|
36
|
+
prePublishRawParts(txp: TxProposal<any>): string[] | null {
|
|
37
|
+
if (!txp?.prePublishRaw) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const parts = Array.isArray(txp.prePublishRaw) ? txp.prePublishRaw : [txp.prePublishRaw];
|
|
41
|
+
if (!parts.length || parts.some(raw => !raw || typeof raw !== 'string')) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return parts;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Shared implementation of IChain.isPrePublishRawBound for account-based chains (SVM/EVM/XRP). True only if
|
|
49
|
+
* txp.prePublishRaw is the same transaction as the current proposal, differing solely in the field BWS
|
|
50
|
+
* mutates at publish (blockhash on SVM, nonce on EVM/XRP). Recovers that field from prePublishRaw, rebuilds
|
|
51
|
+
* the proposal via the chain's own getBitcoreTx, and requires byte-for-byte equality, so a tampered stored
|
|
52
|
+
* proposal cannot reuse an old, still-valid proposalSignature. Fails closed on any error.
|
|
53
|
+
*
|
|
54
|
+
* @param chain - the IChain implementation (provides chain + getBitcoreTx)
|
|
55
|
+
* @param txp - the transaction proposal (must carry prePublishRaw)
|
|
56
|
+
*/
|
|
57
|
+
isPrePublishRawBound(chain: IChain, txp: TxProposal<any>): boolean {
|
|
58
|
+
try {
|
|
59
|
+
const prePublishRaw = Utils.prePublishRawParts(txp);
|
|
60
|
+
if (!prePublishRaw) return false;
|
|
61
|
+
const provider = Transactions.get({ chain: txp.chain }) as any;
|
|
62
|
+
if (typeof provider?.getMutableFields !== 'function') return false;
|
|
63
|
+
const mutableFields = provider.getMutableFields(prePublishRaw[0]);
|
|
64
|
+
if (!mutableFields || Object.values(mutableFields).every(v => v == null)) return false;
|
|
65
|
+
const cloned = Object.assign(Object.create(Object.getPrototypeOf(txp)), txp, mutableFields);
|
|
66
|
+
// signed: false to match prePublishRaw, which is captured before any copayer signature exists.
|
|
67
|
+
const rebuilt = chain.getBitcoreTx(cloned, { signed: false }).uncheckedSerialize();
|
|
68
|
+
const rebuiltArr = Array.isArray(rebuilt) ? rebuilt : [rebuilt];
|
|
69
|
+
if (rebuiltArr.length !== prePublishRaw.length) return false;
|
|
70
|
+
return rebuiltArr.every((raw, i) => raw === prePublishRaw[i]);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
logger.warn('prePublishRaw binding check failed for txp %s on %s: %o', txp?.id, txp?.chain, err.stack || err.message || err);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
26
77
|
/**
|
|
27
78
|
* @deprecated
|
|
28
79
|
* Moved from ChainService to prevent circular dependency
|
|
@@ -35,7 +35,7 @@ export function authTssRequest(): express.RequestHandler {
|
|
|
35
35
|
if (req.path.includes('/tss/keygen/')) {
|
|
36
36
|
session = await storage.fetchTssKeyGenSession({ id });
|
|
37
37
|
partyId = session?.participants.indexOf(copayerId);
|
|
38
|
-
pubKey = partyId > -1 ? session.rounds[0]
|
|
38
|
+
pubKey = partyId > -1 ? session.rounds[0].find(r => r.fromPartyId === partyId)?.messages.publicKey : null;
|
|
39
39
|
} else if (req.path.includes('/tss/sign/')) {
|
|
40
40
|
session = await storage.fetchTssSigSession({ id });
|
|
41
41
|
partyId = session?.participants.find(p => p.copayerId === copayerId)?.partyId;
|
|
@@ -65,7 +65,7 @@ function respondWithAuthServer(req, res, context: RouteContext, handler: RouteHa
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
export function registerServiceRoutes(router: express.Router, context: RouteContext) {
|
|
68
|
-
const {
|
|
68
|
+
const { setPublicCache } = context;
|
|
69
69
|
|
|
70
70
|
const transakCorsOptions = {
|
|
71
71
|
origin: (origin, cb) => {
|
|
@@ -399,19 +399,6 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
|
|
|
399
399
|
});
|
|
400
400
|
});
|
|
401
401
|
|
|
402
|
-
router.get('/v1/service/simplex/events', (req, res) => {
|
|
403
|
-
getServerWithAuth(req, res, server => {
|
|
404
|
-
Promise.resolve()
|
|
405
|
-
.then(() => server.externalServices.simplex.simplexGetEvents({ env: req.query.env }))
|
|
406
|
-
.then(response => {
|
|
407
|
-
res.json(response);
|
|
408
|
-
})
|
|
409
|
-
.catch(err => {
|
|
410
|
-
returnError(err ?? 'unknown', res, req);
|
|
411
|
-
});
|
|
412
|
-
});
|
|
413
|
-
});
|
|
414
|
-
|
|
415
402
|
router.post('/v1/service/thorswap/supportedChains', (req, res) => {
|
|
416
403
|
respondWithPublicServer(req, res, context, server => {
|
|
417
404
|
return server.externalServices.thorswap.thorswapGetSupportedChains(req);
|
|
@@ -108,6 +108,17 @@ export function registerWalletDataRoutes(router: express.Router, context: RouteC
|
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
router.get('/v1/balance/:time/', (req, res) => {
|
|
112
|
+
getServerWithAuth(req, res, server => {
|
|
113
|
+
const opts: { time: string; tokenAddress?: string } = { time: req.params.time };
|
|
114
|
+
if (req.query.tokenAddress) opts.tokenAddress = req.query.tokenAddress as string;
|
|
115
|
+
server.getBalanceAtTime(opts, (err, balance) => {
|
|
116
|
+
if (err) return returnError(err, res, req);
|
|
117
|
+
res.json(balance);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
111
122
|
router.get('/v1/sendmaxinfo/', (req, res) => {
|
|
112
123
|
getServerWithAuth(req, res, server => {
|
|
113
124
|
const query = req.query;
|
package/src/lib/server.ts
CHANGED
|
@@ -2092,6 +2092,44 @@ export class WalletService implements IWalletService {
|
|
|
2092
2092
|
});
|
|
2093
2093
|
}
|
|
2094
2094
|
|
|
2095
|
+
/**
|
|
2096
|
+
* Get wallet balance at a specific time.
|
|
2097
|
+
* @param {Object} opts
|
|
2098
|
+
* @param {string} opts.time - Date or time accepted by the bitcore-node API.
|
|
2099
|
+
* @returns {Object} balance - The chain-state provider balance at the requested time.
|
|
2100
|
+
*/
|
|
2101
|
+
getBalanceAtTime(opts, cb) {
|
|
2102
|
+
opts = opts || {};
|
|
2103
|
+
if (!opts.time) {
|
|
2104
|
+
return cb(new ClientError('time is required in getBalanceAtTime'));
|
|
2105
|
+
}
|
|
2106
|
+
let wallet = opts.wallet;
|
|
2107
|
+
|
|
2108
|
+
const setWallet = cb1 => {
|
|
2109
|
+
if (wallet) return cb1();
|
|
2110
|
+
this.getWallet({}, (err, ret) => {
|
|
2111
|
+
if (err) return cb(err);
|
|
2112
|
+
wallet = ret;
|
|
2113
|
+
return cb1(null, wallet);
|
|
2114
|
+
});
|
|
2115
|
+
};
|
|
2116
|
+
|
|
2117
|
+
setWallet(() => {
|
|
2118
|
+
if (!wallet.isComplete()) {
|
|
2119
|
+
return cb(null, { confirmed: 0, unconfirmed: 0, balance: 0 });
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
this.syncWallet(wallet, err => {
|
|
2123
|
+
if (err) return cb(err);
|
|
2124
|
+
const bc = this._getBlockchainExplorer(wallet.chain, wallet.network);
|
|
2125
|
+
if (!bc) {
|
|
2126
|
+
return cb(new Error('Could not get blockchain explorer instance'));
|
|
2127
|
+
}
|
|
2128
|
+
return bc.getBalanceAtTime({ ...wallet, tokenAddress: opts.tokenAddress }, opts.time, cb);
|
|
2129
|
+
});
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2095
2133
|
/**
|
|
2096
2134
|
* Return info needed to send all funds in the wallet
|
|
2097
2135
|
* @param {Object} opts
|
|
@@ -3082,8 +3120,12 @@ export class WalletService implements IWalletService {
|
|
|
3082
3120
|
|
|
3083
3121
|
let signingKey = this._getSigningKey(raw, opts.proposalSignature, copayer.requestPubKeys);
|
|
3084
3122
|
if (!signingKey) {
|
|
3085
|
-
//
|
|
3086
|
-
if (
|
|
3123
|
+
// The signature didn't match the current (refreshed) raw tx. Fall back to the previously-published
|
|
3124
|
+
// raw tx -- but only if it is bound to THIS proposal (differs from the current tx solely in the
|
|
3125
|
+
// field BWS mutates at publish: blockhash/nonce). Without that check a tampered stored proposal
|
|
3126
|
+
// could reuse an old, still-valid proposalSignature over prePublishRaw. Mirrors the client
|
|
3127
|
+
// Verifier.checkPrePublishRaw guard.
|
|
3128
|
+
if (txp.hasMutableTxData() && txp.prePublishRaw && ChainService.isPrePublishRawBound(txp)) {
|
|
3087
3129
|
raw = txp.prePublishRaw;
|
|
3088
3130
|
signingKey = this._getSigningKey(raw, opts.proposalSignature, copayer.requestPubKeys);
|
|
3089
3131
|
}
|
package/src/types/chain.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export interface IChain {
|
|
|
33
33
|
checkScriptOutput(output: { script: string; amount: number });
|
|
34
34
|
getFee(server: WalletService, wallet: IWallet, opts: { fee: number; feePerKb: number; signatures?: number } & any);
|
|
35
35
|
getBitcoreTx(txp: TxProposal, opts: { signed: boolean });
|
|
36
|
+
isPrePublishRawBound(txp: TxProposal): boolean;
|
|
36
37
|
convertFeePerKb(p: number, feePerKb: number);
|
|
37
38
|
checkTx(server: WalletService, txp: ITxProposal);
|
|
38
39
|
checkTxUTXOs(server: WalletService, txp: ITxProposal, opts: { noCashAddr: boolean } & any, cb);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import * as chai from 'chai';
|
|
4
|
+
import { ChainService } from '../../src/lib/chain';
|
|
5
|
+
import { Defaults } from '../../src/lib/common/defaults';
|
|
6
|
+
|
|
7
|
+
const should = chai.should();
|
|
8
|
+
|
|
9
|
+
describe('Chain SOL', function() {
|
|
10
|
+
describe('#getWalletBalance', function() {
|
|
11
|
+
const cases = [
|
|
12
|
+
{ name: 'pending amounts and fees', amount: 1000000000, txps: [{ amount: '2000000000', fee: '5000' }], reserve: 1002240, locked: 2001007240, available: -1001007240 },
|
|
13
|
+
{ name: 'dust below rent', amount: 10000, txps: [], reserve: 1002240, locked: 1002240, available: -992240 },
|
|
14
|
+
{ name: 'an empty account with a pending proposal', amount: 0, txps: [{ amount: '2000000000', fee: '5000' }], reserve: 0, locked: 2000005000, available: -2000005000 },
|
|
15
|
+
{ name: 'an SPL balance', amount: 1000000, txps: [{ tokenAddress: 'mint', amount: '500000', fee: '5000' }], tokenAddress: 'mint', reserve: 0, locked: 500000, available: 500000 }
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
for (const c of cases) {
|
|
19
|
+
it('should expose the reserve separately for ' + c.name, function(done) {
|
|
20
|
+
const server = {
|
|
21
|
+
walletId: '1',
|
|
22
|
+
_getBlockchainExplorer: () => ({
|
|
23
|
+
getBalance: (_wallet, cb) => cb(null, { balance: c.amount, confirmed: c.amount }),
|
|
24
|
+
getRentMinimum: (_space, cb) => cb(null, Defaults.MIN_SOL_BALANCE)
|
|
25
|
+
}),
|
|
26
|
+
getPendingTxs: (_opts, cb) => cb(null, c.txps),
|
|
27
|
+
storage: { fetchAddresses: (_walletId, cb) => cb(null, []) }
|
|
28
|
+
};
|
|
29
|
+
const wallet = { chain: 'sol', network: 'livenet' };
|
|
30
|
+
|
|
31
|
+
ChainService.get('sol').getWalletBalance(server as any, wallet as any, { tokenAddress: (c as { tokenAddress?: string }).tokenAddress }, (err, balance) => {
|
|
32
|
+
should.not.exist(err);
|
|
33
|
+
balance.should.deep.equal({
|
|
34
|
+
totalAmount: c.amount,
|
|
35
|
+
totalConfirmedAmount: c.amount,
|
|
36
|
+
lockedAmount: c.locked,
|
|
37
|
+
lockedConfirmedAmount: c.locked,
|
|
38
|
+
reserve: c.reserve,
|
|
39
|
+
availableAmount: c.available,
|
|
40
|
+
availableConfirmedAmount: c.available,
|
|
41
|
+
byAddress: []
|
|
42
|
+
});
|
|
43
|
+
done();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
package/test/chain/xrp.test.ts
CHANGED
|
@@ -10,6 +10,36 @@ const should = chai.should();
|
|
|
10
10
|
const xpub = 'tpubDD7tYYerLNNm65Ez7pRjxQ2NpRHDoyRWoLWudnQ8agXjr7qs9BsPsRXk8Z6spPPJodnaY158YqeCKT5oXuZvbuNLfm1R4kXGJ2vPd9pUxDT';
|
|
11
11
|
|
|
12
12
|
describe('Chain XRP', function() {
|
|
13
|
+
describe('#getWalletBalance', function() {
|
|
14
|
+
it('should expose the reserve separately from pending amounts and fees', function(done) {
|
|
15
|
+
const server = {
|
|
16
|
+
walletId: '1',
|
|
17
|
+
_getBlockchainExplorer: () => ({
|
|
18
|
+
getBalance: (_wallet, cb) => cb(null, { balance: 2000000, confirmed: 2000000 }),
|
|
19
|
+
getReserve: cb => cb(null, 1000000)
|
|
20
|
+
}),
|
|
21
|
+
getPendingTxs: (_opts, cb) => cb(null, [{ amount: '3000000', fee: '12' }]),
|
|
22
|
+
storage: { fetchAddresses: (_walletId, cb) => cb(null, []) }
|
|
23
|
+
};
|
|
24
|
+
const wallet = { chain: 'xrp', network: 'livenet' };
|
|
25
|
+
|
|
26
|
+
ChainService.get('xrp').getWalletBalance(server as any, wallet as any, {}, (err, balance) => {
|
|
27
|
+
should.not.exist(err);
|
|
28
|
+
balance.should.deep.equal({
|
|
29
|
+
totalAmount: 2000000,
|
|
30
|
+
totalConfirmedAmount: 2000000,
|
|
31
|
+
lockedAmount: 4000012,
|
|
32
|
+
lockedConfirmedAmount: 4000012,
|
|
33
|
+
reserve: 1000000,
|
|
34
|
+
availableAmount: -2000012,
|
|
35
|
+
availableConfirmedAmount: -2000012,
|
|
36
|
+
byAddress: []
|
|
37
|
+
});
|
|
38
|
+
done();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
13
43
|
describe('#getWalletSendMaxInfo', function() {
|
|
14
44
|
it('should resolve fee level before calculating sendMax amount', function(done) {
|
|
15
45
|
const server = {
|
package/test/expressapp.test.ts
CHANGED
|
@@ -274,6 +274,32 @@ describe('ExpressApp', function() {
|
|
|
274
274
|
});
|
|
275
275
|
});
|
|
276
276
|
});
|
|
277
|
+
|
|
278
|
+
it('should get the balance at a given time', function(done) {
|
|
279
|
+
const server = {
|
|
280
|
+
getBalanceAtTime: sinon.stub().callsArgWith(1, null, { balance: 123 }),
|
|
281
|
+
};
|
|
282
|
+
sandbox.stub(WalletService, 'initialize').callsArg(1);
|
|
283
|
+
sandbox.stub(WalletService, 'getInstanceWithAuth').callsArgWith(1, null, server);
|
|
284
|
+
start(ExpressApp, function() {
|
|
285
|
+
const reqOpts = {
|
|
286
|
+
url: testHost + ':' + testPort + config.basePath + '/v1/balance/2025-01-15T12%3A00%3A00.000Z?tokenAddress=0xToken',
|
|
287
|
+
headers: {
|
|
288
|
+
'x-identity': 'identity',
|
|
289
|
+
'x-signature': 'signature'
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
request(reqOpts, function(err, res, body) {
|
|
293
|
+
should.not.exist(err);
|
|
294
|
+
res.statusCode.should.equal(200);
|
|
295
|
+
server.getBalanceAtTime.calledOnce.should.be.true;
|
|
296
|
+
server.getBalanceAtTime.getCall(0).args[0].time.should.equal('2025-01-15T12:00:00.000Z');
|
|
297
|
+
server.getBalanceAtTime.getCall(0).args[0].tokenAddress.should.equal('0xToken');
|
|
298
|
+
JSON.parse(body).balance.should.equal(123);
|
|
299
|
+
done();
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
277
303
|
});
|
|
278
304
|
|
|
279
305
|
describe('v1/wallets/all', function() {
|