@bitpay-labs/bitcore-wallet-service 11.11.1 → 11.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +1 -1
- package/package.json +5 -5
- package/src/lib/chain/btc/index.ts +3 -2
- package/src/lib/chain/eth/index.ts +134 -117
- package/src/lib/chain/sol/index.ts +1 -1
- package/src/lib/chain/xrp/index.ts +1 -1
- package/src/lib/routes/wallets.ts +3 -1
- package/src/lib/server.ts +136 -88
- package/src/lib/storage.ts +90 -20
- package/test/chain/eth.test.ts +70 -0
- package/test/chain/sol.test.ts +1 -1
- package/test/chain/xrp.test.ts +1 -1
- package/test/expressapp.test.ts +144 -0
- package/test/integration/addressReads.test.ts +503 -0
- package/test/integration/bcmonitor.test.ts +44 -0
- package/test/integration/server.test.ts +198 -48
- package/test/integration/walletReads.test.ts +488 -0
- package/ts_build/package.json +3 -3
- package/ts_build/src/lib/chain/btc/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/btc/index.js +3 -2
- package/ts_build/src/lib/chain/btc/index.js.map +1 -1
- package/ts_build/src/lib/chain/eth/index.d.ts +8 -1
- package/ts_build/src/lib/chain/eth/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/eth/index.js +114 -102
- package/ts_build/src/lib/chain/eth/index.js.map +1 -1
- package/ts_build/src/lib/chain/sol/index.js +1 -1
- package/ts_build/src/lib/chain/sol/index.js.map +1 -1
- package/ts_build/src/lib/chain/xrp/index.js +1 -1
- package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
- package/ts_build/src/lib/routes/wallets.d.ts.map +1 -1
- package/ts_build/src/lib/routes/wallets.js +2 -1
- package/ts_build/src/lib/routes/wallets.js.map +1 -1
- package/ts_build/src/lib/server.d.ts +1 -1
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +128 -94
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/src/lib/storage.d.ts +17 -10
- package/ts_build/src/lib/storage.d.ts.map +1 -1
- package/ts_build/src/lib/storage.js +73 -20
- package/ts_build/src/lib/storage.js.map +1 -1
- package/ts_build/test/chain/eth.test.js +59 -0
- package/ts_build/test/chain/eth.test.js.map +1 -1
- package/ts_build/test/chain/sol.test.js +1 -1
- package/ts_build/test/chain/sol.test.js.map +1 -1
- package/ts_build/test/chain/xrp.test.js +1 -1
- package/ts_build/test/chain/xrp.test.js.map +1 -1
- package/ts_build/test/expressapp.test.js +132 -0
- package/ts_build/test/expressapp.test.js.map +1 -1
- package/ts_build/test/integration/addressReads.test.d.ts +2 -0
- package/ts_build/test/integration/addressReads.test.d.ts.map +1 -0
- package/ts_build/test/integration/addressReads.test.js +465 -0
- package/ts_build/test/integration/addressReads.test.js.map +1 -0
- package/ts_build/test/integration/bcmonitor.test.js +40 -0
- package/ts_build/test/integration/bcmonitor.test.js.map +1 -1
- package/ts_build/test/integration/server.test.js +181 -48
- package/ts_build/test/integration/server.test.js.map +1 -1
- package/ts_build/test/integration/walletReads.test.d.ts +2 -0
- package/ts_build/test/integration/walletReads.test.d.ts.map +1 -0
- package/ts_build/test/integration/walletReads.test.js +436 -0
- package/ts_build/test/integration/walletReads.test.js.map +1 -0
package/src/lib/server.ts
CHANGED
|
@@ -873,26 +873,29 @@ export class WalletService implements IWalletService {
|
|
|
873
873
|
pendingTxps?: ITxProposal[];
|
|
874
874
|
preferences?: boolean;
|
|
875
875
|
} = {};
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
next => {
|
|
879
|
-
this.getWallet({}, (err, wallet: Partial<Wallet>) => {
|
|
880
|
-
if (err) return next(err);
|
|
881
|
-
if (this._upgradeNeeded(UPGRADES.SOL_bwc_$lt_10_10_12, wallet)) {
|
|
882
|
-
return next(Errors.UPGRADE_NEEDED);
|
|
883
|
-
}
|
|
876
|
+
this.getWallet({}, (err, fetchedWallet: Wallet) => {
|
|
877
|
+
if (err) return cb(err);
|
|
884
878
|
|
|
879
|
+
// Read the wallet once, and give each step its own copy, the same as reading it again: the steps change their
|
|
880
|
+
// wallet in place (this one strips copayer data, the balance code sets tokenAddress on it), and the balance step
|
|
881
|
+
// can store its copy whole.
|
|
882
|
+
const copyOfWallet = () => Wallet.fromObj(fetchedWallet.toObject());
|
|
883
|
+
const omit = (obj, keys: string[]): any => Object.fromEntries(Object.entries(obj).filter(([key]) => !keys.includes(key)));
|
|
884
|
+
async.parallel(
|
|
885
|
+
[
|
|
886
|
+
next => {
|
|
887
|
+
let wallet: Partial<Wallet> = copyOfWallet();
|
|
885
888
|
const walletExtendedKeys = ['publicKeyRing', 'pubKey', 'addressManager'];
|
|
886
889
|
const copayerExtendedKeys = ['xPubKey', 'requestPubKey', 'signature', 'addressManager', 'customData'];
|
|
887
890
|
|
|
888
891
|
wallet.copayers = (wallet.copayers || []).map(copayer => {
|
|
889
892
|
if (copayer.id == this.copayerId) return copayer;
|
|
890
|
-
return
|
|
893
|
+
return omit(copayer, ['customData']);
|
|
891
894
|
});
|
|
892
895
|
if (!opts.includeExtendedInfo) {
|
|
893
|
-
wallet =
|
|
896
|
+
wallet = omit(wallet, walletExtendedKeys);
|
|
894
897
|
wallet.copayers = (wallet.copayers || []).map(copayer => {
|
|
895
|
-
return
|
|
898
|
+
return omit(copayer, copayerExtendedKeys);
|
|
896
899
|
}) as Copayer[];
|
|
897
900
|
}
|
|
898
901
|
status.wallet = wallet;
|
|
@@ -903,46 +906,45 @@ export class WalletService implements IWalletService {
|
|
|
903
906
|
status.serverMessage = deprecatedServerMessage(wallet, this.appName, this.appVersion);
|
|
904
907
|
}
|
|
905
908
|
next();
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
909
|
+
},
|
|
910
|
+
next => {
|
|
911
|
+
this.getBalance({ ...opts, wallet: copyOfWallet() }, (err, balance) => {
|
|
912
|
+
// ignore WALLET_NEED_SCAN err is includeExtendedInfo is given
|
|
913
|
+
// (to allow `importWallet` to import a wallet, while scan has
|
|
914
|
+
// failed)
|
|
915
|
+
if (opts.includeExtendedInfo) {
|
|
916
|
+
if (err && err.code != 'WALLET_NEED_SCAN') {
|
|
917
|
+
return next(err);
|
|
918
|
+
}
|
|
919
|
+
} else if (err) {
|
|
916
920
|
return next(err);
|
|
917
921
|
}
|
|
918
|
-
} else if (err) {
|
|
919
|
-
return next(err);
|
|
920
|
-
}
|
|
921
922
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
923
|
+
status.balance = balance;
|
|
924
|
+
next();
|
|
925
|
+
});
|
|
926
|
+
},
|
|
927
|
+
next => {
|
|
928
|
+
this.getPendingTxs({ ...opts, wallet: copyOfWallet() }, (err, pendingTxps) => {
|
|
929
|
+
if (err) return next(err);
|
|
930
|
+
status.pendingTxps = pendingTxps;
|
|
931
|
+
next();
|
|
932
|
+
});
|
|
933
|
+
},
|
|
934
|
+
next => {
|
|
935
|
+
this.getPreferences({}, (err, preferences) => {
|
|
936
|
+
if (err) return next(err);
|
|
937
|
+
status.preferences = preferences;
|
|
938
|
+
next();
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
],
|
|
942
|
+
err => {
|
|
943
|
+
if (err) return cb(err);
|
|
944
|
+
return cb(null, status);
|
|
939
945
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
if (err) return cb(err);
|
|
943
|
-
return cb(null, status);
|
|
944
|
-
}
|
|
945
|
-
);
|
|
946
|
+
);
|
|
947
|
+
});
|
|
946
948
|
}
|
|
947
949
|
|
|
948
950
|
/**
|
|
@@ -1535,9 +1537,10 @@ export class WalletService implements IWalletService {
|
|
|
1535
1537
|
_canCreateAddress(ignoreMaxGap, cb) {
|
|
1536
1538
|
if (ignoreMaxGap) return cb(null, true);
|
|
1537
1539
|
|
|
1538
|
-
|
|
1540
|
+
const newest = { noChange: true, reverse: true, limit: Defaults.MAX_MAIN_ADDRESS_GAP };
|
|
1541
|
+
this.storage.fetchAddressesPage(this.walletId, newest, (err, addresses: IAddress[]) => {
|
|
1539
1542
|
if (err) return cb(err);
|
|
1540
|
-
const latestAddresses = addresses.
|
|
1543
|
+
const latestAddresses = addresses.reverse();
|
|
1541
1544
|
if (
|
|
1542
1545
|
latestAddresses.length < Defaults.MAX_MAIN_ADDRESS_GAP ||
|
|
1543
1546
|
latestAddresses.some(a => a.hasActivity)
|
|
@@ -1638,7 +1641,7 @@ export class WalletService implements IWalletService {
|
|
|
1638
1641
|
};
|
|
1639
1642
|
|
|
1640
1643
|
const getFirstAddress = (wallet, cb) => {
|
|
1641
|
-
this.storage.
|
|
1644
|
+
this.storage.fetchAddressesPage(this.walletId, { limit: 1 }, (err, addresses) => {
|
|
1642
1645
|
if (err) return cb(err);
|
|
1643
1646
|
if (addresses?.length) {
|
|
1644
1647
|
const x = addresses[0];
|
|
@@ -1691,17 +1694,9 @@ export class WalletService implements IWalletService {
|
|
|
1691
1694
|
|
|
1692
1695
|
getAddresses(opts: GetAddressesOpts, cb) {
|
|
1693
1696
|
opts = opts || {};
|
|
1694
|
-
|
|
1697
|
+
const { noChange, addresses: only, reverse, skip, limit } = opts;
|
|
1698
|
+
this.storage.fetchAddressesPage(this.walletId, { noChange, addresses: only, reverse, skip, limit }, (err, addresses) => {
|
|
1695
1699
|
if (err) return cb(err);
|
|
1696
|
-
if (opts.noChange) {
|
|
1697
|
-
addresses = addresses.filter(a => !a.isChange);
|
|
1698
|
-
}
|
|
1699
|
-
if (opts.addresses) {
|
|
1700
|
-
addresses = addresses.filter(a => opts.addresses.includes(a.address));
|
|
1701
|
-
}
|
|
1702
|
-
if (opts.reverse) addresses.reverse();
|
|
1703
|
-
if (opts.skip > 0) addresses = addresses.slice(opts.skip);
|
|
1704
|
-
if (opts.limit > 0) addresses = addresses.slice(0, opts.limit);
|
|
1705
1700
|
|
|
1706
1701
|
this.getWallet({}, (err, wallet) => {
|
|
1707
1702
|
if (err) return cb(err);
|
|
@@ -1783,7 +1778,7 @@ export class WalletService implements IWalletService {
|
|
|
1783
1778
|
async.series(
|
|
1784
1779
|
[
|
|
1785
1780
|
next => {
|
|
1786
|
-
|
|
1781
|
+
const useWallet = (err, w) => {
|
|
1787
1782
|
if (err) return next(err);
|
|
1788
1783
|
|
|
1789
1784
|
wallet = w;
|
|
@@ -1793,7 +1788,10 @@ export class WalletService implements IWalletService {
|
|
|
1793
1788
|
bc = this._getBlockchainExplorer(wallet.chain, wallet.network);
|
|
1794
1789
|
if (!bc) return cb(new Error('Could not get blockchain explorer instance'));
|
|
1795
1790
|
return next();
|
|
1796
|
-
}
|
|
1791
|
+
};
|
|
1792
|
+
// the balance code passes the wallet it already has
|
|
1793
|
+
if (opts.wallet) return useWallet(null, opts.wallet);
|
|
1794
|
+
this.getWallet({}, useWallet);
|
|
1797
1795
|
},
|
|
1798
1796
|
next => {
|
|
1799
1797
|
if (Array.isArray(opts.addresses)) {
|
|
@@ -1889,7 +1887,7 @@ export class WalletService implements IWalletService {
|
|
|
1889
1887
|
next => {
|
|
1890
1888
|
utxoIndex = _.keyBy(allUtxos, utxoKey);
|
|
1891
1889
|
|
|
1892
|
-
this.getPendingTxs({ numberFormat: opts.numberFormat }, (err, txps) => {
|
|
1890
|
+
this.getPendingTxs({ numberFormat: opts.numberFormat, wallet }, (err, txps) => {
|
|
1893
1891
|
if (err) return next(err);
|
|
1894
1892
|
|
|
1895
1893
|
const lockedInputs = txps.flatMap(t => t.inputs).map(utxoKey);
|
|
@@ -1933,7 +1931,8 @@ export class WalletService implements IWalletService {
|
|
|
1933
1931
|
});
|
|
1934
1932
|
logger.debug(`Got ${allUtxos.length} usable UTXOs`);
|
|
1935
1933
|
return next();
|
|
1936
|
-
}
|
|
1934
|
+
},
|
|
1935
|
+
wallet
|
|
1937
1936
|
);
|
|
1938
1937
|
},
|
|
1939
1938
|
next => {
|
|
@@ -1946,6 +1945,7 @@ export class WalletService implements IWalletService {
|
|
|
1946
1945
|
}
|
|
1947
1946
|
utxo.path = addressToPath[utxo.address].path;
|
|
1948
1947
|
utxo.publicKeys = addressToPath[utxo.address].publicKeys;
|
|
1948
|
+
utxo.isChange = addressToPath[utxo.address].isChange;
|
|
1949
1949
|
}
|
|
1950
1950
|
return next();
|
|
1951
1951
|
}
|
|
@@ -2399,6 +2399,37 @@ export class WalletService implements IWalletService {
|
|
|
2399
2399
|
}
|
|
2400
2400
|
|
|
2401
2401
|
_validateAndSanitizeTxOpts(wallet, opts, cb) {
|
|
2402
|
+
const gasFeeFields = ['gasPrice', 'maxGasFee', 'priorityGasFee'];
|
|
2403
|
+
const hasGasFeeOverride = gasFeeFields.some(key => opts[key] != null);
|
|
2404
|
+
if (hasGasFeeOverride) {
|
|
2405
|
+
if (!Constants.EVM_CHAINS[wallet.chain.toUpperCase()]) {
|
|
2406
|
+
return cb(new ClientError('Gas fee overrides are only supported for EVM chains'));
|
|
2407
|
+
}
|
|
2408
|
+
for (const key of gasFeeFields) {
|
|
2409
|
+
if (opts[key] != null && (!Number.isSafeInteger(opts[key]) || opts[key] <= 0)) {
|
|
2410
|
+
return cb(new ClientError(`${key} must be a positive safe integer in wei`));
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
if (opts.gasPrice != null && Number(opts.txType || 0) !== 0) {
|
|
2414
|
+
return cb(new ClientError('gasPrice requires a type 0 transaction'));
|
|
2415
|
+
}
|
|
2416
|
+
if ((opts.maxGasFee != null || opts.priorityGasFee != null) && Number(opts.txType) !== 2) {
|
|
2417
|
+
return cb(new ClientError('maxGasFee and priorityGasFee require a type 2 transaction'));
|
|
2418
|
+
}
|
|
2419
|
+
if ((opts.maxGasFee != null) !== (opts.priorityGasFee != null)) {
|
|
2420
|
+
return cb(new ClientError('maxGasFee and priorityGasFee must be supplied together'));
|
|
2421
|
+
}
|
|
2422
|
+
if (opts.priorityGasFee > opts.maxGasFee) {
|
|
2423
|
+
return cb(new ClientError('priorityGasFee must not exceed maxGasFee'));
|
|
2424
|
+
}
|
|
2425
|
+
if (opts.feePerKb != null || opts.fee != null) {
|
|
2426
|
+
return cb(new ClientError('Gas fee overrides cannot be combined with feePerKb or fee'));
|
|
2427
|
+
}
|
|
2428
|
+
// sendMax calculates its amount before getFee, using a separate fee estimate.
|
|
2429
|
+
if (opts.sendMax) {
|
|
2430
|
+
return cb(new ClientError('Gas fee overrides are not supported with sendMax'));
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2402
2433
|
async.series(
|
|
2403
2434
|
[
|
|
2404
2435
|
next => {
|
|
@@ -2406,7 +2437,7 @@ export class WalletService implements IWalletService {
|
|
|
2406
2437
|
boolToNum(!!opts.feeLevel) + boolToNum(Utils.isNumber(opts.feePerKb)) + boolToNum(Utils.isNumber(opts.fee));
|
|
2407
2438
|
if (feeArgs > 1) return next(new ClientError('Only one of feeLevel/feePerKb/fee can be specified'));
|
|
2408
2439
|
|
|
2409
|
-
if (feeArgs == 0) {
|
|
2440
|
+
if (feeArgs == 0 && !hasGasFeeOverride) {
|
|
2410
2441
|
opts.feeLevel = 'normal';
|
|
2411
2442
|
}
|
|
2412
2443
|
|
|
@@ -2418,6 +2449,11 @@ export class WalletService implements IWalletService {
|
|
|
2418
2449
|
);
|
|
2419
2450
|
}
|
|
2420
2451
|
|
|
2452
|
+
// An explicit gas fee determines the price, not the named fee level.
|
|
2453
|
+
if (hasGasFeeOverride) {
|
|
2454
|
+
delete opts.feeLevel;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2421
2457
|
const error = ChainService.checkUtxos(wallet.chain, opts);
|
|
2422
2458
|
if (error) {
|
|
2423
2459
|
return next(new ClientError('fee can only be set when inputs are specified'));
|
|
@@ -2779,6 +2815,9 @@ export class WalletService implements IWalletService {
|
|
|
2779
2815
|
* @param {Boolean} opts.enableRBF - Optional. enable BTC Replace By Fee
|
|
2780
2816
|
* @param {Boolean} opts.replaceTxByFee - Optional. Ignore locked utxos check ( used for replacing a transaction designated as RBF)
|
|
2781
2817
|
* @param {number} opts.txType - Optional. Type of EVM transaction
|
|
2818
|
+
* @param {number} opts.gasPrice - Optional. Type 0 gas price in wei. Overrides feeLevel; incompatible with fee/feePerKb/sendMax.
|
|
2819
|
+
* @param {number} opts.maxGasFee - Optional. Type 2 maximum fee per gas in wei; requires priorityGasFee.
|
|
2820
|
+
* @param {number} opts.priorityGasFee - Optional. Positive type 2 priority fee per gas in wei; requires maxGasFee and must not exceed it. Gas fee overrides cannot be used with fee/feePerKb/sendMax.
|
|
2782
2821
|
* @param {number} opts.gasLimitBuffer - Optional. Percentage of buffer to add to the gasLimit
|
|
2783
2822
|
* @param {number} opts.priorityFeePercentile - Optional. Percentile of targeted priority fee rate
|
|
2784
2823
|
* @param {Boolean} opts.multiTx - Optional. Proposal will create multiple transactions
|
|
@@ -3169,19 +3208,23 @@ export class WalletService implements IWalletService {
|
|
|
3169
3208
|
* @returns {Object} txProposal
|
|
3170
3209
|
*/
|
|
3171
3210
|
getTx(opts, cb: (err: any, txp?: TxProposal) => void) {
|
|
3172
|
-
|
|
3211
|
+
// the tx and its note are both completed from the wallet, so read it once
|
|
3212
|
+
this.storage.fetchWallet(this.walletId, (err, wallet) => {
|
|
3173
3213
|
if (err) return cb(err);
|
|
3174
|
-
|
|
3214
|
+
this.storage.fetchTx(this.walletId, opts.txProposalId, (err, txp) => {
|
|
3215
|
+
if (err) return cb(err);
|
|
3216
|
+
if (!txp) return cb(Errors.TX_NOT_FOUND);
|
|
3175
3217
|
|
|
3176
|
-
|
|
3218
|
+
if (!txp.txid) return cb(null, txp);
|
|
3177
3219
|
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3220
|
+
this.storage.fetchTxNote(this.walletId, txp.txid, (err, note) => {
|
|
3221
|
+
if (err) {
|
|
3222
|
+
this.logw(`Error fetching tx note for ${txp.txid}:`, err);
|
|
3223
|
+
}
|
|
3224
|
+
txp.note = note;
|
|
3225
|
+
return cb(null, txp);
|
|
3226
|
+
}, wallet);
|
|
3227
|
+
}, wallet);
|
|
3185
3228
|
});
|
|
3186
3229
|
}
|
|
3187
3230
|
|
|
@@ -3199,19 +3242,22 @@ export class WalletService implements IWalletService {
|
|
|
3199
3242
|
// foreign wallet's proposal data to any authenticated copayer. The
|
|
3200
3243
|
// internal global consumers (BlockchainMonitor, getWalletFromIdentifier)
|
|
3201
3244
|
// are unaffected; they keep calling storage.fetchTxByHash directly.
|
|
3202
|
-
this.storage.
|
|
3245
|
+
this.storage.fetchWallet(this.walletId, (err, wallet) => {
|
|
3203
3246
|
if (err) return cb(err);
|
|
3204
|
-
|
|
3247
|
+
this.storage.fetchTxByHashForWallet(this.walletId, opts.txid, (err, txp) => {
|
|
3248
|
+
if (err) return cb(err);
|
|
3249
|
+
if (!txp) return cb(Errors.TX_NOT_FOUND);
|
|
3205
3250
|
|
|
3206
|
-
|
|
3251
|
+
if (!txp.txid) return cb(null, txp);
|
|
3207
3252
|
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3253
|
+
this.storage.fetchTxNote(this.walletId, txp.txid, (err, note) => {
|
|
3254
|
+
if (err) {
|
|
3255
|
+
this.logw(`Error fetching tx note for ${txp.txid}:`, err);
|
|
3256
|
+
}
|
|
3257
|
+
txp.note = note;
|
|
3258
|
+
return cb(null, txp);
|
|
3259
|
+
}, wallet);
|
|
3260
|
+
}, wallet);
|
|
3215
3261
|
});
|
|
3216
3262
|
}
|
|
3217
3263
|
|
|
@@ -3841,7 +3887,7 @@ export class WalletService implements IWalletService {
|
|
|
3841
3887
|
return cb(err, txps);
|
|
3842
3888
|
}
|
|
3843
3889
|
);
|
|
3844
|
-
});
|
|
3890
|
+
}, opts.wallet);
|
|
3845
3891
|
}
|
|
3846
3892
|
}
|
|
3847
3893
|
|
|
@@ -4867,7 +4913,8 @@ export class WalletService implements IWalletService {
|
|
|
4867
4913
|
minTs,
|
|
4868
4914
|
maxTs
|
|
4869
4915
|
},
|
|
4870
|
-
done
|
|
4916
|
+
done,
|
|
4917
|
+
wallet
|
|
4871
4918
|
);
|
|
4872
4919
|
},
|
|
4873
4920
|
done => {
|
|
@@ -4876,7 +4923,8 @@ export class WalletService implements IWalletService {
|
|
|
4876
4923
|
{
|
|
4877
4924
|
minTs
|
|
4878
4925
|
},
|
|
4879
|
-
done
|
|
4926
|
+
done,
|
|
4927
|
+
wallet
|
|
4880
4928
|
);
|
|
4881
4929
|
}
|
|
4882
4930
|
],
|
package/src/lib/storage.ts
CHANGED
|
@@ -304,8 +304,9 @@ export class Storage {
|
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
// TODO: should be done client-side
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
// Callers that already have the wallet pass it, to save reading it again
|
|
308
|
+
_completeTxData(walletId: string, txs: TxProposal | TxProposal[], cb: (err?: any, txs?: TxProposal | TxProposal[]) => void, wallet?: Wallet) {
|
|
309
|
+
const complete = (err, wallet: Wallet) => {
|
|
309
310
|
if (err) return cb(err);
|
|
310
311
|
for (const tx of [].concat(txs) as TxProposal[]) {
|
|
311
312
|
tx.derivationStrategy = wallet.derivationStrategy || 'BIP45';
|
|
@@ -318,11 +319,13 @@ export class Storage {
|
|
|
318
319
|
tx.raw = tx.getRawTx();
|
|
319
320
|
}
|
|
320
321
|
return cb(null, txs);
|
|
321
|
-
}
|
|
322
|
+
};
|
|
323
|
+
if (wallet) return complete(null, wallet);
|
|
324
|
+
this.fetchWallet(walletId, complete);
|
|
322
325
|
}
|
|
323
326
|
|
|
324
327
|
// TODO: remove walletId from signature
|
|
325
|
-
fetchTx(walletId: string, txProposalId: string, cb: (err?: any, tx?: TxProposal) => void) {
|
|
328
|
+
fetchTx(walletId: string, txProposalId: string, cb: (err?: any, tx?: TxProposal) => void, wallet?: Wallet) {
|
|
326
329
|
if (!this.db) return cb();
|
|
327
330
|
|
|
328
331
|
this.db.collection(collections.TXS).findOne(
|
|
@@ -333,7 +336,7 @@ export class Storage {
|
|
|
333
336
|
(err, result) => {
|
|
334
337
|
if (err) return cb(err);
|
|
335
338
|
if (!result) return cb();
|
|
336
|
-
return this._completeTxData(walletId, TxProposal.fromObj(result), cb);
|
|
339
|
+
return this._completeTxData(walletId, TxProposal.fromObj(result), cb, wallet);
|
|
337
340
|
}
|
|
338
341
|
);
|
|
339
342
|
}
|
|
@@ -356,7 +359,7 @@ export class Storage {
|
|
|
356
359
|
|
|
357
360
|
// Wallet-scoped counterpart to fetchTxByHash, used by the authenticated
|
|
358
361
|
// by-hash API read.
|
|
359
|
-
fetchTxByHashForWallet(walletId: string, hash, cb: (err?: any, tx?: TxProposal) => void) {
|
|
362
|
+
fetchTxByHashForWallet(walletId: string, hash, cb: (err?: any, tx?: TxProposal) => void, wallet?: Wallet) {
|
|
360
363
|
if (!this.db) return cb();
|
|
361
364
|
|
|
362
365
|
this.db.collection(collections.TXS).findOne(
|
|
@@ -368,7 +371,7 @@ export class Storage {
|
|
|
368
371
|
if (err) return cb(err);
|
|
369
372
|
if (!result) return cb();
|
|
370
373
|
|
|
371
|
-
return this._completeTxData(walletId, TxProposal.fromObj(result), cb);
|
|
374
|
+
return this._completeTxData(walletId, TxProposal.fromObj(result), cb, wallet);
|
|
372
375
|
}
|
|
373
376
|
);
|
|
374
377
|
}
|
|
@@ -444,7 +447,7 @@ export class Storage {
|
|
|
444
447
|
});
|
|
445
448
|
}
|
|
446
449
|
|
|
447
|
-
fetchPendingTxs(walletId, cb) {
|
|
450
|
+
fetchPendingTxs(walletId, cb, wallet?: Wallet) {
|
|
448
451
|
this.db
|
|
449
452
|
.collection(collections.TXS)
|
|
450
453
|
.find({
|
|
@@ -460,7 +463,7 @@ export class Storage {
|
|
|
460
463
|
const txs = _.map(result, tx => {
|
|
461
464
|
return TxProposal.fromObj(tx);
|
|
462
465
|
});
|
|
463
|
-
return this._completeTxData(walletId, txs, cb);
|
|
466
|
+
return this._completeTxData(walletId, txs, cb, wallet);
|
|
464
467
|
});
|
|
465
468
|
}
|
|
466
469
|
|
|
@@ -472,7 +475,7 @@ export class Storage {
|
|
|
472
475
|
* @param opts.maxTs
|
|
473
476
|
* @param opts.limit
|
|
474
477
|
*/
|
|
475
|
-
fetchTxs(walletId, opts, cb) {
|
|
478
|
+
fetchTxs(walletId, opts, cb, wallet?: Wallet) {
|
|
476
479
|
opts = opts || {};
|
|
477
480
|
|
|
478
481
|
const tsFilter: { $gte?: number; $lte?: number } = {};
|
|
@@ -499,7 +502,7 @@ export class Storage {
|
|
|
499
502
|
const txs = _.map(result, tx => {
|
|
500
503
|
return TxProposal.fromObj(tx);
|
|
501
504
|
});
|
|
502
|
-
return this._completeTxData(walletId, txs, cb);
|
|
505
|
+
return this._completeTxData(walletId, txs, cb, wallet);
|
|
503
506
|
});
|
|
504
507
|
}
|
|
505
508
|
|
|
@@ -511,7 +514,7 @@ export class Storage {
|
|
|
511
514
|
* @param opts.maxTs
|
|
512
515
|
* @param opts.limit
|
|
513
516
|
*/
|
|
514
|
-
fetchBroadcastedTxs(walletId, opts, cb) {
|
|
517
|
+
fetchBroadcastedTxs(walletId, opts, cb, wallet?: Wallet) {
|
|
515
518
|
opts = opts || {};
|
|
516
519
|
|
|
517
520
|
const tsFilter: { $gte?: number; $lte?: number } = {};
|
|
@@ -543,7 +546,7 @@ export class Storage {
|
|
|
543
546
|
const txs = _.map(result, tx => {
|
|
544
547
|
return TxProposal.fromObj(tx);
|
|
545
548
|
});
|
|
546
|
-
return this._completeTxData(walletId, txs, cb);
|
|
549
|
+
return this._completeTxData(walletId, txs, cb, wallet);
|
|
547
550
|
});
|
|
548
551
|
}
|
|
549
552
|
|
|
@@ -678,6 +681,70 @@ export class Storage {
|
|
|
678
681
|
});
|
|
679
682
|
}
|
|
680
683
|
|
|
684
|
+
/**
|
|
685
|
+
* A page of the wallet's addresses, in fetchAddresses order (createdOn, then path), reading only the addresses the
|
|
686
|
+
* page needs. noChange and addresses filter the list, then reverse, skip and limit apply in that order.
|
|
687
|
+
*
|
|
688
|
+
* @param walletId
|
|
689
|
+
* @param opts.noChange - leave out change addresses
|
|
690
|
+
* @param opts.addresses - only these addresses
|
|
691
|
+
* @param opts.reverse - newest first
|
|
692
|
+
* @param opts.skip
|
|
693
|
+
* @param opts.limit
|
|
694
|
+
*/
|
|
695
|
+
fetchAddressesPage(
|
|
696
|
+
walletId: string,
|
|
697
|
+
opts: { noChange?: boolean; addresses?: string[]; reverse?: boolean; skip?: number; limit?: number },
|
|
698
|
+
cb: (err?: any, addresses?: Address[]) => void
|
|
699
|
+
) {
|
|
700
|
+
const query: { walletId: string; isChange?: { $in: Array<false | null> }; address?: { $in: string[] } } = { walletId };
|
|
701
|
+
// false, null or missing, the same addresses as !isChange
|
|
702
|
+
if (opts.noChange) query.isChange = { $in: [false, null] };
|
|
703
|
+
if (opts.addresses) query.address = { $in: opts.addresses };
|
|
704
|
+
// skip and limit apply only when positive, and as whole numbers, as slice does
|
|
705
|
+
const skip = opts.skip > 0 ? Math.floor(opts.skip) : 0;
|
|
706
|
+
const limit = opts.limit > 0 ? Math.floor(opts.limit) : null;
|
|
707
|
+
const collection = this.db.collection(collections.ADDRESSES);
|
|
708
|
+
|
|
709
|
+
const page = docs => {
|
|
710
|
+
let addresses = Utils.sortAsc(docs.map(Address.fromObj), 'createdOn', 'path');
|
|
711
|
+
if (opts.reverse) addresses.reverse();
|
|
712
|
+
if (skip) addresses = addresses.slice(skip);
|
|
713
|
+
if (limit !== null) addresses = addresses.slice(0, limit);
|
|
714
|
+
return addresses;
|
|
715
|
+
};
|
|
716
|
+
const readAll = () => collection.find(query).sort({ createdOn: 1 }).toArray((err, docs) => {
|
|
717
|
+
if (err) return cb(err);
|
|
718
|
+
return cb(null, page(docs));
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// filtering by address, or reading without a limit or with one too large for a query limit, reads every matching
|
|
722
|
+
// address
|
|
723
|
+
if (opts.addresses || !(limit > 0) || !Number.isSafeInteger(skip + limit + 1)) return readAll();
|
|
724
|
+
|
|
725
|
+
// fetchAddresses sorts an address with a missing createdOn last and one with a null createdOn first, but Mongo sorts
|
|
726
|
+
// both first, so a wallet that has either is read whole
|
|
727
|
+
collection.findOne({ walletId, createdOn: null }, { projection: { _id: 1 } }, (err, unordered) => {
|
|
728
|
+
if (err) return cb(err);
|
|
729
|
+
if (unordered) return readAll();
|
|
730
|
+
|
|
731
|
+
// The page is within the first skip + limit addresses by createdOn plus any others sharing the last one's
|
|
732
|
+
// createdOn, whatever order addresses created in the same second are in. One more address shows whether there are any.
|
|
733
|
+
const count = skip + limit;
|
|
734
|
+
collection.find(query).sort({ createdOn: opts.reverse ? -1 : 1 }).limit(count + 1).toArray((err, docs) => {
|
|
735
|
+
if (err) return cb(err);
|
|
736
|
+
if (docs.length <= count || docs[count].createdOn !== docs[count - 1].createdOn) {
|
|
737
|
+
return cb(null, page(docs.slice(0, count)));
|
|
738
|
+
}
|
|
739
|
+
collection.find({ ...query, createdOn: docs[count - 1].createdOn }).toArray((err, ties) => {
|
|
740
|
+
if (err) return cb(err);
|
|
741
|
+
const read = new Set(docs.slice(0, count).map(doc => String(doc._id)));
|
|
742
|
+
return cb(null, page([...docs.slice(0, count), ...ties.filter(doc => !read.has(String(doc._id)))]));
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
|
|
681
748
|
migrateToCashAddr(walletId, cb) {
|
|
682
749
|
const cursor = this.db.collection(collections.ADDRESSES).find({
|
|
683
750
|
walletId
|
|
@@ -1300,7 +1367,7 @@ export class Storage {
|
|
|
1300
1367
|
});
|
|
1301
1368
|
}
|
|
1302
1369
|
|
|
1303
|
-
fetchTxNote(walletId, txid, cb: (err?: any, note?: TxNote) => void) {
|
|
1370
|
+
fetchTxNote(walletId, txid, cb: (err?: any, note?: TxNote) => void, wallet?: Wallet) {
|
|
1304
1371
|
this.db.collection(collections.TX_NOTES).findOne(
|
|
1305
1372
|
{
|
|
1306
1373
|
walletId,
|
|
@@ -1309,20 +1376,23 @@ export class Storage {
|
|
|
1309
1376
|
(err, result) => {
|
|
1310
1377
|
if (err) return cb(err);
|
|
1311
1378
|
if (!result) return cb();
|
|
1312
|
-
return this._completeTxNotesData(walletId, TxNote.fromObj(result), cb);
|
|
1379
|
+
return this._completeTxNotesData(walletId, TxNote.fromObj(result), cb, wallet);
|
|
1313
1380
|
}
|
|
1314
1381
|
);
|
|
1315
1382
|
}
|
|
1316
1383
|
|
|
1317
1384
|
// TODO: should be done client-side
|
|
1318
|
-
|
|
1319
|
-
|
|
1385
|
+
// Callers that already have the wallet pass it, to save reading it again
|
|
1386
|
+
_completeTxNotesData(walletId, notes: TxNote | TxNote[], cb: (err?: any, notes?: TxNote | TxNote[]) => void, wallet?: Wallet) {
|
|
1387
|
+
const complete = (err, wallet: Wallet) => {
|
|
1320
1388
|
if (err) return cb(err);
|
|
1321
1389
|
for (const note of [].concat(notes) as TxNote[]) {
|
|
1322
1390
|
note.editedByName = wallet.getCopayer(note.editedBy).name;
|
|
1323
1391
|
}
|
|
1324
1392
|
return cb(null, notes);
|
|
1325
|
-
}
|
|
1393
|
+
};
|
|
1394
|
+
if (wallet) return complete(null, wallet);
|
|
1395
|
+
this.fetchWallet(walletId, complete);
|
|
1326
1396
|
}
|
|
1327
1397
|
|
|
1328
1398
|
/**
|
|
@@ -1331,7 +1401,7 @@ export class Storage {
|
|
|
1331
1401
|
* @param walletId
|
|
1332
1402
|
* @param opts.minTs
|
|
1333
1403
|
*/
|
|
1334
|
-
fetchTxNotes(walletId, opts, cb) {
|
|
1404
|
+
fetchTxNotes(walletId, opts, cb, wallet?: Wallet) {
|
|
1335
1405
|
const filter: { walletId: string; editedOn?: { $gte: number } } = {
|
|
1336
1406
|
walletId
|
|
1337
1407
|
};
|
|
@@ -1349,7 +1419,7 @@ export class Storage {
|
|
|
1349
1419
|
return TxNote.fromObj(note);
|
|
1350
1420
|
})
|
|
1351
1421
|
);
|
|
1352
|
-
return this._completeTxNotesData(walletId, notes, cb);
|
|
1422
|
+
return this._completeTxNotesData(walletId, notes, cb, wallet);
|
|
1353
1423
|
});
|
|
1354
1424
|
}
|
|
1355
1425
|
|