@bitpay-labs/bitcore-wallet-service 11.11.0 → 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/config.ts +0 -20
- package/src/externalservices/banxa.ts +51 -10
- package/src/externalservices/changelly.ts +54 -6
- package/src/externalservices/moonpay.ts +85 -19
- package/src/externalservices/oneInch.ts +13 -5
- package/src/externalservices/ramp.ts +42 -8
- package/src/externalservices/sardine.ts +51 -10
- package/src/externalservices/simplex.ts +58 -11
- package/src/externalservices/thorswap.ts +46 -11
- package/src/externalservices/transak.ts +53 -11
- 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/services.ts +0 -12
- package/src/lib/routes/wallets.ts +3 -1
- package/src/lib/server.ts +136 -91
- package/src/lib/storage.ts +90 -20
- package/src/types/externalservices.d.ts +0 -1
- 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/externalservices/banxa.test.ts +95 -5
- package/test/integration/externalservices/changelly.test.ts +118 -6
- package/test/integration/externalservices/coinGecko.test.ts +4 -0
- package/test/integration/externalservices/moonpay.test.ts +178 -9
- package/test/integration/externalservices/oneInch.test.ts +28 -1
- package/test/integration/externalservices/ramp.test.ts +126 -4
- package/test/integration/externalservices/sardine.test.ts +95 -5
- package/test/integration/externalservices/simplex.test.ts +95 -5
- package/test/integration/externalservices/thorswap.test.ts +81 -5
- package/test/integration/externalservices/transak.test.ts +98 -6
- package/test/integration/helpers.ts +1 -1
- package/test/integration/server.test.ts +254 -136
- package/test/integration/walletReads.test.ts +488 -0
- package/ts_build/package.json +3 -3
- package/ts_build/src/config.d.ts.map +1 -1
- package/ts_build/src/config.js +0 -4
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/externalservices/banxa.d.ts.map +1 -1
- package/ts_build/src/externalservices/banxa.js +51 -10
- package/ts_build/src/externalservices/banxa.js.map +1 -1
- package/ts_build/src/externalservices/changelly.d.ts.map +1 -1
- package/ts_build/src/externalservices/changelly.js +54 -6
- package/ts_build/src/externalservices/changelly.js.map +1 -1
- package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
- package/ts_build/src/externalservices/moonpay.js +85 -19
- package/ts_build/src/externalservices/moonpay.js.map +1 -1
- package/ts_build/src/externalservices/oneInch.d.ts.map +1 -1
- package/ts_build/src/externalservices/oneInch.js +13 -5
- package/ts_build/src/externalservices/oneInch.js.map +1 -1
- package/ts_build/src/externalservices/ramp.d.ts.map +1 -1
- package/ts_build/src/externalservices/ramp.js +42 -8
- package/ts_build/src/externalservices/ramp.js.map +1 -1
- package/ts_build/src/externalservices/sardine.d.ts.map +1 -1
- package/ts_build/src/externalservices/sardine.js +51 -10
- package/ts_build/src/externalservices/sardine.js.map +1 -1
- package/ts_build/src/externalservices/simplex.d.ts.map +1 -1
- package/ts_build/src/externalservices/simplex.js +55 -11
- package/ts_build/src/externalservices/simplex.js.map +1 -1
- package/ts_build/src/externalservices/thorswap.d.ts.map +1 -1
- package/ts_build/src/externalservices/thorswap.js +45 -11
- package/ts_build/src/externalservices/thorswap.js.map +1 -1
- package/ts_build/src/externalservices/transak.d.ts.map +1 -1
- package/ts_build/src/externalservices/transak.js +53 -11
- package/ts_build/src/externalservices/transak.js.map +1 -1
- 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/services.d.ts.map +1 -1
- package/ts_build/src/lib/routes/services.js +0 -10
- package/ts_build/src/lib/routes/services.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 -3
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +128 -96
- 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/externalservices/banxa.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/banxa.test.js +93 -5
- package/ts_build/test/integration/externalservices/banxa.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/changelly.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/changelly.test.js +111 -6
- package/ts_build/test/integration/externalservices/changelly.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/coinGecko.test.js +4 -0
- package/ts_build/test/integration/externalservices/coinGecko.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/moonpay.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/moonpay.test.js +169 -9
- package/ts_build/test/integration/externalservices/moonpay.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/oneInch.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/oneInch.test.js +26 -1
- package/ts_build/test/integration/externalservices/oneInch.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/ramp.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/ramp.test.js +115 -4
- package/ts_build/test/integration/externalservices/ramp.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/sardine.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/sardine.test.js +94 -5
- package/ts_build/test/integration/externalservices/sardine.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/simplex.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/simplex.test.js +89 -5
- package/ts_build/test/integration/externalservices/simplex.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/thorswap.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/thorswap.test.js +76 -5
- package/ts_build/test/integration/externalservices/thorswap.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/transak.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/transak.test.js +93 -6
- package/ts_build/test/integration/externalservices/transak.test.js.map +1 -1
- package/ts_build/test/integration/helpers.js +1 -1
- package/ts_build/test/integration/helpers.js.map +1 -1
- package/ts_build/test/integration/server.test.js +225 -124
- 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/externalservices/wyre.ts +0 -131
- package/test/integration/externalservices/wyre.test.ts +0 -270
- package/ts_build/src/externalservices/wyre.d.ts +0 -7
- package/ts_build/src/externalservices/wyre.d.ts.map +0 -1
- package/ts_build/src/externalservices/wyre.js +0 -144
- package/ts_build/src/externalservices/wyre.js.map +0 -1
- package/ts_build/test/integration/externalservices/wyre.test.d.ts +0 -2
- package/ts_build/test/integration/externalservices/wyre.test.d.ts.map +0 -1
- package/ts_build/test/integration/externalservices/wyre.test.js +0 -281
- package/ts_build/test/integration/externalservices/wyre.test.js.map +0 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import * as chai from 'chai';
|
|
4
|
+
import 'chai/register-should';
|
|
5
|
+
import util from 'util';
|
|
6
|
+
import sinon from 'sinon';
|
|
7
|
+
import { BCHAddressTranslator } from '../../src/lib/bchaddresstranslator';
|
|
8
|
+
import { Common } from '../../src/lib/common';
|
|
9
|
+
import { Address } from '../../src/lib/model';
|
|
10
|
+
import { WalletService } from '../../src/lib/server';
|
|
11
|
+
import { Storage } from '../../src/lib/storage';
|
|
12
|
+
import * as TestData from '../testdata';
|
|
13
|
+
import helpers from './helpers';
|
|
14
|
+
|
|
15
|
+
const should = chai.should();
|
|
16
|
+
const { Defaults } = Common;
|
|
17
|
+
|
|
18
|
+
// The order the wallet's addresses come back in: by createdOn, then by path compared as a string, so within one second
|
|
19
|
+
// m/0/10 comes before m/0/2. Every case below is checked against this list.
|
|
20
|
+
const ORDER = [
|
|
21
|
+
// createdOn 1000
|
|
22
|
+
'm/0/0', 'm/0/1', 'm/0/2', 'm/0/3', 'm/0/4', 'm/1/0',
|
|
23
|
+
// createdOn 1001
|
|
24
|
+
'm/0/10', 'm/0/11', 'm/0/12', 'm/0/5', 'm/0/6', 'm/0/7', 'm/0/8', 'm/0/9', 'm/1/1', 'm/1/2'
|
|
25
|
+
];
|
|
26
|
+
const CREATED_ON = path => ['m/0/0', 'm/0/1', 'm/0/2', 'm/0/3', 'm/0/4', 'm/1/0'].includes(path) ? 1000 : 1001;
|
|
27
|
+
|
|
28
|
+
describe('Address reads', function() {
|
|
29
|
+
let blockchainExplorer;
|
|
30
|
+
let storage: Storage;
|
|
31
|
+
|
|
32
|
+
before(async function() {
|
|
33
|
+
({ blockchainExplorer, storage } = await helpers.before());
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
beforeEach(async function() {
|
|
37
|
+
await helpers.beforeEach();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// undoes direct assignments to the mock explorer
|
|
41
|
+
const restores: Array<() => void> = [];
|
|
42
|
+
afterEach(function() {
|
|
43
|
+
sinon.restore();
|
|
44
|
+
while (restores.length) restores.pop()();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
after(async function() {
|
|
48
|
+
await helpers.after();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const call = (server: WalletService, method: string, opts = {}) => util.promisify(server[method]).call(server, opts);
|
|
52
|
+
const addresses = () => storage.db.collection(Storage.collections.ADDRESSES);
|
|
53
|
+
const setCreatedOn = (walletId: string, paths: string[], createdOn) => addresses().updateMany({ walletId, path: { $in: paths } }, { $set: { createdOn } });
|
|
54
|
+
|
|
55
|
+
// A 1-of-1 wallet with 13 receive and 3 change addresses, stored with the createdOn values of ORDER
|
|
56
|
+
const walletWithAddresses = async (opts = {}) => {
|
|
57
|
+
const { server, wallet } = await helpers.createAndJoinWallet(1, 1, opts);
|
|
58
|
+
await helpers.createAddresses(server, wallet, 13, 3);
|
|
59
|
+
for (const createdOn of [1000, 1001]) {
|
|
60
|
+
await setCreatedOn(wallet.id, ORDER.filter(path => CREATED_ON(path) === createdOn), createdOn);
|
|
61
|
+
}
|
|
62
|
+
const docs = await addresses().find({ walletId: wallet.id }).toArray();
|
|
63
|
+
const addressOf = Object.fromEntries(docs.map(d => [d.path, d.address]));
|
|
64
|
+
return { server, wallet, addressOf };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// What getAddresses should return for these options, applied to ORDER the way the API documents them
|
|
68
|
+
const expectedPaths = ({ noChange = false, reverse = false, skip = 0, limit = 0, paths = null, order = ORDER }) => {
|
|
69
|
+
let list = order.filter(path => !(noChange && path.startsWith('m/1/')) && (!paths || paths.includes(path)));
|
|
70
|
+
if (reverse) list = [...list].reverse();
|
|
71
|
+
if (skip > 0) list = list.slice(skip);
|
|
72
|
+
if (limit > 0) list = list.slice(0, limit);
|
|
73
|
+
return list;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Makes the addresses collection fail every query
|
|
77
|
+
const failAddressQueries = () => {
|
|
78
|
+
const realCollection = storage.db.collection.bind(storage.db);
|
|
79
|
+
const failing = {
|
|
80
|
+
find: () => failing,
|
|
81
|
+
sort: () => failing,
|
|
82
|
+
skip: () => failing,
|
|
83
|
+
limit: () => failing,
|
|
84
|
+
project: () => failing,
|
|
85
|
+
toArray: cb => cb(new Error('addresses read failed')),
|
|
86
|
+
findOne: (...args) => args.pop()(new Error('addresses read failed'))
|
|
87
|
+
};
|
|
88
|
+
sinon.stub(storage.db, 'collection').callsFake(name => name === Storage.collections.ADDRESSES ? failing : realCollection(name));
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
describe('getAddresses', function() {
|
|
92
|
+
it('orders by createdOn, then by path as a string', async function() {
|
|
93
|
+
const { server } = await walletWithAddresses();
|
|
94
|
+
const result = await call(server, 'getAddresses');
|
|
95
|
+
result.map(a => a.path).should.deep.equal(ORDER);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('applies noChange, reverse, skip and limit in that order, for every combination', async function() {
|
|
99
|
+
const { server } = await walletWithAddresses();
|
|
100
|
+
for (const noChange of [false, true]) {
|
|
101
|
+
for (const reverse of [false, true]) {
|
|
102
|
+
for (const skip of [0, 2, 3, 4, 7, 20]) {
|
|
103
|
+
for (const limit of [0, 1, 3, 4, 5, 6, 8, 10, 20]) {
|
|
104
|
+
const opts = { noChange, reverse, skip, limit };
|
|
105
|
+
const result = await call(server, 'getAddresses', opts);
|
|
106
|
+
result.map(a => a.path).should.deep.equal(expectedPaths(opts), JSON.stringify(opts));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('filters by address, keeping the order and the paging', async function() {
|
|
114
|
+
const { server, addressOf } = await walletWithAddresses();
|
|
115
|
+
const paths = ['m/0/2', 'm/0/11', 'm/1/2', 'm/0/9', 'm/0/0'];
|
|
116
|
+
for (const reverse of [false, true]) {
|
|
117
|
+
for (const [skip, limit] of [[0, 0], [1, 2], [0, 10]]) {
|
|
118
|
+
for (const noChange of [false, true]) {
|
|
119
|
+
const opts = { addresses: paths.map(p => addressOf[p]), noChange, reverse, skip, limit };
|
|
120
|
+
const result = await call(server, 'getAddresses', opts);
|
|
121
|
+
result.map(a => a.path).should.deep.equal(expectedPaths({ paths, noChange, reverse, skip, limit }), JSON.stringify({ reverse, skip, limit, noChange }));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const expectOrder = async (server, order: string[], opts = {}) => {
|
|
128
|
+
for (const paging of [{}, { limit: 2 }, { reverse: true, limit: 2 }, { skip: 1, limit: 3 }]) {
|
|
129
|
+
const all = { ...opts, ...paging };
|
|
130
|
+
(await call(server, 'getAddresses', all)).map(a => a.path).should.deep.equal(expectedPaths({ ...all, order }), JSON.stringify(all));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
it('puts an address with no createdOn last', async function() {
|
|
135
|
+
const { server, wallet } = await walletWithAddresses();
|
|
136
|
+
await addresses().updateOne({ walletId: wallet.id, path: 'm/0/3' }, { $unset: { createdOn: '' } });
|
|
137
|
+
await expectOrder(server, [...ORDER.filter(p => p !== 'm/0/3'), 'm/0/3']);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('puts an address with a null createdOn first', async function() {
|
|
141
|
+
const { server, wallet } = await walletWithAddresses();
|
|
142
|
+
await setCreatedOn(wallet.id, ['m/0/9'], null);
|
|
143
|
+
await expectOrder(server, ['m/0/9', ...ORDER.filter(p => p !== 'm/0/9')]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('keeps the order of receive addresses when only a change address has no createdOn', async function() {
|
|
147
|
+
const { server, wallet } = await walletWithAddresses();
|
|
148
|
+
await addresses().updateOne({ walletId: wallet.id, path: 'm/1/0' }, { $unset: { createdOn: '' } });
|
|
149
|
+
await expectOrder(server, [...ORDER.filter(p => p !== 'm/1/0'), 'm/1/0'], { noChange: true });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('leaves out addresses whose isChange is any truthy value when noChange is set', async function() {
|
|
153
|
+
const { server, wallet } = await walletWithAddresses();
|
|
154
|
+
await addresses().updateOne({ walletId: wallet.id, path: 'm/1/1' }, { $set: { isChange: 1 } });
|
|
155
|
+
for (const opts of [{ noChange: true }, { noChange: true, limit: 20 }]) {
|
|
156
|
+
(await call(server, 'getAddresses', opts)).map(a => a.path).should.deep.equal(expectedPaths({ noChange: true }), JSON.stringify(opts));
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('returns nothing for a wallet with no addresses, an empty address filter, or a skip past the end', async function() {
|
|
161
|
+
const { server: empty } = await helpers.createAndJoinWallet(1, 1);
|
|
162
|
+
for (const opts of [{}, { limit: 1 }, { reverse: true, limit: 1 }]) {
|
|
163
|
+
(await call(empty, 'getAddresses', opts)).should.deep.equal([]);
|
|
164
|
+
}
|
|
165
|
+
const { server } = await walletWithAddresses({ offset: 1 });
|
|
166
|
+
(await call(server, 'getAddresses', { addresses: [] })).should.deep.equal([]);
|
|
167
|
+
(await call(server, 'getAddresses', { skip: 30, limit: 2 })).should.deep.equal([]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('treats fractional, huge, infinite, negative and NaN skips and limits the way slice does', async function() {
|
|
171
|
+
const { server } = await walletWithAddresses();
|
|
172
|
+
for (const opts of [
|
|
173
|
+
{ limit: 1.5 }, { limit: 0.5 }, { limit: Infinity }, { limit: 1e300 }, { skip: 2, limit: 2 ** 60 }, { limit: -1 }, { limit: NaN },
|
|
174
|
+
{ skip: 1.5, limit: 2 }, { skip: Infinity, limit: 1 }, { skip: -2, limit: 2 }, { skip: NaN, limit: 2 }, { reverse: true, skip: 2.7, limit: 3.2 }
|
|
175
|
+
]) {
|
|
176
|
+
(await call(server, 'getAddresses', opts)).map(a => a.path).should.deep.equal(expectedPaths(opts), JSON.stringify(opts));
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('returns BCH addresses in cash address format', async function() {
|
|
181
|
+
const { server, wallet } = await helpers.createAndJoinWallet(1, 1, { coin: 'bch' });
|
|
182
|
+
await helpers.createAddresses(server, wallet, 2, 0);
|
|
183
|
+
const stored = await addresses().find({ walletId: wallet.id }).sort({ path: 1 }).toArray();
|
|
184
|
+
|
|
185
|
+
const result = await call(server, 'getAddresses', { limit: 1 });
|
|
186
|
+
|
|
187
|
+
result.length.should.equal(1);
|
|
188
|
+
result[0].address.should.equal(BCHAddressTranslator.translate(stored[0].address, 'cashaddr'));
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('passes on a storage error', async function() {
|
|
192
|
+
const { server } = await walletWithAddresses();
|
|
193
|
+
failAddressQueries();
|
|
194
|
+
for (const opts of [{}, { limit: 2 }]) {
|
|
195
|
+
const err: any = await call(server, 'getAddresses', opts).catch(e => e);
|
|
196
|
+
err.message.should.equal('addresses read failed');
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('createAddress', function() {
|
|
202
|
+
// 13 receive addresses, all created in the same second, so the newest three are m/0/7, m/0/8 and m/0/9
|
|
203
|
+
const walletAtTheGap = async () => {
|
|
204
|
+
const { server, wallet } = await helpers.createAndJoinWallet(1, 1);
|
|
205
|
+
await helpers.createAddresses(server, wallet, 13, 3);
|
|
206
|
+
await addresses().updateMany({ walletId: wallet.id }, { $set: { createdOn: 1001 } });
|
|
207
|
+
sinon.stub(Defaults, 'MAX_MAIN_ADDRESS_GAP').value(3);
|
|
208
|
+
const docs = await addresses().find({ walletId: wallet.id }).toArray();
|
|
209
|
+
const pathOf = Object.fromEntries(docs.map(d => [d.address, d.path]));
|
|
210
|
+
const addressOf = Object.fromEntries(docs.map(d => [d.path, d.address]));
|
|
211
|
+
return { server, wallet, pathOf, addressOf };
|
|
212
|
+
};
|
|
213
|
+
const recordActivityChecks = (pathOf, active: string[], failsOn?: number) => {
|
|
214
|
+
helpers.stubAddressActivity(active, failsOn);
|
|
215
|
+
return sinon.spy(blockchainExplorer, 'getAddressActivity');
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
it('fails at the gap after checking the newest receive addresses, newest first', async function() {
|
|
219
|
+
const { server, pathOf } = await walletAtTheGap();
|
|
220
|
+
const checks = recordActivityChecks(pathOf, []);
|
|
221
|
+
|
|
222
|
+
const err: any = await call(server, 'createAddress').catch(e => e);
|
|
223
|
+
|
|
224
|
+
err.code.should.equal('MAIN_ADDRESS_GAP_REACHED');
|
|
225
|
+
checks.getCalls().map(c => pathOf[c.args[0]]).should.deep.equal(['m/0/9', 'm/0/8', 'm/0/7']);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('stops at the newest address with activity, marks it, and creates the next address', async function() {
|
|
229
|
+
const { server, wallet, pathOf, addressOf } = await walletAtTheGap();
|
|
230
|
+
const checks = recordActivityChecks(pathOf, [addressOf['m/0/8'], addressOf['m/0/7']]);
|
|
231
|
+
|
|
232
|
+
const address = await call(server, 'createAddress');
|
|
233
|
+
|
|
234
|
+
address.path.should.equal('m/0/13');
|
|
235
|
+
checks.getCalls().map(c => pathOf[c.args[0]]).should.deep.equal(['m/0/9', 'm/0/8']);
|
|
236
|
+
const marked = await addresses().find({ walletId: wallet.id, hasActivity: true }).toArray();
|
|
237
|
+
marked.map(d => d.path).should.deep.equal(['m/0/8']);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('creates without checking when an address in the window already has activity', async function() {
|
|
241
|
+
const { server, wallet, pathOf } = await walletAtTheGap();
|
|
242
|
+
await addresses().updateOne({ walletId: wallet.id, path: 'm/0/7' }, { $set: { hasActivity: true } });
|
|
243
|
+
const checks = recordActivityChecks(pathOf, []);
|
|
244
|
+
|
|
245
|
+
(await call(server, 'createAddress')).path.should.equal('m/0/13');
|
|
246
|
+
checks.callCount.should.equal(0);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('ignores activity on addresses outside the window and on change addresses', async function() {
|
|
250
|
+
const { server, wallet, pathOf } = await walletAtTheGap();
|
|
251
|
+
await addresses().updateMany({ walletId: wallet.id, path: { $in: ['m/0/6', 'm/0/10', 'm/1/2'] } }, { $set: { hasActivity: true } });
|
|
252
|
+
const checks = recordActivityChecks(pathOf, []);
|
|
253
|
+
|
|
254
|
+
const err: any = await call(server, 'createAddress').catch(e => e);
|
|
255
|
+
|
|
256
|
+
err.code.should.equal('MAIN_ADDRESS_GAP_REACHED');
|
|
257
|
+
checks.callCount.should.equal(3);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('creates without checking when there are fewer receive addresses than the gap', async function() {
|
|
261
|
+
const { server, wallet } = await helpers.createAndJoinWallet(1, 1);
|
|
262
|
+
await helpers.createAddresses(server, wallet, 2, 5);
|
|
263
|
+
sinon.stub(Defaults, 'MAX_MAIN_ADDRESS_GAP').value(3);
|
|
264
|
+
const checks = recordActivityChecks({}, []);
|
|
265
|
+
|
|
266
|
+
(await call(server, 'createAddress')).path.should.equal('m/0/2');
|
|
267
|
+
checks.callCount.should.equal(0);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('passes on an error checking activity', async function() {
|
|
271
|
+
const { server, pathOf } = await walletAtTheGap();
|
|
272
|
+
recordActivityChecks(pathOf, [], 2);
|
|
273
|
+
const err = await call(server, 'createAddress').catch(e => e);
|
|
274
|
+
err.should.equal('failed on request');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('passes on a storage error', async function() {
|
|
278
|
+
const { server } = await walletAtTheGap();
|
|
279
|
+
failAddressQueries();
|
|
280
|
+
const err: any = await call(server, 'createAddress').catch(e => e);
|
|
281
|
+
err.message.should.equal('addresses read failed');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('returns the first address of a single-address wallet', async function() {
|
|
285
|
+
const { server, wallet } = await helpers.createAndJoinWallet(1, 1, { singleAddress: true });
|
|
286
|
+
const first = await call(server, 'createAddress');
|
|
287
|
+
first.path.should.equal('m/0/0');
|
|
288
|
+
(await call(server, 'createAddress')).address.should.equal(first.address);
|
|
289
|
+
(await addresses().countDocuments({ walletId: wallet.id })).should.equal(1);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('passes on a storage error for a single-address wallet', async function() {
|
|
293
|
+
const { server } = await helpers.createAndJoinWallet(1, 1, { singleAddress: true });
|
|
294
|
+
await call(server, 'createAddress');
|
|
295
|
+
failAddressQueries();
|
|
296
|
+
const err: any = await call(server, 'createAddress').catch(e => e);
|
|
297
|
+
err.message.should.equal('addresses read failed');
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('reads only the addresses it needs', function() {
|
|
302
|
+
// 30 receive and 10 change addresses, each created in its own second
|
|
303
|
+
const largeWallet = async () => {
|
|
304
|
+
const { server, wallet } = await helpers.createAndJoinWallet(1, 1);
|
|
305
|
+
await helpers.createAddresses(server, wallet, 30, 10);
|
|
306
|
+
const docs = await addresses().find({ walletId: wallet.id }).toArray();
|
|
307
|
+
for (const [i, doc] of docs.entries()) {
|
|
308
|
+
await addresses().updateOne({ _id: doc._id }, { $set: { createdOn: 1000 + i } });
|
|
309
|
+
}
|
|
310
|
+
return { server, wallet };
|
|
311
|
+
};
|
|
312
|
+
const countBuilt = () => sinon.spy(Address, 'fromObj');
|
|
313
|
+
// Wraps the addresses collection: counts the documents its queries return, and fails the queries after the first
|
|
314
|
+
// `passing` finds when that is given. The cursors are the real ones, so the query shape doesn't matter.
|
|
315
|
+
const wrapAddressQueries = ({ passing = Infinity } = {}) => {
|
|
316
|
+
const realCollection = storage.db.collection.bind(storage.db);
|
|
317
|
+
const counts = { finds: 0, docs: 0, findOnes: 0 };
|
|
318
|
+
sinon.stub(storage.db, 'collection').callsFake(name => {
|
|
319
|
+
const collection = realCollection(name);
|
|
320
|
+
if (name !== Storage.collections.ADDRESSES) return collection;
|
|
321
|
+
return Object.assign(Object.create(collection), {
|
|
322
|
+
find: (...args) => {
|
|
323
|
+
const cursor = collection.find(...args);
|
|
324
|
+
const fail = ++counts.finds > passing;
|
|
325
|
+
const toArray = cursor.toArray.bind(cursor);
|
|
326
|
+
cursor.toArray = cb => fail ? cb(new Error('addresses read failed')) : toArray((err, docs) => {
|
|
327
|
+
counts.docs += docs?.length || 0;
|
|
328
|
+
cb(err, docs);
|
|
329
|
+
});
|
|
330
|
+
return cursor;
|
|
331
|
+
},
|
|
332
|
+
findOne: (...args) => {
|
|
333
|
+
counts.findOnes++;
|
|
334
|
+
return collection.findOne(...args);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
return counts;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
it('getAddresses with a limit builds only that many addresses', async function() {
|
|
342
|
+
const { server } = await largeWallet();
|
|
343
|
+
const built = countBuilt();
|
|
344
|
+
const read = wrapAddressQueries();
|
|
345
|
+
(await call(server, 'getAddresses', { limit: 1 })).length.should.equal(1);
|
|
346
|
+
built.callCount.should.equal(1);
|
|
347
|
+
read.docs.should.equal(2); // the page and one more, to see that the next address has a different createdOn
|
|
348
|
+
read.findOnes.should.equal(1);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('getAddresses with skip and a limit builds only the addresses up to the end of the page', async function() {
|
|
352
|
+
const { server } = await largeWallet();
|
|
353
|
+
const built = countBuilt();
|
|
354
|
+
const read = wrapAddressQueries();
|
|
355
|
+
(await call(server, 'getAddresses', { noChange: true, reverse: true, skip: 2, limit: 3 })).length.should.equal(3);
|
|
356
|
+
built.callCount.should.equal(5);
|
|
357
|
+
read.docs.should.equal(6);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('getAddresses also builds the addresses that share the page\'s last createdOn', async function() {
|
|
361
|
+
const { server, wallet } = await largeWallet();
|
|
362
|
+
const first = await addresses().find({ walletId: wallet.id }).sort({ createdOn: 1 }).limit(4).toArray();
|
|
363
|
+
await addresses().updateMany({ _id: { $in: first.slice(1).map(d => d._id) } }, { $set: { createdOn: first[1].createdOn } });
|
|
364
|
+
const built = countBuilt();
|
|
365
|
+
const read = wrapAddressQueries();
|
|
366
|
+
(await call(server, 'getAddresses', { limit: 2 })).length.should.equal(2);
|
|
367
|
+
built.callCount.should.equal(4);
|
|
368
|
+
read.docs.should.equal(6); // the page and one more, then the three sharing the last createdOn
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('getAddresses passes on a storage error reading the page', async function() {
|
|
372
|
+
const { server } = await largeWallet();
|
|
373
|
+
wrapAddressQueries({ passing: 0 });
|
|
374
|
+
const err: any = await call(server, 'getAddresses', { limit: 2 }).catch(e => e);
|
|
375
|
+
err.message.should.equal('addresses read failed');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('getAddresses passes on a storage error reading the addresses that share the last createdOn', async function() {
|
|
379
|
+
const { server, wallet } = await largeWallet();
|
|
380
|
+
await addresses().updateMany({ walletId: wallet.id }, { $set: { createdOn: 1000 } });
|
|
381
|
+
wrapAddressQueries({ passing: 1 });
|
|
382
|
+
const err: any = await call(server, 'getAddresses', { limit: 2 }).catch(e => e);
|
|
383
|
+
err.message.should.equal('addresses read failed');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('the single-address paths build only the first address', async function() {
|
|
387
|
+
for (const [coin, singleAddress] of [['eth', false], ['xrp', false], ['btc', true]] as Array<[string, boolean]>) {
|
|
388
|
+
const { server, wallet } = await helpers.createAndJoinWallet(1, 1, { coin, singleAddress, offset: coin === 'xrp' ? 1 : coin === 'btc' ? 2 : 0 });
|
|
389
|
+
await helpers.createAddresses(server, wallet, 10, 0);
|
|
390
|
+
const docs = await addresses().find({ walletId: wallet.id }).toArray();
|
|
391
|
+
for (const [i, doc] of docs.entries()) {
|
|
392
|
+
await addresses().updateOne({ _id: doc._id }, { $set: { createdOn: 1000 + i } });
|
|
393
|
+
}
|
|
394
|
+
await helpers.stubUtxos(server, wallet, [1]);
|
|
395
|
+
const built = countBuilt();
|
|
396
|
+
await call(server, coin === 'btc' ? 'createAddress' : 'getBalance');
|
|
397
|
+
built.callCount.should.equal(1, coin);
|
|
398
|
+
built.restore();
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('createAddress builds only the addresses in the gap window', async function() {
|
|
403
|
+
const { server } = await largeWallet();
|
|
404
|
+
sinon.stub(Defaults, 'MAX_MAIN_ADDRESS_GAP').value(3);
|
|
405
|
+
helpers.stubAddressActivity([]);
|
|
406
|
+
const built = countBuilt();
|
|
407
|
+
const read = wrapAddressQueries();
|
|
408
|
+
const err: any = await call(server, 'createAddress').catch(e => e);
|
|
409
|
+
err.code.should.equal('MAIN_ADDRESS_GAP_REACHED');
|
|
410
|
+
built.callCount.should.equal(3);
|
|
411
|
+
read.docs.should.equal(4);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe('single-address chains use their first address', function() {
|
|
416
|
+
// BWS can't derive a SOL address from an xpub (the client supplies a derived key), so the SOL wallet's address is
|
|
417
|
+
// stored directly
|
|
418
|
+
const SOL_ADDRESS = '7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV';
|
|
419
|
+
const SOL_ADDRESS_2 = 'GZNcvqb38Yd3TPD5JMvZJAGxxrDu8JLGECykcmzBpBw7';
|
|
420
|
+
const firstAddress = async (coin: string, server: WalletService, wallet) => {
|
|
421
|
+
if (coin !== 'sol') return call(server, 'createAddress');
|
|
422
|
+
const address = { walletId: wallet.id, address: SOL_ADDRESS, path: 'm/0/0', isChange: false, publicKeys: [], coin: 'sol', chain: 'sol', network: 'livenet', createdOn: 1000 };
|
|
423
|
+
await addresses().insertOne({ ...address });
|
|
424
|
+
return address;
|
|
425
|
+
};
|
|
426
|
+
// stubUtxos doesn't handle SOL: the mock explorer's getBalance already answers with a balance, and the rent minimum
|
|
427
|
+
// is stubbed here
|
|
428
|
+
const stubBalance = async (coin: string, server: WalletService, wallet) => {
|
|
429
|
+
if (coin !== 'sol') return helpers.stubUtxos(server, wallet, [1]);
|
|
430
|
+
const getRentMinimum = blockchainExplorer.getRentMinimum;
|
|
431
|
+
blockchainExplorer.getRentMinimum = (space, cb) => cb(null, Defaults.MIN_SOL_BALANCE);
|
|
432
|
+
restores.push(() => { blockchainExplorer.getRentMinimum = getRentMinimum; });
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
for (const coin of ['eth', 'xrp', 'sol']) {
|
|
436
|
+
it(`${coin} balance lists the first address`, async function() {
|
|
437
|
+
const { server, wallet } = await helpers.createAndJoinWallet(1, 1, { coin });
|
|
438
|
+
const address = await firstAddress(coin, server, wallet);
|
|
439
|
+
await stubBalance(coin, server, wallet);
|
|
440
|
+
|
|
441
|
+
const balance = await call(server, 'getBalance');
|
|
442
|
+
|
|
443
|
+
balance.byAddress.length.should.equal(1);
|
|
444
|
+
balance.byAddress[0].address.should.equal(address.address);
|
|
445
|
+
balance.byAddress[0].path.should.equal(address.path);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it(`${coin} balance passes on a storage error`, async function() {
|
|
449
|
+
const { server, wallet } = await helpers.createAndJoinWallet(1, 1, { coin });
|
|
450
|
+
await firstAddress(coin, server, wallet);
|
|
451
|
+
await stubBalance(coin, server, wallet);
|
|
452
|
+
failAddressQueries();
|
|
453
|
+
const err: any = await call(server, 'getBalance').catch(e => e);
|
|
454
|
+
err.message.should.equal('addresses read failed');
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// 11 receive addresses where m/0/2 and m/0/10 are the oldest: comparing paths as strings makes m/0/10 first
|
|
459
|
+
const tieForFirst = async (server, wallet) => {
|
|
460
|
+
await helpers.createAddresses(server, wallet, 11, 0);
|
|
461
|
+
await addresses().updateMany({ walletId: wallet.id }, { $set: { createdOn: 1001 } });
|
|
462
|
+
await setCreatedOn(wallet.id, ['m/0/2', 'm/0/10'], 1000);
|
|
463
|
+
return (await addresses().findOne({ walletId: wallet.id, path: 'm/0/10' })).address;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
it('the first address is the first by createdOn, then by path as a string', async function() {
|
|
467
|
+
const { server: eth, wallet: ethWallet } = await helpers.createAndJoinWallet(1, 1, { coin: 'eth' });
|
|
468
|
+
const ethFirst = await tieForFirst(eth, ethWallet);
|
|
469
|
+
await helpers.stubUtxos(eth, ethWallet, [1]);
|
|
470
|
+
(await call(eth, 'getBalance')).byAddress.map(a => a.address).should.deep.equal([ethFirst]);
|
|
471
|
+
|
|
472
|
+
const { server: btc, wallet: btcWallet } = await helpers.createAndJoinWallet(1, 1, { singleAddress: true, offset: 1 });
|
|
473
|
+
const btcFirst = await tieForFirst(btc, btcWallet);
|
|
474
|
+
(await call(btc, 'createAddress')).address.should.equal(btcFirst);
|
|
475
|
+
// the change goes to the first address too
|
|
476
|
+
await helpers.stubUtxos(btc, btcWallet, [1]);
|
|
477
|
+
const txp = await helpers.createAndPublishTx(btc, {
|
|
478
|
+
outputs: [{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 0.5e8 }],
|
|
479
|
+
feePerKb: 100e2
|
|
480
|
+
}, TestData.copayers[1].privKey_1H_0);
|
|
481
|
+
txp.changeAddress.address.should.equal(btcFirst);
|
|
482
|
+
|
|
483
|
+
const { server: sol, wallet: solWallet } = await helpers.createAndJoinWallet(1, 1, { coin: 'sol', offset: 2 });
|
|
484
|
+
await firstAddress('sol', sol, solWallet);
|
|
485
|
+
await addresses().insertOne({ walletId: solWallet.id, address: SOL_ADDRESS_2, path: 'm/0/1', isChange: false, publicKeys: [], coin: 'sol', chain: 'sol', network: 'livenet', createdOn: 2000 });
|
|
486
|
+
await stubBalance('sol', sol, solWallet);
|
|
487
|
+
(await call(sol, 'getBalance')).byAddress.map(a => a.address).should.deep.equal([SOL_ADDRESS]);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('a single-address BTC wallet sends change to its first address', async function() {
|
|
491
|
+
const { server, wallet } = await helpers.createAndJoinWallet(1, 1, { singleAddress: true });
|
|
492
|
+
const address = await call(server, 'createAddress');
|
|
493
|
+
await helpers.stubUtxos(server, wallet, [1]);
|
|
494
|
+
|
|
495
|
+
const txp = await helpers.createAndPublishTx(server, {
|
|
496
|
+
outputs: [{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 0.5e8 }],
|
|
497
|
+
feePerKb: 100e2
|
|
498
|
+
}, TestData.copayers[0].privKey_1H_0);
|
|
499
|
+
|
|
500
|
+
txp.changeAddress.address.should.equal(address.address);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
});
|
|
@@ -5,6 +5,7 @@ import sinon from 'sinon';
|
|
|
5
5
|
import util from 'util';
|
|
6
6
|
import * as CWC from '@bitpay-labs/crypto-wallet-core';
|
|
7
7
|
import { BlockchainMonitor } from '../../src/lib/blockchainmonitor';
|
|
8
|
+
import { ChainService } from '../../src/lib/chain';
|
|
8
9
|
import { Constants } from '../../src/lib/common/constants';
|
|
9
10
|
import helpers from './helpers';
|
|
10
11
|
|
|
@@ -190,6 +191,49 @@ describe('Blockchain monitor', function() {
|
|
|
190
191
|
});
|
|
191
192
|
});
|
|
192
193
|
|
|
194
|
+
describe('EVM tx events, as bitcore-node emits them', function() {
|
|
195
|
+
const token = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
|
|
196
|
+
const sender = '0xC877cBCF020A8AA259A1Efab1559B2A3A7259086';
|
|
197
|
+
// passes a tx event through the chain's onTx to the monitor, as the v8 explorer's wallets socket does
|
|
198
|
+
const receive = tx => bcmonitor._handleIncomingPayments('eth', 'livenet', ChainService.onTx('eth', tx));
|
|
199
|
+
const incomingNotifications = async s => {
|
|
200
|
+
const notifications = await util.promisify(s.getNotifications).call(s, {});
|
|
201
|
+
return notifications.filter(n => n.type === 'NewIncomingTx');
|
|
202
|
+
};
|
|
203
|
+
const ethAddress = async () => {
|
|
204
|
+
const { server: s } = await helpers.createAndJoinWallet(1, 1, { coin: 'eth' });
|
|
205
|
+
const address = await util.promisify(s.createAddress).call(s, {});
|
|
206
|
+
return { s, address: address.address };
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
it('notifies the recipient of a native transfer', async function() {
|
|
210
|
+
const { s, address } = await ethAddress();
|
|
211
|
+
receive({ chain: 'ETH', network: 'mainnet', txid: '0x01', to: address, from: sender, value: 5000, effects: [] });
|
|
212
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
213
|
+
const notifications = await incomingNotifications(s);
|
|
214
|
+
notifications.length.should.equal(1);
|
|
215
|
+
notifications[0].data.should.include({ txid: '0x01', address, amount: 5000 });
|
|
216
|
+
should.not.exist(notifications[0].data.tokenAddress);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('notifies the recipient of an ERC20 transfer, with the token', async function() {
|
|
220
|
+
const { s, address } = await ethAddress();
|
|
221
|
+
receive({
|
|
222
|
+
chain: 'ETH',
|
|
223
|
+
network: 'mainnet',
|
|
224
|
+
txid: '0x02',
|
|
225
|
+
to: token,
|
|
226
|
+
from: sender,
|
|
227
|
+
value: 0,
|
|
228
|
+
effects: [{ type: 'ERC20:transfer', to: address, from: sender, amount: '1000000', contractAddress: token, callStack: '' }]
|
|
229
|
+
});
|
|
230
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
231
|
+
const notifications = await incomingNotifications(s);
|
|
232
|
+
notifications.length.should.equal(1);
|
|
233
|
+
notifications[0].data.should.include({ txid: '0x02', address, amount: '1000000', tokenAddress: token });
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
193
237
|
it('should not notify copayers of incoming txs more than once', function(done) {
|
|
194
238
|
server.createAddress({}, function(err, address) {
|
|
195
239
|
should.not.exist(err);
|