Compare commits

...

268 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
Matias Alejo Garcia 4f7ddace5f Merge pull request #638 from cmgustavo/bug/text-notification
Replaces (es) text for push notification
2017-03-22 10:23:04 -03:00
Gustavo Maximiliano Cortez aaef977db8
Replaces (es) text for push notification 2017-03-22 10:11:29 -03:00
Matias Alejo Garcia 6862e43515 Merge pull request #636 from isocolsky/fix/address-generation-test
Serialize async method in address creation tests
2017-03-15 19:27:21 -03:00
Ivan Socolsky 77ddbb0ca2
serialize async method 2017-03-15 17:01:28 -03:00
Matias Alejo Garcia 892c869490 Merge pull request #635 from isocolsky/update-bitcore-lib
Update bitcore-lib v0.14
2017-03-15 16:40:03 -03:00
Ivan Socolsky f302499245
v1.15 2017-03-15 16:12:13 -03:00
Matias Alejo Garcia ae532625d4 Merge pull request #627 from isocolsky/feat/new-pushnotifications
Refactor push notifications
2017-03-15 15:57:34 -03:00
Ivan Socolsky ee5a413598
add ignoreRateLimiter option 2017-03-15 15:39:43 -03:00
Ivan Socolsky 558c6fbc38
derive() -> deriveChild() 2017-03-15 15:39:43 -03:00
Ivan Socolsky 6485b1c6bd
bitcore-lib 0.14.0 2017-03-15 15:39:43 -03:00
Matias Alejo Garcia de8056fcfb Merge pull request #634 from matiu/v/0.14
update bitcore to 0.14
2017-03-13 18:54:46 -03:00
Matias Alejo Garcia 755400c94b
update bitcore to 0.14 2017-03-13 18:53:00 -03:00
Matias Alejo Garcia 8a63447dc0 Merge pull request #631 from isocolsky/feat/urgent-fee
Add 'Urgent' fee level
2017-03-02 11:18:18 -03:00
Ivan Socolsky d93cd4f279
implement multiplier for fee levels 2017-03-01 18:51:22 -03:00
Ivan Socolsky aa35fc21bc
test fee multiplier 2017-03-01 18:51:07 -03:00
Ivan Socolsky 22ede6ec1b
increase default fee per kb values + add urgent level 2017-03-01 18:50:46 -03:00
Ivan Socolsky 51114b3bff Merge pull request #6 from cmgustavo/bug/test-notifications-01
Fix tests for notifications
2017-02-03 19:38:28 -03:00
Gustavo Maximiliano Cortez 78224e7837
Fix tests for notifications 2017-02-03 16:43:55 -03:00
Ivan Socolsky 0a059fa5ae Merge pull request #5 from cmgustavo/bug/text-notifications-01
Updates wording for notifications
2017-02-03 15:37:09 -03:00
Gustavo Maximiliano Cortez 19534efac0
Updates wording for notifications 2017-02-03 14:54:10 -03:00
Ivan Socolsky b484e2754b
unsubscribe v2 2017-02-03 14:35:05 -03:00
Ivan Socolsky f7d7dc691a
default to google push notification url 2017-02-03 14:25:25 -03:00
Ivan Socolsky b9d0b16674
fix request headers 2017-02-03 12:17:41 -03:00
Ivan Socolsky fd7e26372c
fix index definition 2017-02-03 11:35:16 -03:00
Ivan Socolsky c4f6290fda
test subscribe/unsubscribe 2017-02-02 21:38:20 -03:00
Ivan Socolsky 6d55be53d7
test sending push notifications 2017-02-02 21:25:54 -03:00
Ivan Socolsky 0341a865b3
add push notification subscription model to index 2017-02-02 15:36:45 -03:00
Ivan Socolsky 8b62a3b4d8
retrieve all subscription tokens for each recipient 2017-02-02 15:19:11 -03:00
Ivan Socolsky bfcd14d297
add supporting methods for storing/fetching/removing subscriptions 2017-02-02 15:18:45 -03:00
Ivan Socolsky c65704a29c
add secret key placeholder to config 2017-02-02 15:17:49 -03:00
Ivan Socolsky 62de18b8ab
add push notification subscription model 2017-02-02 15:17:27 -03:00
Matias Alejo Garcia 7096157bdb Merge pull request #625 from isocolsky/fix/change-on-single-address
Fix/change on single address
2017-01-16 15:05:10 -03:00
Ivan Socolsky 75c8e28e73
treat single address in output as change 2017-01-16 14:14:28 -03:00
Ivan Socolsky d4ff5232df
test outgoing tx 2017-01-16 13:47:48 -03:00
Matias Alejo Garcia b3fd228616 Merge pull request #624 from bitpay/revert-546-rm/2-step-balance
Revert "Remove two step balance & active addresses cache"
2017-01-13 16:35:28 -03:00
Ivan Socolsky 09e778212c Revert "Remove two step balance & active addresses cache" 2017-01-11 18:15:00 -03:00
Ivan Socolsky 0d16810593 Merge pull request #618 from matiu/mongodb-clean
add mongodb clean script
2017-01-06 13:57:56 -03:00
Matias Alejo Garcia 06779dabfe
add mongodb clean script 2017-01-06 13:03:56 -03:00
Matias Alejo Garcia b4afcd7a34 Merge pull request #617 from isocolsky/ref/history
Optimize tx decoration using tx proposals in tx history
2017-01-05 17:48:31 -03:00
Ivan Socolsky 82e02e2d49
optimize fetching of tx proposals for history 2017-01-05 15:13:10 -03:00
Matias Alejo Garcia 5d5f887edb Merge pull request #616 from matiu/history-timeout
longer timeout for getHistory
2017-01-04 09:40:00 -03:00
Matias Alejo Garcia 611169d591
longer timeout for getHistory 2017-01-04 09:34:19 -03:00
Ivan Socolsky ee081eaf49 Merge pull request #607 from tuladhar/patch-1
Fix Copay Wallet Link
2016-12-19 11:15:04 -03:00
Puru ced183297e Fix Copay Wallet Link 2016-12-11 21:52:45 +05:45
Matias Alejo Garcia 163418f504 Merge pull request #546 from isocolsky/rm/2-step-balance
Remove two step balance & active addresses cache
2016-12-05 16:33:07 -03:00
Ivan Socolsky 098aad0a21
restore cache collection for tx history 2016-12-05 15:24:38 -03:00
Ivan Socolsky c102515ab4
remove active addresses from bc monitor 2016-12-05 15:18:45 -03:00
Ivan Socolsky 82aa97e65c
rm two step balance & active addresses cache 2016-12-05 15:17:36 -03:00
Matias Alejo Garcia ee304ff9c5 Merge pull request #604 from isocolsky/ref/bcmonitor-test
Tests for blockchain monitor service
2016-12-05 10:54:14 -03:00
Ivan Socolsky 4e3e76aecf
make blockchain monitor component testable
Conflicts:
	lib/blockchainmonitor.js
2016-12-05 10:41:21 -03:00
Matias Alejo Garcia 13d6421c01 Update README.md 2016-12-04 22:57:12 -03:00
Matias Alejo Garcia 6061a9bbe9 Merge pull request #603 from matiu/fix-rate-limitation
fix rate limitation only for POST
2016-12-01 12:49:23 -03:00
Matias Alejo Garcia 856768103e
fix rate limitation only for POST 2016-12-01 12:42:35 -03:00
Ivan Socolsky ecb9560555 Merge pull request #602 from matiu/rate-limiter
adds configurable rate limitation to createWallet
2016-11-30 13:05:11 -03:00
Matias Alejo Garcia d8c786df79
better reg 2016-11-30 13:01:53 -03:00
Matias Alejo Garcia 711f9516e5
fix endpoint matching 2016-11-30 11:16:29 -03:00
Matias Alejo Garcia 7f72a481ea
add rate limitation to createWallet 2016-11-30 10:53:33 -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
65 changed files with 12122 additions and 1702 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,24 +13,35 @@ 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 have a extensive test suite but have not been tested on production environments yet and have been recently released, so it it is still should be considered BETA software.
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`
BWS supports SSL and Clustering. For a detailed guide on installing BWS with extra features see [Installing BWS](https://github.com/bitpay/bitcore-wallet-service/blob/master/installation.md).
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.
@ -40,7 +51,29 @@ BWS supports SSL and Clustering. For a detailed guide on installing BWS with ext
* 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:
@ -100,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
@ -115,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
@ -127,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.
@ -140,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.
@ -168,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
@ -175,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)
@ -192,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,24 +38,40 @@ 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',
defaultUnit: 'btc',
subjectPrefix: '',
pushServerUrl: 'http://localhost:8000',
pushServerUrl: 'https://fcm.googleapis.com/fcm',
authorizationKey: '',
},
fiatRateServiceOpts: {
defaultProvider: 'BitPay',

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,11 +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)) {
@ -128,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);
});
};
@ -137,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);
});
@ -162,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);
});
@ -176,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._handleIncommingTx, 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._handleIncommingTx = 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,25 +24,35 @@ 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,
defaultValue: 150000,
}, {
name: 'priority',
nbBlocks: 2,
defaultValue: 50000
}, {
defaultValue: 100000
}, {
name: 'normal',
nbBlocks: 3,
defaultValue: 40000
}, {
defaultValue: 80000
}, {
name: 'economy',
nbBlocks: 6,
defaultValue: 25000
}, {
defaultValue: 50000
}, {
name: 'superEconomy',
nbBlocks: 24,
defaultValue: 10000
}];
Defaults.DEFAULT_FEE_PER_KB = Defaults.FEE_LEVELS[1].defaultValue;
defaultValue: 20000
}],
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;
@ -50,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
@ -80,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;
@ -90,4 +112,29 @@ Defaults.NOTIFICATIONS_TIMESPAN = 60;
Defaults.SESSION_EXPIRATION = 1 * 60 * 60; // 1 hour to session expiration
Defaults.RateLimit = {
createWallet: {
windowMs: 60 * 60 * 1000, // hour window
delayAfter: 8, // begin slowing down responses after the 3rd request
delayMs: 3000, // slow down subsequent responses by 3 seconds per request
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);
@ -64,7 +71,7 @@ Utils._tryImportPublicKey = function(publicKey) {
publicKeyBuffer = new Buffer(publicKey, 'hex');
}
return publicKeyBuffer;
} catch(e) {
} catch (e) {
return false;
}
};
@ -76,7 +83,7 @@ Utils._tryImportSignature = function(signature) {
signatureBuffer = new Buffer(signature, 'hex');
}
return secp256k1.signatureImport(signatureBuffer);
} catch(e) {
} catch (e) {
return false;
}
};
@ -84,7 +91,7 @@ Utils._tryImportSignature = function(signature) {
Utils._tryVerifyMessage = function(hash, sig, publicKeyBuffer) {
try {
return secp256k1.verify(hash, sig, publicKeyBuffer);
} catch(e) {
} catch (e) {
return false;
}
};
@ -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

@ -7,6 +7,7 @@ var log = require('npmlog');
var express = require('express');
var bodyParser = require('body-parser');
var compression = require('compression');
var RateLimit = require('express-rate-limit');
var Common = require('./common');
var Defaults = Common.Defaults;
@ -16,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();
@ -38,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();
});
@ -53,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({
@ -64,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;
@ -81,15 +88,15 @@ ExpressApp.prototype.start = function(opts, cb) {
this.app.use(morgan(logFormat, logOpts));
}
var router = express.Router();
function returnError(err, res, req) {
if (err instanceof WalletService.ClientError) {
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,
@ -154,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;
@ -161,16 +169,34 @@ 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);
});
};
var createWalletLimiter;
if (Defaults.RateLimit.createWallet && !opts.ignoreRateLimiter) {
log.info('', 'Limiting wallet creation per IP: %d req/h', (Defaults.RateLimit.createWallet.max / Defaults.RateLimit.createWallet.windowMs * 60 * 60 * 1000).toFixed(2))
createWalletLimiter = new RateLimit(Defaults.RateLimit.createWallet);
// router.use(/\/v\d+\/wallets\/$/, createWalletLimiter)
} else {
createWalletLimiter = function(req, res, next) {
next()
};
}
// DEPRECATED
router.post('/v1/wallets/', function(req, res) {
router.post('/v1/wallets/', createWalletLimiter, function(req, res) {
logDeprecated(req);
var server;
try {
@ -187,7 +213,7 @@ ExpressApp.prototype.start = function(opts, cb) {
});
});
router.post('/v2/wallets/', function(req, res) {
router.post('/v2/wallets/', createWalletLimiter, function(req, res) {
var server;
try {
server = getServer(req, res);
@ -275,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) {
@ -368,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);
@ -376,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;
@ -397,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);
@ -546,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;
@ -635,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) {
@ -657,9 +727,47 @@ ExpressApp.prototype.start = function(opts, cb) {
});
});
// DEPRECATED
router.delete('/v1/pushnotifications/subscriptions/', function(req, res) {
logDeprecated(req);
getServerWithAuth(req, res, function(server) {
server.pushNotificationsUnsubscribe(function(err, response) {
server.pushNotificationsUnsubscribe({
token: 'dummy'
}, function(err, response) {
if (err) return returnError(err, res, req);
res.json(response);
});
});
});
router.delete('/v2/pushnotifications/subscriptions/:token', function(req, res) {
var opts = {
token: req.params['token'],
};
getServerWithAuth(req, res, function(server) {
server.pushNotificationsUnsubscribe(opts, function(err, response) {
if (err) return returnError(err, res, req);
res.json(response);
});
});
});
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);
});

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);
return xpub.derive(path).publicKey;
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

@ -9,5 +9,7 @@ Model.Preferences = require('./preferences');
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 PushNotificationSub() {};
PushNotificationSub.create = function(opts) {
opts = opts || {};
var x = new PushNotificationSub();
x.version = '1.0.0';
x.createdOn = Math.floor(Date.now() / 1000);
x.copayerId = opts.copayerId;
x.token = opts.token;
x.packageName = opts.packageName;
x.platform = opts.platform;
return x;
};
PushNotificationSub.fromObj = function(obj) {
var x = new PushNotificationSub();
x.version = obj.version;
x.createdOn = obj.createdOn;
x.copayerId = obj.copayerId;
x.token = obj.token;
x.packageName = obj.packageName;
x.platform = obj.platform;
return x;
};
module.exports = PushNotificationSub;

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 pub = x.derive(self.inputPaths[i]).publicKey;
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() {};
@ -60,6 +65,10 @@ PushNotificationsService.prototype.start = function(opts, cb) {
self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc';
self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || '';
self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl;
self.authorizationKey = opts.pushNotificationsOpts.authorizationKey;
if (!self.authorizationKey) return cb(new Error('Missing authorizationKey attribute in configuration.'))
async.parallel([
function(done) {
@ -107,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([
@ -117,34 +126,40 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio
},
function(contents, next) {
async.map(recipientsList, function(recipient, next) {
var opts = {};
var content = contents[recipient.language];
opts.users = [notification.walletId + '$' + recipient.copayerId];
opts.android = {
"data": {
"title": content.plain.subject,
"message": content.plain.body,
"walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)),
"notId": Math.floor(Math.random() * 100000) + 1
}
};
opts.ios = {
"alert": {
"title": content.plain.subject,
"body": content.plain.body
self.storage.fetchPushNotificationSubs(recipient.copayerId, function(err, subs) {
if (err) return next(err);
var notifications = _.map(subs, function(sub) {
return {
to: sub.token,
priority: 'high',
restricted_package_name: sub.packageName,
notification: {
title: content.plain.subject,
body: content.plain.body,
sound: "default",
click_action: "FCM_PLUGIN_ACTIVITY",
icon: "fcm_push_icon",
},
"sound": "default",
"payload": {
"walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId))
}
};
return next(err, opts);
}, next);
data: {
walletId: sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)),
copayerId: sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(recipient.copayerId))
},
function(optsList, next) {
async.each(optsList,
function(opts, next) {
self._makeRequest(opts, function(err, response) {
};
});
return next(err, notifications);
});
}, function(err, allNotifications) {
if (err) return next(err);
return next(null, _.flatten(allNotifications));
});
},
function(notifications, next) {
async.each(notifications,
function(notification, next) {
self._makeRequest(notification, function(err, response) {
if (err) log.error(err);
if (response) {
log.debug('Request status: ', response.statusCode);
@ -174,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);
@ -200,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);
});
@ -260,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);
@ -275,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;
@ -360,10 +382,12 @@ PushNotificationsService.prototype._makeRequest = function(opts, cb) {
url: self.pushServerUrl + '/send',
method: 'POST',
json: true,
body: opts
}, function(err, response) {
return cb(err, response);
});
headers: {
'Content-Type': 'application/json',
'Authorization': 'key=' + self.authorizationKey,
},
body: opts,
}, cb);
};
module.exports = PushNotificationsService;

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');
@ -24,6 +24,8 @@ var collections = {
FIAT_RATES: 'fiat_rates',
TX_NOTES: 'tx_notes',
SESSIONS: 'sessions',
PUSH_NOTIFICATION_SUBS: 'push_notification_subs',
TX_CONFIRMATION_SUBS: 'tx_confirmation_subs',
};
var Storage = function(opts) {
@ -38,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,
@ -51,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,
@ -62,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,
});
@ -74,6 +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) {
@ -107,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) {
@ -195,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,
@ -208,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,
@ -439,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,
@ -449,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,
@ -460,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);
@ -500,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,
@ -563,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({
self.db.collection(collections.CACHE).update( {
walletId: walletId,
type: 'activeAddresses',
type: 'twoStep',
key: null,
}, {
w: 1
}, next);
},
function(next) {
self.db.collection(collections.CACHE).insert({
walletId: walletId,
type: 'activeAddresses',
key: null
"$set":
{
addressCount: cacheStatus.addressCount,
lastEmpty: cacheStatus.lastEmpty,
}
}, {
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, {
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
@ -658,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);
});
})
@ -760,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;
@ -890,6 +979,79 @@ Storage.prototype.storeSession = function(session, cb) {
}, cb);
};
Storage.prototype.fetchPushNotificationSubs = function(copayerId, cb) {
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).find({
copayerId: copayerId,
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var tokens = _.map([].concat(result), function(r) {
return Model.PushNotificationSub.fromObj(r);
});
return cb(null, tokens);
});
};
Storage.prototype.storePushNotificationSub = function(pushNotificationSub, cb) {
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).update({
copayerId: pushNotificationSub.copayerId,
token: pushNotificationSub.token,
}, pushNotificationSub, {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype.removePushNotificationSub = function(copayerId, token, cb) {
this.db.collection(collections.PUSH_NOTIFICATION_SUBS).remove({
copayerId: copayerId,
token: token,
}, {
w: 1
}, 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() {};
@ -908,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

@ -1,2 +1,2 @@
{{subjectPrefix}}New copayer
A new copayer just joined your wallet {{walletName}}.
A new copayer just joined your wallet.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}New payment received
A payment of {{amount}} has been received into your wallet {{walletName}}.
A payment of {{amount}} has been received into your wallet.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Payment sent
A Payment of {{amount}} has been sent from your wallet {{walletName}}.
A Payment of {{amount}} has been sent from your wallet.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}New payment proposal
A new payment proposal has been created in your wallet {{walletName}} by {{copayerName}}.
A new payment proposal has been created in your wallet.

View File

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

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Payment proposal rejected
A payment proposal in your wallet {{walletName}} has been rejected by {{rejectorsNames}}.
A payment proposal in your wallet has been rejected.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Wallet complete
Your wallet {{walletName}} is complete.
Your wallet is complete.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nuevo copayer
Un nuevo copayer ha ingresado a su monedero {{walletName}}.
Un nuevo copayer ha ingresado a su billetera.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nuevo pago recibido
Un pago de {{amount}} fue recibido en su monedero {{walletName}}.
Un pago de {{amount}} fue recibido en su billetera.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Pago enviado
Un pago de {{amount}} ha sido enviado de su monedero {{walletName}}.
Un pago de {{amount}} ha sido enviado de su billetera.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nueva propuesta de pago
Una nueva propuesta de pago ha sido creada en su monedero {{walletName}} por {{copayerName}}.
Una nueva propuesta de pago ha sido creada en su billetera.

View File

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

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Propuesta de pago rechazada
Una propuesta de pago en su monedero {{walletName}} ha sido rechazada por {{rejectorsNames}}.
Una propuesta de pago en su billetera ha sido rechazada.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Monedero completo
Su monedero {{walletName}} está completo.
{{subjectPrefix}}Billetera completa
Su billetera está completa.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nouveau copayer
Un nouveau copayer vient de rejoindre votre portefeuille {{walletName}}.
Un nouveau copayer vient de rejoindre votre portefeuille.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nouveau paiement reçu
Un paiement de {{amount}} a été reçu dans votre portefeuille {{walletName}}.
Un paiement de {{amount}} a été reçu dans votre portefeuille.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Paiement envoyé
Un paiement de {{amount}} a été envoyé de votre portefeuille {{walletName}}.
Un paiement de {{amount}} a été envoyé de votre portefeuille.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Nouvelle proposition de paiement
Une nouvelle proposition de paiement a été créée dans votre portefeuille {{walletName}} par {{copayerName}}.
Une nouvelle proposition de paiement a été créée dans votre portefeuille.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Proposition de paiement rejetée
Une proposition de paiement dans votre portefeuille {{walletName}} a été rejetée par {{rejectorsNames}}.
Une proposition de paiement dans votre portefeuille a été rejetée.

View File

@ -1,2 +1,2 @@
{{subjectPrefix}}Portefeuille terminé
Votre portefeuille {{walletName}} est terminé.
Votre portefeuille est terminé.

View File

@ -2,7 +2,8 @@
"name": "bitcore-wallet-service",
"description": "A service for Mutisig HD Bitcoin Wallets",
"author": "BitPay Inc",
"version": "1.13.0",
"version": "2.4.0",
"licence": "MIT",
"keywords": [
"bitcoin",
"copay",
@ -12,20 +13,22 @@
"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.13.7",
"bitcore-lib": "ch4ot1c/bitcore-lib",
"bitcore-lib-cash": "^0.17.0",
"body-parser": "^1.11.0",
"compression": "^1.6.2",
"coveralls": "^2.11.2",
"email-validator": "^1.0.1",
"express": "^4.10.0",
"express-rate-limit": "^2.6.0",
"inherits": "^2.0.1",
"json-stable-stringify": "^1.0.0",
"locker": "^0.1.0",
@ -52,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",
@ -68,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"
}]
}
]
}

4
scripts/clean_db.mongodb Normal file
View File

@ -0,0 +1,4 @@
db.email_queue.remove({createdOn: {$lt: Date.now()/1000-86400*10 }});
db.notifications.remove({createdOn: {$lt: Date.now()/1000-86400*10 }});

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

@ -0,0 +1,265 @@
'use strict';
var _ = require('lodash');
var async = require('async');
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var log = require('npmlog');
log.debug = log.verbose;
log.level = 'info';
var WalletService = require('../../lib/server');
var BlockchainMonitor = require('../../lib/blockchainmonitor');
var helpers = require('./helpers');
var storage, blockchainExplorer;
var socket = {
handlers: {},
};
socket.on = function(eventName, handler) {
this.handlers[eventName] = handler;
};
describe('Blockchain monitor', function() {
var server, wallet;
before(function(done) {
helpers.before(done);
});
after(function(done) {
helpers.after(done);
});
beforeEach(function(done) {
helpers.beforeEach(function(res) {
storage = res.storage;
blockchainExplorer = res.blockchainExplorer;
blockchainExplorer.initSocket = sinon.stub().returns(socket);
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
var bcmonitor = new BlockchainMonitor();
bcmonitor.start({
lockOpts: {},
messageBroker: server.messageBroker,
storage: storage,
blockchainExplorers: {
'btc': {
'testnet': blockchainExplorer,
'livenet': blockchainExplorer
}
},
}, function(err) {
should.not.exist(err);
done();
});
});
});
});
it('should notify copayers of incoming txs', 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() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notification = _.find(notifications, {
type: 'NewIncomingTx'
});
should.exist(notification);
notification.walletId.should.equal(wallet.id);
notification.data.txid.should.equal('123');
notification.data.address.should.equal(address.address);
notification.data.amount.should.equal(1500);
done();
});
}, 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

@ -98,11 +98,8 @@ describe('Email notifications', function() {
var one = emails[0];
one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment proposal');
one.text.should.contain(wallet.name);
one.text.should.contain(wallet.copayers[0].name);
should.exist(one.html);
one.html.indexOf('<html>').should.equal(0);
one.html.should.contain(wallet.name);
server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err);
unsent.should.be.empty;
@ -172,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,
@ -202,7 +199,6 @@ describe('Email notifications', function() {
var one = emails[0];
one.from.should.equal('bws@dummy.net');
one.subject.should.contain('Payment sent');
one.text.should.contain(wallet.name);
one.text.should.contain('800,000');
should.exist(one.html);
one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid);
@ -239,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);
@ -258,9 +254,6 @@ describe('Email notifications', function() {
var one = emails[0];
one.from.should.equal('bws@dummy.net');
one.subject.should.contain('Payment proposal rejected');
one.text.should.contain(wallet.name);
one.text.should.contain('copayer 2, copayer 3');
one.text.should.not.contain('copayer 1');
server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err);
unsent.should.be.empty;
@ -291,7 +284,6 @@ describe('Email notifications', function() {
var one = emails[0];
one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment received');
one.text.should.contain(wallet.name);
one.text.should.contain('123,000');
server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err);
@ -303,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({
@ -327,7 +351,6 @@ describe('Email notifications', function() {
var one = emails[0];
one.from.should.equal('bws@dummy.net');
one.subject.should.contain('New payment received');
one.text.should.contain(wallet.name);
one.text.should.contain('123,000');
server.storage.fetchUnsentEmails(function(err, unsent) {
should.not.exist(err);
@ -367,14 +390,12 @@ describe('Email notifications', function() {
});
spanish.from.should.equal('bws@dummy.net');
spanish.subject.should.contain('Nuevo pago recibido');
spanish.text.should.contain(wallet.name);
spanish.text.should.contain('0.123 BTC');
var english = _.find(emails, {
to: 'copayer2@domain.com'
});
english.from.should.equal('bws@dummy.net');
english.subject.should.contain('New payment received');
english.text.should.contain(wallet.name);
english.text.should.contain('123,000 bits');
done();
}, 100);

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;
@ -88,13 +92,14 @@ helpers.signMessage = function(text, privKey) {
};
helpers.signRequestPubKey = function(requestPubKey, xPrivKey) {
var priv = new Bitcore.HDPrivateKey(xPrivKey).derive(Constants.PATHS.REQUEST_KEY_AUTH).privateKey;
var priv = new Bitcore.HDPrivateKey(xPrivKey).deriveChild(Constants.PATHS.REQUEST_KEY_AUTH).privateKey;
return helpers.signMessage(requestPubKey, priv);
};
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.derive(45, true);
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.derive(44, true).derive(0, true).derive(0, true);
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.derive(1, true);
var xpriv_1H = xpriv.deriveChild(1, true);
var xpub_1H = Bitcore.HDPublicKey(xpriv_1H);
var priv = xpriv_1H.derive(0).privateKey;
var pub = xpub_1H.derive(0).publicKey;
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));
};
};
@ -374,7 +420,7 @@ helpers.clientSign = function(txp, derivedXPrivKey) {
_.each(txp.inputs, function(i) {
if (!derived[i.path]) {
derived[i.path] = xpriv.derive(i.path).privateKey;
derived[i.path] = xpriv.deriveChild(i.path).privateKey;
privs.push(derived[i.path]);
}
});

6751
test/integration/hugetx.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,8 @@ var log = require('npmlog');
log.debug = log.verbose;
log.level = 'info';
var sjcl = require('sjcl');
var WalletService = require('../../lib/server');
var PushNotificationsService = require('../../lib/pushnotificationsservice');
@ -36,11 +38,24 @@ describe('Push notifications', function() {
var i = 0;
async.eachSeries(w.copayers, function(copayer, next) {
helpers.getAuthServer(copayer.id, function(server) {
async.parallel([
function(done) {
server.savePreferences({
email: 'copayer' + (++i) + '@domain.com',
language: 'en',
unit: 'bit',
}, next);
}, done);
},
function(done) {
server.pushNotificationsSubscribe({
token: '1234',
packageName: 'com.wallet',
platform: 'Android',
}, done);
},
], next);
});
}, function(err) {
should.not.exist(err);
@ -59,8 +74,8 @@ describe('Push notifications', function() {
defaultLanguage: 'en',
defaultUnit: 'btc',
subjectPrefix: '',
pushServerUrl: 'http://localhost:8000/send',
pushServerUrl: 'http://localhost:8000',
authorizationKey: 'secret',
},
}, function(err) {
should.not.exist(err);
@ -93,8 +108,9 @@ describe('Push notifications', function() {
return c.args[0];
});
calls.length.should.equal(1);
args[0].body.android.data.title.should.contain('New payment received');
args[0].body.android.data.message.should.contain('123,000');
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);
});
@ -143,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() {
@ -154,11 +193,24 @@ describe('Push notifications', function() {
var i = 0;
async.eachSeries(w.copayers, function(copayer, next) {
helpers.getAuthServer(copayer.id, function(server) {
async.parallel([
function(done) {
server.savePreferences({
email: 'copayer' + (++i) + '@domain.com',
language: 'en',
unit: 'bit',
}, next);
}, done);
},
function(done) {
server.pushNotificationsSubscribe({
token: '1234',
packageName: 'com.wallet',
platform: 'Android',
}, done);
},
], next);
});
}, function(err) {
should.not.exist(err);
@ -177,8 +229,8 @@ describe('Push notifications', function() {
defaultLanguage: 'en',
defaultUnit: 'btc',
subjectPrefix: '',
pushServerUrl: 'http://localhost:8000/send',
pushServerUrl: 'http://localhost:8000',
authorizationKey: 'secret',
},
}, function(err) {
should.not.exist(err);
@ -214,14 +266,14 @@ describe('Push notifications', function() {
calls.length.should.equal(3);
args[0].body.android.data.title.should.contain('Nuevo pago recibido');
args[0].body.android.data.message.should.contain('0.123');
args[0].body.notification.title.should.contain('Nuevo pago recibido');
args[0].body.notification.body.should.contain('0.123');
args[1].body.android.data.title.should.contain('New payment received');
args[1].body.android.data.message.should.contain('123,000');
args[1].body.notification.title.should.contain('New payment received');
args[1].body.notification.body.should.contain('123,000');
args[2].body.android.data.title.should.contain('New payment received');
args[2].body.android.data.message.should.contain('123,000');
args[2].body.notification.title.should.contain('New payment received');
args[2].body.notification.body.should.contain('123,000');
done();
}, 100);
});
@ -317,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);
@ -333,9 +385,7 @@ describe('Push notifications', function() {
return c.args[0];
});
args[0].body.android.data.title.should.contain('Payment proposal rejected');
args[0].body.android.data.message.should.contain('copayer 2, copayer 3');
args[0].body.android.data.message.should.not.contain('copayer 1');
args[0].body.notification.title.should.contain('Payment proposal rejected');
done();
}, 100);
});
@ -364,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({
@ -392,11 +442,11 @@ describe('Push notifications', function() {
return c.args[0];
});
args[0].body.android.data.title.should.contain('Payment sent');
args[1].body.android.data.title.should.contain('Payment sent');
args[0].body.notification.title.should.contain('Payment sent');
args[1].body.notification.title.should.contain('Payment sent');
server.copayerId.should.not.equal((args[0].body.users[0]).split('$')[1]);
server.copayerId.should.not.equal((args[1].body.users[0]).split('$')[1]);
sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(server.copayerId)).should.not.equal(args[0].body.data.copayerId);
sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(server.copayerId)).should.not.equal(args[1].body.data.copayerId);
done();
}, 100);
});
@ -432,7 +482,8 @@ describe('Push notifications', function() {
defaultLanguage: 'en',
defaultUnit: 'btc',
subjectPrefix: '',
pushServerUrl: 'http://localhost:8000/send',
pushServerUrl: 'http://localhost:8000',
authorizationKey: 'secret',
},
}, function(err) {
should.not.exist(err);
@ -452,44 +503,54 @@ describe('Push notifications', function() {
customData: 'custom data ' + (i + 1),
});
server.joinWallet(copayerOpts, next);
server.joinWallet(copayerOpts, function(err, res) {
if (err) return next(err);
helpers.getAuthServer(res.copayerId, function(server) {
server.pushNotificationsSubscribe({
token: 'token:' + copayerOpts.name,
packageName: 'com.wallet',
platform: 'Android',
}, next);
});
});
}, function(err) {
should.not.exist(err);
setTimeout(function() {
var calls = requestStub.getCalls();
var args = _.map(calls, function(c) {
return c.args[0];
var args = _.filter(_.map(calls, function(call) {
return call.args[0];
}), function(arg) {
return arg.body.notification.title == 'New copayer';
});
var argu = _.compact(_.map(args, function(a) {
if (a.body.android.data.title == 'New copayer')
return a;
}));
server.getWallet(null, function(err, w) {
server.getWallet(null, function(err, wallet) {
/*
First call - copayer2 joined
copayer2 should notify to copayer1
copayer2 should NOT be notifyed
*/
w.copayers[0].id.should.contain((argu[0].body.users[0]).split('$')[1]);
w.copayers[1].id.should.not.contain((argu[0].body.users[0]).split('$')[1]);
var hashedCopayerIds = _.map(wallet.copayers, function(copayer) {
return sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(copayer.id));
});
hashedCopayerIds[0].should.equal((args[0].body.data.copayerId));
hashedCopayerIds[1].should.not.equal((args[0].body.data.copayerId));
/*
Second call - copayer3 joined
copayer3 should notify to copayer1
*/
w.copayers[0].id.should.contain((argu[1].body.users[0]).split('$')[1]);
hashedCopayerIds[0].should.equal((args[1].body.data.copayerId));
/*
Third call - copayer3 joined
copayer3 should notify to copayer2
*/
w.copayers[1].id.should.contain((argu[2].body.users[0]).split('$')[1]);
hashedCopayerIds[1].should.equal((args[2].body.data.copayerId));
// copayer3 should NOT notify any other copayer
w.copayers[2].id.should.not.contain((argu[1].body.users[0]).split('$')[1]);
w.copayers[2].id.should.not.contain((argu[2].body.users[0]).split('$')[1]);
hashedCopayerIds[2].should.not.equal((args[1].body.data.copayerId));
hashedCopayerIds[2].should.not.equal((args[2].body.data.copayerId));
done();
});
}, 100);

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');
});
});
});