@bitpay-labs/bitcore-wallet-service 11.6.6 → 11.7.1

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 (52) hide show
  1. package/package.json +22 -11
  2. package/src/externalservices/moonpay.ts +3 -0
  3. package/src/lib/blockchainexplorers/v8.ts +42 -0
  4. package/src/lib/chain/eth/index.ts +3 -2
  5. package/src/lib/expressapp.ts +25 -0
  6. package/src/lib/model/txproposal.ts +8 -0
  7. package/src/lib/routes/aave.ts +48 -0
  8. package/src/lib/routes/tss.ts +2 -2
  9. package/src/lib/server.ts +188 -6
  10. package/src/lib/tss.ts +9 -4
  11. package/test/expressapp.test.ts +170 -0
  12. package/test/integration/server.test.ts +460 -0
  13. package/test/v8.test.ts +103 -0
  14. package/ts_build/package.json +22 -11
  15. package/ts_build/src/externalservices/changelly.d.ts +2 -2
  16. package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
  17. package/ts_build/src/externalservices/moonpay.js +6 -0
  18. package/ts_build/src/externalservices/moonpay.js.map +1 -1
  19. package/ts_build/src/lib/blockchainexplorers/v8.d.ts +12 -0
  20. package/ts_build/src/lib/blockchainexplorers/v8.d.ts.map +1 -1
  21. package/ts_build/src/lib/blockchainexplorers/v8.js +39 -0
  22. package/ts_build/src/lib/blockchainexplorers/v8.js.map +1 -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 +3 -2
  25. package/ts_build/src/lib/chain/eth/index.js.map +1 -1
  26. package/ts_build/src/lib/expressapp.d.ts.map +1 -1
  27. package/ts_build/src/lib/expressapp.js +23 -0
  28. package/ts_build/src/lib/expressapp.js.map +1 -1
  29. package/ts_build/src/lib/model/txproposal.d.ts +3 -0
  30. package/ts_build/src/lib/model/txproposal.d.ts.map +1 -1
  31. package/ts_build/src/lib/model/txproposal.js +6 -0
  32. package/ts_build/src/lib/model/txproposal.js.map +1 -1
  33. package/ts_build/src/lib/routes/aave.d.ts +12 -0
  34. package/ts_build/src/lib/routes/aave.d.ts.map +1 -0
  35. package/ts_build/src/lib/routes/aave.js +47 -0
  36. package/ts_build/src/lib/routes/aave.js.map +1 -0
  37. package/ts_build/src/lib/routes/tss.js +2 -2
  38. package/ts_build/src/lib/routes/tss.js.map +1 -1
  39. package/ts_build/src/lib/server.d.ts +5 -0
  40. package/ts_build/src/lib/server.d.ts.map +1 -1
  41. package/ts_build/src/lib/server.js +134 -6
  42. package/ts_build/src/lib/server.js.map +1 -1
  43. package/ts_build/src/lib/tss.d.ts +1 -0
  44. package/ts_build/src/lib/tss.d.ts.map +1 -1
  45. package/ts_build/src/lib/tss.js +6 -2
  46. package/ts_build/src/lib/tss.js.map +1 -1
  47. package/ts_build/test/expressapp.test.js +162 -0
  48. package/ts_build/test/expressapp.test.js.map +1 -1
  49. package/ts_build/test/integration/server.test.js +391 -0
  50. package/ts_build/test/integration/server.test.js.map +1 -1
  51. package/ts_build/test/v8.test.js +89 -0
  52. package/ts_build/test/v8.test.js.map +1 -1
@@ -10,6 +10,7 @@ import config from '../src/config';
10
10
  import { Common } from '../src/lib/common';
11
11
  import { WalletService } from '../src/lib/server';
12
12
  import { ExpressApp } from '../src/lib/expressapp';
13
+ import { ClientError } from '../src/lib/errors/clienterror';
13
14
 
14
15
  const Defaults = Common.Defaults;
15
16
  const should = chai.should();
@@ -483,6 +484,175 @@ describe('ExpressApp', function() {
483
484
  });
484
485
 
485
486
  });
487
+ describe('Aave service routes', function() {
488
+ it('/v1/service/aave/userAccountData', function(done) {
489
+ const server = {
490
+ getAaveUserAccountData: sinon.stub().resolves({ totalCollateralBase: '1000', healthFactor: '2.0' }),
491
+ };
492
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
493
+ sandbox.stub(WalletService, 'getInstance').returns(server);
494
+ start(ExpressApp, function() {
495
+ const requestOptions = {
496
+ url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/userAccountData',
497
+ method: 'post',
498
+ json: { chain: 'eth', network: 'mainnet', address: '0x123', version: 'v3' }
499
+ };
500
+ request(requestOptions, function(err, res, body) {
501
+ should.not.exist(err);
502
+ res.statusCode.should.equal(200);
503
+ body.totalCollateralBase.should.equal('1000');
504
+ body.healthFactor.should.equal('2.0');
505
+ done();
506
+ });
507
+ });
508
+ });
509
+
510
+ it('/v1/service/aave/userAccountData should fail with missing arguments', function(done) {
511
+ const server = {
512
+ getAaveUserAccountData: sinon.stub().rejects(new ClientError('getAaveUserAccountData request missing arguments')),
513
+ };
514
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
515
+ sandbox.stub(WalletService, 'getInstance').returns(server);
516
+ start(ExpressApp, function() {
517
+ const requestOptions = {
518
+ url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/userAccountData',
519
+ method: 'post',
520
+ json: { version: 'v3' }
521
+ };
522
+ request(requestOptions, function(err, res) {
523
+ should.not.exist(err);
524
+ res.statusCode.should.equal(400);
525
+ done();
526
+ });
527
+ });
528
+ });
529
+
530
+ it('/v1/service/aave/reserveData', function(done) {
531
+ const server = {
532
+ getAaveReserveData: sinon.stub().resolves({ currentVariableBorrowRate: '35000000' }),
533
+ };
534
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
535
+ sandbox.stub(WalletService, 'getInstance').returns(server);
536
+ start(ExpressApp, function() {
537
+ const requestOptions = {
538
+ url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/reserveData',
539
+ method: 'post',
540
+ json: { chain: 'eth', network: 'mainnet', asset: '0xabc', version: 'v3' }
541
+ };
542
+ request(requestOptions, function(err, res, body) {
543
+ should.not.exist(err);
544
+ res.statusCode.should.equal(200);
545
+ body.currentVariableBorrowRate.should.equal('35000000');
546
+ done();
547
+ });
548
+ });
549
+ });
550
+
551
+ it('/v1/service/aave/reserveData should fail with missing arguments', function(done) {
552
+ const server = {
553
+ getAaveReserveData: sinon.stub().rejects(new ClientError('getAaveReserveData request missing arguments')),
554
+ };
555
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
556
+ sandbox.stub(WalletService, 'getInstance').returns(server);
557
+ start(ExpressApp, function() {
558
+ const requestOptions = {
559
+ url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/reserveData',
560
+ method: 'post',
561
+ json: { version: 'v3' }
562
+ };
563
+ request(requestOptions, function(err, res) {
564
+ should.not.exist(err);
565
+ res.statusCode.should.equal(400);
566
+ done();
567
+ });
568
+ });
569
+ });
570
+
571
+ it('/v1/service/aave/reserveTokensAddresses', function(done) {
572
+ const server = {
573
+ getAaveReserveTokensAddresses: sinon.stub().resolves({ variableDebtTokenAddress: '0xdef' }),
574
+ };
575
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
576
+ sandbox.stub(WalletService, 'getInstance').returns(server);
577
+ start(ExpressApp, function() {
578
+ const requestOptions = {
579
+ url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/reserveTokensAddresses',
580
+ method: 'post',
581
+ json: { chain: 'eth', network: 'mainnet', asset: '0xabc', version: 'v3' }
582
+ };
583
+ request(requestOptions, function(err, res, body) {
584
+ should.not.exist(err);
585
+ res.statusCode.should.equal(200);
586
+ body.variableDebtTokenAddress.should.equal('0xdef');
587
+ done();
588
+ });
589
+ });
590
+ });
591
+
592
+ it('/v1/service/aave/reserveTokensAddresses should fail with missing arguments', function(done) {
593
+ const server = {
594
+ getAaveReserveTokensAddresses: sinon.stub().rejects(new ClientError('getAaveReserveTokensAddresses request missing arguments')),
595
+ };
596
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
597
+ sandbox.stub(WalletService, 'getInstance').returns(server);
598
+ start(ExpressApp, function() {
599
+ const requestOptions = {
600
+ url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/reserveTokensAddresses',
601
+ method: 'post',
602
+ json: { version: 'v3' }
603
+ };
604
+ request(requestOptions, function(err, res) {
605
+ should.not.exist(err);
606
+ res.statusCode.should.equal(400);
607
+ done();
608
+ });
609
+ });
610
+ });
611
+ });
612
+
613
+ describe('Token allowance', function() {
614
+ it('/v1/token/allowance', function(done) {
615
+ const server = {
616
+ getTokenAllowance: sinon.stub().resolves(5000000),
617
+ };
618
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
619
+ sandbox.stub(WalletService, 'getInstance').returns(server);
620
+ start(ExpressApp, function() {
621
+ const requestOptions = {
622
+ url: testHost + ':' + testPort + config.basePath + '/v1/token/allowance',
623
+ method: 'post',
624
+ json: { chain: 'eth', network: 'mainnet', tokenAddress: '0xtoken', ownerAddress: '0xowner', spenderAddress: '0xspender' }
625
+ };
626
+ request(requestOptions, function(err, res, body) {
627
+ should.not.exist(err);
628
+ res.statusCode.should.equal(200);
629
+ body.should.equal(5000000);
630
+ done();
631
+ });
632
+ });
633
+ });
634
+
635
+ it('/v1/token/allowance should fail with missing arguments', function(done) {
636
+ const server = {
637
+ getTokenAllowance: sinon.stub().rejects(new ClientError('getTokenAllowance request missing arguments')),
638
+ };
639
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
640
+ sandbox.stub(WalletService, 'getInstance').returns(server);
641
+ start(ExpressApp, function() {
642
+ const requestOptions = {
643
+ url: testHost + ':' + testPort + config.basePath + '/v1/token/allowance',
644
+ method: 'post',
645
+ json: {}
646
+ };
647
+ request(requestOptions, function(err, res) {
648
+ should.not.exist(err);
649
+ res.statusCode.should.equal(400);
650
+ done();
651
+ });
652
+ });
653
+ });
654
+ });
655
+
486
656
  describe('Clear cache', function() {
487
657
  it('/v1/clearcache/', function(done) {
488
658
  const resolveStub = sinon.stub().callsFake( () => { return Promise.resolve(true);});
@@ -7801,6 +7801,466 @@ describe('Wallet service', function() {
7801
7801
  });
7802
7802
  });
7803
7803
 
7804
+ describe('#prepareTx (deferred nonce)', function() {
7805
+ const ETH_ADDR = '0x37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08A';
7806
+ let server, wallet, fromAddr;
7807
+
7808
+ beforeEach(async function() {
7809
+ ({ server, wallet } = await helpers.createAndJoinWallet(1, 1, { coin: 'eth' }));
7810
+ const address = await util.promisify(server.createAddress).call(server, {});
7811
+ fromAddr = address.address;
7812
+ await helpers.stubUtxos(server, wallet, [1, 2], { coin: 'eth' });
7813
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '5');
7814
+ });
7815
+
7816
+ it('should create txp with nonce=null when deferNonce is true', async function() {
7817
+ const txOpts = {
7818
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
7819
+ feePerKb: 123e2,
7820
+ from: fromAddr,
7821
+ deferNonce: true
7822
+ };
7823
+ const txp = await util.promisify(server.createTx).call(server, txOpts);
7824
+ should.exist(txp);
7825
+ txp.deferNonce.should.be.true;
7826
+ should.not.exist(txp.nonce);
7827
+ });
7828
+
7829
+ it('should create txp with nonce when deferNonce is absent', async function() {
7830
+ const txOpts = {
7831
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
7832
+ feePerKb: 123e2,
7833
+ from: fromAddr
7834
+ };
7835
+ const txp = await util.promisify(server.createTx).call(server, txOpts);
7836
+ should.exist(txp);
7837
+ should.not.exist(txp.deferNonce);
7838
+ txp.nonce.should.equal('5');
7839
+ });
7840
+
7841
+ it('should publish a deferred-nonce txp and save prePublishRaw', async function() {
7842
+ const txOpts = {
7843
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
7844
+ feePerKb: 123e2,
7845
+ from: fromAddr,
7846
+ deferNonce: true
7847
+ };
7848
+ const txp = await util.promisify(server.createTx).call(server, txOpts);
7849
+ should.not.exist(txp.nonce);
7850
+
7851
+ const publishOpts = helpers.getProposalSignatureOpts(txp, TestData.copayers[0].privKey_1H_0);
7852
+ const published = await util.promisify(server.publishTx).call(server, publishOpts);
7853
+ should.exist(published);
7854
+ published.status.should.equal('pending');
7855
+ published.deferNonce.should.be.true;
7856
+ should.exist(published.prePublishRaw);
7857
+ });
7858
+
7859
+ it('should assign nonce to a deferred-nonce txp', async function() {
7860
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '10');
7861
+
7862
+ const txp = await helpers.createAndPublishTx(server, {
7863
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
7864
+ feePerKb: 123e2,
7865
+ from: fromAddr,
7866
+ deferNonce: true
7867
+ }, TestData.copayers[0].privKey_1H_0);
7868
+
7869
+ should.not.exist(txp.nonce);
7870
+
7871
+ const result = await util.promisify(server.prepareTx).call(server, {
7872
+ txProposalId: txp.id
7873
+ });
7874
+ should.exist(result);
7875
+ result.nonce.should.equal(10);
7876
+ });
7877
+
7878
+ it('should return txp as-is if deferNonce is not set', async function() {
7879
+ const txp = await helpers.createAndPublishTx(server, {
7880
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
7881
+ feePerKb: 123e2,
7882
+ from: fromAddr
7883
+ }, TestData.copayers[0].privKey_1H_0);
7884
+
7885
+ txp.nonce.should.equal('5');
7886
+
7887
+ const result = await util.promisify(server.prepareTx).call(server, {
7888
+ txProposalId: txp.id
7889
+ });
7890
+ result.nonce.should.equal('5');
7891
+ });
7892
+
7893
+ it('should fail for non-existent txp', function(done) {
7894
+ server.prepareTx({ txProposalId: 'nonexistent' }, function(err) {
7895
+ should.exist(err);
7896
+ err.message.should.contain('not found');
7897
+ done();
7898
+ });
7899
+ });
7900
+
7901
+ it('should calculate gap-free nonce skipping pending txp nonces', async function() {
7902
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '5');
7903
+
7904
+ const txp1 = await helpers.createAndPublishTx(server, {
7905
+ outputs: [{ toAddress: ETH_ADDR, amount: 1000 }],
7906
+ feePerKb: 123e2,
7907
+ from: fromAddr
7908
+ }, TestData.copayers[0].privKey_1H_0);
7909
+ txp1.nonce.should.equal('5');
7910
+
7911
+ const txp2 = await helpers.createAndPublishTx(server, {
7912
+ outputs: [{ toAddress: ETH_ADDR, amount: 2000 }],
7913
+ feePerKb: 123e2,
7914
+ from: fromAddr,
7915
+ deferNonce: true
7916
+ }, TestData.copayers[0].privKey_1H_0);
7917
+ should.not.exist(txp2.nonce);
7918
+
7919
+ const result = await util.promisify(server.prepareTx).call(server, {
7920
+ txProposalId: txp2.id
7921
+ });
7922
+ result.nonce.should.equal(6);
7923
+ });
7924
+
7925
+ it('should assign sequential nonces for multiple deferred txps', async function() {
7926
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '0');
7927
+
7928
+ const txps = [];
7929
+ for (let i = 0; i < 3; i++) {
7930
+ const txp = await helpers.createAndPublishTx(server, {
7931
+ outputs: [{ toAddress: ETH_ADDR, amount: 1000 * (i + 1) }],
7932
+ feePerKb: 123e2,
7933
+ from: fromAddr,
7934
+ deferNonce: true
7935
+ }, TestData.copayers[0].privKey_1H_0);
7936
+ txps.push(txp);
7937
+ }
7938
+
7939
+ const results = [];
7940
+ for (const txp of txps) {
7941
+ const result = await util.promisify(server.prepareTx).call(server, {
7942
+ txProposalId: txp.id
7943
+ });
7944
+ results.push(result);
7945
+ }
7946
+
7947
+ results[0].nonce.should.equal(0);
7948
+ results[1].nonce.should.equal(1);
7949
+ results[2].nonce.should.equal(2);
7950
+ });
7951
+
7952
+ it('should handle mix of normal and deferred-nonce txps', async function() {
7953
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '3');
7954
+
7955
+ const normalTxp = await helpers.createAndPublishTx(server, {
7956
+ outputs: [{ toAddress: ETH_ADDR, amount: 1000 }],
7957
+ feePerKb: 123e2,
7958
+ from: fromAddr
7959
+ }, TestData.copayers[0].privKey_1H_0);
7960
+ normalTxp.nonce.should.equal('3');
7961
+
7962
+ const deferred1 = await helpers.createAndPublishTx(server, {
7963
+ outputs: [{ toAddress: ETH_ADDR, amount: 2000 }],
7964
+ feePerKb: 123e2,
7965
+ from: fromAddr,
7966
+ deferNonce: true
7967
+ }, TestData.copayers[0].privKey_1H_0);
7968
+
7969
+ const deferred2 = await helpers.createAndPublishTx(server, {
7970
+ outputs: [{ toAddress: ETH_ADDR, amount: 3000 }],
7971
+ feePerKb: 123e2,
7972
+ from: fromAddr,
7973
+ deferNonce: true
7974
+ }, TestData.copayers[0].privKey_1H_0);
7975
+
7976
+ const result1 = await util.promisify(server.prepareTx).call(server, {
7977
+ txProposalId: deferred1.id
7978
+ });
7979
+ result1.nonce.should.equal(4);
7980
+
7981
+ const result2 = await util.promisify(server.prepareTx).call(server, {
7982
+ txProposalId: deferred2.id
7983
+ });
7984
+ result2.nonce.should.equal(5);
7985
+ });
7986
+
7987
+ it('should sign a deferred-nonce txp after assignNonce', async function() {
7988
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '7');
7989
+ helpers.stubBroadcast('txid123');
7990
+
7991
+ const txp = await helpers.createAndPublishTx(server, {
7992
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
7993
+ feePerKb: 123e2,
7994
+ from: fromAddr,
7995
+ deferNonce: true
7996
+ }, TestData.copayers[0].privKey_1H_0);
7997
+
7998
+ const withNonce = await util.promisify(server.prepareTx).call(server, {
7999
+ txProposalId: txp.id
8000
+ });
8001
+ withNonce.nonce.should.equal(7);
8002
+
8003
+ const fetched = await util.promisify(server.getTx).call(server, { txProposalId: txp.id });
8004
+ const signatures = helpers.clientSign(fetched, TestData.copayers[0].xPrivKey_44H_0H_0H);
8005
+ const signed = await util.promisify(server.signTx).call(server, {
8006
+ txProposalId: txp.id,
8007
+ signatures
8008
+ });
8009
+ signed.status.should.equal('accepted');
8010
+ });
8011
+
8012
+ it('should not trigger nonce conflict for deferred txps with null nonce', async function() {
8013
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '5');
8014
+
8015
+ const normalTxp = await helpers.createAndPublishTx(server, {
8016
+ outputs: [{ toAddress: ETH_ADDR, amount: 1000 }],
8017
+ feePerKb: 123e2,
8018
+ from: fromAddr
8019
+ }, TestData.copayers[0].privKey_1H_0);
8020
+ normalTxp.nonce.should.equal('5');
8021
+
8022
+ const deferredTxp = await helpers.createAndPublishTx(server, {
8023
+ outputs: [{ toAddress: ETH_ADDR, amount: 2000 }],
8024
+ feePerKb: 123e2,
8025
+ from: fromAddr,
8026
+ deferNonce: true
8027
+ }, TestData.copayers[0].privKey_1H_0);
8028
+
8029
+ const withNonce = await util.promisify(server.prepareTx).call(server, {
8030
+ txProposalId: deferredTxp.id
8031
+ });
8032
+ withNonce.nonce.should.equal(6);
8033
+
8034
+ const sigs1 = helpers.clientSign(normalTxp, TestData.copayers[0].xPrivKey_44H_0H_0H);
8035
+ const signed1 = await util.promisify(server.signTx).call(server, {
8036
+ txProposalId: normalTxp.id,
8037
+ signatures: sigs1
8038
+ });
8039
+ signed1.status.should.equal('accepted');
8040
+ helpers.stubBroadcast(signed1.txid);
8041
+ await util.promisify(server.broadcastTx).call(server, { txProposalId: normalTxp.id });
8042
+
8043
+ const fetchedDeferred = await util.promisify(server.getTx).call(server, { txProposalId: deferredTxp.id });
8044
+ const sigs2 = helpers.clientSign(fetchedDeferred, TestData.copayers[0].xPrivKey_44H_0H_0H);
8045
+ const signed2 = await util.promisify(server.signTx).call(server, {
8046
+ txProposalId: deferredTxp.id,
8047
+ signatures: sigs2
8048
+ });
8049
+ signed2.status.should.equal('accepted');
8050
+ });
8051
+
8052
+ it('should complete full lifecycle for a deferred-nonce txp', async function() {
8053
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '42');
8054
+
8055
+ const txOpts = {
8056
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
8057
+ feePerKb: 123e2,
8058
+ from: fromAddr,
8059
+ deferNonce: true
8060
+ };
8061
+ const created = await util.promisify(server.createTx).call(server, txOpts);
8062
+ created.isTemporary().should.be.true;
8063
+ created.deferNonce.should.be.true;
8064
+ should.not.exist(created.nonce);
8065
+
8066
+ const publishOpts = helpers.getProposalSignatureOpts(created, TestData.copayers[0].privKey_1H_0);
8067
+ const published = await util.promisify(server.publishTx).call(server, publishOpts);
8068
+ published.status.should.equal('pending');
8069
+ should.exist(published.prePublishRaw);
8070
+
8071
+ const withNonce = await util.promisify(server.prepareTx).call(server, {
8072
+ txProposalId: created.id
8073
+ });
8074
+ withNonce.nonce.should.equal(42);
8075
+
8076
+ const fetched = await util.promisify(server.getTx).call(server, { txProposalId: created.id });
8077
+ const signatures = helpers.clientSign(fetched, TestData.copayers[0].xPrivKey_44H_0H_0H);
8078
+ const signed = await util.promisify(server.signTx).call(server, {
8079
+ txProposalId: created.id,
8080
+ signatures
8081
+ });
8082
+ signed.status.should.equal('accepted');
8083
+
8084
+ helpers.stubBroadcast(signed.txid);
8085
+ const broadcasted = await util.promisify(server.broadcastTx).call(server, {
8086
+ txProposalId: created.id
8087
+ });
8088
+ broadcasted.status.should.equal('broadcasted');
8089
+ should.exist(broadcasted.txid);
8090
+ });
8091
+
8092
+ it('should handle bulk sign scenario (3 deferred txps signed sequentially)', async function() {
8093
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '10');
8094
+
8095
+ const txps = [];
8096
+ for (let i = 0; i < 3; i++) {
8097
+ const txp = await helpers.createAndPublishTx(server, {
8098
+ outputs: [{ toAddress: ETH_ADDR, amount: 1000 * (i + 1) }],
8099
+ feePerKb: 123e2,
8100
+ from: fromAddr,
8101
+ deferNonce: true
8102
+ }, TestData.copayers[0].privKey_1H_0);
8103
+ txps.push(txp);
8104
+ }
8105
+
8106
+ const assignedNonces = [];
8107
+ for (let i = 0; i < txps.length; i++) {
8108
+ const withNonce = await util.promisify(server.prepareTx).call(server, {
8109
+ txProposalId: txps[i].id
8110
+ });
8111
+ withNonce.nonce.should.equal(10 + i);
8112
+ assignedNonces.push(withNonce.nonce);
8113
+ }
8114
+
8115
+ assignedNonces.should.deep.equal([10, 11, 12]);
8116
+ });
8117
+
8118
+ it('should return txp with nonce set (extensibility baseline)', async function() {
8119
+ const txp = await helpers.createAndPublishTx(server, {
8120
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
8121
+ feePerKb: 123e2,
8122
+ from: fromAddr,
8123
+ deferNonce: true
8124
+ }, TestData.copayers[0].privKey_1H_0);
8125
+
8126
+ should.not.exist(txp.nonce);
8127
+
8128
+ const result = await util.promisify(server.prepareTx).call(server, {
8129
+ txProposalId: txp.id
8130
+ });
8131
+ result.nonce.should.be.a('number');
8132
+ result.nonce.should.equal(5);
8133
+ // future: result.gasPrice, result.maxFee would also be set here
8134
+ });
8135
+ });
8136
+
8137
+ describe('#signTx nonce override', function() {
8138
+ const ETH_ADDR = '0x37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08A';
8139
+ let server, wallet, fromAddr;
8140
+
8141
+ beforeEach(async function() {
8142
+ ({ server, wallet } = await helpers.createAndJoinWallet(1, 1, { coin: 'eth' }));
8143
+ const address = await util.promisify(server.createAddress).call(server, {});
8144
+ fromAddr = address.address;
8145
+ await helpers.stubUtxos(server, wallet, [1, 2], { coin: 'eth' });
8146
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '5');
8147
+ });
8148
+
8149
+ it('should accept nonce passthrough in signTx', async function() {
8150
+ const txp = await helpers.createAndPublishTx(server, {
8151
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
8152
+ feePerKb: 123e2,
8153
+ from: fromAddr
8154
+ }, TestData.copayers[0].privKey_1H_0);
8155
+ txp.nonce.should.equal('5');
8156
+
8157
+ const signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey_44H_0H_0H);
8158
+ const signed = await util.promisify(server.signTx).call(server, {
8159
+ txProposalId: txp.id,
8160
+ signatures,
8161
+ nonce: Number(txp.nonce)
8162
+ });
8163
+ signed.status.should.equal('accepted');
8164
+ signed.nonce.should.equal(5);
8165
+ });
8166
+
8167
+ it('should accept nonce override in signTx', async function() {
8168
+ const txp = await helpers.createAndPublishTx(server, {
8169
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
8170
+ feePerKb: 123e2,
8171
+ from: fromAddr
8172
+ }, TestData.copayers[0].privKey_1H_0);
8173
+ txp.nonce.should.equal('5');
8174
+
8175
+ const overrideNonce = 99;
8176
+ const fetched = await util.promisify(server.getTx).call(server, { txProposalId: txp.id });
8177
+ fetched.nonce = overrideNonce;
8178
+
8179
+ const signatures = helpers.clientSign(fetched, TestData.copayers[0].xPrivKey_44H_0H_0H);
8180
+ const signed = await util.promisify(server.signTx).call(server, {
8181
+ txProposalId: txp.id,
8182
+ signatures,
8183
+ nonce: overrideNonce
8184
+ });
8185
+ signed.status.should.equal('accepted');
8186
+ signed.nonce.should.equal(overrideNonce);
8187
+ });
8188
+
8189
+ it('should reject nonce override that conflicts with pending txp', async function() {
8190
+ const txp1 = await helpers.createAndPublishTx(server, {
8191
+ outputs: [{ toAddress: ETH_ADDR, amount: 1000 }],
8192
+ feePerKb: 123e2,
8193
+ from: fromAddr
8194
+ }, TestData.copayers[0].privKey_1H_0);
8195
+ txp1.nonce.should.equal('5');
8196
+
8197
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '6');
8198
+ const txp2 = await helpers.createAndPublishTx(server, {
8199
+ outputs: [{ toAddress: ETH_ADDR, amount: 2000 }],
8200
+ feePerKb: 123e2,
8201
+ from: fromAddr
8202
+ }, TestData.copayers[0].privKey_1H_0);
8203
+ txp2.nonce.should.equal('6');
8204
+
8205
+ // sign txp2 with nonce 5 (conflicts with txp1)
8206
+ const fetched = await util.promisify(server.getTx).call(server, { txProposalId: txp2.id });
8207
+ fetched.nonce = 5;
8208
+ const signatures = helpers.clientSign(fetched, TestData.copayers[0].xPrivKey_44H_0H_0H);
8209
+
8210
+ try {
8211
+ await util.promisify(server.signTx).call(server, {
8212
+ txProposalId: txp2.id,
8213
+ signatures,
8214
+ nonce: 5
8215
+ });
8216
+ throw new Error('should have thrown');
8217
+ } catch (err) {
8218
+ err.code.should.equal('TX_NONCE_CONFLICT');
8219
+ }
8220
+ });
8221
+
8222
+ it('should not crash on nonce conflict check when nonce is null', async function() {
8223
+ const txp = await helpers.createAndPublishTx(server, {
8224
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
8225
+ feePerKb: 123e2,
8226
+ from: fromAddr,
8227
+ deferNonce: true
8228
+ }, TestData.copayers[0].privKey_1H_0);
8229
+ should.not.exist(txp.nonce);
8230
+
8231
+ // null nonce on other txps should not throw in conflict check
8232
+ const fetched = await util.promisify(server.getTx).call(server, { txProposalId: txp.id });
8233
+ fetched.nonce = 99;
8234
+ const signatures = helpers.clientSign(fetched, TestData.copayers[0].xPrivKey_44H_0H_0H);
8235
+ const signed = await util.promisify(server.signTx).call(server, {
8236
+ txProposalId: txp.id,
8237
+ signatures,
8238
+ nonce: 99
8239
+ });
8240
+ signed.status.should.equal('accepted');
8241
+ signed.nonce.should.equal(99);
8242
+ });
8243
+
8244
+ it('should ignore nonce override for non-EVM chains', async function() {
8245
+ const { server: btcServer, wallet: btcWallet } = await helpers.createAndJoinWallet(1, 1);
8246
+ await helpers.stubUtxos(btcServer, btcWallet, [1, 2]);
8247
+
8248
+ const txp = await helpers.createAndPublishTx(btcServer, {
8249
+ outputs: [{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 8000 }],
8250
+ feePerKb: 100e2,
8251
+ }, TestData.copayers[0].privKey_1H_0);
8252
+
8253
+ const signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey_44H_0H_0H);
8254
+ const signed = await util.promisify(btcServer.signTx).call(btcServer, {
8255
+ txProposalId: txp.id,
8256
+ signatures,
8257
+ nonce: 99
8258
+ });
8259
+ signed.status.should.equal('accepted');
8260
+ should.not.exist(signed.nonce);
8261
+ });
8262
+ });
8263
+
7804
8264
  describe('#broadcastTx & #broadcastRawTx', function() {
7805
8265
  let server: WalletService;
7806
8266
  let wallet: Model.Wallet;