Compare commits

...

211 Commits

Author SHA1 Message Date
Jon Layton 6064e2a8bc Swap bitcore-lib, btcprivate.org explorers 2018-03-29 19:33:56 -05:00
Matias Alejo Garcia 9f27a33f0e
Merge pull request #772 from matiu/feat/bch-addresses-formats
Feat/bch addresses formats
2018-03-13 16:33:24 -03:00
Matias Alejo Garcia 01bedc939f
fix lib reference 2018-03-13 16:24:01 -03:00
Matias Alejo Garcia 547e984b2c
rm console.log 2018-03-13 16:13:33 -03:00
Matias Alejo Garcia 52c26ced2e
rm -lock 2018-03-13 15:07:50 -03:00
Matias Alejo Garcia b5b3c51d98
bump 2018-03-13 15:06:50 -03:00
Matias Alejo Garcia 8f35e78b57
better handling of different BCH addresses formats 2018-03-13 14:58:28 -03:00
Matias Alejo Garcia 58447bbceb
change addresstranslator format naming 2018-03-13 12:24:22 -03:00
Matias Alejo Garcia 0f0d605a3f
Merge pull request #771 from matiu/config/bch-insight
update config URLs
2018-03-13 10:20:29 -04:00
Matias Alejo Garcia 5b63d8ff92
update config URLs 2018-03-07 05:03:42 -05:00
Matias Alejo Garcia 1f46848b03
Merge pull request #768 from matiu/feat/cashaddr-in-addrtranslator
Feat/cashaddr in addrtranslator
2018-03-07 06:05:09 -03:00
Matias Alejo Garcia 771cf5d82c
modifies address translator to support Cashaddr 2018-03-06 11:17:37 -05:00
Matias Alejo Garcia d4ef5345f6
add testcases to BCH/testnet support 2018-03-06 10:44:50 -05:00
Matias Alejo Garcia b9836f6069
Update README.md 2018-02-09 19:12:52 -03:00
Matias Alejo Garcia 267cfdba8a
Update README.md 2018-02-09 19:01:07 -03:00
Matias Alejo Garcia 1ab4e27bde
Merge pull request #765 from matiu/feat/interrupted-scan-cache
Feat/interrupted scan cache
2018-02-09 18:51:31 -03:00
Matias Alejo Garcia 4a19302a89
fix helper 2018-02-09 13:24:38 -03:00
Matias Alejo Garcia 41af549ed2
Add interrupted scan cache + status 2018-02-09 13:07:41 -03:00
Matias Alejo Garcia 242c98ebd3
Merge pull request #746 from georgematheos/patch-1
Specify units for bitcoin amounts
2018-02-09 11:20:35 -03:00
Matias Alejo Garcia 696b5b298b
Merge pull request #749 from matiu/bug/startup
fix async init
2018-02-09 11:17:08 -03:00
Matias Alejo Garcia 1c271cfff3
Merge pull request #761 from matiu/bug/n-a-to-address
rm n/a as addressTo possible value
2018-01-24 15:43:24 -03:00
Matias Alejo Garcia 77f776a6b1
rm n/a as addressTo possible value 2018-01-24 15:30:23 -03:00
Matias Alejo Garcia 41683bf10a
Merge pull request #760 from matiu/bug/bch-monitor2
fix amount
2018-01-19 18:31:31 -03:00
Matias Alejo Garcia 8e483953c9
fix amount 2018-01-19 18:27:07 -03:00
Matias Alejo Garcia 1333dd6ab7
Merge pull request #759 from matiu/bug/bch-monitor
Bug/bch monitor
2018-01-19 18:23:29 -03:00
Matias Alejo Garcia 072bb336c5
translate bch addr if needed 2018-01-19 18:16:44 -03:00
Matias Alejo Garcia b40a24691a
translate bch addr from monitor 2018-01-19 18:14:02 -03:00
Matias Alejo Garcia ff89650337
fix race condition at push service 2018-01-17 21:12:56 -03:00
Matias Alejo Garcia 1d629c5674
Merge pull request #758 from matiu/bug/validate-pubkey
validate xpubkeys
2018-01-17 21:04:39 -03:00
Matias Alejo Garcia 23b183714a
validate xpubkeys 2018-01-17 20:45:47 -03:00
Matias Alejo Garcia 031e47ad5e
Merge pull request #751 from michaelsdunn1/master
Still use previously defined blockchainExplorerOpts even if explicit …
2018-01-17 15:31:24 -03:00
Matias Alejo Garcia 938bd05685
Merge pull request #756 from matiu/def/insight-pull
change insight pool to 20
2018-01-15 11:51:30 -03:00
Matias Alejo Garcia 36f88ec891
change insight pool to 20 2018-01-15 11:48:05 -03:00
Matias Alejo Garcia 6b293a64d6
Merge pull request #754 from matiu/deps/update
Deps/update
2018-01-13 11:34:17 -03:00
Matias Alejo Garcia 0e82c99e07
update jsdoc 2018-01-13 11:25:34 -03:00
Matias Alejo Garcia 7272b5234e
update -cash lib. bump version 2018-01-13 11:17:53 -03:00
Michael Dunn 2605bfeca5 Still use previously defined blockchainExplorerOpts even if explicit cointype isn't specified. This allows for apiPrefix to be appropriately passed through the entire stack when running a local instance of a bitcore node. 2018-01-05 15:11:56 -06:00
Matias Alejo Garcia 89d2e21b29
fix async init 2018-01-05 11:24:05 -03:00
georgematheos 82fd50be9d
Specify units for bitcoin amounts
I added a line at the beginning of the API specifying that amounts are given in satoshis.
2017-12-29 11:23:32 -05:00
Matias Alejo Garcia 9c5abaf8bc
Merge pull request #745 from matiu/bug/bc-monitor-startup
fix bcmonitor startup
2017-12-26 16:09:43 -03:00
Matias Alejo Garcia 35d1fc3e12
fix 2017-12-26 16:03:36 -03:00
Matias Alejo Garcia 2f93137767
fix bcmonitor startup 2017-12-26 15:57:28 -03:00
Matias Alejo Garcia c62c464d45
Merge pull request #744 from matiu/bug/max-fee-bump
bumped max_fee_per_kb from 1000 to 10k sat/b :(
2017-12-26 12:02:41 -03:00
Matias Alejo Garcia d5bcb182d6
bumped max_fee_per_kb from 1000 to 10k sat/b :( 2017-12-26 10:56:45 -03:00
Matias Alejo Garcia c5259a6b0d
Merge pull request #736 from matiu/feat/timeouts
different timeouts for getutxos
2017-11-23 10:49:39 -03:00
Matias Alejo Garcia 6a916bdf17
different timeouts for the utxos 2017-11-23 10:04:24 -03:00
Matias Alejo Garcia 80d609c014
Merge pull request #734 from matiu/feat/foreign-sent
fix: handle outgoing TXs foreign crafted
2017-11-16 16:33:17 -03:00
Matias Alejo Garcia 869840eae4
fix: handle outgoing TXs foreign crafted 2017-11-16 16:20:53 -03:00
Matias Alejo Garcia acb14a1877
Merge pull request #733 from matiu/bug/tx-translation
fix translation of txs
2017-11-14 00:13:43 -03:00
Matias Alejo Garcia 9f248357e6
fix translation of txs 2017-11-14 00:13:00 -03:00
Matias Alejo Garcia 72608f64f3
Merge pull request #732 from matiu/bug/log
rm log
2017-11-13 23:44:24 -03:00
Matias Alejo Garcia 616a8c40a7
rm log 2017-11-13 23:43:51 -03:00
Matias Alejo Garcia a62cb3da6f
Merge pull request #731 from matiu/feat/addr-translate
Feat/addr translate
2017-11-13 23:38:31 -03:00
Matias Alejo Garcia 89ab474d2a
. 2017-11-13 23:35:58 -03:00
Matias Alejo Garcia 920680b35f
add missing files 2017-11-13 23:32:57 -03:00
Matias Alejo Garcia 55ce16b552
translate addr module 2017-11-13 23:32:19 -03:00
Matias Alejo Garcia 391898a06e
add logs 2017-11-13 21:56:06 -03:00
Matias Alejo Garcia 3718493277
Merge pull request #729 from matiu/bug/fix-rebase
fix rebase
2017-11-08 10:13:09 -03:00
Matias Alejo Garcia a00572a194
fix rebase 2017-11-08 10:11:57 -03:00
Matias Alejo Garcia b3558ae1e5
Merge pull request #728 from matiu/feat/fee-cache
Feat/fee cache
2017-11-08 09:47:27 -03:00
Matias Alejo Garcia fe02071a38
fix rebase 2017-11-08 08:52:01 -03:00
Matias Alejo Garcia b12634f1d8
Merge pull request #726 from lukechilds/patch-1
Fix typo in readme
2017-11-08 08:49:22 -03:00
Matias Alejo Garcia f9ec2e1d5b
fee levels cache 2017-11-08 08:47:46 -03:00
Matias Alejo Garcia 71d146bc9e
fix utxo get 2017-11-08 08:43:40 -03:00
Matias Alejo Garcia 2692f579e2
Merge pull request #727 from nitsujlangston/queueInsightRequests
Adding insight request queue
2017-11-07 18:20:53 -03:00
Justin Langston ead5ddb817
Adding insight request queue 2017-11-07 15:58:54 -05:00
Luke Childs 7e788e202c
Fix typo in readme 2017-11-07 11:36:53 +07:00
Matias Alejo Garcia bdc0ba9699
Merge pull request #723 from matiu/bug/utxo-query
restrict UTXO queries when sending a tx
2017-11-05 09:43:37 -03:00
Matias Alejo Garcia 2493dbb98d
restrict UTXO queries when sending a tx 2017-11-05 09:36:29 -03:00
Matias Alejo Garcia a7cd4e6171
Merge pull request #722 from matiu/feat/logs2
better logs
2017-11-03 11:39:25 -03:00
Matias Alejo Garcia 64e5a292e4
fix logw 2017-11-03 11:10:21 -03:00
Matias Alejo Garcia 6361006b8d
better logs 2017-11-03 10:17:13 -03:00
Matias Alejo Garcia a1ec459311
Merge pull request #721 from matiu/opt/fast-balance-cache
Opt/fast balance cache
2017-11-02 15:19:22 -03:00
Matias Alejo Garcia 5c22852328
better tests 2017-11-02 14:31:56 -03:00
Matias Alejo Garcia 23278a06fb
skip doing 2 queries if all addresses are active (only in new imported wallets, mainly) 2017-11-02 13:38:10 -03:00
Matias Alejo Garcia 1db21481d6
fast balance cache 2017-11-02 13:38:10 -03:00
Matias Alejo Garcia bf2e281787
WIP short address cache 2017-11-02 13:38:10 -03:00
Matias Alejo Garcia cc9e0ba403
Merge pull request #718 from bitpay/feat/2-step-opt
Feat/2 step opt
2017-11-02 13:03:19 -03:00
Matias Alejo Garcia bd24613261
add pm2 doc 2017-11-02 08:06:38 -03:00
Matias Alejo Garcia 9140f89944
Update README.md 2017-11-02 08:06:26 -03:00
Matias Alejo Garcia 545dd4e51a
Merge pull request #598 from huangbong/master
Added app.js to spawn service scripts, PM2 compatibility
2017-11-02 08:04:17 -03:00
Matias Alejo Garcia a04ac1383c
Merge pull request #720 from matiu/opt/db-indexes2
Opt/db indexes2
2017-11-02 08:00:02 -03:00
Matias Alejo Garcia 0d839a1c74
adds new DB index 2017-11-02 07:58:00 -03:00
Matias Alejo Garcia e4d7f3ee7b
adds JSHint 2017-11-02 07:57:51 -03:00
Matias Alejo Garcia 8bffde15a2
Merge pull request #717 from JDonadio/stats/bch
BCH Stats
2017-11-02 07:31:15 -03:00
Matias Alejo Garcia b8c52a814c
add log to derive 2017-11-01 16:56:32 -03:00
Matias Alejo Garcia 0b6e5771c4
rm twoStep cache on address scan 2017-11-01 16:15:35 -03:00
Matias Alejo Garcia 2d46dca887
change default 2017-11-01 11:45:38 -03:00
Matias Alejo Garcia f911ac3cc6
add new test, and better logs 2017-11-01 11:43:47 -03:00
Matias Alejo Garcia 002f9c77a6
fix tests, add lock to long utxo queries 2017-10-31 23:45:42 -03:00
Matias Alejo Garcia 3a75b38232
add lock 2017-10-31 20:13:37 -03:00
Matias Alejo Garcia 85156a8898
2step optimizations 2017-10-31 19:21:36 -03:00
JDonadio 1949924c1c
search bch txps 2017-10-31 10:28:08 -03:00
JDonadio dac3d1ab0b
search bch wallets 2017-10-31 09:49:12 -03:00
Matias Alejo Garcia 74f71230d8
add TWO_STEP_CREATION_HOURS default 2017-10-30 17:52:38 -03:00
Matias Alejo Garcia 9177033ac0
add TWO_STEP_CREATION_HOURS default 2017-10-30 17:50:56 -03:00
Matias Alejo Garcia 7a12b1761b
Merge pull request #715 from bitpay/ref/deps-bitcore
Ref/deps bitcore
2017-10-27 19:26:53 -03:00
Matias Alejo Garcia f15d69f7cf
update bitcore-lib-cash 2017-10-27 19:09:30 -03:00
Matias Alejo Garcia ec47ec0b61
bump 2017-10-27 13:12:06 -03:00
Matias Alejo Garcia ad772b7f6e
update bitcore 2017-10-27 13:02:37 -03:00
Matias Alejo Garcia f07bda2d22 Merge pull request #714 from bitpay/bug/timeouts
new timeouts
2017-10-26 17:47:46 -03:00
Matias Alejo Garcia 46bee919da
fix deps 2017-10-26 17:37:58 -03:00
Matias Alejo Garcia e87521149a
new timeouts 2017-10-26 16:59:50 -03:00
Matias Alejo Garcia 13229b8aa9 Merge pull request #713 from bitpay/feat/estimateFee-limit
add rateLimiter to estimateFee
2017-10-19 15:01:42 -03:00
Matias Alejo Garcia 9d6af8cd0e
add rateLimiter to estimateFee 2017-10-19 14:52:53 -03:00
Matias Alejo Garcia 15de4716f7 Merge pull request #705 from nitsujlangston/addEmailQueueIdIndex
adding needed index for email_queue.id
2017-10-02 10:01:57 -03:00
Justin Langston 622f9cf7c4
adding needed index for email_queue.id 2017-09-26 22:06:05 -04:00
Matias Alejo Garcia ffea739a19 Merge pull request #702 from matiu/bug/cross-coin-balance
do not decorate address with .coin param is given
2017-09-14 20:04:16 -03:00
matiu 51f66c7f8d do not decorate address with .coin param is given 2017-09-14 19:54:01 -03:00
Matias Alejo Garcia 12dee6fd59 Merge pull request #701 from matiu/feat/cash-utxo
add coin to getUtxo
2017-09-14 18:40:11 -03:00
matiu 357419b53c bump version 2017-09-14 15:45:50 -03:00
matiu a9e56dd3be fix getUtxos + tests 2017-09-14 00:49:44 -03:00
matiu 5186392558 add BCH createTX tests 2017-09-13 16:46:23 -03:00
matiu d8e6964587 support cross-coin balance querying, using different address versions 2017-09-13 15:35:48 -03:00
matiu 570c8c193f add coin to getUtxo 2017-09-12 23:42:14 -03:00
Matias Alejo Garcia c6269a39a5 Merge pull request #699 from isocolsky/bch-unit
Use BCH unit on email & push notifications
2017-09-08 16:24:55 -03:00
Ivan Socolsky f0a48e1217
use wallet unit for BCH 2017-09-08 16:18:25 -03:00
Matias Alejo Garcia 03a61f68f2 Merge pull request #698 from isocolsky/fix/support-tool
Bugfix for getWalletFromIdentifier
2017-09-08 15:25:12 -03:00
Ivan Socolsky 8dd76a0dba
handle unsupported pair coin/network 2017-09-08 13:06:13 -03:00
Ivan Socolsky fd9a2ad839
fix check for txid 2017-09-08 12:46:55 -03:00
Matias Alejo Garcia f81b5484c5 Merge pull request #697 from isocolsky/fix/send-max-cash
Fix UTXOs verification on send max
2017-09-04 10:42:08 -03:00
Ivan Socolsky 5c3b0f4d37
get utxos based on current wallet coin 2017-09-04 10:40:16 -03:00
Matias Alejo Garcia 5ba71f1641 Merge pull request #676 from russellpwirtz/patch-1
Update README - fix typo
2017-09-04 09:35:50 -03:00
Matias Alejo Garcia 3d967298f0 Merge pull request #677 from wrinkl3/patch-1
Update README.md
2017-09-04 09:35:16 -03:00
Ivan Socolsky d25e21b0d5 Merge pull request #696 from matiu/bug/sigle-address-dups
fix change address storage for single address wallets
2017-09-04 09:27:33 -03:00
matiu 05982ba166 fix change address storage for single address wallets 2017-09-04 08:46:45 -03:00
Matias Alejo Garcia 5cab2a722b Merge pull request #695 from matiu/bug/uniq
add uniq to insight queries
2017-09-03 11:22:08 -03:00
matiu b74318862e add uniq to insight queries 2017-09-03 11:16:02 -03:00
Matias Alejo Garcia c4f87ccb17 Merge pull request #692 from isocolsky/feat/cross-balance
Allow checking balance of a wallet on a different coin
2017-09-01 15:07:18 -03:00
Ivan Socolsky 7bbc6aea82
check coin arg is valid 2017-09-01 15:06:58 -03:00
Ivan Socolsky 87ba10de84
add coin arg to getBalance 2017-09-01 11:45:19 -03:00
Ivan Socolsky 5dffb5f958 Merge pull request #691 from isocolsky/bump/v2.0.0
v2.0.0
2017-08-31 14:47:41 -03:00
Ivan Socolsky 5babd07201
v2.0.0 2017-08-31 14:47:21 -03:00
Matias Alejo Garcia b2845456d3 Merge pull request #690 from isocolsky/feat/cash
v1.2.0
2017-08-31 14:43:31 -03:00
Ivan Socolsky d3bb52ccf6
v1.2.0 2017-08-31 14:38:42 -03:00
Ivan Socolsky f16b33908f
fix default config 2017-08-31 13:58:45 -03:00
Matias Alejo Garcia e2edfd394f Merge pull request #688 from isocolsky/feat/cash
Bitcoin Cash support
2017-08-31 13:34:16 -03:00
Ivan Socolsky 30494a8133
fetch address by walletId/coin 2017-08-31 11:59:30 -03:00
Ivan Socolsky 98bdaebbfb
fix testdata 2017-08-31 11:04:47 -03:00
Ivan Socolsky c125950891
require bitcore-lib-cash through https 2017-08-30 18:23:05 -03:00
Ivan Socolsky 398c5ccc64
node 8 2017-08-30 17:13:56 -03:00
Ivan Socolsky fbe2040cbb
fix feelevel tests 2017-08-30 15:23:19 -03:00
Ivan Socolsky 9ef45b4409
use SIGHASH_FORKID for cash txs 2017-08-30 15:05:48 -03:00
Ivan Socolsky acdf99a34f
coin dependent default fee levels 2017-08-29 16:33:35 -03:00
Ivan Socolsky cb4ebe8c50
fix block explorer URL 2017-08-29 10:42:12 -03:00
Ivan Socolsky 371d315138
parse coin param on /v2/feelevels/ 2017-08-27 20:54:43 -03:00
Ivan Socolsky 5fbb262ace
allow copayers with same xpub for different coins 2017-08-25 17:20:29 -03:00
Ivan Socolsky ba6fbab240
add coin to bc monitor 2017-08-25 16:31:58 -03:00
Ivan Socolsky 58cb560b79
select appropriate Bitcore when building/signing tx proposal 2017-08-25 16:23:27 -03:00
Ivan Socolsky 06b63e0311
all methods working with coin 2017-08-25 16:04:14 -03:00
Ivan Socolsky 4d7a5ee3d5
prevent joining wallet for different coin 2017-08-25 10:11:46 -03:00
Ivan Socolsky daad0367d7
add coin argument to wallet 2017-08-25 09:52:58 -03:00
Ivan Socolsky 827e05e08e
add bitcore-lib-cash dep 2017-08-22 17:16:05 -03:00
Matias Alejo Garcia b6d9048001 Merge pull request #683 from magmahindenburg/master
Added an index for copayerId in sessions
2017-08-16 15:06:16 +02:00
magmahindenburg b47dfaeb95 Added an index for copayerId in sessions 2017-08-16 14:51:25 +09:00
Alex Shatberashvili 5f68cb27f2 Update README.md 2017-07-28 02:38:47 +04:00
Russ bad1826ec0 Update README - fix typo 2017-07-27 09:41:00 -07:00
Ivan Socolsky 5f3295722b Merge pull request #670 from isocolsky/version-bump
v1.18.0
2017-06-29 15:13:52 -03:00
Ivan Socolsky 0ed05ee808
v1.18.0 2017-06-29 15:09:38 -03:00
Matias Alejo Garcia d416284fc3 Merge pull request #669 from isocolsky/feat/support
Add API methods for support staff
2017-06-29 19:58:52 +02:00
Ivan Socolsky 279d2ecc68
test #getWalletFromIdentifier 2017-06-29 14:51:13 -03:00
Ivan Socolsky 565bc01339
bug fixes 2017-06-29 14:51:13 -03:00
Ivan Socolsky 2ab93cea1d
get wallet from identifier + REST endpoint 2017-06-29 14:51:13 -03:00
Ivan Socolsky 41c82e9e76
add support staff flag to copayer lookup collection 2017-06-29 14:51:12 -03:00
Ivan Socolsky c5d9f44b2d Merge pull request #664 from arve0/patch-1
Fix links in readme
2017-06-05 16:54:44 -03:00
Arve Seljebu 3be1e616e7 Fix links in readme 2017-06-03 13:33:11 +02:00
Ivan Socolsky 7cbfe71a79 Merge pull request #662 from isocolsky/v1.17
v1.17
2017-05-24 11:10:26 -03:00
Ivan Socolsky 88ecc303bb
v1.17 2017-05-24 11:09:57 -03:00
Ivan Socolsky eabf3c978f Merge pull request #661 from isocolsky/v1.16
v1.16.0
2017-05-24 11:01:54 -03:00
Ivan Socolsky a2f1e0e40e
v1.16.0 2017-05-24 11:01:16 -03:00
Matias Alejo Garcia 954b88aceb Merge pull request #658 from isocolsky/feat/confirmation-notif
Notify of tx confirmations
2017-05-24 15:41:27 +02:00
Ivan Socolsky ca0bd7fbcd
update README 2017-05-24 10:27:25 -03:00
Gustavo Maximiliano Cortez 3aaa2bf82e
Adds template for tx-confirmation 2017-05-24 10:27:25 -03:00
Ivan Socolsky acd75cd62c
fix insight block url 2017-05-24 10:27:25 -03:00
Ivan Socolsky dd1c90f2bc
fix arg in call to insight 2017-05-24 10:27:25 -03:00
Ivan Socolsky da6e61b7e7
add REST endpoint 2017-05-24 10:27:25 -03:00
Ivan Socolsky 8df8327395
send email 2017-05-24 10:27:25 -03:00
Ivan Socolsky c621d1c573
send push notification 2017-05-24 10:27:25 -03:00
Ivan Socolsky c87350d7ef
send notification 2017-05-24 10:27:25 -03:00
Ivan Socolsky b856d1cb02
fix small test issue 2017-05-24 10:27:25 -03:00
Ivan Socolsky 7803e4576b
tx confirmation subs API endpoints 2017-05-24 10:27:25 -03:00
Ivan Socolsky fdea614d81
storage fns for tx confirmation subs 2017-05-24 10:27:25 -03:00
Ivan Socolsky 300102e053
add tx confirmation model 2017-05-24 10:27:24 -03:00
Ivan Socolsky 0dbdfa1857
refactor fn names 2017-05-24 10:27:24 -03:00
Ivan Socolsky 13472e147a
add method to get txids from a specified block 2017-05-24 10:27:24 -03:00
Matias Alejo Garcia 7587540113 Merge pull request #655 from isocolsky/fix/send-max
Fix send max when resulting amount is below dust
2017-05-24 14:35:36 +02:00
Ivan Socolsky 4be0937e5b
fix resulting amount below dust 2017-05-15 10:24:15 -03:00
Matias Alejo Garcia a9b44ee5f1 Merge pull request #651 from isocolsky/fix/notif-replay
Avoid replaying notification for incoming txs
2017-04-14 19:54:40 +02:00
Ivan Socolsky a2944af07e
avoid replaying notification for incoming txs 2017-04-14 14:28:50 -03:00
Matias Alejo Garcia cb3ee35deb Merge pull request #650 from isocolsky/feat/low-fees
Tag only unconfirmed txs with low fee
2017-04-14 19:25:00 +02:00
Ivan Socolsky bbb6890fd5
only tag unconfirmed txs 2017-04-13 12:50:00 -03:00
Matias Alejo Garcia be430ace03 Merge pull request #649 from isocolsky/fix/addrs-gen
Avoid storing empty collection when creating new addresses
2017-04-13 17:39:49 +02:00
Ivan Socolsky 8e60cf01de
avoid storing empty collection 2017-04-13 12:35:15 -03:00
Matias Alejo Garcia 11c5bd92a7 Merge pull request #648 from isocolsky/fix/addrs-gen
Fix address generation
2017-04-13 17:19:32 +02:00
Ivan Socolsky 4fd339da29
store address index even when no new addresses are generated 2017-04-13 11:57:40 -03:00
Matias Alejo Garcia 2d3df0a12e Merge pull request #647 from isocolsky/feat/low-fees
WIP: Feat/low fees
2017-04-12 16:19:40 +02:00
Ivan Socolsky 785fc4676e
improve robustness & tests 2017-04-12 11:12:46 -03:00
Ivan Socolsky 1d4cf86a56
detect low fee txs in tx history 2017-04-12 10:49:28 -03:00
Matias Alejo Garcia d94290181e Merge pull request #646 from itsyogesh/patch-1
Update README for typo.
2017-04-12 02:39:44 +02:00
Yogesh 3eea8ccfc4 Update README.md 2017-04-12 05:35:39 +05:30
Yogesh e2f497a6e5 Update the getting started commands.
Doing an `npm install bitcore-wallet-service` wouldn't install the project in the directory since there is no `package.json` file. It should rather be a `git clone` and then `cd bitcore-wallet-service && npm start`.
2017-04-12 05:35:14 +05:30
Yogesh f1d9d2c499 Update README for typo.
Two characters were causing an issue with the readability, updated them for clearer reading.
2017-04-12 00:54:17 +05:30
Ivan Socolsky 98165ab5a7 Merge pull request #644 from carlosfaria94/master
Replace ServerWithAuth by Server in getFiatRate
2017-04-06 19:16:11 -03:00
CarlosFaria94 ea54fe5a58 Replace ServerWithAuth by Server in getFiatRate
To get a fiat rate there is no need to pass by the authentication mechanisms.

Also, previously the BWS was waiting for a 'source' in query, but the BWC it is sending a 'provider' query, not 'source', turns out that it is impossible to the client to query a specific provider, because fiatRateService do not recognize the variable 'source'.
2017-04-06 12:46:54 +01:00
Ivan Socolsky 752f86029a Merge pull request #642 from matiu/feat/abort
Feat/abort
2017-04-05 12:08:06 -03:00
Matias Alejo Garcia d13fe55b66
. 2017-04-05 12:03:45 -03:00
Matias Alejo Garcia ba2fea93d7
add abort log 2017-04-05 12:02:36 -03:00
Matias Alejo Garcia 3a0431fac6 Merge pull request #641 from isocolsky/fix/utxo-selection
Fix spent utxos check on publish
2017-04-05 11:41:42 -03:00
Ivan Socolsky aac8b274eb
fix spent utxos check on publish 2017-04-05 10:51:50 -03:00
Mitchell Huang dee50e8d30 Remove unused process require, fixed spacing 2016-11-07 17:11:55 +00:00
Mitchell Huang dba1601560 Added app.js to spawn service scripts, PM2 compatibility 2016-11-07 17:00:08 +00:00
45 changed files with 11557 additions and 1534 deletions

44
.jshintrc Normal file
View File

@ -0,0 +1,44 @@
{
"bitwise": true,
"camelcase": true,
"curly": true,
"devel": false,
"eqeqeq": true,
"eqnull": false,
"freeze": true,
"funcscope": false,
"immed": true,
"indent": 2,
"latedef": "nofunc",
"maxcomplexity": 10,
"maxdepth": 4,
"maxerr": 99999,
"maxlen": 120,
"maxparams": 4,
"maxstatements": 15,
"mocha": true,
"newcap": true,
"noarg": true,
"node": true,
"noempty": true,
"nonew": true,
"quotmark": "single",
"regexp": true,
"smarttabs": false,
"strict": true,
"trailing": true,
"undef": true,
"unused": true,
"predef": [
"after",
"afterEach",
"before",
"beforeEach",
"describe",
"exports",
"it",
"xit",
"module",
"require"
]
}

View File

@ -12,7 +12,7 @@ addons:
- g++-4.8
- clang
node_js:
- '4'
- '8'
before_install:
- export CXX="g++-4.8" CC="gcc-4.8"
install:

View File

@ -13,18 +13,19 @@ Bitcore Wallet Service facilitates multisig HD wallets creation and operation th
BWS can usually be installed within minutes and accommodates all the needed infrastructure for peers in a multisig wallet to communicate and operate with minimum server trust.
See [Bitcore-wallet-client] (https://github.com/bitpay/bitcore-wallet-client) for the *official* client library that communicates to BWS and verifies its response. Also check [Bitcore-wallet] (https://github.com/bitpay/bitcore-wallet) for a simple CLI wallet implementation that relays on BWS.
See [Bitcore-wallet-client](https://github.com/bitpay/bitcore-wallet-client) for the *official* client library that communicates to BWS and verifies its response. Also check [Bitcore-wallet](https://github.com/bitpay/bitcore-wallet) for a simple CLI wallet implementation that relies on BWS.
BWS is been used in production enviroments for [Copay Wallet](https://copay.io), [Bitpay App wallet](https://bitpay.com/wallet) and others.
More about BWS at https://blog.bitpay.com/announcing-the-bitcore-wallet-suite/
# Install
# Getting Started
```
npm install bitcore-wallet-service
npm start
git clone https://github.com/bitpay/bitcore-wallet-service.git
cd bitcore-wallet-service && npm start
```
This will launch the BWS service (with default settings) at `http://localhost:3232/bws/api`.
BWS needs mongoDB. You can configure the connection at `config.js`
@ -33,6 +34,14 @@ BWS supports SSL and Clustering. For a detailed guide on installing BWS with ext
BWS uses by default a Request Rate Limitation to CreateWallet endpoint. If you need to modify it, check defaults.js' `Defaults.RateLimit`
# Using BWS with PM2
BWS can be used with PM2 with the provided `app.js` script:
```
pm2 start app.js --name "bitcoin-wallet-service"
```
# Security Considerations
* Private keys are never sent to BWS. Copayers store them locally.
* Extended public keys are stored on BWS. This allows BWS to easily check wallet balance, send offline notifications to copayers, etc.
@ -42,7 +51,29 @@ BWS uses by default a Request Rate Limitation to CreateWallet endpoint. If you n
* Addresses and change addresses are derived independently and locally by the copayers from their local data.
* TX Proposals templates are signed by copayers and verified by others, so the BWS cannot create or tamper with them.
# Using SSL
You can add your certificates at the config.js using:
``` json
https: true,
privateKeyFile: 'private.pem',
certificateFile: 'cert.pem',
////// The following is only for certs which are not
////// trusted by nodejs 'https' by default
////// CAs like Verisign do not require this
// CAinter1: '', // ex. 'COMODORSADomainValidationSecureServerCA.crt'
// CAinter2: '', // ex. 'COMODORSAAddTrustCA.crt'
// CAroot: '', // ex. 'AddTrustExternalCARoot.crt'
```
@dabura667 made a report about how to use letsencrypt with BWS: https://github.com/bitpay/bitcore-wallet-service/issues/423
# REST API
Note: all currency amounts are in units of satoshis (1/100,000,000 of a bitcoin).
## Authentication
In order to access a wallet, clients are required to send the headers:
@ -102,6 +133,18 @@ Returns:
* byAddress array ['address', 'path', 'amount']: A list of addresses holding funds.
* totalKbToSendMax: An estimation of the number of KiB required to include all available UTXOs in a tx (including unconfirmed).
`/v1/txnotes/:txid`: Get user notes associated to the specified transaction.
Returns:
* The note associated to the `txid` as a string.
`/v1/fiatrates/:code`: Get the fiat rate for the specified ISO 4217 code.
Optional Arguments:
* provider: An identifier representing the source of the rates.
* ts: The timestamp for the fiat rate (defaults to now).
Returns:
* The fiat exchange rate.
## POST Endpoints
`/v1/wallets/`: Create a new Wallet
@ -117,6 +160,7 @@ Returns:
`/v1/wallets/:id/copayers/`: Join a Wallet in creation
Required Arguments:
* walletId: Id of the wallet to join
* name: Copayer Name
@ -129,10 +173,11 @@ Returns:
* wallet: Object with wallet's information
`/v1/txproposals/`: Add a new transaction proposal
Required Arguments:
* toAddress: RCPT Bitcoin address.
* amount: amount (in satoshis) of the mount proposed to be transfered
* proposalsSignature: Signature of the proposal by the creator peer, using prososalSigningKey.
* proposalsSignature: Signature of the proposal by the creator peer, using proposalSigningKey.
* (opt) message: Encrypted private message to peers.
* (opt) payProUrl: Paypro URL for peers to verify TX
* (opt) feePerKb: Use an alternative fee per KB for this TX.
@ -142,7 +187,7 @@ Returns:
* TX Proposal object. (see [fields on the source code](https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/txproposal.js)). `.id` is probably needed in this case.
`/v1/addresses/`: Request a new main address from wallet
`/v3/addresses/`: Request a new main address from wallet . (creates an address on normal conditions)
Returns:
* Address object: (https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/address.js)). Note that `path` is returned so client can derive the address independently and check server's response.
@ -170,6 +215,13 @@ Returns:
Optional Arguments:
* includeCopayerBranches: Scan all copayer branches following BIP45 recommendation (defaults to false).
`/v1/txconfirmations/`: Subscribe to receive push notifications when the specified transaction gets confirmed.
Required Arguments:
* txid: The transaction to subscribe to.
## PUT Endpoints
`/v1/txnotes/:txid/`: Modify a note for a tx.
## DELETE Endpoints
`/v1/txproposals/:id/`: Deletes a transaction proposal. Only the creator can delete a TX Proposal, and only if it has no other signatures or rejections
@ -177,13 +229,10 @@ Returns:
Returns:
* TX Proposal object. (see [fields on the source code](https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/txproposal.js)). `.id` is probably needed in this case.
`/v1/txconfirmations/:txid`: Unsubscribe from transaction `txid` and no longer listen to its confirmation.
# Push Notifications
## Installation
In order to use push notifications service, you need install:
* [node-pushserver](https://www.npmjs.com/package/node-pushserver)
Recomended to complete config.js file:
* [GCM documentation to get your API key](https://developers.google.com/cloud-messaging/gcm)
@ -194,8 +243,8 @@ Returns:
`/v1/pushnotifications/subscriptions/`: Adds subscriptions for push notifications service at database.
## DELETE Endopints
`/v1/pushnotifications/subscriptions/`: Remove subscriptions for push notifications service from database.
## DELETE Endpoints
`/v2/pushnotifications/subscriptions/`: Remove subscriptions for push notifications service from database.

23
app.js Normal file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env node
var spawn = require('child_process').spawn;
var async = require('async');
var scripts = ['locker/locker.js', 'messagebroker/messagebroker.js',
'bcmonitor/bcmonitor.js', 'emailservice/emailservice.js',
'pushnotificationsservice/pushnotificationsservice.js',
'fiatrateservice/fiatrateservice.js', 'bws.js'];
async.eachSeries(scripts, function(script, callback) {
console.log(`Spawning ${script}`);
var node = spawn('node', [script]);
node.stdout.on('data', (data) => {
console.log(`${data}`);
});
node.stderr.on('data', (data) => {
console.error(`${data}`);
});
callback();
});

View File

@ -38,18 +38,33 @@ var config = {
},
},
blockchainExplorerOpts: {
btc: {
livenet: {
provider: 'insight',
url: 'https://insight.bitpay.com:443',
url: 'https://explorer.btcprivate.org:443',
},
testnet: {
provider: 'insight',
url: 'https://test-insight.bitpay.com:443',
// url: 'http://localhost:3001',
url: 'https://explorer.testnet.btcprivate.org:443',
// Multiple servers (in priority order)
// url: ['http://a.b.c', 'https://test-insight.bitpay.com:443'],
},
},
bch: {
livenet: {
provider: 'insight',
//url: 'https://cashexplorer.bitcoin.com',
url: 'https://bch-insight.bitpay.com:443',
addressFormat: 'cashaddr', // copay, cashaddr, or legacy
},
testnet: {
provider: 'insight',
url: 'https://test-bch-insight.bitpay.com:443',
addressFormat: 'cashaddr', // copay, cashaddr, or legacy
},
},
},
pushNotificationsOpts: {
templatePath: './lib/templates',
defaultLanguage: 'en',

View File

@ -0,0 +1,61 @@
var Bitcore_ = {
btc: require('bitcore-lib'),
bch: require('bitcore-lib-cash')
};
var _ = require('lodash');
function BCHAddressTranslator() {
};
BCHAddressTranslator.getAddressCoin = function(address) {
try {
new Bitcore_['btc'].Address(address);
return 'legacy';
} catch (e) {
try {
var a= new Bitcore_['bch'].Address(address);
if (a.toString() == address) return 'copay';
return 'cashaddr';
} catch (e) {
return;
}
}
};
// Supports 3 formats: legacy (1xxx, mxxxx); Copay: (Cxxx, Hxxx), Cashaddr(qxxx);
BCHAddressTranslator.translate = function(addresses, to, from) {
var wasArray = true;
if (!_.isArray(addresses)) {
wasArray = false;
addresses = [addresses];
}
from = from || BCHAddressTranslator.getAddressCoin(addresses[0]);
if (from == to) return addresses;
var ret = _.map(addresses, function(x) {
var bitcore = Bitcore_[from == 'legacy' ? 'btc' : 'bch'];
var orig = new bitcore.Address(x).toObject();
if (to == 'cashaddr') {
return Bitcore_['bch'].Address.fromObject(orig).toCashAddress(true);
} else if (to == 'copay') {
return Bitcore_['bch'].Address.fromObject(orig).toString();
} else if (to == 'legacy') {
return Bitcore_['btc'].Address.fromObject(orig).toString();
}
});
if (wasArray)
return ret;
else
return ret[0];
};
module.exports = BCHAddressTranslator;

View File

@ -6,11 +6,21 @@ var log = require('npmlog');
log.debug = log.verbose;
var Insight = require('./blockchainexplorers/insight');
var Common = require('./common');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
var PROVIDERS = {
'insight': {
'livenet': 'https://insight.bitpay.com:443',
'testnet': 'https://test-insight.bitpay.com:443',
'btc': {
'livenet': 'https://explorer.btcprivate.org:443',
'testnet': 'https://explorer.testnet.btcprivate.org:443',
},
'bch': {
'livenet': 'https://bch-insight.bitpay.com:443',
'testnet': 'https://test-bch-insight.bitpay.com:443',
},
},
};
@ -18,20 +28,32 @@ function BlockChainExplorer(opts) {
$.checkArgument(opts);
var provider = opts.provider || 'insight';
var coin = opts.coin || Defaults.COIN;
var network = opts.network || 'livenet';
$.checkState(PROVIDERS[provider], 'Provider ' + provider + ' not supported');
$.checkState(_.contains(_.keys(PROVIDERS[provider]), network), 'Network ' + network + ' not supported by this provider');
$.checkState(_.contains(_.keys(PROVIDERS[provider]), coin), 'Coin ' + coin + ' not supported by this provider');
$.checkState(_.contains(_.keys(PROVIDERS[provider][coin]), network), 'Network ' + network + ' not supported by this provider for coin ' + coin);
var url = opts.url || PROVIDERS[provider][coin][network];
if (coin != 'bch' && opts.addressFormat)
throw new Error('addressFormat only supported for bch');
if (coin == 'bch' && !opts.addressFormat)
opts.addressFormat = 'cashaddr';
var url = opts.url || PROVIDERS[provider][network];
switch (provider) {
case 'insight':
return new Insight({
coin: coin,
network: network,
url: url,
apiPrefix: opts.apiPrefix,
userAgent: opts.userAgent,
addressFormat: opts.addressFormat,
});
default:
throw new Error('Provider ' + provider + ' not supported.');

View File

@ -1,23 +1,38 @@
'use strict';
var _ = require('lodash');
var async = require('async');
var $ = require('preconditions').singleton();
var log = require('npmlog');
log.debug = log.verbose;
var io = require('socket.io-client');
var requestList = require('./request-list');
var Common = require('../common');
var BCHAddressTranslator = require('../bchaddresstranslator');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
function Insight(opts) {
$.checkArgument(opts);
$.checkArgument(_.contains(['livenet', 'testnet'], opts.network));
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
$.checkArgument(opts.url);
this.apiPrefix = opts.apiPrefix || '/api';
this.apiPrefix = _.isUndefined(opts.apiPrefix)? '/api' : opts.apiPrefix;
this.coin = opts.coin || Defaults.COIN;
this.network = opts.network || 'livenet';
this.hosts = opts.url;
this.userAgent = opts.userAgent || 'bws';
};
if (opts.addressFormat) {
$.checkArgument(Constants.ADDRESS_FORMATS.includes(opts.addressFormat), 'Unkown addr format:' + opts.addressFormat);
this.addressFormat = opts.addressFormat != 'copay' ? opts.addressFormat : null;
}
this.requestQueue = async.queue(this._doRequest.bind(this), Defaults.INSIGHT_REQUEST_POOL_SIZE);
}
var _parseErr = function(err, res) {
if (err) {
@ -28,6 +43,42 @@ var _parseErr = function(err, res) {
return "Error querying the blockchain";
};
// Translate Request Address query
Insight.prototype.translateQueryAddresses = function(addresses) {
if (!this.addressFormat) return addresses;
return BCHAddressTranslator.translate(addresses, this.addressFormat, 'copay');
};
// Translate Result Address
Insight.prototype.translateResultAddresses = function(addresses) {
if (!this.addressFormat) return addresses;
return BCHAddressTranslator.translate(addresses, 'copay', this.addressFormat);
};
Insight.prototype.translateTx = function(tx) {
var self = this;
if (!this.addressFormat) return tx;
_.each(tx.vin, function(x){
if (x.addr) {
x.addr = self.translateResultAddresses(x.addr);
}
});
_.each(tx.vout, function(x){
if (x.scriptPubKey && x.scriptPubKey.addresses) {
x.scriptPubKey.addresses = self.translateResultAddresses(x.scriptPubKey.addresses);
}
});
};
Insight.prototype._doRequest = function(args, cb) {
var opts = {
hosts: this.hosts,
@ -35,28 +86,43 @@ Insight.prototype._doRequest = function(args, cb) {
'User-Agent': this.userAgent,
}
};
var s = JSON.stringify(args);
// if ( s.length > 100 )
// s= s.substr(0,100) + '...';
log.debug('', 'Insight Q: %s', s);
requestList(_.defaults(args, opts), cb);
};
Insight.prototype.getConnectionInfo = function() {
return 'Insight (' + this.network + ') @ ' + this.hosts;
return 'Insight (' + this.coin + '/' + this.network + ') @ ' + this.hosts;
};
/**
* Retrieve a list of unspent outputs associated with an address or set of addresses
*/
Insight.prototype.getUtxos = function(addresses, cb) {
var self = this;
var url = this.url + this.apiPrefix + '/addrs/utxo';
var args = {
method: 'POST',
path: this.apiPrefix + '/addrs/utxo',
json: {
addrs: [].concat(addresses).join(',')
addrs: this.translateQueryAddresses(_.uniq([].concat(addresses))).join(',')
},
};
this._doRequest(args, function(err, res, unspent) {
this.requestQueue.push(args, function(err, res, unspent) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
if (self.addressFormat) {
_.each(unspent, function(x) {
x.address = self.translateResultAddresses(x.address);
});
}
return cb(null, unspent);
});
};
@ -73,29 +139,35 @@ Insight.prototype.broadcast = function(rawTx, cb) {
},
};
this._doRequest(args, function(err, res, body) {
this.requestQueue.push(args, function(err, res, body) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
return cb(null, body ? body.txid : null);
});
};
Insight.prototype.getTransaction = function(txid, cb) {
var self = this;
var args = {
method: 'GET',
path: this.apiPrefix + '/tx/' + txid,
json: true,
};
this._doRequest(args, function(err, res, tx) {
this.requestQueue.push(args, function(err, res, tx) {
if (res && res.statusCode == 404) return cb();
if (err || res.statusCode !== 200)
return cb(_parseErr(err, res));
self.translateTx(tx);
return cb(null, tx);
});
};
Insight.prototype.getTransactions = function(addresses, from, to, cb) {
var self = this;
var qs = [];
var total;
if (_.isNumber(from)) qs.push('from=' + from);
@ -110,12 +182,13 @@ Insight.prototype.getTransactions = function(addresses, from, to, cb) {
method: 'POST',
path: this.apiPrefix + '/addrs/txs' + (qs.length > 0 ? '?' + qs.join('&') : ''),
json: {
addrs: [].concat(addresses).join(',')
addrs: this.translateQueryAddresses(_.uniq([].concat(addresses))).join(',')
},
timeout: 120000,
};
this._doRequest(args, function(err, res, txs) {
this.requestQueue.push(args, function(err, res, txs) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
if (_.isObject(txs)) {
@ -129,6 +202,13 @@ Insight.prototype.getTransactions = function(addresses, from, to, cb) {
// NOTE: Whenever Insight breaks communication with bitcoind, it returns invalid data but no error code.
if (!_.isArray(txs) || (txs.length != _.compact(txs).length)) return cb(new Error('Could not retrieve transactions from blockchain. Request was:' + JSON.stringify(args)));
if (self.addressFormat) {
_.each(txs, function(tx){
self.translateTx(tx);
});
}
return cb(null, txs, total);
});
};
@ -138,15 +218,17 @@ Insight.prototype.getAddressActivity = function(address, cb) {
var args = {
method: 'GET',
path: self.apiPrefix + '/addr/' + address,
path: self.apiPrefix + '/addr/' + this.translateQueryAddresses(address),
json: true,
};
this._doRequest(args, function(err, res, result) {
this.requestQueue.push(args, function(err, res, result) {
if (res && res.statusCode == 404) return cb();
if (err || res.statusCode !== 200)
return cb(_parseErr(err, res));
// note: result.addrStr is not translated, but not used.
var nbTxs = result.unconfirmedTxApperances + result.txApperances;
return cb(null, nbTxs > 0);
});
@ -163,7 +245,7 @@ Insight.prototype.estimateFee = function(nbBlocks, cb) {
path: path,
json: true,
};
this._doRequest(args, function(err, res, body) {
this.requestQueue.push(args, function(err, res, body) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
return cb(null, body);
});
@ -177,12 +259,27 @@ Insight.prototype.getBlockchainHeight = function(cb) {
path: path,
json: true,
};
this._doRequest(args, function(err, res, body) {
this.requestQueue.push(args, function(err, res, body) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
return cb(null, body.blockChainHeight);
});
};
Insight.prototype.getTxidsInBlock = function(blockHash, cb) {
var self = this;
var args = {
method: 'GET',
path: this.apiPrefix + '/block/' + blockHash,
json: true,
};
this.requestQueue.push(args, function(err, res, body) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
return cb(null, body.tx);
});
};
Insight.prototype.initSocket = function() {
// sockets always use the first server on the pull

View File

@ -31,11 +31,23 @@ var requestList = function(args, cb) {
async.whilst(
function() {
nextUrl = urls.shift();
if (!nextUrl && success === 'false')
log.warn('no more servers to test for the request');
return nextUrl && !success;
},
function(a_cb) {
args.uri = nextUrl;
var time = 0;
var interval = setInterval(function() {
time += 10;
log.debug('', 'Delayed insight query: %s, time: %d s', args.uri, time);
}, 10000);
request(args, function(err, res, body) {
clearInterval(interval);
sucess = false;
if (err) {
log.warn('REQUEST FAIL: ' + nextUrl + ' ERROR: ' + err);
}

View File

@ -14,6 +14,9 @@ var Lock = require('./lock');
var Notification = require('./model/notification');
var WalletService = require('./server');
var Common = require('./common');
var Constants = Common.Constants;
var Utils = Common.Utils;
function BlockchainMonitor() {};
@ -25,25 +28,42 @@ BlockchainMonitor.prototype.start = function(opts, cb) {
async.parallel([
function(done) {
self.explorers = _.map(['livenet', 'testnet'], function(network) {
self.explorers = {
btc: {},
bch: {},
};
var coinNetworkPairs = [];
_.each(_.values(Constants.COINS), function(coin) {
_.each(_.values(Constants.NETWORKS), function(network) {
coinNetworkPairs.push({
coin: coin,
network: network
});
});
});
_.each(coinNetworkPairs, function(pair) {
var explorer;
if (opts.blockchainExplorers) {
explorer = opts.blockchainExplorers[network];
if (opts.blockchainExplorers && opts.blockchainExplorers[pair.coin] && opts.blockchainExplorers[pair.coin][pair.network]) {
explorer = opts.blockchainExplorers[pair.coin][pair.network];
} else {
var config = {}
if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[network]) {
config = opts.blockchainExplorerOpts[network];
if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[pair.coin] && opts.blockchainExplorerOpts[pair.coin][pair.network]) {
config = opts.blockchainExplorerOpts[pair.coin][pair.network];
} else {
return;
}
var explorer = new BlockchainExplorer({
provider: config.provider,
network: network,
coin: pair.coin,
network: pair.network,
url: config.url,
userAgent: WalletService.getServiceVersion(),
});
}
$.checkState(explorer);
self._initExplorer(explorer);
return explorer;
self._initExplorer(pair.coin, pair.network, explorer);
self.explorers[pair.coin][pair.network] = explorer;
});
done();
},
@ -72,7 +92,7 @@ BlockchainMonitor.prototype.start = function(opts, cb) {
});
};
BlockchainMonitor.prototype._initExplorer = function(explorer) {
BlockchainMonitor.prototype._initExplorer = function(coin, network, explorer) {
var self = this;
var socket = explorer.initSocket();
@ -84,11 +104,11 @@ BlockchainMonitor.prototype._initExplorer = function(explorer) {
socket.on('connect_error', function() {
log.error('Error connecting to ' + explorer.getConnectionInfo());
});
socket.on('tx', _.bind(self._handleIncomingTx, self));
socket.on('block', _.bind(self._handleNewBlock, self, explorer.network));
socket.on('tx', _.bind(self._handleIncomingTx, self, coin, network));
socket.on('block', _.bind(self._handleNewBlock, self, coin, network));
};
BlockchainMonitor.prototype._handleTxId = function(data, processIt) {
BlockchainMonitor.prototype._handleThirdPartyBroadcasts = function(data, processIt) {
var self = this;
if (!data || !data.txid) return;
@ -103,7 +123,7 @@ BlockchainMonitor.prototype._handleTxId = function(data, processIt) {
if (!processIt) {
log.info('Detected broadcast ' + data.txid + ' of an accepted txp [' + txp.id + '] for wallet ' + walletId + ' [' + txp.amount + 'sat ]');
return setTimeout(self._handleTxId.bind(self, data, true), 20 * 1000);
return setTimeout(self._handleThirdPartyBroadcasts.bind(self, data, true), 20 * 1000);
}
log.info('Processing accepted txp [' + txp.id + '] for wallet ' + walletId + ' [' + txp.amount + 'sat ]');
@ -132,25 +152,30 @@ BlockchainMonitor.prototype._handleTxId = function(data, processIt) {
});
};
BlockchainMonitor.prototype._handleTxOuts = function(data) {
BlockchainMonitor.prototype._handleIncomingPayments = function(coin, network, data) {
var self = this;
if (!data || !data.vout) return;
var outs = _.compact(_.map(data.vout, function(v) {
var addr = _.keys(v)[0];
var amount = +v[addr];
// This is because a bug on insight, that always return no copay addr
if (coin == 'bch' && Utils.getAddressCoin(addr) !='bch') {
addr = Utils.translateAddress(addr, coin);
}
return {
address: addr,
amount: +v[addr]
amount: amount,
};
}));
if (_.isEmpty(outs)) return;
async.each(outs, function(out, next) {
self.storage.fetchAddress(out.address, function(err, address) {
// toDo, remove coin here: no more same address for diff coins
self.storage.fetchAddressByCoin(coin, out.address, function(err, address) {
if (err) {
log.error('Could not fetch addresses from the db');
return next(err);
@ -160,6 +185,17 @@ BlockchainMonitor.prototype._handleTxOuts = function(data) {
var walletId = address.walletId;
log.info('Incoming tx for wallet ' + walletId + ' [' + out.amount + 'sat -> ' + out.address + ']');
var fromTs = Date.now() - 24 * 3600 * 1000;
self.storage.fetchNotifications(walletId, null, fromTs, function(err, notifications) {
if (err) return next(err);
var alreadyNotified = _.any(notifications, function(n) {
return n.type == 'NewIncomingTx' && n.data && n.data.txid == data.txid;
});
if (alreadyNotified) {
log.info('The incoming tx ' + data.txid + ' was already notified');
return next();
}
var notification = Notification.create({
type: 'NewIncomingTx',
data: {
@ -170,41 +206,58 @@ BlockchainMonitor.prototype._handleTxOuts = function(data) {
walletId: walletId,
});
self.storage.softResetTxHistoryCache(walletId, function() {
self._updateActiveAddresses(address, function() {
self._updateAddressesWithBalance(address, function() {
self._storeAndBroadcastNotification(notification, next);
});
});
});
});
}, function(err) {
return;
});
};
BlockchainMonitor.prototype._updateActiveAddresses = function(address, cb) {
BlockchainMonitor.prototype._updateAddressesWithBalance = function(address, cb) {
var self = this;
self.storage.storeActiveAddresses(address.walletId, address.address, function(err) {
self.storage.fetchAddressesWithBalance(address.walletId, function(err, result) {
if (err) {
log.warn('Could not update wallet cache', err);
return cb(err);
}
var addresses = _.map(result,'address');
if (_.indexOf(addresses, address.address) >= 0) {
return cb();
}
addresses.push(address.address);
log.info('Activating address ' + address.address);
self.storage.storeAddressesWithBalance(address.walletId, addresses, function(err) {
if (err) {
log.warn('Could not update wallet cache', err);
}
return cb(err);
});
});
};
BlockchainMonitor.prototype._handleIncomingTx = function(data) {
this._handleTxId(data);
this._handleTxOuts(data);
BlockchainMonitor.prototype._handleIncomingTx = function(coin, network, data) {
this._handleThirdPartyBroadcasts(data);
this._handleIncomingPayments(coin, network, data);
};
BlockchainMonitor.prototype._handleNewBlock = function(network, hash) {
BlockchainMonitor.prototype._notifyNewBlock = function(coin, network, hash) {
var self = this;
log.info('New ' + network + ' block: ', hash);
log.info('New ' + network + ' block: ' + hash);
var notification = Notification.create({
type: 'NewBlock',
walletId: network, // use network name as wallet id for global notifications
data: {
hash: hash,
coin: coin,
network: network,
},
});
@ -216,6 +269,64 @@ BlockchainMonitor.prototype._handleNewBlock = function(network, hash) {
});
};
BlockchainMonitor.prototype._handleTxConfirmations = function(coin, network, hash) {
var self = this;
function processTriggeredSubs(subs, cb) {
async.each(subs, function(sub) {
log.info('New tx confirmation ' + sub.txid);
sub.isActive = false;
self.storage.storeTxConfirmationSub(sub, function(err) {
if (err) return cb(err);
var notification = Notification.create({
type: 'TxConfirmation',
walletId: sub.walletId,
creatorId: sub.copayerId,
data: {
txid: sub.txid,
coin: coin,
network: network,
// TODO: amount
},
});
self._storeAndBroadcastNotification(notification, cb);
});
});
};
var explorer = self.explorers[coin][network];
if (!explorer) return;
explorer.getTxidsInBlock(hash, function(err, txids) {
if (err) {
log.error('Could not fetch txids from block ' + hash, err);
return;
}
self.storage.fetchActiveTxConfirmationSubs(null, function(err, subs) {
if (err) return;
if (_.isEmpty(subs)) return;
var indexedSubs = _.indexBy(subs, 'txid');
var triggered = [];
_.each(txids, function(txid) {
if (indexedSubs[txid]) triggered.push(indexedSubs[txid]);
});
processTriggeredSubs(triggered, function(err) {
if (err) {
log.error('Could not process tx confirmations', err);
}
return;
});
});
});
};
BlockchainMonitor.prototype._handleNewBlock = function(coin, network, hash) {
this._notifyNewBlock(coin, network, hash);
this._handleTxConfirmations(coin, network, hash);
};
BlockchainMonitor.prototype._storeAndBroadcastNotification = function(notification, cb) {
var self = this;

View File

@ -2,11 +2,18 @@
var Constants = {};
Constants.COINS = {
BTC: 'btc',
BCH: 'bch',
};
Constants.NETWORKS = {
LIVENET: 'livenet',
TESTNET: 'testnet',
};
Constants.ADDRESS_FORMATS = ['copay', 'cashaddr', 'legacy'];
Constants.SCRIPT_TYPES = {
P2SH: 'P2SH',
P2PKH: 'P2PKH',

View File

@ -3,7 +3,7 @@
var Defaults = {};
Defaults.MIN_FEE_PER_KB = 0;
Defaults.MAX_FEE_PER_KB = 1000000;
Defaults.MAX_FEE_PER_KB = 10000 * 1000; // 10k sat/b
Defaults.MIN_TX_FEE = 0;
Defaults.MAX_TX_FEE = 0.1 * 1e8;
Defaults.MAX_TX_SIZE_IN_KB = 100;
@ -24,7 +24,8 @@ Defaults.MAX_MAIN_ADDRESS_GAP = 20;
// TODO: should allow different gap sizes for external/internal chains
Defaults.SCAN_ADDRESS_GAP = Defaults.MAX_MAIN_ADDRESS_GAP + 20;
Defaults.FEE_LEVELS = [{
Defaults.FEE_LEVELS = {
btc: [{
name: 'urgent',
nbBlocks: 2,
multiplier: 1.5,
@ -45,9 +46,13 @@ Defaults.FEE_LEVELS = [{
name: 'superEconomy',
nbBlocks: 24,
defaultValue: 20000
}];
Defaults.DEFAULT_FEE_PER_KB = Defaults.FEE_LEVELS[1].defaultValue;
}],
bch: [{
name: 'normal',
nbBlocks: 2,
defaultValue: 2000,
}]
};
// How many levels to fallback to if the value returned by the network for a given nbBlocks is -1
Defaults.FEE_LEVELS_FALLBACK = 2;
@ -55,6 +60,12 @@ Defaults.FEE_LEVELS_FALLBACK = 2;
// Minimum nb of addresses a wallet must have to start using 2-step balance optimization
Defaults.TWO_STEP_BALANCE_THRESHOLD = 100;
// Age Limit for addresses to be considered 'active' always
Defaults.TWO_STEP_CREATION_HOURS = 24;
// Time to prevent re-quering inactive addresses (MIN)
Defaults.TWO_STEP_INACTIVE_CLEAN_DURATION_MIN = 60;
Defaults.FIAT_RATE_PROVIDER = 'BitPay';
Defaults.FIAT_RATE_FETCH_INTERVAL = 10; // In minutes
Defaults.FIAT_RATE_MAX_LOOK_BACK_TIME = 120; // In minutes
@ -85,6 +96,12 @@ Defaults.CONFIRMATIONS_TO_START_CACHING = 6 * 6; // ~ 6hrs
// Number of addresses from which tx history is enabled in a wallet
Defaults.HISTORY_CACHE_ADDRESS_THRESOLD = 100;
// Number of addresses from which balance in cache for a few seconds
Defaults.BALANCE_CACHE_ADDRESS_THRESOLD = Defaults.HISTORY_CACHE_ADDRESS_THRESOLD;
Defaults.BALANCE_CACHE_DIRECT_DURATION = 60;
Defaults.BALANCE_CACHE_DURATION = 10;
// Cache time for blockchain height (in seconds)
Defaults.BLOCKHEIGHT_CACHE_TIME = 10 * 60;
@ -98,15 +115,26 @@ Defaults.SESSION_EXPIRATION = 1 * 60 * 60; // 1 hour to session expiration
Defaults.RateLimit = {
createWallet: {
windowMs: 60 * 60 * 1000, // hour window
delayAfter: 10, // begin slowing down responses after the 3rd request
delayAfter: 8, // begin slowing down responses after the 3rd request
delayMs: 3000, // slow down subsequent responses by 3 seconds per request
max: 20, // start blocking after 20 request
message: "Too many wallets created from this IP, please try again after an hour"
max: 15, // start blocking after 20 request
message: 'Too many wallets created from this IP, please try again after an hour',
},
estimateFee: {
windowMs: 60 * 10 *1000, // 10 min window
delayAfter: 5, // begin slowing down responses after the 3rd request
delayMs: 300, // slow down subsequent responses by 3 seconds per request
max: 10, // start blocking after 200 request
message: 'Too many request',
},
// otherPosts: {
// windowMs: 60 * 60 * 1000, // 1 hour window
// max: 1200 , // 1 post every 3 sec average, max.
// },
};
Defaults.COIN = 'btc';
Defaults.INSIGHT_REQUEST_POOL_SIZE = 20;
module.exports = Defaults;

View File

@ -7,6 +7,13 @@ var encoding = bitcore.encoding;
var secp256k1 = require('secp256k1');
var Utils = {};
var Bitcore = require('bitcore-lib');
var Bitcore_ = {
btc: Bitcore,
bch: require('bitcore-lib-cash')
};
Utils.getMissingFields = function(obj, args) {
args = [].concat(args);
@ -105,7 +112,12 @@ Utils.formatAmount = function(satoshis, unit, opts) {
toSatoshis: 1,
maxDecimals: 0,
minDecimals: 0,
}
},
bch: {
toSatoshis: 100000000,
maxDecimals: 6,
minDecimals: 2,
},
};
$.shouldBeNumber(satoshis);
@ -176,5 +188,35 @@ Utils.parseVersion = function(version) {
return v;
};
Utils.checkValueInCollection = function(value, collection) {
if (!value || !_.isString(value)) return false;
return _.contains(_.values(collection), value);
};
Utils.getAddressCoin = function(address) {
try {
new Bitcore_['btc'].Address(address);
return 'btc';
} catch (e) {
try {
new Bitcore_['bch'].Address(address);
return 'bch';
} catch (e) {
return;
}
}
};
Utils.translateAddress = function(address, coin) {
var origCoin = Utils.getAddressCoin(address);
var origAddress = new Bitcore_[origCoin].Address(address);
var origObj = origAddress.toObject();
var result = Bitcore_[coin].Address.fromObject(origObj)
return result.toString();
};
module.exports = Utils;

View File

@ -11,6 +11,7 @@ var path = require('path');
var nodemailer = require('nodemailer');
var Utils = require('./common/utils');
var Defaults = require('./common/defaults');
var Storage = require('./storage');
var MessageBroker = require('./messagebroker');
var Lock = require('./lock');
@ -21,26 +22,37 @@ var EMAIL_TYPES = {
'NewCopayer': {
filename: 'new_copayer',
notifyDoer: false,
notifyOthers: true,
},
'WalletComplete': {
filename: 'wallet_complete',
notifyDoer: true,
notifyOthers: true,
},
'NewTxProposal': {
filename: 'new_tx_proposal',
notifyDoer: false,
notifyOthers: true,
},
'NewOutgoingTx': {
filename: 'new_outgoing_tx',
notifyDoer: true,
notifyOthers: true,
},
'NewIncomingTx': {
filename: 'new_incoming_tx',
notifyDoer: true,
notifyOthers: true,
},
'TxProposalFinallyRejected': {
filename: 'txp_finally_rejected',
notifyDoer: false,
notifyOthers: true,
},
'TxConfirmation': {
filename: 'tx_confirmation',
notifyDoer: true,
notifyOthers: false,
},
};
@ -162,6 +174,9 @@ EmailService.prototype._applyTemplate = function(template, data, cb) {
EmailService.prototype._getRecipientsList = function(notification, emailType, cb) {
var self = this;
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
if (err) return cb(err);
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
if (err) return cb(err);
if (_.isEmpty(preferences)) return cb(null, []);
@ -172,6 +187,7 @@ EmailService.prototype._getRecipientsList = function(notification, emailType, cb
usedEmails[p.email] = true;
if (notification.creatorId == p.copayerId && !emailType.notifyDoer) return;
if (notification.creatorId != p.copayerId && !emailType.notifyOthers) return;
if (!_.contains(self.availableLanguages, p.language)) {
if (p.language) {
log.warn('Language for email "' + p.language + '" not available.');
@ -179,16 +195,24 @@ EmailService.prototype._getRecipientsList = function(notification, emailType, cb
p.language = self.defaultLanguage;
}
var unit;
if (wallet.coin != Defaults.COIN) {
unit = wallet.coin;
} else {
unit = p.unit || self.defaultUnit;
}
return {
copayerId: p.copayerId,
emailAddress: p.email,
language: p.language,
unit: p.unit || self.defaultUnit,
unit: unit,
};
}));
return cb(null, recipients);
});
});
};
EmailService.prototype._getDataForTemplate = function(notification, recipient, cb) {
@ -197,7 +221,8 @@ EmailService.prototype._getDataForTemplate = function(notification, recipient, c
// TODO: Declare these in BWU
var UNIT_LABELS = {
btc: 'BTC',
bit: 'bits'
bit: 'bits',
bch: 'BCH',
};
var data = _.cloneDeep(notification.data);

View File

@ -33,9 +33,10 @@ var errors = {
UPGRADE_NEEDED: 'Client app needs to be upgraded',
WALLET_ALREADY_EXISTS: 'Wallet already exists',
WALLET_FULL: 'Wallet full',
WALLET_LOCKED: 'Wallet is locked',
WALLET_BUSY: 'Wallet is busy, try later',
WALLET_NOT_COMPLETE: 'Wallet is not complete',
WALLET_NOT_FOUND: 'Wallet not found',
WALLET_NEED_SCAN: 'Wallet needs addresses scan',
};
var errorObjects = _.zipObject(_.map(errors, function(msg, code) {

View File

@ -17,7 +17,7 @@ var Stats = require('./stats');
log.disableColor();
log.debug = log.verbose;
log.level = 'info';
log.level = 'verbose';
var ExpressApp = function() {
this.app = express();
@ -39,7 +39,7 @@ ExpressApp.prototype.start = function(opts, cb) {
this.app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'x-signature,x-identity,x-session,x-client-version,X-Requested-With,Content-Type,Authorization');
res.setHeader('Access-Control-Allow-Headers', 'x-signature,x-identity,x-session,x-client-version,x-wallet-id,X-Requested-With,Content-Type,Authorization');
res.setHeader('x-service-version', WalletService.getServiceVersion());
next();
});
@ -54,6 +54,16 @@ ExpressApp.prototype.start = function(opts, cb) {
this.app.use(allowCORS);
this.app.enable('trust proxy');
// handle `abort` https://nodejs.org/api/http.html#http_event_abort
this.app.use(function(req, res, next) {
req.on('abort', function() {
log.warn('Request aborted by the client');
});
next();
});
var POST_LIMIT = 1024 * 100 /* Max POST 100 kb */ ;
this.app.use(bodyParser.json({
@ -65,14 +75,10 @@ ExpressApp.prototype.start = function(opts, cb) {
} else {
var morgan = require('morgan');
morgan.token('walletId', function getId(req) {
return req.walletId
return req.walletId ? '<' + req.walletId + '>' : '<>';
});
morgan.token('copayerId', function getId(req) {
return req.copayerId
});
var logFormat = ':remote-addr :date[iso] ":method :url" :status :res[content-length] :response-time ":user-agent" :walletId :copayerId';
var logFormat = ':walletId :remote-addr :date[iso] ":method :url" :status :res[content-length] :response-time ":user-agent" ';
var logOpts = {
skip: function(req, res) {
if (res.statusCode != 200) return false;
@ -90,7 +96,7 @@ ExpressApp.prototype.start = function(opts, cb) {
var status = (err.code == 'NOT_AUTHORIZED') ? 401 : 400;
if (!opts.disableLogs)
log.info('Client Err: ' + status + ' ' + req.url + ' ' + err);
log.info('Client Err: ' + status + ' ' + req.url + ' ' + JSON.stringify(err));
res.status(status).json({
code: err.code,
@ -155,6 +161,7 @@ ExpressApp.prototype.start = function(opts, cb) {
message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body),
signature: credentials.signature,
clientVersion: req.header('x-client-version'),
walletId: req.header('x-wallet-id'),
};
if (opts.allowSession) {
auth.session = credentials.session;
@ -162,10 +169,15 @@ ExpressApp.prototype.start = function(opts, cb) {
WalletService.getInstanceWithAuth(auth, function(err, server) {
if (err) return returnError(err, res, req);
if (opts.onlySupportStaff && !server.copayerIsSupportStaff) {
return returnError(new WalletService.ClientError({
code: 'NOT_AUTHORIZED'
}), res, req);
}
// For logging
req.walletId = server.walletId;
req.copayerId = server.copayerId;
return cb(server);
});
};
@ -184,7 +196,6 @@ ExpressApp.prototype.start = function(opts, cb) {
}
// DEPRECATED
router.post('/v1/wallets/', createWalletLimiter, function(req, res) {
logDeprecated(req);
var server;
@ -290,6 +301,29 @@ ExpressApp.prototype.start = function(opts, cb) {
});
});
router.get('/v1/wallets/:identifier/', function(req, res) {
getServerWithAuth(req, res, {
onlySupportStaff: true
}, function(server) {
var opts = {
identifier: req.params['identifier'],
};
server.getWalletFromIdentifier(opts, function(err, wallet) {
if (err) return returnError(err, res, req);
if (!wallet) return res.end();
server.walletId = wallet.id;
var opts = {};
if (req.query.includeExtendedInfo == '1') opts.includeExtendedInfo = true;
if (req.query.twoStep == '1') opts.twoStep = true;
server.getStatus(opts, function(err, status) {
if (err) return returnError(err, res, req);
res.json(status);
});
});
});
});
router.get('/v1/preferences/', function(req, res) {
getServerWithAuth(req, res, function(server) {
server.getPreferences({}, function(err, preferences) {
@ -383,6 +417,7 @@ ExpressApp.prototype.start = function(opts, cb) {
router.get('/v1/balance/', function(req, res) {
getServerWithAuth(req, res, function(server) {
var opts = {};
if (req.query.coin) opts.coin = req.query.coin;
if (req.query.twoStep == '1') opts.twoStep = true;
server.getBalance(opts, function(err, balance) {
if (err) return returnError(err, res, req);
@ -391,8 +426,21 @@ ExpressApp.prototype.start = function(opts, cb) {
});
});
var estimateFeeLimiter;
if (Defaults.RateLimit.estimateFee && !opts.ignoreRateLimiter) {
log.info('', 'Limiting estimate fee per IP: %d req/h', (Defaults.RateLimit.estimateFee.max / Defaults.RateLimit.estimateFee.windowMs * 60 * 60 * 1000).toFixed(2))
estimateFeeLimiter = new RateLimit(Defaults.RateLimit.estimateFee);
// router.use(/\/v\d+\/wallets\/$/, createWalletLimiter)
} else {
estimateFeeLimiter = function(req, res, next) {
next()
};
}
// DEPRECATED
router.get('/v1/feelevels/', function(req, res) {
router.get('/v1/feelevels/', estimateFeeLimiter, function(req, res) {
logDeprecated(req);
var opts = {};
if (req.query.network) opts.network = req.query.network;
@ -412,9 +460,11 @@ ExpressApp.prototype.start = function(opts, cb) {
});
});
router.get('/v2/feelevels/', function(req, res) {
router.get('/v2/feelevels/', estimateFeeLimiter, function(req, res) {
var opts = {};
if (req.query.coin) opts.coin = req.query.coin;
if (req.query.network) opts.network = req.query.network;
var server;
try {
server = getServer(req, res);
@ -561,6 +611,7 @@ ExpressApp.prototype.start = function(opts, cb) {
router.get('/v1/stats/', function(req, res) {
var opts = {};
if (req.query.network) opts.network = req.query.network;
if (req.query.coin) opts.coin = req.query.coin;
if (req.query.from) opts.from = req.query.from;
if (req.query.to) opts.to = req.query.to;
@ -650,18 +701,22 @@ ExpressApp.prototype.start = function(opts, cb) {
});
router.get('/v1/fiatrates/:code/', function(req, res) {
getServerWithAuth(req, res, function(server) {
var server;
var opts = {
code: req.params['code'],
source: req.query.source,
provider: req.query.provider,
ts: +req.query.ts,
};
try {
server = getServer(req, res);
} catch (ex) {
return returnError(ex, res, req);
}
server.getFiatRate(opts, function(err, rates) {
if (err) return returnError(err, res, req);
res.json(rates);
});
});
});
router.post('/v1/pushnotifications/subscriptions/', function(req, res) {
getServerWithAuth(req, res, function(server) {
@ -697,6 +752,28 @@ ExpressApp.prototype.start = function(opts, cb) {
});
});
router.post('/v1/txconfirmations/', function(req, res) {
getServerWithAuth(req, res, function(server) {
server.txConfirmationSubscribe(req.body, function(err, response) {
if (err) return returnError(err, res, req);
res.json(response);
});
});
});
router.delete('/v1/txconfirmations/:txid', function(req, res) {
var opts = {
txid: req.params['txid'],
};
getServerWithAuth(req, res, function(server) {
server.txConfirmationUnsubscribe(opts, function(err, response) {
if (err) return returnError(err, res, req);
res.json(response);
});
});
});
this.app.use(opts.basePath || '/bws/api', router);
WalletService.initialize(opts, cb);

View File

@ -27,11 +27,13 @@ function Lock(opts) {
}
};
Lock.prototype.runLocked = function(token, cb, task) {
Lock.prototype.runLocked = function(token, cb, task, waitTime) {
$.shouldBeDefined(token);
this.lock.locked(token, 5 * 1000, 5 * 60 * 1000, function(err, release) {
if (err) return cb(Errors.WALLET_LOCKED);
waitTime = waitTime || 5 * 1000;
this.lock.locked(token, waitTime , 5 * 60 * 1000, function(err, release) {
if (err) return cb(Errors.WALLET_BUSY);
var _cb = function() {
cb.apply(null, arguments);
release();

View File

@ -3,8 +3,14 @@
var $ = require('preconditions').singleton();
var _ = require('lodash');
var Bitcore = require('bitcore-lib');
var Constants = require('../common/constants');
var Bitcore = {
'btc': require('bitcore-lib'),
'bch': require('bitcore-lib-cash'),
};
var Common = require('../common');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
function Address() {};
@ -13,6 +19,8 @@ Address.create = function(opts) {
var x = new Address();
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
x.version = '1.0.0';
x.createdOn = Math.floor(Date.now() / 1000);
x.address = opts.address;
@ -20,7 +28,8 @@ Address.create = function(opts) {
x.isChange = opts.isChange;
x.path = opts.path;
x.publicKeys = opts.publicKeys;
x.network = Bitcore.Address(x.address).toObject().network;
x.coin = opts.coin;
x.network = Bitcore[opts.coin].Address(x.address).toObject().network;
x.type = opts.type || Constants.SCRIPT_TYPES.P2SH;
x.hasActivity = undefined;
return x;
@ -33,6 +42,7 @@ Address.fromObj = function(obj) {
x.createdOn = obj.createdOn;
x.address = obj.address;
x.walletId = obj.walletId;
x.coin = obj.coin || Defaults.COIN;
x.network = obj.network;
x.isChange = obj.isChange;
x.path = obj.path;
@ -42,22 +52,22 @@ Address.fromObj = function(obj) {
return x;
};
Address._deriveAddress = function(scriptType, publicKeyRing, path, m, network) {
$.checkArgument(_.contains(_.values(Constants.SCRIPT_TYPES), scriptType));
Address._deriveAddress = function(scriptType, publicKeyRing, path, m, coin, network) {
$.checkArgument(Utils.checkValueInCollection(scriptType, Constants.SCRIPT_TYPES));
var publicKeys = _.map(publicKeyRing, function(item) {
var xpub = new Bitcore.HDPublicKey(item.xPubKey);
var xpub = new Bitcore[coin].HDPublicKey(item.xPubKey);
return xpub.deriveChild(path).publicKey;
});
var bitcoreAddress;
switch (scriptType) {
case Constants.SCRIPT_TYPES.P2SH:
bitcoreAddress = Bitcore.Address.createMultisig(publicKeys, m, network);
bitcoreAddress = Bitcore[coin].Address.createMultisig(publicKeys, m, network);
break;
case Constants.SCRIPT_TYPES.P2PKH:
$.checkState(_.isArray(publicKeys) && publicKeys.length == 1);
bitcoreAddress = Bitcore.Address.fromPublicKey(publicKeys[0], network);
bitcoreAddress = Bitcore[coin].Address.fromPublicKey(publicKeys[0], network);
break;
}
@ -68,9 +78,10 @@ Address._deriveAddress = function(scriptType, publicKeyRing, path, m, network) {
};
};
Address.derive = function(walletId, scriptType, publicKeyRing, path, m, network, isChange) {
var raw = Address._deriveAddress(scriptType, publicKeyRing, path, m, network);
Address.derive = function(walletId, scriptType, publicKeyRing, path, m, coin, network, isChange) {
var raw = Address._deriveAddress(scriptType, publicKeyRing, path, m, coin, network);
return Address.create(_.extend(raw, {
coin: coin,
walletId: walletId,
type: scriptType,
isChange: isChange,

View File

@ -1,8 +1,8 @@
var _ = require('lodash');
var $ = require('preconditions').singleton();
var Bitcore = require('bitcore-lib');
var Constants = require('../common/constants');
var Utils = require('../common/utils');
function AddressManager() {};
@ -13,7 +13,7 @@ AddressManager.create = function(opts) {
x.version = 2;
x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
$.checkState(_.contains(_.values(Constants.DERIVATION_STRATEGIES), x.derivationStrategy));
$.checkState(Utils.checkValueInCollection(x.derivationStrategy, Constants.DERIVATION_STRATEGIES));
x.receiveAddressIndex = 0;
x.changeAddressIndex = 0;
@ -55,6 +55,18 @@ AddressManager.prototype.rewindIndex = function(isChange, n) {
}
};
AddressManager.prototype.getCurrentIndex = function(isChange) {
return isChange ? this.changeAddressIndex : this.receiveAddressIndex;
};
AddressManager.prototype.getBaseAddressPath = function(isChange) {
return 'm/' +
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +
(isChange ? 1 : 0) + '/' +
0;
};
AddressManager.prototype.getCurrentAddressPath = function(isChange) {
return 'm/' +
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +

View File

@ -10,12 +10,16 @@ var Address = require('./address');
var AddressManager = require('./addressmanager');
var Bitcore = require('bitcore-lib');
var Constants = require('../common/constants');
var Common = require('../common');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
function Copayer() {};
Copayer._xPubToCopayerId = function(xpub) {
var hash = sjcl.hash.sha256.hash(xpub);
Copayer._xPubToCopayerId = function(coin, xpub) {
var str = coin == Defaults.COIN ? xpub : coin + xpub;
var hash = sjcl.hash.sha256.hash(str);
return sjcl.codec.hex.fromBits(hash);
};
@ -25,14 +29,17 @@ Copayer.create = function(opts) {
.checkArgument(opts.requestPubKey, 'Missing copayer request public key')
.checkArgument(opts.signature, 'Missing copayer request public key signature');
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
opts.copayerIndex = opts.copayerIndex || 0;
var x = new Copayer();
x.version = 2;
x.createdOn = Math.floor(Date.now() / 1000);
x.coin = opts.coin;
x.xPubKey = opts.xPubKey;
x.id = Copayer._xPubToCopayerId(x.xPubKey);
x.id = Copayer._xPubToCopayerId(opts.coin, x.xPubKey);
x.name = opts.name;
x.requestPubKey = opts.requestPubKey;
x.signature = opts.signature;
@ -59,6 +66,7 @@ Copayer.fromObj = function(obj) {
x.version = obj.version;
x.createdOn = obj.createdOn;
x.coin = obj.coin || Defaults.COIN;
x.id = obj.id;
x.name = obj.name;
x.xPubKey = obj.xPubKey;
@ -87,7 +95,7 @@ Copayer.prototype.createAddress = function(wallet, isChange) {
$.checkState(wallet.isComplete());
var path = this.addressManager.getNewAddressPath(isChange);
var address = Address.derive(wallet.id, wallet.addressType, wallet.publicKeyRing, path, wallet.m, wallet.network, isChange);
var address = Address.derive(wallet.id, wallet.addressType, wallet.publicKeyRing, path, wallet.m, wallet.coin, wallet.network, isChange);
return address;
};

View File

@ -10,5 +10,6 @@ Model.Email = require('./email');
Model.TxNote = require('./txnote');
Model.Session = require('./session');
Model.PushNotificationSub = require('./pushnotificationsub');
Model.TxConfirmationSub = require('./txconfirmationsub');
module.exports = Model;

View File

@ -0,0 +1,32 @@
'use strict';
function TxConfirmationSub() {};
TxConfirmationSub.create = function(opts) {
opts = opts || {};
var x = new TxConfirmationSub();
x.version = 1;
x.createdOn = Math.floor(Date.now() / 1000);
x.walletId = opts.walletId;
x.copayerId = opts.copayerId;
x.txid = opts.txid;
x.isActive = true;
return x;
};
TxConfirmationSub.fromObj = function(obj) {
var x = new TxConfirmationSub();
x.version = obj.version;
x.createdOn = obj.createdOn;
x.walletId = obj.walletId;
x.copayerId = obj.copayerId;
x.txid = obj.txid;
x.isActive = obj.isActive;
return x;
};
module.exports = TxConfirmationSub;

View File

@ -7,11 +7,15 @@ var log = require('npmlog');
log.debug = log.verbose;
log.disableColor();
var Bitcore = require('bitcore-lib');
var Bitcore = {
'btc': require('bitcore-lib'),
'bch': require('bitcore-lib-cash'),
};
var Common = require('../common');
var Constants = Common.Constants;
var Defaults = Common.Defaults;
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
var TxProposalLegacy = require('./txproposal_legacy');
var TxProposalAction = require('./txproposalaction');
@ -21,6 +25,9 @@ function TxProposal() {};
TxProposal.create = function(opts) {
opts = opts || {};
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
var x = new TxProposal();
x.version = 3;
@ -30,6 +37,8 @@ TxProposal.create = function(opts) {
x.id = opts.id || Uuid.v4();
x.walletId = opts.walletId;
x.creatorId = opts.creatorId;
x.coin = opts.coin;
x.network = opts.network;
x.message = opts.message;
x.payProUrl = opts.payProUrl;
x.changeAddress = opts.changeAddress;
@ -51,15 +60,11 @@ TxProposal.create = function(opts) {
x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos;
x.addressType = opts.addressType || (x.walletN > 1 ? Constants.SCRIPT_TYPES.P2SH : Constants.SCRIPT_TYPES.P2PKH);
$.checkState(_.contains(_.values(Constants.SCRIPT_TYPES), x.addressType));
$.checkState(Utils.checkValueInCollection(x.addressType, Constants.SCRIPT_TYPES));
x.customData = opts.customData;
x.amount = x.getTotalAmount();
try {
x.network = opts.network || Bitcore.Address(x.outputs[0].toAddress).toObject().network;
} catch (ex) {}
$.checkState(_.contains(_.values(Constants.NETWORKS), x.network));
x.setInputs(opts.inputs);
x.fee = opts.fee;
@ -79,6 +84,7 @@ TxProposal.fromObj = function(obj) {
x.id = obj.id;
x.walletId = obj.walletId;
x.creatorId = obj.creatorId;
x.coin = obj.coin || Defaults.COIN;
x.network = obj.network;
x.outputs = obj.outputs;
x.amount = obj.amount;
@ -136,9 +142,9 @@ TxProposal.prototype._updateStatus = function() {
TxProposal.prototype._buildTx = function() {
var self = this;
var t = new Bitcore.Transaction();
var t = new Bitcore[self.coin].Transaction();
$.checkState(_.contains(_.values(Constants.SCRIPT_TYPES), self.addressType));
$.checkState(Utils.checkValueInCollection(self.addressType, Constants.SCRIPT_TYPES));
switch (self.addressType) {
case Constants.SCRIPT_TYPES.P2SH:
@ -155,7 +161,7 @@ TxProposal.prototype._buildTx = function() {
_.each(self.outputs, function(o) {
$.checkState(o.script || o.toAddress, 'Output should have either toAddress or script specified');
if (o.script) {
t.addOutput(new Bitcore.Transaction.Output({
t.addOutput(new Bitcore[self.coin].Transaction.Output({
script: o.script,
satoshis: o.amount
}));
@ -220,10 +226,6 @@ TxProposal.prototype.getBitcoreTx = function() {
return t;
};
TxProposal.prototype.getNetworkName = function() {
return this.network;
};
TxProposal.prototype.getRawTx = function() {
var t = this.getBitcoreTx();
@ -323,21 +325,23 @@ TxProposal.prototype.addAction = function(copayerId, type, comment, signatures,
TxProposal.prototype._addSignaturesToBitcoreTx = function(tx, signatures, xpub) {
var self = this;
var bitcore = Bitcore[self.coin];
if (signatures.length != this.inputs.length)
throw new Error('Number of signatures does not match number of inputs');
var i = 0,
x = new Bitcore.HDPublicKey(xpub);
x = new bitcore.HDPublicKey(xpub);
_.each(signatures, function(signatureHex) {
var input = self.inputs[i];
try {
var signature = Bitcore.crypto.Signature.fromString(signatureHex);
var signature = bitcore.crypto.Signature.fromString(signatureHex);
var pub = x.deriveChild(self.inputPaths[i]).publicKey;
var s = {
inputIndex: i,
signature: signature,
sigtype: Bitcore.crypto.Signature.SIGHASH_ALL,
sigtype: bitcore.crypto.Signature.SIGHASH_ALL | bitcore.crypto.Signature.SIGHASH_FORKID,
publicKey: pub,
};
tx.inputs[i].addSignature(tx, s);

View File

@ -54,6 +54,7 @@ TxProposal.fromObj = function(obj) {
return TxProposalAction.fromObj(action);
});
x.outputOrder = obj.outputOrder;
x.coin = obj.coin || Defaults.COIN;
x.network = obj.network;
x.fee = obj.fee;
x.feePerKb = obj.feePerKb;
@ -93,10 +94,6 @@ TxProposal.prototype.getBitcoreTx = function() {
throwUnsupportedError();
};
TxProposal.prototype.getNetworkName = function() {
return Bitcore.Address(this.changeAddress.address).toObject().network;
};
TxProposal.prototype.getRawTx = function() {
throwUnsupportedError();
};

View File

@ -2,6 +2,7 @@
var _ = require('lodash');
var util = require('util');
var log = require('npmlog');
var $ = require('preconditions').singleton();
var Uuid = require('uuid');
@ -9,7 +10,10 @@ var Address = require('./address');
var Copayer = require('./copayer');
var AddressManager = require('./addressmanager');
var Constants = require('../common/constants');
var Common = require('../common');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
function Wallet() {};
@ -20,6 +24,8 @@ Wallet.create = function(opts) {
$.shouldBeNumber(opts.m);
$.shouldBeNumber(opts.n);
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
x.version = '1.0.0';
x.createdOn = Math.floor(Date.now() / 1000);
@ -33,6 +39,7 @@ Wallet.create = function(opts) {
x.addressIndex = 0;
x.copayers = [];
x.pubKey = opts.pubKey;
x.coin = opts.coin;
x.network = opts.network;
x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
x.addressType = opts.addressType || Constants.SCRIPT_TYPES.P2SH;
@ -64,6 +71,7 @@ Wallet.fromObj = function(obj) {
return Copayer.fromObj(copayer);
});
x.pubKey = obj.pubKey;
x.coin = obj.coin || Defaults.COIN;
x.network = obj.network;
x.derivationStrategy = obj.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
x.addressType = obj.addressType || Constants.SCRIPT_TYPES.P2SH;
@ -105,6 +113,7 @@ Wallet.prototype._updatePublicKeyRing = function() {
};
Wallet.prototype.addCopayer = function(copayer) {
$.checkState(copayer.coin == this.coin);
this.copayers.push(copayer);
if (this.copayers.length < this.n) return;
@ -134,10 +143,6 @@ Wallet.prototype.getCopayer = function(copayerId) {
});
};
Wallet.prototype.getNetworkName = function() {
return this.network;
};
Wallet.prototype.isComplete = function() {
return this.status == 'complete';
};
@ -152,7 +157,8 @@ Wallet.prototype.createAddress = function(isChange) {
var self = this;
var path = this.addressManager.getNewAddressPath(isChange);
var address = Address.derive(self.id, this.addressType, this.publicKeyRing, path, this.m, this.network, isChange);
log.verbose('Deriving addr:' + path);
var address = Address.derive(self.id, this.addressType, this.publicKeyRing, path, this.m, this.coin, this.network, isChange);
return address;
};

View File

@ -9,6 +9,7 @@ var Storage = require('./storage');
var fs = require('fs');
var path = require('path');
var Utils = require('./common/utils');
var Defaults = require('./common/defaults');
var Model = require('./model');
var sjcl = require('sjcl');
var log = require('npmlog');
@ -33,6 +34,10 @@ var PUSHNOTIFICATIONS_TYPES = {
'TxProposalFinallyRejected': {
filename: 'txp_finally_rejected',
},
'TxConfirmation': {
filename: 'tx_confirmation',
notifyCreatorOnly: true,
},
};
function PushNotificationsService() {};
@ -111,7 +116,7 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio
log.debug('Should send notification: ', should);
if (!should) return cb();
self._getRecipientsList(notification, function(err, recipientsList) {
self._getRecipientsList(notification, notifType, function(err, recipientsList) {
if (err) return cb(err);
async.waterfall([
@ -184,16 +189,21 @@ PushNotificationsService.prototype._checkShouldSendNotif = function(notification
if (notification.type != 'NewTxProposal') return cb(null, true);
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
return cb(err, wallet.m > 1);
return cb(err, wallet && wallet.m > 1);
});
};
PushNotificationsService.prototype._getRecipientsList = function(notification, cb) {
PushNotificationsService.prototype._getRecipientsList = function(notification, notificationType, cb) {
var self = this;
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
if (err) return cb(err);
var unit;
if (wallet.coin != Defaults.COIN) {
unit = wallet.coin;
}
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
if (err) log.error(err);
@ -210,22 +220,23 @@ PushNotificationsService.prototype._getRecipientsList = function(notification, c
return {
copayerId: p.copayerId,
language: p.language,
unit: p.unit,
unit: unit || p.unit || self.defaultUnit,
};
}));
recipientPreferences = _.indexBy(recipientPreferences, 'copayerId');
var recipientsList = _.reject(_.map(wallet.copayers, function(copayer) {
var recipientsList = _.compact(_.map(wallet.copayers, function(copayer) {
if ((copayer.id == notification.creatorId && notificationType.notifyCreatorOnly) ||
(copayer.id != notification.creatorId && !notificationType.notifyCreatorOnly)) {
var p = recipientPreferences[copayer.id] || {};
return {
copayerId: copayer.id,
language: p.language || self.defaultLanguage,
unit: p.unit || self.defaultUnit,
unit: unit || p.unit || self.defaultUnit,
}
}), {
copayerId: notification.creatorId
});
}
}));
return cb(null, recipientsList);
});
@ -270,7 +281,8 @@ PushNotificationsService.prototype._getDataForTemplate = function(notification,
var self = this;
var UNIT_LABELS = {
btc: 'BTC',
bit: 'bits'
bit: 'bits',
bch: 'BCH',
};
var data = _.cloneDeep(notification.data);
@ -285,7 +297,7 @@ PushNotificationsService.prototype._getDataForTemplate = function(notification,
}
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
if (err) return cb(err);
if (err || !wallet) return cb(err);
data.walletId = wallet.id;
data.walletName = wallet.name;

File diff suppressed because it is too large Load Diff

View File

@ -15,12 +15,13 @@ var config = require('../config');
var storage = require('./storage');
var INITIAL_DATE = '2015-01-01';
var INITIAL_DATE = '2018-01-01';
function Stats(opts) {
opts = opts || {};
this.network = opts.network || 'livenet';
this.coin = opts.coin || 'btc';
this.from = moment(opts.from || INITIAL_DATE);
this.to = moment(opts.to);
this.fromTs = this.from.startOf('day').valueOf();
@ -70,7 +71,7 @@ Stats.prototype._getNewWallets = function(cb) {
function getLastDate(cb) {
self.db.collection('stats_wallets')
.find({})
.find({'_id.coin': self.coin})
.sort({
'_id.day': -1
})
@ -91,6 +92,7 @@ Stats.prototype._getNewWallets = function(cb) {
var key = {
day: +day,
network: this.network,
coin: this.coin
};
var value = {
count: 1
@ -127,6 +129,7 @@ Stats.prototype._getNewWallets = function(cb) {
self.db.collection('stats_wallets')
.find({
'_id.network': self.network,
'_id.coin': self.coin,
'_id.day': {
$gte: self.fromTs,
$lte: self.toTs,
@ -142,6 +145,7 @@ Stats.prototype._getNewWallets = function(cb) {
var day = moment(record._id.day).format('YYYYMMDD');
return {
day: day,
coin: record._id.coin,
count: record.value.count,
};
});
@ -181,7 +185,7 @@ Stats.prototype._getTxProposals = function(cb) {
function getLastDate(cb) {
self.db.collection('stats_txps')
.find({})
.find({'_id.coin': self.coin })
.sort({
'_id.day': -1
})
@ -202,6 +206,7 @@ Stats.prototype._getTxProposals = function(cb) {
var key = {
day: +day,
network: this.network,
coin: this.coin
};
var value = {
count: 1,
@ -243,6 +248,7 @@ Stats.prototype._getTxProposals = function(cb) {
self.db.collection('stats_txps')
.find({
'_id.network': self.network,
'_id.coin': self.coin,
'_id.day': {
$gte: self.fromTs,
$lte: self.toTs,
@ -262,6 +268,7 @@ Stats.prototype._getTxProposals = function(cb) {
var day = moment(record._id.day).format('YYYYMMDD');
stats.nbByDay.push({
day: day,
coin: record._id.coin,
count: record.value.count,
});
stats.amountByDay.push({

View File

@ -7,7 +7,7 @@ var log = require('npmlog');
log.debug = log.verbose;
log.disableColor();
var util = require('util');
var Bitcore = require('bitcore-lib');
var mongodb = require('mongodb');
var Model = require('./model');
@ -25,6 +25,7 @@ var collections = {
TX_NOTES: 'tx_notes',
SESSIONS: 'sessions',
PUSH_NOTIFICATION_SUBS: 'push_notification_subs',
TX_CONFIRMATION_SUBS: 'tx_confirmation_subs',
};
var Storage = function(opts) {
@ -39,6 +40,9 @@ Storage.prototype._createIndexes = function() {
this.db.collection(collections.COPAYERS_LOOKUP).createIndex({
copayerId: 1
});
this.db.collection(collections.COPAYERS_LOOKUP).createIndex({
walletId: 1
});
this.db.collection(collections.TXS).createIndex({
walletId: 1,
id: 1,
@ -52,6 +56,9 @@ Storage.prototype._createIndexes = function() {
walletId: 1,
createdOn: -1,
});
this.db.collection(collections.TXS).createIndex({
txid: 1
});
this.db.collection(collections.NOTIFICATIONS).createIndex({
walletId: 1,
id: 1,
@ -63,6 +70,13 @@ Storage.prototype._createIndexes = function() {
this.db.collection(collections.ADDRESSES).createIndex({
address: 1,
});
this.db.collection(collections.ADDRESSES).createIndex({
walletId: 1,
address: 1,
});
this.db.collection(collections.EMAIL_QUEUE).createIndex({
id: 1,
});
this.db.collection(collections.EMAIL_QUEUE).createIndex({
notificationId: 1,
});
@ -75,9 +89,24 @@ Storage.prototype._createIndexes = function() {
walletId: 1,
txid: 1,
});
this.db.collection(collections.PREFERENCES).createIndex({
walletId: 1
});
this.db.collection(collections.FIAT_RATES).createIndex({
provider: 1,
code: 1,
ts: 1
});
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).createIndex({
copayerId: 1,
});
this.db.collection(collections.TX_CONFIRMATION_SUBS).createIndex({
copayerId: 1,
txid: 1,
});
this.db.collection(collections.SESSIONS).createIndex({
copayerId: 1
});
};
Storage.prototype.connect = function(opts, cb) {
@ -111,6 +140,8 @@ Storage.prototype.disconnect = function(cb) {
};
Storage.prototype.fetchWallet = function(id, cb) {
if (!this.db) return cb('not ready');
this.db.collection(collections.WALLETS).findOne({
id: id
}, function(err, result) {
@ -199,6 +230,7 @@ Storage.prototype._completeTxData = function(walletId, txs, cb) {
// TODO: remove walletId from signature
Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
var self = this;
if (!this.db) return cb();
this.db.collection(collections.TXS).findOne({
id: txProposalId,
@ -212,6 +244,7 @@ Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
Storage.prototype.fetchTxByHash = function(hash, cb) {
var self = this;
if (!this.db) return cb();
this.db.collection(collections.TXS).findOne({
txid: hash,
@ -443,6 +476,28 @@ Storage.prototype.fetchAddresses = function(walletId, cb) {
});
};
Storage.prototype.fetchNewAddresses = function(walletId, fromTs, cb) {
var self = this;
this.db.collection(collections.ADDRESSES).find({
walletId: walletId,
createdOn: {
$gte: fromTs,
},
}).sort({
createdOn: 1
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var addresses = _.map(result, function(address) {
return Model.Address.fromObj(address);
});
return cb(null, addresses);
});
};
Storage.prototype.countAddresses = function(walletId, cb) {
this.db.collection(collections.ADDRESSES).find({
walletId: walletId,
@ -453,6 +508,7 @@ Storage.prototype.storeAddress = function(address, cb) {
var self = this;
self.db.collection(collections.ADDRESSES).update({
walletId: address.walletId,
address: address.address
}, address, {
w: 1,
@ -464,37 +520,21 @@ Storage.prototype.storeAddressAndWallet = function(wallet, addresses, cb) {
var self = this;
var addresses = [].concat(addresses);
if (addresses.length == 0) return cb();
if (_.isEmpty(addresses)) return cb();
async.filter(addresses, function(address, next) {
self.db.collection(collections.ADDRESSES).findOne({
address: address.address,
}, {
walletId: true,
}, function(err, result) {
if (err || !result) return next(true);
if (result.walletId != wallet.id) {
log.warn('Address ' + address.address + ' exists in more than one wallet.');
return next(true);
}
// Ignore if address was already in wallet
return next(false);
});
}, function(newAddresses) {
if (newAddresses.length == 0) return cb();
self.db.collection(collections.ADDRESSES).insert(newAddresses, {
self.db.collection(collections.ADDRESSES).insert(addresses, {
w: 1
}, function(err) {
if (err) return cb(err);
self.storeWallet(wallet, cb);
});
});
};
Storage.prototype.fetchAddress = function(address, cb) {
Storage.prototype.fetchAddressByWalletId = function(walletId, address, cb) {
var self = this;
this.db.collection(collections.ADDRESSES).findOne({
walletId: walletId,
address: address,
}, function(err, result) {
if (err) return cb(err);
@ -504,6 +544,28 @@ Storage.prototype.fetchAddress = function(address, cb) {
});
};
Storage.prototype.fetchAddressByCoin = function(coin, address, cb) {
var self = this;
if (!this.db) return cb();
this.db.collection(collections.ADDRESSES).find({
address: address,
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result || _.isEmpty(result)) return cb();
if (result.length > 1) {
result = _.find(result, function(address) {
return coin == (address.coin || 'btc');
});
} else {
result = _.first(result);
}
if (!result) return cb();
return cb(null, Model.Address.fromObj(result));
});
};
Storage.prototype.fetchPreferences = function(walletId, copayerId, cb) {
this.db.collection(collections.PREFERENCES).find({
walletId: walletId,
@ -567,51 +629,91 @@ Storage.prototype.fetchEmailByNotification = function(notificationId, cb) {
});
};
Storage.prototype.cleanActiveAddresses = function(walletId, cb) {
Storage.prototype.storeTwoStepCache = function(walletId, cacheStatus, cb) {
var self = this;
async.series([
function(next) {
self.db.collection(collections.CACHE).remove({
walletId: walletId,
type: 'activeAddresses',
}, {
w: 1
}, next);
},
function(next) {
self.db.collection(collections.CACHE).insert({
walletId: walletId,
type: 'activeAddresses',
key: null
}, {
w: 1
}, next);
},
], cb);
};
Storage.prototype.storeActiveAddresses = function(walletId, addresses, cb) {
var self = this;
async.each(addresses, function(address, next) {
var record = {
walletId: walletId,
type: 'activeAddresses',
key: address,
};
self.db.collection(collections.CACHE).update( {
walletId: record.walletId,
type: record.type,
key: record.key,
}, record, {
walletId: walletId,
type: 'twoStep',
key: null,
}, {
"$set":
{
addressCount: cacheStatus.addressCount,
lastEmpty: cacheStatus.lastEmpty,
}
}, {
w: 1,
upsert: true,
}, next);
}, cb);
};
Storage.prototype.getTwoStepCache = function(walletId, cb) {
var self = this;
self.db.collection(collections.CACHE).findOne({
walletId: walletId,
type: 'twoStep',
key: null
}, function(err, result) {
if (err) return cb(err);
if (!result) return cb();
return cb(null, result);
});
};
Storage.prototype.storeAddressesWithBalance = function(walletId, addresses, cb) {
var self = this;
if (_.isEmpty(addresses))
addresses = [];
self.db.collection(collections.CACHE).update({
walletId: walletId,
type: 'addressesWithBalance',
key: null,
}, {
"$set":
{
addresses: addresses,
}
}, {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype.fetchAddressesWithBalance = function(walletId, cb) {
var self = this;
self.db.collection(collections.CACHE).findOne({
walletId: walletId,
type: 'addressesWithBalance',
key: null,
}, function(err, result) {
if (err) return cb(err);
if (_.isEmpty(result)) return cb(null, []);
self.db.collection(collections.ADDRESSES).find({
walletId: walletId,
address: { $in: result.addresses },
}).toArray(function(err, result2) {
if (err) return cb(err);
if (!result2) return cb(null, []);
var addresses = _.map(result2, function(address) {
return Model.Address.fromObj(address);
});
return cb(null, addresses);
});
});
};
// -------- --------------------------- Total
// > Time >
// ^to <= ^from
@ -662,7 +764,7 @@ Storage.prototype.getTxHistoryCache = function(walletId, from, to, cb) {
return cb();
}
var txs = _.pluck(result, 'tx');
var txs = _.map(result, 'tx');
return cb(null, txs);
});
})
@ -764,23 +866,6 @@ Storage.prototype.storeTxHistoryCache = function(walletId, totalItems, firstPosi
};
Storage.prototype.fetchActiveAddresses = function(walletId, cb) {
var self = this;
self.db.collection(collections.CACHE).find({
walletId: walletId,
type: 'activeAddresses',
}).toArray(function(err, result) {
if (err) return cb(err);
if (_.isEmpty(result)) return cb();
return cb(null, _.compact(_.pluck(result, 'key')));
});
};
Storage.prototype.storeFiatRate = function(providerName, rates, cb) {
var self = this;
@ -928,6 +1013,45 @@ Storage.prototype.removePushNotificationSub = function(copayerId, token, cb) {
}, cb);
};
Storage.prototype.fetchActiveTxConfirmationSubs = function(copayerId, cb) {
var filter = {
isActive: true
};
if (copayerId) filter.copayerId = copayerId;
this.db.collection(collections.TX_CONFIRMATION_SUBS).find(filter)
.toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var subs = _.map([].concat(result), function(r) {
return Model.TxConfirmationSub.fromObj(r);
});
return cb(null, subs);
});
};
Storage.prototype.storeTxConfirmationSub = function(txConfirmationSub, cb) {
this.db.collection(collections.TX_CONFIRMATION_SUBS).update({
copayerId: txConfirmationSub.copayerId,
txid: txConfirmationSub.txid,
}, txConfirmationSub, {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype.removeTxConfirmationSub = function(copayerId, txid, cb) {
this.db.collection(collections.TX_CONFIRMATION_SUBS).remove({
copayerId: copayerId,
txid: txid,
}, {
w: 1
}, cb);
};
Storage.prototype._dump = function(cb, fn) {
fn = fn || console.log;
cb = cb || function() {};
@ -946,5 +1070,140 @@ Storage.prototype._dump = function(cb, fn) {
});
};
Storage.prototype.fetchAddressIndexCache = function (walletId, key, cb) {
this.db.collection(collections.CACHE).findOne({
walletId: walletId,
type: 'addressIndexCache',
key: key,
}, function(err, ret) {
if (err) return cb(err);
if (!ret) return cb();
cb(null, ret.index);
});
}
Storage.prototype.storeAddressIndexCache = function (walletId, key, index, cb) {
this.db.collection(collections.CACHE).update({
walletId: walletId,
type: 'addressIndexCache',
key: key,
}, {
"$set":
{
index: index,
}
}, {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype._addressHash = function(addresses) {
var all = addresses.join();
return Bitcore.crypto.Hash.ripemd160(new Buffer(all)).toString('hex');
};
Storage.prototype.checkAndUseBalanceCache = function(walletId, addresses, duration, cb) {
var self = this;
var key = self._addressHash(addresses);
var now = Date.now();
self.db.collection(collections.CACHE).findOne({
walletId: walletId || key,
type: 'balanceCache',
key: key,
}, function(err, ret) {
if (err) return cb(err);
if (!ret) return cb();
var validFor = ret.ts + duration * 1000 - now;
if (validFor > 0) {
log.debug('','Using Balance Cache valid for %d ms more', validFor);
cb(null, ret.result);
return true;
}
cb();
log.debug('','Balance cache expired, deleting');
self.db.collection(collections.CACHE).remove({
walletId: walletId,
type: 'balanceCache',
key: key,
}, {}, function() {});
return false;
});
};
Storage.prototype.storeBalanceCache = function (walletId, addresses, balance, cb) {
var key = this._addressHash(addresses);
var now = Date.now();
this.db.collection(collections.CACHE).update({
walletId: walletId || key,
type: 'balanceCache',
key: key,
}, {
"$set":
{
ts: now,
result: balance,
}
}, {
w: 1,
upsert: true,
}, cb);
};
// FEE_LEVEL_DURATION = 5min
var FEE_LEVEL_DURATION = 5 * 60 * 1000;
Storage.prototype.checkAndUseFeeLevelsCache = function(opts, cb) {
var self = this;
var key = JSON.stringify(opts);
var now = Date.now();
self.db.collection(collections.CACHE).findOne({
walletId: null,
type: 'feeLevels',
key: key,
}, function(err, ret) {
if (err) return cb(err);
if (!ret) return cb();
var validFor = ret.ts + FEE_LEVEL_DURATION - now;
return cb(null, validFor > 0 ? ret.result : null);
});
};
Storage.prototype.storeFeeLevelsCache = function (opts, values, cb) {
var key = JSON.stringify(opts);
var now = Date.now();
this.db.collection(collections.CACHE).update({
walletId: null,
type: 'feeLevels',
key: key,
}, {
"$set":
{
ts: now,
result: values,
}
}, {
w: 1,
upsert: true,
}, cb);
};
Storage.collections = collections;
module.exports = Storage;

View File

@ -0,0 +1,2 @@
{{subjectPrefix}} Transaction confirmed
The transaction you were waiting for has been confirmed.

View File

@ -0,0 +1,2 @@
{{subjectPrefix}} Transacción confirmada
La transacción que estabas esperando se ha confirmado.

View File

@ -2,7 +2,7 @@
"name": "bitcore-wallet-service",
"description": "A service for Mutisig HD Bitcoin Wallets",
"author": "BitPay Inc",
"version": "1.15.0",
"version": "2.4.0",
"licence": "MIT",
"keywords": [
"bitcoin",
@ -13,15 +13,16 @@
"BWS"
],
"repository": {
"url": "git@github.com:bitpay/bitcore-wallet-service.git",
"url": "git@github.com:BTCPrivate/bitcore-wallet-service.git",
"type": "git"
},
"bugs": {
"url": "https://github.com/bitpay/bitcore-wallet-service/issues"
"url": "https://github.com/BTCPrivate/bitcore-wallet-service/issues"
},
"dependencies": {
"async": "^0.9.2",
"bitcore-lib": "^0.14.0",
"bitcore-lib": "ch4ot1c/bitcore-lib",
"bitcore-lib-cash": "^0.17.0",
"body-parser": "^1.11.0",
"compression": "^1.6.2",
"coveralls": "^2.11.2",
@ -54,13 +55,13 @@
"devDependencies": {
"chai": "^1.9.1",
"istanbul": "*",
"jsdoc": "^3.3.0-beta1",
"jsdoc": "^3.5.5",
"memdown": "^1.0.0",
"mocha": "^1.18.2",
"proxyquire": "^1.7.2",
"sinon": "1.10.3",
"supertest": "*",
"tingodb": "^0.3.4"
"tingodb": "^0.5.1"
},
"scripts": {
"start": "./start.sh",
@ -70,14 +71,18 @@
"coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
},
"bitcoreNode": "./bitcorenode",
"contributors": [{
"contributors": [
{
"name": "Braydon Fuller",
"email": "braydon@bitpay.com"
}, {
},
{
"name": "Ivan Socolsky",
"email": "ivan@bitpay.com"
}, {
},
{
"name": "Matias Alejo Garcia",
"email": "ematiu@gmail.com"
}]
}
]
}

View File

@ -0,0 +1,18 @@
// json support
a='f42c8c47-0f7e-4cb0-9056-6c50bb821d7d';
b= {'walletId':a};
db.addresses.remove(b);
db.cache.remove(b);
db.copayers_lookup.remove(b);
db.notifications.remove(b);
db.preferences.remove(b);
db.sessions.remove(b);
db.tx_confirmation_subs.remove(b);
db.txs.remove(b);
db.tx_notes.remove(b);
db.wallets.remove({'id':a});
db.push_notification_subs.remove(b);
db.email_queue.remove(b);

View File

@ -13,7 +13,6 @@ log.level = 'info';
var WalletService = require('../../lib/server');
var BlockchainMonitor = require('../../lib/blockchainmonitor');
var TestData = require('../testdata');
var helpers = require('./helpers');
var storage, blockchainExplorer;
@ -49,8 +48,10 @@ describe('Blockchain monitor', function() {
messageBroker: server.messageBroker,
storage: storage,
blockchainExplorers: {
'btc': {
'testnet': blockchainExplorer,
'livenet': blockchainExplorer
}
},
}, function(err) {
should.not.exist(err);
@ -87,4 +88,178 @@ describe('Blockchain monitor', function() {
}, 100);
});
});
it('should update addressWithBalance cache on 1 incoming tx', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123',
vout: [{}],
};
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
_.isEmpty(ret).should.equal(true);
incoming.vout[0][address.address] = 1500;
socket.handlers['tx'](incoming);
setTimeout(function() {
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
ret.length.should.equal(1);
ret[0].address.should.equal(address.address);
done();
});
}, 100);
});
});
});
it('should update addressWithBalance cache on 2 incoming tx, same address', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
_.isEmpty(ret).should.equal(true);
var incoming = {
txid: '123',
vout: [{}],
};
incoming.vout[0][address.address] = 1500;
socket.handlers['tx'](incoming);
setTimeout(function() {
var incoming2 = {
txid: '456',
vout: [{}],
};
incoming2.vout[0][address.address] = 2500;
socket.handlers['tx'](incoming2);
setTimeout(function() {
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
ret.length.should.equal(1);
ret[0].address.should.equal(address.address);
done();
});
}, 100);
}, 100);
});
});
});
it('should update addressWithBalance cache on 2 incoming tx, different address', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
server.createAddress({}, function(err, address2) {
should.not.exist(err);
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
_.isEmpty(ret).should.equal(true);
var incoming = {
txid: '123',
vout: [{}],
};
incoming.vout[0][address.address] = 1500;
socket.handlers['tx'](incoming);
setTimeout(function() {
var incoming2 = {
txid: '456',
vout: [{}],
};
incoming2.vout[0][address2.address] = 500;
socket.handlers['tx'](incoming2);
setTimeout(function() {
server.storage.fetchAddressesWithBalance(wallet.id, function(err,ret) {
should.not.exist(err);
ret.length.should.equal(2);
ret[0].address.should.equal(address.address);
done();
});
}, 100);
}, 100);
});
});
});
});
it('should not notify copayers of incoming txs more than once', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123',
vout: [{}],
};
incoming.vout[0][address.address] = 1500;
socket.handlers['tx'](incoming);
setTimeout(function() {
socket.handlers['tx'](incoming);
setTimeout(function() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notification = _.filter(notifications, {
type: 'NewIncomingTx'
});
notification.length.should.equal(1);
done();
});
}, 100);
}, 50);
});
});
it('should notify copayers of tx confirmation', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123',
vout: [{}],
};
incoming.vout[0][address.address] = 1500;
server.txConfirmationSubscribe({
txid: '123'
}, function(err) {
should.not.exist(err);
blockchainExplorer.getTxidsInBlock = sinon.stub().callsArgWith(1, null, ['123', '456']);
socket.handlers['block']('block1');
setTimeout(function() {
blockchainExplorer.getTxidsInBlock = sinon.stub().callsArgWith(1, null, ['123', '456']);
socket.handlers['block']('block2');
setTimeout(function() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notifications = _.filter(notifications, {
type: 'TxConfirmation'
});
notifications.length.should.equal(1);
var n = notifications[0];
n.walletId.should.equal(wallet.id);
n.creatorId.should.equal(server.copayerId);
n.data.txid.should.equal('123');
done();
});
}, 50);
}, 50);
});
});
});
});

View File

@ -169,7 +169,7 @@ describe('Email notifications', function() {
txp = t;
async.eachSeries(_.range(2), function(i, next) {
var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44, function(server) {
helpers.getAuthServer(copayer.id44btc, function(server) {
var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H);
server.signTx({
txProposalId: txp.id,
@ -235,7 +235,7 @@ describe('Email notifications', function() {
txpId = txp.id;
async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44, function(server) {
helpers.getAuthServer(copayer.id44btc, function(server) {
server.rejectTx({
txProposalId: txp.id,
}, next);
@ -295,6 +295,38 @@ describe('Email notifications', function() {
});
});
it('should notify copayers when tx is confirmed if they are subscribed', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
server.txConfirmationSubscribe({
txid: '123'
}, function(err) {
should.not.exist(err);
// Simulate tx confirmation notification
server._notify('TxConfirmation', {
txid: '123',
}, function(err) {
setTimeout(function() {
var calls = mailerStub.sendMail.getCalls();
calls.length.should.equal(1);
var email = calls[0].args[0];
email.to.should.equal('copayer1@domain.com');
email.from.should.equal('bws@dummy.net');
email.subject.should.contain('Transaction confirmed');
server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err);
unsent.should.be.empty;
done();
});
}, 100);
});
});
});
});
it('should notify each email address only once', function(done) {
// Set same email address for copayer1 and copayer2
server.savePreferences({

View File

@ -13,6 +13,10 @@ var tingodb = require('tingodb')({
});
var Bitcore = require('bitcore-lib');
var Bitcore_ = {
btc: Bitcore,
bch: require('bitcore-lib-cash')
};
var Common = require('../../lib/common');
var Utils = Common.Utils;
@ -95,6 +99,7 @@ helpers.signRequestPubKey = function(requestPubKey, xPrivKey) {
helpers.getAuthServer = function(copayerId, cb) {
var verifyStub = sinon.stub(WalletService.prototype, '_verifySignature');
verifyStub.returns(true);
WalletService.getInstanceWithAuth({
copayerId: copayerId,
message: 'dummy',
@ -107,26 +112,40 @@ helpers.getAuthServer = function(copayerId, cb) {
});
};
helpers._generateCopayersTestData = function(n) {
helpers._generateCopayersTestData = function() {
var xPrivKeys = ['xprv9s21ZrQH143K2n4rV4AtAJFptEmd1tNMKCcSyQBCSuN5eq1dCUhcv6KQJS49joRxu8NNdFxy8yuwTtzCPNYUZvVGC7EPRm2st2cvE7oyTbB',
'xprv9s21ZrQH143K3BwkLceWNLUsgES15JoZuv8BZfnmDRcCGtDooUAPhY8KovhCWcRLXUun5AYL5vVtUNRrmPEibtfk9ongxAGLXZzEHifpvwZ',
'xprv9s21ZrQH143K3xgLzxd6SuWqG5Zp1iUmyGgSsJVhdQNeTzAqBFvXXLZqZzFZqocTx4HD9vUVYU27At5i8q46LmBXXL97fo4H9C3tHm4BnjY',
'xprv9s21ZrQH143K48nfuK14gKJtML7eQzV2dAH1RaqAMj8v2zs79uaavA9UTWMxpBdgbMH2mhJLeKGq8AFA6GDnFyWP4rLmknqZAfgFFV718vo',
'xprv9s21ZrQH143K44Bb9G3EVNmLfAUKjTBAA2YtKxF4zc8SLV1o15JBoddhGHE9PGLXePMbEsSjCCvTvP3fUv6yMXZrnHigBboRBn2DmNoJkJg',
'xprv9s21ZrQH143K48PpVxrh71KdViTFhAaiDSVtNFkmbWNYjwwwPbTrcqoVXsgBfue3Gq9b71hQeEbk67JgtTBcpYgKLF8pTwVnGz56f1BaCYt',
'xprv9s21ZrQH143K3pgRcRBRnmcxNkNNLmJrpneMkEXY6o5TWBuJLMfdRpAWdb2cG3yxbL4DxfpUnQpjfQUmwPdVrRGoDJmtAf5u8cyqKCoDV97',
'xprv9s21ZrQH143K3nvcmdjDDDZbDJHpfWZCUiunwraZdcamYcafHvUnZfV51fivH9FPyfo12NyKH5JDxGLsQePyWKtTiJx3pkEaiwxsMLkVapp',
'xprv9s21ZrQH143K2uYgqtYtphEQkFAgiWSqahFUWjgCdKykJagiNDz6Lf7xRVQdtZ7MvkhX9V3pEcK3xTAWZ6Y6ecJqrXnCpzrH9GSHn8wyrT5',
'xprv9s21ZrQH143K2wcRMP75tAEL5JnUx4xU2AbUBQzVVUDP7DHZJkjF3kaRE7tcnPLLLL9PGjYTWTJmCQPaQ4GGzgWEUFJ6snwJG9YnQHBFRNR'
];
console.log('var copayers = [');
_.each(_.range(n), function(c) {
var xpriv = new Bitcore.HDPrivateKey();
_.each(xPrivKeys, function(xPrivKeyStr, c) {
var xpriv = Bitcore.HDPrivateKey(xPrivKeyStr);
var xpub = Bitcore.HDPublicKey(xpriv);
var xpriv_45H = xpriv.deriveChild(45, true);
var xpub_45H = Bitcore.HDPublicKey(xpriv_45H);
var id45 = Copayer._xPubToCopayerId(xpub_45H.toString());
var id45 = Model.Copayer._xPubToCopayerId('btc', xpub_45H.toString());
var xpriv_44H_0H_0H = xpriv.deriveChild(44, true).deriveChild(0, true).deriveChild(0, true);
var xpub_44H_0H_0H = Bitcore.HDPublicKey(xpriv_44H_0H_0H);
var id44 = Copayer._xPubToCopayerId(xpub_44H_0H_0H.toString());
var id44btc = Model.Copayer._xPubToCopayerId('btc', xpub_44H_0H_0H.toString());
var id44bch = Model.Copayer._xPubToCopayerId('bch', xpub_44H_0H_0H.toString());
var xpriv_1H = xpriv.deriveChild(1, true);
var xpub_1H = Bitcore.HDPublicKey(xpriv_1H);
var priv = xpriv_1H.deriveChild(0).privateKey;
var pub = xpub_1H.deriveChild(0).publicKey;
console.log('{id44: ', "'" + id44 + "',");
console.log('{id44btc: ', "'" + id44btc + "',");
console.log('id44bch: ', "'" + id44bch + "',");
console.log('id45: ', "'" + id45 + "',");
console.log('xPrivKey: ', "'" + xpriv.toString() + "',");
console.log('xPubKey: ', "'" + xpub.toString() + "',");
@ -165,6 +184,8 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
n: n,
pubKey: TestData.keyPair.pub,
singleAddress: !!opts.singleAddress,
coin: opts.coin || 'btc',
network: opts.network || 'livenet',
};
if (_.isBoolean(opts.supportBIP44AndP2PKH))
walletOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH;
@ -174,10 +195,18 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
async.each(_.range(n), function(i, cb) {
var copayerData = TestData.copayers[i + offset];
var pub = (_.isBoolean(opts.supportBIP44AndP2PKH) && !opts.supportBIP44AndP2PKH) ? copayerData.xPubKey_45H : copayerData.xPubKey_44H_0H_0H;
if (opts.network == 'testnet')
pub = copayerData.xPubKey_44H_0H_0Ht;
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
coin: opts.coin,
name: 'copayer ' + (i + 1),
xPubKey: (_.isBoolean(opts.supportBIP44AndP2PKH) && !opts.supportBIP44AndP2PKH) ? copayerData.xPubKey_45H : copayerData.xPubKey_44H_0H_0H,
xPubKey: pub,
requestPubKey: copayerData.pubKey_1H_0,
customData: 'custom data ' + (i + 1),
});
@ -185,6 +214,7 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
copayerOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH;
server.joinWallet(copayerOpts, function(err, result) {
if (err) console.log(err);
should.not.exist(err);
copayerIds.push(result.copayerId);
return cb(err);
@ -256,6 +286,8 @@ helpers.stubUtxos = function(server, wallet, amounts, opts, cb) {
if (!helpers._utxos) helpers._utxos = {};
var S = Bitcore_[wallet.coin].Script;
async.waterfall([
function(next) {
@ -277,10 +309,10 @@ helpers.stubUtxos = function(server, wallet, amounts, opts, cb) {
var scriptPubKey;
switch (wallet.addressType) {
case Constants.SCRIPT_TYPES.P2SH:
scriptPubKey = Bitcore.Script.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut();
scriptPubKey = S.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut();
break;
case Constants.SCRIPT_TYPES.P2PKH:
scriptPubKey = Bitcore.Script.buildPublicKeyHashOut(address.address);
scriptPubKey = S.buildPublicKeyHashOut(address.address);
break;
}
should.exist(scriptPubKey);
@ -357,8 +389,22 @@ helpers.stubFeeLevels = function(levels) {
};
};
helpers.stubAddressActivity = function(activeAddresses) {
var stubAddressActivityFailsOn = null;
var stubAddressActivityFailsOnCount=1;
helpers.stubAddressActivity = function(activeAddresses, failsOn) {
stubAddressActivityFailsOnCount=1;
// could be null
stubAddressActivityFailsOn = failsOn;
blockchainExplorer.getAddressActivity = function(address, cb) {
if (stubAddressActivityFailsOnCount === stubAddressActivityFailsOn)
return cb('failed on request');
stubAddressActivityFailsOnCount++;
return cb(null, _.contains(activeAddresses, address));
};
};

6751
test/integration/hugetx.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -110,6 +110,7 @@ describe('Push notifications', function() {
calls.length.should.equal(1);
args[0].body.notification.title.should.contain('New payment received');
args[0].body.notification.body.should.contain('123,000');
args[0].body.notification.body.should.contain('bits');
done();
}, 100);
});
@ -158,6 +159,29 @@ describe('Push notifications', function() {
});
});
});
it('should notify copayers when tx is confirmed if they are subscribed', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
server.txConfirmationSubscribe({
txid: '123'
}, function(err) {
should.not.exist(err);
// Simulate tx confirmation notification
server._notify('TxConfirmation', {
txid: '123',
}, function(err) {
setTimeout(function() {
var calls = requestStub.getCalls();
calls.length.should.equal(1);
done();
}, 100);
});
});
});
});
});
describe('Shared wallet', function() {
@ -345,7 +369,7 @@ describe('Push notifications', function() {
txpId = txp.id;
async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44, function(server) {
helpers.getAuthServer(copayer.id44btc, function(server) {
server.rejectTx({
txProposalId: txp.id,
}, next);
@ -390,7 +414,7 @@ describe('Push notifications', function() {
txp = t;
async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id44, function(s) {
helpers.getAuthServer(copayer.id44btc, function(s) {
server = s;
var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H);
server.signTx({

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ describe('Address', function() {
it('should create livenet address', function() {
var x = Address.create({
address: '3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg',
coin: 'btc',
walletId: '123',
isChange: false,
path: 'm/0/1',
@ -23,6 +24,7 @@ describe('Address', function() {
it('should create testnet address', function() {
var x = Address.create({
address: 'mp5xaa4uBj16DJt1fuA3D9fejHuCzeb7hj',
coin: 'btc',
walletId: '123',
isChange: false,
path: 'm/0/1',
@ -39,7 +41,7 @@ describe('Address', function() {
}, {
xPubKey: 'xpub68tpbrfk747AvDUCdtEUgK2yDPmtGKf7YXzEcUUqnF3jmAMeZgcpoZqgXwwoi8CpwDkyzVX6wxUktTw2wh9EhhVjh5S71MLL3FkZDGF5GeY'
// PubKey(xPubKey/0/0) -> 03162179906dbe6a67979d4f8f46ee1db6ff81715f465e6615a4f5969478ad2171
}], 'm/0/0', 1, 'livenet', false);
}], 'm/0/0', 1, 'btc', 'livenet', false);
should.exist(address);
address.walletId.should.equal('wallet-id');
address.address.should.equal('3QN2CiSxcUsFuRxZJwXMNDQ2esnr5RXTvw');
@ -52,7 +54,7 @@ describe('Address', function() {
var address = Address.derive('wallet-id', 'P2SH', [{
xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1'
// PubKey(xPubKey/0/0) -> 03fe466ea829aa4c9a1c289f9ba61ebc26a61816500860c8d23f94aad9af152ecd
}], 'm/0/0', 1, 'livenet', false);
}], 'm/0/0', 1, 'btc', 'livenet', false);
should.exist(address);
address.walletId.should.equal('wallet-id');
address.address.should.equal('3BY4K8dfsHryhWh2MJ6XHxxsRfcvPAyseH');
@ -65,7 +67,7 @@ describe('Address', function() {
var address = Address.derive('wallet-id', 'P2PKH', [{
xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1'
// PubKey(xPubKey/1/2) -> 0232c09a6edd8e2189628132d530c038e0b15b414cf3984e532358cbcfb83a7bd7
}], 'm/1/2', 1, 'livenet', true);
}], 'm/1/2', 1, 'btc', 'livenet', true);
should.exist(address);
address.walletId.should.equal('wallet-id');
address.address.should.equal('1G4wgi9YzmSSwQaQVLXQ5HUVquQDgJf8oT');

View File

@ -17,7 +17,7 @@ describe('Copayer', function() {
});
});
describe('#createAddress', function() {
it('create an address', function() {
it('should create an address', function() {
var w = Wallet.fromObj(testWallet);
var c = Copayer.fromObj(testWallet.copayers[2]);
should.exist(c.requestPubKeys);
@ -42,6 +42,7 @@ var testWallet = {
createdOn: 1422904188,
id: '123',
name: '123 wallet',
network: 'livenet',
m: 2,
n: 3,
status: 'complete',

View File

@ -24,6 +24,11 @@ describe('TxProposal', function() {
should.exist(txp);
txp.amount.should.equal(aTXP().amount);
});
it('should default to BTC coin', function() {
var txp = TxProposal.fromObj(aTXP());
should.exist(txp);
txp.coin.should.equal('btc');
});
});
describe('#getBitcoreTx', function() {
@ -108,8 +113,10 @@ var theXPub = 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2
var theSignatures = ['304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6'];
var theRawTx = '0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e08000000910047304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0380969800000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac002d3101000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70f62b040000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000';
var aTxpOpts = function(type) {
var aTxpOpts = function() {
var opts = {
coin: 'btc',
network: 'livenet',
message: 'some message'
};
opts.outputs = [{
@ -125,7 +132,7 @@ var aTxpOpts = function(type) {
return opts;
};
var aTXP = function(type) {
var aTXP = function() {
var txp = {
"version": 3,
"createdOn": 1423146231,

View File

@ -54,6 +54,8 @@ describe('Storage', function() {
name: 'my wallet',
m: 2,
n: 3,
coin: 'btc',
network: 'livenet',
});
should.exist(wallet);
storage.storeWallet(wallet, function(err) {
@ -85,9 +87,12 @@ describe('Storage', function() {
name: 'my wallet',
m: 2,
n: 3,
coin: 'btc',
network: 'livenet',
});
_.each(_.range(3), function(i) {
var copayer = Model.Copayer.create({
coin: 'btc',
name: 'copayer ' + i,
xPubKey: 'xPubKey ' + i,
requestPubKey: 'requestPubKey ' + i,
@ -127,9 +132,12 @@ describe('Storage', function() {
name: 'my wallet',
m: 2,
n: 3,
coin: 'btc',
network: 'livenet',
});
_.each(_.range(3), function(i) {
var copayer = Model.Copayer.create({
coin: 'btc',
name: 'copayer ' + i,
xPubKey: 'xPubKey ' + i,
requestPubKey: 'requestPubKey ' + i,
@ -144,6 +152,8 @@ describe('Storage', function() {
proposals = _.map(_.range(4), function(i) {
var tx = Model.TxProposal.create({
walletId: '123',
coin: 'btc',
network: 'livenet',
outputs: [{
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: i + 100,

View File

@ -4,7 +4,8 @@ var keyPair = {
};
var copayers = [{
id44: '626452e5e0e35df4d9ae4d3e60653c9ae9a814f00c84dc40f5887069b18e2110',
id44btc: '626452e5e0e35df4d9ae4d3e60653c9ae9a814f00c84dc40f5887069b18e2110',
id44bch: '671fee02a6c1c4de2e2609f9f9a6180dc03acfff6b759fe0b13a616ed4880065',
id45: 'e7467366d5754be2b7d386c9737ab87214c26314bdc3489702e09c719be1bdb7',
xPrivKey: 'xprv9s21ZrQH143K2n4rV4AtAJFptEmd1tNMKCcSyQBCSuN5eq1dCUhcv6KQJS49joRxu8NNdFxy8yuwTtzCPNYUZvVGC7EPRm2st2cvE7oyTbB',
xPubKey: 'xpub661MyMwAqRbcFG9Kb5htXSCZSGc7RM6CgRY3mnap1Eu4XdLmk21sTtdt9iWAiL64KazU3QWrYEYSRAKgLvkMRe8JMxffDvt4AhCDzyMsnsT',
@ -12,12 +13,18 @@ var copayers = [{
xPubKey_45H: 'xpub68pKcb8jHWqWuTgPz2czjFSJJBJTsTNdd87Mgh5bVz4sNFBJBus5KyptGBWgA4V6LGCi12s4Mw4S1JC2GkqX4NJ4kfQ47XqRZLbyM2DY9Jd',
xPrivKey_44H_0H_0H: 'xprv9zWRZ7CXrC4z9xA9RRBFXohmPKbyCajWaCNTHPtwNeJwTnysHG5QK7WMqpNLVtvqGxts7WNcNtqBLfdaFdCGknDPXjLKt2E2BUrPaFDqrLh',
xPubKey_44H_0H_0H: 'xpub6DVmxcjRgZdHNSEcXSiFtweVwMSTc3TMwRJ45nJYvyqvLbK1poPerupqh87rSoz27wvckb1CKnGZoLmLXSZyNGZtVd7neqSvdwJL6fceQpe',
xPrivKey_44H_0H_0Ht: 'tprv8ZgxMBicQKsPcxUEtgtQ2wKpkmuNKS6R2w3UmFTUHHURv4PKGE2aGkkbQEcQs9gGsoW4zPr7VM98xdbjQuWc3cZ6bkEyKy1sywhV9gLUcUi',
xPubKey_44H_0H_0Ht: 'tpubD6NzVbkrYhZ4WRW2nLYzSLywKoRJUmHKcEeG3mVmhZGpkYe5tcrATFNTaQRAWM3dzL2QyXoctpjkaAXruDXyc6xkF4EDGu3eQdwZXFzoFSW',
xPrivKey_1H: 'xprv9upyD5bqT9HBkWym7TvTH3njEzTnjrtkLB2sg3DD2CxxA5hZKGee1sYJUtD8C4QaeATLXQ33TirRzRhuTGDBA6XRoYDMwfXAj1KSmGyNBio',
xPubKey_1H: 'xpub68pKcb8jHWqUy14EDVTTeBjTo2JH9KcbhPxUURcpaYVw2t2hroxtZfrnLBw1bWzBrHbEJA48QmZ8DB9gTvhphKSitC15SiYx9k2ncGh55Hq',
privKey_1H_0: 'a710be25950738a7d13637e2e09affd7f579a3479fd7cc024bd9459f8fba6659',
pubKey_1H_0: '026e3020913420a5b9425952627f0a074c9235e7a329869b322061f786e997ae0d'
}, {
id44: '842c048066e7d10ae1bbf67edccf69f2e5ff9a754d0c2b5524f0d01a87d6acbb',
id44btc: '842c048066e7d10ae1bbf67edccf69f2e5ff9a754d0c2b5524f0d01a87d6acbb',
id44bch: '0d8f0c0ebfb11ad589002fd4539075c6fb625fb1725406ca442726c6bc6746b1',
id45: 'ee75154b646277c8d0d256fc1a0aa0470e4c3435497f208092c865737040b55b',
xPrivKey: 'xprv9s21ZrQH143K3BwkLceWNLUsgES15JoZuv8BZfnmDRcCGtDooUAPhY8KovhCWcRLXUun5AYL5vVtUNRrmPEibtfk9ongxAGLXZzEHifpvwZ',
xPubKey: 'xpub661MyMwAqRbcFg2DSeBWjURcEGGVUmXRH93nN4CNmm9B9gYxM1UeFLSofD6gtMnRYeucgPjfrWNxaAEhiT4di6HLty8Un6aheCKev4REvhZ',
@ -30,7 +37,8 @@ var copayers = [{
privKey_1H_0: 'ee062ce6dc5ece50e8110646b5e858c98dba9315cdfdd19da85ab0d33dcac74a',
pubKey_1H_0: '02c679bf169233a273dec87fae5a1830481866c4e96a350d56346ac267808c905d'
}, {
id44: '719f4ee61c691fbf0ebefa34e2151a1a3dbe39cf2fa4a498cb6af53600d30d1a',
id44btc: '719f4ee61c691fbf0ebefa34e2151a1a3dbe39cf2fa4a498cb6af53600d30d1a',
id44bch: '56ed2c8d04c4aa29e9d6408724197c27d1fa0b71e2c2a6b91a4cf9710f09eb0a',
id45: 'acd666d7c677d9f2c85b55a5fad1610fe272eac46ef7a577c7aeeab0b1474e43',
xPrivKey: 'xprv9s21ZrQH143K3xgLzxd6SuWqG5Zp1iUmyGgSsJVhdQNeTzAqBFvXXLZqZzFZqocTx4HD9vUVYU27At5i8q46LmBXXL97fo4H9C3tHm4BnjY',
xPubKey: 'xpub661MyMwAqRbcGSkp6zA6p3TZp7QJRBCdLVc3fguKBjudLnVyioEn58tKRFPmGMkdGJWMX69mgZWHKrKmpQ3fwBXeFjLc5Sd2rnxcQthSW42',
@ -43,7 +51,8 @@ var copayers = [{
privKey_1H_0: '5009c8488e9a364fc24a999d99a81ae955271de1d06d46c2f2f09e20c6281b04',
pubKey_1H_0: '03338a3b7c08e9d9832e1baff0758e08f9cc691497dd6e91d4c191cd960fb2f043'
}, {
id44: 'e225a29864060823df67b98432b070a40aad1bf9af517005b0b5fe09c96e29c9',
id44btc: 'e225a29864060823df67b98432b070a40aad1bf9af517005b0b5fe09c96e29c9',
id44bch: '2baf290be693407fd9c32597608b6fd90ba60f65b2b81b58e9fe9c960938de11',
id45: 'c65a89f64794cb7e1886c7010a32dd6fa362d3e81710bac32e97e325b9109fd8',
xPrivKey: 'xprv9s21ZrQH143K48nfuK14gKJtML7eQzV2dAH1RaqAMj8v2zs79uaavA9UTWMxpBdgbMH2mhJLeKGq8AFA6GDnFyWP4rLmknqZAfgFFV718vo',
xPubKey: 'xpub661MyMwAqRbcGcs91LY53TFcuMx8pTCszPCcDyEmv4ftuoCFhStqTxTxJoy35yjp2H3qQtxDYGe1gtkZu4T7mR7ARK1MLYte2fptZVt6hkD',
@ -56,7 +65,8 @@ var copayers = [{
privKey_1H_0: '460ee692f05de66b5d8e2fa1d005a8b6bdb1442e2ce6b3facfcee2f9012c9474',
pubKey_1H_0: '03d0e0c526619b158aac9a8de8082f439df43d389ec50cb54386c3d87cfde4c99b'
}, {
id44: '120416cd4c427a7e4d94213cebe242f56a06bc6dd5c5c6cae27dc920a0ddf1fb',
id44btc: '120416cd4c427a7e4d94213cebe242f56a06bc6dd5c5c6cae27dc920a0ddf1fb',
id44bch: '4abc36e7731c08e0a93483691c3cb451013463ccee1b676e4a20d98cd1de8af3',
id45: '65ae087eb9efdc7e0ada3a7ef954285e9e5ba4b8c7ab2d36747ddd286f7a334f',
xPrivKey: 'xprv9s21ZrQH143K44Bb9G3EVNmLfAUKjTBAA2YtKxF4zc8SLV1o15JBoddhGHE9PGLXePMbEsSjCCvTvP3fUv6yMXZrnHigBboRBn2DmNoJkJg',
xPubKey: 'xpub661MyMwAqRbcGYG4FHaErWi5DCJp8uu1XFUV8LegYwfRDHLwYccSMRxB7Z3L1NgKychKdXQvbVEyDhSwNnNnnNKh9mBEAdQ5tv2guK8ywKU',
@ -69,7 +79,8 @@ var copayers = [{
privKey_1H_0: '7a5158b92d9ed4cb9644ddbd472b43428832a5f3bb91a481532a081908e62b2e',
pubKey_1H_0: '02b47d5c977c93c883f369165ebc2b564d14a52712ec6892f7097fa99e0d36ca20'
}, {
id44: '85de9f025ee190fab7cb1bd9b6772c64df26188ce705d4f258c5adaf7bc610f9',
id44btc: '85de9f025ee190fab7cb1bd9b6772c64df26188ce705d4f258c5adaf7bc610f9',
id44bch: '0845739e508fb8f7b28e10bed9d827968a12d2dbd6ecbac3303305fcaf535bfe',
id45: 'dacc5c350cef4449a3ca12939711c7449d0d6189e5e7f33cff60095a7a29b0f9',
xPrivKey: 'xprv9s21ZrQH143K48PpVxrh71KdViTFhAaiDSVtNFkmbWNYjwwwPbTrcqoVXsgBfue3Gq9b71hQeEbk67JgtTBcpYgKLF8pTwVnGz56f1BaCYt',
xPubKey: 'xpub661MyMwAqRbcGcUHbzPhU9GN3kHk6dJZafRVAeAP9quXckH5w8n7Ae7yP8e2Zh6SPPKFn2K6oE3GBpcz9QzfJTNRWXbY7w1L3nGLE5beZL1',
@ -82,7 +93,8 @@ var copayers = [{
privKey_1H_0: '3c49816d4e83d8758f89e8e104e3566a8a61426a9b7d4945b34212fbbb8e8290',
pubKey_1H_0: '0307ab8c0d8eea1fe3c3781050a69e71f9e7c8cc8476a77103e08a461506a0e780'
}, {
id44: '4d0c1eaab0aafc08aea7328f9ed1d3fc2812791ad2ebb9cbc1a8537b51b18afa',
id44btc: '4d0c1eaab0aafc08aea7328f9ed1d3fc2812791ad2ebb9cbc1a8537b51b18afa',
id44bch: '63ed91d8b7c4f06028d4a795cbb30d91772d93c99e7cc612d9f0b33a4fa215de',
id45: '9129a0454adcf659f4f9d65a9b4dc4f9793bd1f59664268b56a7ef73f29f1b8a',
xPrivKey: 'xprv9s21ZrQH143K3pgRcRBRnmcxNkNNLmJrpneMkEXY6o5TWBuJLMfdRpAWdb2cG3yxbL4DxfpUnQpjfQUmwPdVrRGoDJmtAf5u8cyqKCoDV97',
xPubKey: 'xpub661MyMwAqRbcGJktiSiS9uZgvnCrkE2iC1ZxYcw9f8cSNzESstysycUzUsDCU6KnnjR29VZ1eRAXDgEXfYxGw1B9E7VLSAcHa9UuifSozmy',
@ -95,7 +107,8 @@ var copayers = [{
privKey_1H_0: '87f8a2b92dd04d2782c3d40a34f09f2ab42076bd02b81fbe4a4a72f87ad2e6df',
pubKey_1H_0: '02a0370d6f1213ab3390ac666585614ad71146f3f28ec326e2e779f999c1a497eb'
}, {
id44: '5ae7b75deb3b4d7e251f1fc5613904c9ef8548af7601d93ef668299be4f75ddd',
id44btc: '5ae7b75deb3b4d7e251f1fc5613904c9ef8548af7601d93ef668299be4f75ddd',
id44bch: '375a87b5614473ad359fee0385e9ffcb01d78c7880b34987e59da06eeac8029a',
id45: '37b81e2544b43ce7f37a132a748426e1566ecbb758564d4d7d07b716fbe1b368',
xPrivKey: 'xprv9s21ZrQH143K3nvcmdjDDDZbDJHpfWZCUiunwraZdcamYcafHvUnZfV51fivH9FPyfo12NyKH5JDxGLsQePyWKtTiJx3pkEaiwxsMLkVapp',
xPubKey: 'xpub661MyMwAqRbcGH15sfGDaMWKmL8K4yH3qwqPkEzBBx7kRQuoqTo37ToYrvLJh7JpV5FQSverERMcdF4HcP1UCiie2ayeMXRq67zr75PzMKs',
@ -108,7 +121,8 @@ var copayers = [{
privKey_1H_0: '66230b6b8b65725162ea43313fcc233f4f0dd135cea00d04b73a84d3f681ef25',
pubKey_1H_0: '03f148bde0784c80051acd159b28a30022e685aca56418f8f50100d9f8a0192c37'
}, {
id44: '98e78a9cb2ab340a245c5082897eadb28c367319f97b93e7b51b4d5ca5cdc68e',
id44btc: '98e78a9cb2ab340a245c5082897eadb28c367319f97b93e7b51b4d5ca5cdc68e',
id44bch: 'f390e03140593c0c724e0d3a2a9cf39d63319edc833024a149a72efacb368737',
id45: 'e1557d3421a8884fe007674f3f0b6f0feafa76289a0edcc5ec736161b4d02257',
xPrivKey: 'xprv9s21ZrQH143K2uYgqtYtphEQkFAgiWSqahFUWjgCdKykJagiNDz6Lf7xRVQdtZ7MvkhX9V3pEcK3xTAWZ6Y6ecJqrXnCpzrH9GSHn8wyrT5',
xPubKey: 'xpub661MyMwAqRbcFPd9wv5uBqB9JH1B7yAgwvB5K85pBfWjBP1rumJLtTSSGnCdsJSXfwmTyexsRjbUhzB4J6LWfL8mC2Ka117JrnXetyCzk3r',
@ -121,7 +135,8 @@ var copayers = [{
privKey_1H_0: '9e215580c8e5876215ad101ded325bcacc5ab9d97b26e8fdfab89ef5bb6e0ab7',
pubKey_1H_0: '0265d33caaa128a77cc38ab8751c7d730e0274a212f1f65b73f637eddb3a3fb151'
}, {
id44: 'f716dbeec58e44c698b34c2d81bae4699ed5a5a522281733ec50aa03caf76a19',
id44btc: 'f716dbeec58e44c698b34c2d81bae4699ed5a5a522281733ec50aa03caf76a19',
id44bch: 'da39b3d560d2d99d9557a5a70ca3dc4561c3930e2850748fa80bdcecb650a9bf',
id45: '8a6d840580549a34422c9b150dbd1e96e369c5db69ee736caab95616f8abb22b',
xPrivKey: 'xprv9s21ZrQH143K2wcRMP75tAEL5JnUx4xU2AbUBQzVVUDP7DHZJkjF3kaRE7tcnPLLLL9PGjYTWTJmCQPaQ4GGzgWEUFJ6snwJG9YnQHBFRNR',
xPubKey: 'xpub661MyMwAqRbcFRgtTQe6FJB4dLcyMXgKPPX4yoQ73okMz1chrJ3VbYtu5PRTxMBGuXt6eyqwAuG2BEBzQPLc1x8gnSQiATS3GRzKi1BuQAR',
@ -135,8 +150,8 @@ var copayers = [{
pubKey_1H_0: '0266cdb57b8a4d7c1b5b20ddeea43705420c6e3aef2c2979a3768b7b585839a0d3'
}, ];
var history = [
{
var history = [{
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
vin: [{
txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d",
@ -175,8 +190,7 @@ var history = [
valueOut: 0.01345753,
valueIn: 0.01371235,
fees: 0.00025482
},
{
}, {
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
vin: [{
txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d",

View File

@ -131,4 +131,42 @@ describe('Utils', function() {
});
});
});
describe('#getAddressCoin', function() {
it('should identify btc as coin for 1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', function() {
Utils.getAddressCoin('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA').should.equal('btc');
});
it('should identify bch as coin for CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz', function() {
Utils.getAddressCoin('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz').should.equal('bch');
});
it('should return null for 1L', function() {
should.not.exist(Utils.getAddressCoin('1L'));
});
});
describe('#translateAddress', function() {
it('should translate address from btc to bch', function() {
var res = Utils.translateAddress('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', 'bch');
res.should.equal('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz');
});
it('should translate address from bch to btc', function() {
var res = Utils.translateAddress('HBf8isgS8EXG1r3X6GP89FmooUmiJ42wHS', 'btc');
res.should.equal('36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r');
});
it('should keep the address if there is nothing to do (bch)', function() {
var res = Utils.translateAddress('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz', 'bch');
res.should.equal('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz');
});
it('should keep the address if there is nothing to do (btc)', function() {
var res = Utils.translateAddress('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', 'btc');
should.exist(res);
res.should.equal('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA');
});
});
});