@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.
Files changed (60) hide show
  1. package/Dockerfile +1 -1
  2. package/package.json +5 -5
  3. package/src/lib/chain/btc/index.ts +3 -2
  4. package/src/lib/chain/eth/index.ts +134 -117
  5. package/src/lib/chain/sol/index.ts +1 -1
  6. package/src/lib/chain/xrp/index.ts +1 -1
  7. package/src/lib/routes/wallets.ts +3 -1
  8. package/src/lib/server.ts +136 -88
  9. package/src/lib/storage.ts +90 -20
  10. package/test/chain/eth.test.ts +70 -0
  11. package/test/chain/sol.test.ts +1 -1
  12. package/test/chain/xrp.test.ts +1 -1
  13. package/test/expressapp.test.ts +144 -0
  14. package/test/integration/addressReads.test.ts +503 -0
  15. package/test/integration/bcmonitor.test.ts +44 -0
  16. package/test/integration/server.test.ts +198 -48
  17. package/test/integration/walletReads.test.ts +488 -0
  18. package/ts_build/package.json +3 -3
  19. package/ts_build/src/lib/chain/btc/index.d.ts.map +1 -1
  20. package/ts_build/src/lib/chain/btc/index.js +3 -2
  21. package/ts_build/src/lib/chain/btc/index.js.map +1 -1
  22. package/ts_build/src/lib/chain/eth/index.d.ts +8 -1
  23. package/ts_build/src/lib/chain/eth/index.d.ts.map +1 -1
  24. package/ts_build/src/lib/chain/eth/index.js +114 -102
  25. package/ts_build/src/lib/chain/eth/index.js.map +1 -1
  26. package/ts_build/src/lib/chain/sol/index.js +1 -1
  27. package/ts_build/src/lib/chain/sol/index.js.map +1 -1
  28. package/ts_build/src/lib/chain/xrp/index.js +1 -1
  29. package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
  30. package/ts_build/src/lib/routes/wallets.d.ts.map +1 -1
  31. package/ts_build/src/lib/routes/wallets.js +2 -1
  32. package/ts_build/src/lib/routes/wallets.js.map +1 -1
  33. package/ts_build/src/lib/server.d.ts +1 -1
  34. package/ts_build/src/lib/server.d.ts.map +1 -1
  35. package/ts_build/src/lib/server.js +128 -94
  36. package/ts_build/src/lib/server.js.map +1 -1
  37. package/ts_build/src/lib/storage.d.ts +17 -10
  38. package/ts_build/src/lib/storage.d.ts.map +1 -1
  39. package/ts_build/src/lib/storage.js +73 -20
  40. package/ts_build/src/lib/storage.js.map +1 -1
  41. package/ts_build/test/chain/eth.test.js +59 -0
  42. package/ts_build/test/chain/eth.test.js.map +1 -1
  43. package/ts_build/test/chain/sol.test.js +1 -1
  44. package/ts_build/test/chain/sol.test.js.map +1 -1
  45. package/ts_build/test/chain/xrp.test.js +1 -1
  46. package/ts_build/test/chain/xrp.test.js.map +1 -1
  47. package/ts_build/test/expressapp.test.js +132 -0
  48. package/ts_build/test/expressapp.test.js.map +1 -1
  49. package/ts_build/test/integration/addressReads.test.d.ts +2 -0
  50. package/ts_build/test/integration/addressReads.test.d.ts.map +1 -0
  51. package/ts_build/test/integration/addressReads.test.js +465 -0
  52. package/ts_build/test/integration/addressReads.test.js.map +1 -0
  53. package/ts_build/test/integration/bcmonitor.test.js +40 -0
  54. package/ts_build/test/integration/bcmonitor.test.js.map +1 -1
  55. package/ts_build/test/integration/server.test.js +181 -48
  56. package/ts_build/test/integration/server.test.js.map +1 -1
  57. package/ts_build/test/integration/walletReads.test.d.ts +2 -0
  58. package/ts_build/test/integration/walletReads.test.d.ts.map +1 -0
  59. package/ts_build/test/integration/walletReads.test.js +436 -0
  60. package/ts_build/test/integration/walletReads.test.js.map +1 -0
@@ -19,6 +19,76 @@ describe('Chain ETH', function() {
19
19
  x.address.should.equal('0x01');
20
20
  });
21
21
 
22
+ describe('#onTx', function() {
23
+ const token = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
24
+ const sender = '0xC877cBCF020A8AA259A1Efab1559B2A3A7259086';
25
+ const recipient = '0x37D3bCDA9d7d5Dc41e32DAd77a5BA89a77aA8BD0';
26
+ const multisig = '0x9527a556f0D9AA753dE0D1f9Ad024a17dd281B1D';
27
+ // a tx event as bitcore-node emits it to the wallets room
28
+ const tx = (fields = {}) => ({ chain: 'ETH', network: 'mainnet', txid: '0x01', to: token, from: sender, value: 0, ...fields });
29
+ // effects as bitcore-node builds them
30
+ const erc20 = { type: 'ERC20:transfer', to: recipient, from: sender, amount: '1000000', contractAddress: token, callStack: '' };
31
+ const internal = { to: recipient, from: sender, amount: '2000', callStack: '0' };
32
+ const multisigEffect = type => ({ type, to: recipient, from: sender, amount: '3000', contractAddress: multisig, callStack: '' });
33
+
34
+ it('notifies the tx recipient and value for a tx without effects', function() {
35
+ ChainService.onTx('eth', tx({ to: recipient, value: 5000 })).should.deep.equal({
36
+ txid: '0x01',
37
+ out: { address: recipient, amount: 5000, tokenAddress: undefined, multisigContractAddress: undefined }
38
+ });
39
+ ChainService.onTx('eth', tx({ to: recipient, value: 5000, effects: [] })).out.should.deep.equal({
40
+ address: recipient, amount: 5000, tokenAddress: undefined, multisigContractAddress: undefined
41
+ });
42
+ });
43
+
44
+ it('notifies the recipient of an internal value transfer', function() {
45
+ ChainService.onTx('eth', tx({ effects: [internal] })).out.should.deep.equal({
46
+ address: recipient, amount: '2000', tokenAddress: undefined, multisigContractAddress: undefined
47
+ });
48
+ });
49
+
50
+ for (const type of ['MULTISIG:confirmTransaction', 'MULTISIG:submitTransaction']) {
51
+ it(`notifies the recipient of a ${type} with its multisig contract`, function() {
52
+ ChainService.onTx('eth', tx({ effects: [internal, multisigEffect(type)] })).out.should.deep.equal({
53
+ address: recipient, amount: '3000', tokenAddress: undefined, multisigContractAddress: multisig
54
+ });
55
+ });
56
+ }
57
+
58
+ it('prefers a multisig effect to an ERC20 transfer', function() {
59
+ ChainService.onTx('eth', tx({ effects: [erc20, multisigEffect('MULTISIG:submitTransaction')] })).out.should.deep.equal({
60
+ address: recipient, amount: '3000', tokenAddress: undefined, multisigContractAddress: multisig
61
+ });
62
+ });
63
+
64
+ it('falls back to the tx recipient for effects it does not know', function() {
65
+ const other = { type: 'OTHER:call', to: recipient, from: sender, amount: '1', contractAddress: token, callStack: '' };
66
+ ChainService.onTx('eth', tx({ effects: [other] })).out.should.deep.equal({
67
+ address: token, amount: 0, tokenAddress: undefined, multisigContractAddress: undefined
68
+ });
69
+ });
70
+
71
+ it('handles MATIC txs the same way', function() {
72
+ ChainService.onTx('matic', tx({ chain: 'MATIC', to: recipient, value: 5000 })).out.should.deep.equal({
73
+ address: recipient, amount: 5000, tokenAddress: undefined, multisigContractAddress: undefined
74
+ });
75
+ });
76
+
77
+ it('notifies the token recipient, amount and token of an ERC20 transfer', function() {
78
+ ChainService.onTx('eth', tx({ effects: [erc20] })).should.deep.equal({
79
+ txid: '0x01',
80
+ out: { address: recipient, amount: '1000000', tokenAddress: token, multisigContractAddress: undefined }
81
+ });
82
+ ChainService.onTx('matic', tx({ chain: 'MATIC', effects: [erc20] })).out.tokenAddress.should.equal(token);
83
+ });
84
+
85
+ it('prefers an ERC20 transfer to an internal value transfer', function() {
86
+ ChainService.onTx('eth', tx({ effects: [internal, erc20] })).out.should.deep.equal({
87
+ address: recipient, amount: '1000000', tokenAddress: token, multisigContractAddress: undefined
88
+ });
89
+ });
90
+ });
91
+
22
92
  describe('#selectTxInputs', function() {
23
93
  it('should reject a total that a number rounds down to the balance', function(done) {
24
94
  const server = {
@@ -27,7 +27,7 @@ describe('Chain SOL', function() {
27
27
  getRentMinimum: (_space, cb) => cb(null, Defaults.MIN_SOL_BALANCE)
28
28
  }),
29
29
  getPendingTxs: (_opts, cb) => cb(null, c.txps),
30
- storage: { fetchAddresses: (_walletId, cb) => cb(null, []) }
30
+ storage: { fetchAddressesPage: (_walletId, _opts, cb) => cb(null, []) }
31
31
  };
32
32
  const wallet = { chain: 'sol', network: 'livenet' };
33
33
 
@@ -20,7 +20,7 @@ describe('Chain XRP', function() {
20
20
  getReserve: cb => cb(null, 1000000)
21
21
  }),
22
22
  getPendingTxs: (_opts, cb) => cb(null, [{ amount: '3000000', fee: '12' }]),
23
- storage: { fetchAddresses: (_walletId, cb) => cb(null, []) }
23
+ storage: { fetchAddressesPage: (_walletId, _opts, cb) => cb(null, []) }
24
24
  };
25
25
  const wallet = { chain: 'xrp', network: 'livenet' };
26
26
 
@@ -11,6 +11,8 @@ import { Common } from '../src/lib/common';
11
11
  import { WalletService } from '../src/lib/server';
12
12
  import { ExpressApp } from '../src/lib/expressapp';
13
13
  import { ClientError } from '../src/lib/errors/clienterror';
14
+ import { BitcoreLib } from '@bitpay-labs/crypto-wallet-core';
15
+ import * as TestData from './testdata';
14
16
 
15
17
  const Defaults = Common.Defaults;
16
18
  const should = chai.should();
@@ -762,6 +764,148 @@ describe('ExpressApp', function() {
762
764
  });
763
765
  });
764
766
 
767
+ describe('/v1/wallets/exist', function() {
768
+ const idA = 'a'.repeat(64);
769
+ const idB = 'b'.repeat(64);
770
+ const idC = 'c'.repeat(64);
771
+ let storage;
772
+
773
+ function sign(message) {
774
+ const hash = Common.Utils.hashMessage(message, false);
775
+ return BitcoreLib.crypto.ECDSA.sign(hash, new BitcoreLib.PrivateKey(TestData.keyPair.priv), { endian: 'little' }).toString();
776
+ }
777
+
778
+ // posts each body in turn, and calls back with each response's status and body
779
+ function postEach(bodies, cb) {
780
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
781
+ sandbox.stub(WalletService, 'getStorage').returns(storage);
782
+ start(ExpressApp, function() {
783
+ const results = [];
784
+ (function next() {
785
+ if (results.length === bodies.length) {
786
+ return cb(null, results);
787
+ }
788
+ request({
789
+ url: testHost + ':' + testPort + config.basePath + '/v1/wallets/exist',
790
+ method: 'post',
791
+ json: true,
792
+ body: bodies[results.length]
793
+ }, function(err, res, body) {
794
+ if (err) {
795
+ return cb(err);
796
+ }
797
+ results.push({ status: res.statusCode, body });
798
+ next();
799
+ });
800
+ })();
801
+ });
802
+ }
803
+
804
+ function post(body, cb) {
805
+ postEach([body], (err, results) => (err ? cb(err) : cb(null, { statusCode: results[0].status }, results[0].body)));
806
+ }
807
+
808
+ beforeEach(function() {
809
+ const copayer = { requestPubKeys: [{ key: TestData.keyPair.pub }] };
810
+ storage = {
811
+ fetchCopayerLookup: sinon.stub().callsFake(async id => (id === idA || id === idB ? copayer : undefined))
812
+ };
813
+ });
814
+
815
+ it('returns the copayers whose signature verifies against their request key', function(done) {
816
+ post({
817
+ copayers: [
818
+ { copayerId: idA, signature: sign(idA) },
819
+ { copayerId: idB, signature: sign(idA) },
820
+ { copayerId: idC, signature: sign(idC) }
821
+ ]
822
+ }, function(err, res, body) {
823
+ should.not.exist(err);
824
+ res.statusCode.should.equal(200);
825
+ body.should.deep.equal([idA]);
826
+ storage.fetchCopayerLookup.args.should.deep.equal([[idA], [idB], [idC]]);
827
+ done();
828
+ });
829
+ });
830
+
831
+ it('returns no copayers when given none to check', function(done) {
832
+ postEach([
833
+ {},
834
+ { copayers: null },
835
+ { copayers: idA },
836
+ { copayers: { copayerId: idA, signature: sign(idA) } },
837
+ { copayers: [] }
838
+ ], function(err, results) {
839
+ should.not.exist(err);
840
+ results.should.deep.equal(Array(5).fill({ status: 200, body: [] }));
841
+ storage.fetchCopayerLookup.callCount.should.equal(0);
842
+ done();
843
+ });
844
+ });
845
+
846
+ it('does not verify a copayer whose signature is not a string', function(done) {
847
+ post({
848
+ copayers: [
849
+ { copayerId: idA, signature: 1 },
850
+ { copayerId: idA, signature: [sign(idA)] },
851
+ { copayerId: idB, signature: {} },
852
+ { copayerId: idB }
853
+ ]
854
+ }, function(err, res, body) {
855
+ should.not.exist(err);
856
+ res.statusCode.should.equal(200);
857
+ body.should.deep.equal([]);
858
+ storage.fetchCopayerLookup.callCount.should.equal(4);
859
+ done();
860
+ });
861
+ });
862
+
863
+ it('treats a copayer that fails to load as not found', function(done) {
864
+ storage.fetchCopayerLookup.withArgs(idB).rejects(new Error('lookup failed'));
865
+ post({
866
+ copayers: [
867
+ { copayerId: idA, signature: sign(idA) },
868
+ { copayerId: idB, signature: sign(idB) }
869
+ ]
870
+ }, function(err, res, body) {
871
+ should.not.exist(err);
872
+ res.statusCode.should.equal(200);
873
+ body.should.deep.equal([idA]);
874
+ done();
875
+ });
876
+ });
877
+
878
+ it('looks up only copayer ids that are strings', function(done) {
879
+ post({
880
+ copayers: [
881
+ { copayerId: { id: idB }, signature: sign(idB) },
882
+ { copayerId: [idB], signature: sign(idB) },
883
+ { copayerId: 1, signature: sign(idB) },
884
+ { signature: sign(idB) },
885
+ { copayerId: idA, signature: sign(idA) }
886
+ ]
887
+ }, function(err, res, body) {
888
+ should.not.exist(err);
889
+ res.statusCode.should.equal(200);
890
+ body.should.deep.equal([idA]);
891
+ storage.fetchCopayerLookup.args.should.deep.equal([[idA]]);
892
+ done();
893
+ });
894
+ });
895
+
896
+ it('skips copayers that are not objects', function(done) {
897
+ post({
898
+ copayers: [null, idB, { copayerId: idA, signature: sign(idA) }]
899
+ }, function(err, res, body) {
900
+ should.not.exist(err);
901
+ res.statusCode.should.equal(200);
902
+ body.should.deep.equal([idA]);
903
+ storage.fetchCopayerLookup.args.should.deep.equal([[idA]]);
904
+ done();
905
+ });
906
+ });
907
+ });
908
+
765
909
  describe('Clear cache', function() {
766
910
  it('/v1/clearcache/', function(done) {
767
911
  const resolveStub = sinon.stub().callsFake( () => { return Promise.resolve(true);});