Compare commits
268 Commits
Author | SHA1 | Date |
---|---|---|
|
6064e2a8bc | |
|
9f27a33f0e | |
|
01bedc939f | |
|
547e984b2c | |
|
52c26ced2e | |
|
b5b3c51d98 | |
|
8f35e78b57 | |
|
58447bbceb | |
|
0f0d605a3f | |
|
5b63d8ff92 | |
|
1f46848b03 | |
|
771cf5d82c | |
|
d4ef5345f6 | |
|
b9836f6069 | |
|
267cfdba8a | |
|
1ab4e27bde | |
|
4a19302a89 | |
|
41af549ed2 | |
|
242c98ebd3 | |
|
696b5b298b | |
|
1c271cfff3 | |
|
77f776a6b1 | |
|
41683bf10a | |
|
8e483953c9 | |
|
1333dd6ab7 | |
|
072bb336c5 | |
|
b40a24691a | |
|
ff89650337 | |
|
1d629c5674 | |
|
23b183714a | |
|
031e47ad5e | |
|
938bd05685 | |
|
36f88ec891 | |
|
6b293a64d6 | |
|
0e82c99e07 | |
|
7272b5234e | |
|
2605bfeca5 | |
|
89d2e21b29 | |
|
82fd50be9d | |
|
9c5abaf8bc | |
|
35d1fc3e12 | |
|
2f93137767 | |
|
c62c464d45 | |
|
d5bcb182d6 | |
|
c5259a6b0d | |
|
6a916bdf17 | |
|
80d609c014 | |
|
869840eae4 | |
|
acb14a1877 | |
|
9f248357e6 | |
|
72608f64f3 | |
|
616a8c40a7 | |
|
a62cb3da6f | |
|
89ab474d2a | |
|
920680b35f | |
|
55ce16b552 | |
|
391898a06e | |
|
3718493277 | |
|
a00572a194 | |
|
b3558ae1e5 | |
|
fe02071a38 | |
|
b12634f1d8 | |
|
f9ec2e1d5b | |
|
71d146bc9e | |
|
2692f579e2 | |
|
ead5ddb817 | |
|
7e788e202c | |
|
bdc0ba9699 | |
|
2493dbb98d | |
|
a7cd4e6171 | |
|
64e5a292e4 | |
|
6361006b8d | |
|
a1ec459311 | |
|
5c22852328 | |
|
23278a06fb | |
|
1db21481d6 | |
|
bf2e281787 | |
|
cc9e0ba403 | |
|
bd24613261 | |
|
9140f89944 | |
|
545dd4e51a | |
|
a04ac1383c | |
|
0d839a1c74 | |
|
e4d7f3ee7b | |
|
8bffde15a2 | |
|
b8c52a814c | |
|
0b6e5771c4 | |
|
2d46dca887 | |
|
f911ac3cc6 | |
|
002f9c77a6 | |
|
3a75b38232 | |
|
85156a8898 | |
|
1949924c1c | |
|
dac3d1ab0b | |
|
74f71230d8 | |
|
9177033ac0 | |
|
7a12b1761b | |
|
f15d69f7cf | |
|
ec47ec0b61 | |
|
ad772b7f6e | |
|
f07bda2d22 | |
|
46bee919da | |
|
e87521149a | |
|
13229b8aa9 | |
|
9d6af8cd0e | |
|
15de4716f7 | |
|
622f9cf7c4 | |
|
ffea739a19 | |
|
51f66c7f8d | |
|
12dee6fd59 | |
|
357419b53c | |
|
a9e56dd3be | |
|
5186392558 | |
|
d8e6964587 | |
|
570c8c193f | |
|
c6269a39a5 | |
|
f0a48e1217 | |
|
03a61f68f2 | |
|
8dd76a0dba | |
|
fd9a2ad839 | |
|
f81b5484c5 | |
|
5c3b0f4d37 | |
|
5ba71f1641 | |
|
3d967298f0 | |
|
d25e21b0d5 | |
|
05982ba166 | |
|
5cab2a722b | |
|
b74318862e | |
|
c4f87ccb17 | |
|
7bbc6aea82 | |
|
87ba10de84 | |
|
5dffb5f958 | |
|
5babd07201 | |
|
b2845456d3 | |
|
d3bb52ccf6 | |
|
f16b33908f | |
|
e2edfd394f | |
|
30494a8133 | |
|
98bdaebbfb | |
|
c125950891 | |
|
398c5ccc64 | |
|
fbe2040cbb | |
|
9ef45b4409 | |
|
acdf99a34f | |
|
cb4ebe8c50 | |
|
371d315138 | |
|
5fbb262ace | |
|
ba6fbab240 | |
|
58cb560b79 | |
|
06b63e0311 | |
|
4d7a5ee3d5 | |
|
daad0367d7 | |
|
827e05e08e | |
|
b6d9048001 | |
|
b47dfaeb95 | |
|
5f68cb27f2 | |
|
bad1826ec0 | |
|
5f3295722b | |
|
0ed05ee808 | |
|
d416284fc3 | |
|
279d2ecc68 | |
|
565bc01339 | |
|
2ab93cea1d | |
|
41c82e9e76 | |
|
c5d9f44b2d | |
|
3be1e616e7 | |
|
7cbfe71a79 | |
|
88ecc303bb | |
|
eabf3c978f | |
|
a2f1e0e40e | |
|
954b88aceb | |
|
ca0bd7fbcd | |
|
3aaa2bf82e | |
|
acd75cd62c | |
|
dd1c90f2bc | |
|
da6e61b7e7 | |
|
8df8327395 | |
|
c621d1c573 | |
|
c87350d7ef | |
|
b856d1cb02 | |
|
7803e4576b | |
|
fdea614d81 | |
|
300102e053 | |
|
0dbdfa1857 | |
|
13472e147a | |
|
7587540113 | |
|
4be0937e5b | |
|
a9b44ee5f1 | |
|
a2944af07e | |
|
cb3ee35deb | |
|
bbb6890fd5 | |
|
be430ace03 | |
|
8e60cf01de | |
|
11c5bd92a7 | |
|
4fd339da29 | |
|
2d3df0a12e | |
|
785fc4676e | |
|
1d4cf86a56 | |
|
d94290181e | |
|
3eea8ccfc4 | |
|
e2f497a6e5 | |
|
f1d9d2c499 | |
|
98165ab5a7 | |
|
ea54fe5a58 | |
|
752f86029a | |
|
d13fe55b66 | |
|
ba2fea93d7 | |
|
3a0431fac6 | |
|
aac8b274eb | |
|
4f7ddace5f | |
|
aaef977db8 | |
|
6862e43515 | |
|
77ddbb0ca2 | |
|
892c869490 | |
|
f302499245 | |
|
ae532625d4 | |
|
ee5a413598 | |
|
558c6fbc38 | |
|
6485b1c6bd | |
|
de8056fcfb | |
|
755400c94b | |
|
8a63447dc0 | |
|
d93cd4f279 | |
|
aa35fc21bc | |
|
22ede6ec1b | |
|
51114b3bff | |
|
78224e7837 | |
|
0a059fa5ae | |
|
19534efac0 | |
|
b484e2754b | |
|
f7d7dc691a | |
|
b9d0b16674 | |
|
fd7e26372c | |
|
c4f6290fda | |
|
6d55be53d7 | |
|
0341a865b3 | |
|
8b62a3b4d8 | |
|
bfcd14d297 | |
|
c65704a29c | |
|
62de18b8ab | |
|
7096157bdb | |
|
75c8e28e73 | |
|
d4ff5232df | |
|
b3fd228616 | |
|
09e778212c | |
|
0d16810593 | |
|
06779dabfe | |
|
b4afcd7a34 | |
|
82e02e2d49 | |
|
5d5f887edb | |
|
611169d591 | |
|
ee081eaf49 | |
|
ced183297e | |
|
163418f504 | |
|
098aad0a21 | |
|
c102515ab4 | |
|
82aa97e65c | |
|
ee304ff9c5 | |
|
4e3e76aecf | |
|
13d6421c01 | |
|
6061a9bbe9 | |
|
856768103e | |
|
ecb9560555 | |
|
d8c786df79 | |
|
711f9516e5 | |
|
7f72a481ea | |
|
dee50e8d30 | |
|
dba1601560 |
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ addons:
|
||||||
- g++-4.8
|
- g++-4.8
|
||||||
- clang
|
- clang
|
||||||
node_js:
|
node_js:
|
||||||
- '4'
|
- '8'
|
||||||
before_install:
|
before_install:
|
||||||
- export CXX="g++-4.8" CC="gcc-4.8"
|
- export CXX="g++-4.8" CC="gcc-4.8"
|
||||||
install:
|
install:
|
||||||
|
|
83
README.md
83
README.md
|
@ -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.
|
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/
|
More about BWS at https://blog.bitpay.com/announcing-the-bitcore-wallet-suite/
|
||||||
|
|
||||||
# Install
|
# Getting Started
|
||||||
```
|
```
|
||||||
npm install bitcore-wallet-service
|
git clone https://github.com/bitpay/bitcore-wallet-service.git
|
||||||
npm start
|
cd bitcore-wallet-service && npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
This will launch the BWS service (with default settings) at `http://localhost:3232/bws/api`.
|
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 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 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
|
# Security Considerations
|
||||||
* Private keys are never sent to BWS. Copayers store them locally.
|
* 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.
|
* 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.
|
* 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.
|
* 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
|
# REST API
|
||||||
|
|
||||||
|
Note: all currency amounts are in units of satoshis (1/100,000,000 of a bitcoin).
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
In order to access a wallet, clients are required to send the headers:
|
In order to access a wallet, clients are required to send the headers:
|
||||||
|
@ -99,6 +132,18 @@ Returns:
|
||||||
* availableConfirmedAmount: Same as availableAmount for confirmed UTXOs only.
|
* availableConfirmedAmount: Same as availableAmount for confirmed UTXOs only.
|
||||||
* byAddress array ['address', 'path', 'amount']: A list of addresses holding funds.
|
* 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).
|
* 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
|
## POST Endpoints
|
||||||
`/v1/wallets/`: Create a new Wallet
|
`/v1/wallets/`: Create a new Wallet
|
||||||
|
@ -115,6 +160,7 @@ Returns:
|
||||||
|
|
||||||
|
|
||||||
`/v1/wallets/:id/copayers/`: Join a Wallet in creation
|
`/v1/wallets/:id/copayers/`: Join a Wallet in creation
|
||||||
|
|
||||||
Required Arguments:
|
Required Arguments:
|
||||||
* walletId: Id of the wallet to join
|
* walletId: Id of the wallet to join
|
||||||
* name: Copayer Name
|
* name: Copayer Name
|
||||||
|
@ -127,10 +173,11 @@ Returns:
|
||||||
* wallet: Object with wallet's information
|
* wallet: Object with wallet's information
|
||||||
|
|
||||||
`/v1/txproposals/`: Add a new transaction proposal
|
`/v1/txproposals/`: Add a new transaction proposal
|
||||||
|
|
||||||
Required Arguments:
|
Required Arguments:
|
||||||
* toAddress: RCPT Bitcoin address.
|
* toAddress: RCPT Bitcoin address.
|
||||||
* amount: amount (in satoshis) of the mount proposed to be transfered
|
* 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) message: Encrypted private message to peers.
|
||||||
* (opt) payProUrl: Paypro URL for peers to verify TX
|
* (opt) payProUrl: Paypro URL for peers to verify TX
|
||||||
* (opt) feePerKb: Use an alternative fee per KB for this 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.
|
* 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:
|
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.
|
* 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,20 +215,24 @@ Returns:
|
||||||
Optional Arguments:
|
Optional Arguments:
|
||||||
* includeCopayerBranches: Scan all copayer branches following BIP45 recommendation (defaults to false).
|
* 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
|
## 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
|
`/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
|
||||||
|
|
||||||
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.
|
* 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
|
# 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:
|
Recomended to complete config.js file:
|
||||||
|
|
||||||
* [GCM documentation to get your API key](https://developers.google.com/cloud-messaging/gcm)
|
* [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.
|
`/v1/pushnotifications/subscriptions/`: Adds subscriptions for push notifications service at database.
|
||||||
|
|
||||||
|
|
||||||
## DELETE Endopints
|
## DELETE Endpoints
|
||||||
`/v1/pushnotifications/subscriptions/`: Remove subscriptions for push notifications service from database.
|
`/v2/pushnotifications/subscriptions/`: Remove subscriptions for push notifications service from database.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
36
config.js
36
config.js
|
@ -38,16 +38,31 @@ var config = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
blockchainExplorerOpts: {
|
blockchainExplorerOpts: {
|
||||||
livenet: {
|
btc: {
|
||||||
provider: 'insight',
|
livenet: {
|
||||||
url: 'https://insight.bitpay.com:443',
|
provider: 'insight',
|
||||||
|
url: 'https://explorer.btcprivate.org:443',
|
||||||
|
},
|
||||||
|
testnet: {
|
||||||
|
provider: 'insight',
|
||||||
|
url: 'https://explorer.testnet.btcprivate.org:443',
|
||||||
|
// Multiple servers (in priority order)
|
||||||
|
// url: ['http://a.b.c', 'https://test-insight.bitpay.com:443'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
testnet: {
|
bch: {
|
||||||
provider: 'insight',
|
livenet: {
|
||||||
url: 'https://test-insight.bitpay.com:443',
|
provider: 'insight',
|
||||||
// url: 'http://localhost:3001',
|
//url: 'https://cashexplorer.bitcoin.com',
|
||||||
// Multiple servers (in priority order)
|
url: 'https://bch-insight.bitpay.com:443',
|
||||||
// url: ['http://a.b.c', 'https://test-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: {
|
pushNotificationsOpts: {
|
||||||
|
@ -55,7 +70,8 @@ var config = {
|
||||||
defaultLanguage: 'en',
|
defaultLanguage: 'en',
|
||||||
defaultUnit: 'btc',
|
defaultUnit: 'btc',
|
||||||
subjectPrefix: '',
|
subjectPrefix: '',
|
||||||
pushServerUrl: 'http://localhost:8000',
|
pushServerUrl: 'https://fcm.googleapis.com/fcm',
|
||||||
|
authorizationKey: '',
|
||||||
},
|
},
|
||||||
fiatRateServiceOpts: {
|
fiatRateServiceOpts: {
|
||||||
defaultProvider: 'BitPay',
|
defaultProvider: 'BitPay',
|
||||||
|
|
|
@ -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;
|
|
@ -6,11 +6,21 @@ var log = require('npmlog');
|
||||||
log.debug = log.verbose;
|
log.debug = log.verbose;
|
||||||
|
|
||||||
var Insight = require('./blockchainexplorers/insight');
|
var Insight = require('./blockchainexplorers/insight');
|
||||||
|
var Common = require('./common');
|
||||||
|
var Constants = Common.Constants,
|
||||||
|
Defaults = Common.Defaults,
|
||||||
|
Utils = Common.Utils;
|
||||||
|
|
||||||
var PROVIDERS = {
|
var PROVIDERS = {
|
||||||
'insight': {
|
'insight': {
|
||||||
'livenet': 'https://insight.bitpay.com:443',
|
'btc': {
|
||||||
'testnet': 'https://test-insight.bitpay.com:443',
|
'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);
|
$.checkArgument(opts);
|
||||||
|
|
||||||
var provider = opts.provider || 'insight';
|
var provider = opts.provider || 'insight';
|
||||||
|
var coin = opts.coin || Defaults.COIN;
|
||||||
var network = opts.network || 'livenet';
|
var network = opts.network || 'livenet';
|
||||||
|
|
||||||
$.checkState(PROVIDERS[provider], 'Provider ' + provider + ' not supported');
|
$.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][network];
|
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';
|
||||||
|
|
||||||
|
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case 'insight':
|
case 'insight':
|
||||||
return new Insight({
|
return new Insight({
|
||||||
|
coin: coin,
|
||||||
network: network,
|
network: network,
|
||||||
url: url,
|
url: url,
|
||||||
apiPrefix: opts.apiPrefix,
|
apiPrefix: opts.apiPrefix,
|
||||||
userAgent: opts.userAgent,
|
userAgent: opts.userAgent,
|
||||||
|
addressFormat: opts.addressFormat,
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
throw new Error('Provider ' + provider + ' not supported.');
|
throw new Error('Provider ' + provider + ' not supported.');
|
||||||
|
|
|
@ -1,23 +1,38 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
var async = require('async');
|
||||||
var $ = require('preconditions').singleton();
|
var $ = require('preconditions').singleton();
|
||||||
var log = require('npmlog');
|
var log = require('npmlog');
|
||||||
log.debug = log.verbose;
|
log.debug = log.verbose;
|
||||||
var io = require('socket.io-client');
|
var io = require('socket.io-client');
|
||||||
var requestList = require('./request-list');
|
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) {
|
function Insight(opts) {
|
||||||
$.checkArgument(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);
|
$.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.network = opts.network || 'livenet';
|
||||||
this.hosts = opts.url;
|
this.hosts = opts.url;
|
||||||
this.userAgent = opts.userAgent || 'bws';
|
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) {
|
var _parseErr = function(err, res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -28,6 +43,42 @@ var _parseErr = function(err, res) {
|
||||||
return "Error querying the blockchain";
|
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) {
|
Insight.prototype._doRequest = function(args, cb) {
|
||||||
var opts = {
|
var opts = {
|
||||||
hosts: this.hosts,
|
hosts: this.hosts,
|
||||||
|
@ -35,28 +86,43 @@ Insight.prototype._doRequest = function(args, cb) {
|
||||||
'User-Agent': this.userAgent,
|
'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);
|
requestList(_.defaults(args, opts), cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
Insight.prototype.getConnectionInfo = function() {
|
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
|
* Retrieve a list of unspent outputs associated with an address or set of addresses
|
||||||
*/
|
*/
|
||||||
Insight.prototype.getUtxos = function(addresses, cb) {
|
Insight.prototype.getUtxos = function(addresses, cb) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
var url = this.url + this.apiPrefix + '/addrs/utxo';
|
var url = this.url + this.apiPrefix + '/addrs/utxo';
|
||||||
var args = {
|
var args = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: this.apiPrefix + '/addrs/utxo',
|
path: this.apiPrefix + '/addrs/utxo',
|
||||||
json: {
|
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 (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);
|
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));
|
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
|
||||||
return cb(null, body ? body.txid : null);
|
return cb(null, body ? body.txid : null);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Insight.prototype.getTransaction = function(txid, cb) {
|
Insight.prototype.getTransaction = function(txid, cb) {
|
||||||
|
var self = this;
|
||||||
var args = {
|
var args = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: this.apiPrefix + '/tx/' + txid,
|
path: this.apiPrefix + '/tx/' + txid,
|
||||||
json: true,
|
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 (res && res.statusCode == 404) return cb();
|
||||||
if (err || res.statusCode !== 200)
|
if (err || res.statusCode !== 200)
|
||||||
return cb(_parseErr(err, res));
|
return cb(_parseErr(err, res));
|
||||||
|
|
||||||
|
self.translateTx(tx);
|
||||||
|
|
||||||
return cb(null, tx);
|
return cb(null, tx);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Insight.prototype.getTransactions = function(addresses, from, to, cb) {
|
Insight.prototype.getTransactions = function(addresses, from, to, cb) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
|
||||||
var qs = [];
|
var qs = [];
|
||||||
var total;
|
var total;
|
||||||
if (_.isNumber(from)) qs.push('from=' + from);
|
if (_.isNumber(from)) qs.push('from=' + from);
|
||||||
|
@ -110,24 +182,33 @@ Insight.prototype.getTransactions = function(addresses, from, to, cb) {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: this.apiPrefix + '/addrs/txs' + (qs.length > 0 ? '?' + qs.join('&') : ''),
|
path: this.apiPrefix + '/addrs/txs' + (qs.length > 0 ? '?' + qs.join('&') : ''),
|
||||||
json: {
|
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 (err || res.statusCode !== 200) return cb(_parseErr(err, res));
|
||||||
|
|
||||||
if (_.isObject(txs)) {
|
if (_.isObject(txs)) {
|
||||||
if (txs.totalItems)
|
if (txs.totalItems)
|
||||||
total = txs.totalItems;
|
total = txs.totalItems;
|
||||||
|
|
||||||
if (txs.items)
|
if (txs.items)
|
||||||
txs = txs.items;
|
txs = txs.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Whenever Insight breaks communication with bitcoind, it returns invalid data but no error code.
|
// 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 (!_.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);
|
return cb(null, txs, total);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -137,15 +218,17 @@ Insight.prototype.getAddressActivity = function(address, cb) {
|
||||||
|
|
||||||
var args = {
|
var args = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: self.apiPrefix + '/addr/' + address,
|
path: self.apiPrefix + '/addr/' + this.translateQueryAddresses(address),
|
||||||
json: true,
|
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 (res && res.statusCode == 404) return cb();
|
||||||
if (err || res.statusCode !== 200)
|
if (err || res.statusCode !== 200)
|
||||||
return cb(_parseErr(err, res));
|
return cb(_parseErr(err, res));
|
||||||
|
|
||||||
|
// note: result.addrStr is not translated, but not used.
|
||||||
|
|
||||||
var nbTxs = result.unconfirmedTxApperances + result.txApperances;
|
var nbTxs = result.unconfirmedTxApperances + result.txApperances;
|
||||||
return cb(null, nbTxs > 0);
|
return cb(null, nbTxs > 0);
|
||||||
});
|
});
|
||||||
|
@ -162,7 +245,7 @@ Insight.prototype.estimateFee = function(nbBlocks, cb) {
|
||||||
path: path,
|
path: path,
|
||||||
json: true,
|
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));
|
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
|
||||||
return cb(null, body);
|
return cb(null, body);
|
||||||
});
|
});
|
||||||
|
@ -176,12 +259,27 @@ Insight.prototype.getBlockchainHeight = function(cb) {
|
||||||
path: path,
|
path: path,
|
||||||
json: true,
|
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));
|
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
|
||||||
return cb(null, body.blockChainHeight);
|
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() {
|
Insight.prototype.initSocket = function() {
|
||||||
|
|
||||||
// sockets always use the first server on the pull
|
// sockets always use the first server on the pull
|
||||||
|
|
|
@ -31,11 +31,23 @@ var requestList = function(args, cb) {
|
||||||
async.whilst(
|
async.whilst(
|
||||||
function() {
|
function() {
|
||||||
nextUrl = urls.shift();
|
nextUrl = urls.shift();
|
||||||
|
if (!nextUrl && success === 'false')
|
||||||
|
log.warn('no more servers to test for the request');
|
||||||
return nextUrl && !success;
|
return nextUrl && !success;
|
||||||
},
|
},
|
||||||
function(a_cb) {
|
function(a_cb) {
|
||||||
args.uri = nextUrl;
|
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) {
|
request(args, function(err, res, body) {
|
||||||
|
clearInterval(interval);
|
||||||
|
sucess = false;
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
log.warn('REQUEST FAIL: ' + nextUrl + ' ERROR: ' + err);
|
log.warn('REQUEST FAIL: ' + nextUrl + ' ERROR: ' + err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@ var Lock = require('./lock');
|
||||||
var Notification = require('./model/notification');
|
var Notification = require('./model/notification');
|
||||||
|
|
||||||
var WalletService = require('./server');
|
var WalletService = require('./server');
|
||||||
|
var Common = require('./common');
|
||||||
|
var Constants = Common.Constants;
|
||||||
|
var Utils = Common.Utils;
|
||||||
|
|
||||||
function BlockchainMonitor() {};
|
function BlockchainMonitor() {};
|
||||||
|
|
||||||
|
@ -25,25 +28,42 @@ BlockchainMonitor.prototype.start = function(opts, cb) {
|
||||||
async.parallel([
|
async.parallel([
|
||||||
|
|
||||||
function(done) {
|
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;
|
var explorer;
|
||||||
if (opts.blockchainExplorers) {
|
if (opts.blockchainExplorers && opts.blockchainExplorers[pair.coin] && opts.blockchainExplorers[pair.coin][pair.network]) {
|
||||||
explorer = opts.blockchainExplorers[network];
|
explorer = opts.blockchainExplorers[pair.coin][pair.network];
|
||||||
} else {
|
} else {
|
||||||
var config = {}
|
var config = {}
|
||||||
if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[network]) {
|
if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[pair.coin] && opts.blockchainExplorerOpts[pair.coin][pair.network]) {
|
||||||
config = opts.blockchainExplorerOpts[network];
|
config = opts.blockchainExplorerOpts[pair.coin][pair.network];
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
var explorer = new BlockchainExplorer({
|
var explorer = new BlockchainExplorer({
|
||||||
provider: config.provider,
|
provider: config.provider,
|
||||||
network: network,
|
coin: pair.coin,
|
||||||
|
network: pair.network,
|
||||||
url: config.url,
|
url: config.url,
|
||||||
userAgent: WalletService.getServiceVersion(),
|
userAgent: WalletService.getServiceVersion(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$.checkState(explorer);
|
$.checkState(explorer);
|
||||||
self._initExplorer(explorer);
|
self._initExplorer(pair.coin, pair.network, explorer);
|
||||||
return explorer;
|
self.explorers[pair.coin][pair.network] = explorer;
|
||||||
});
|
});
|
||||||
done();
|
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 self = this;
|
||||||
|
|
||||||
var socket = explorer.initSocket();
|
var socket = explorer.initSocket();
|
||||||
|
@ -84,11 +104,11 @@ BlockchainMonitor.prototype._initExplorer = function(explorer) {
|
||||||
socket.on('connect_error', function() {
|
socket.on('connect_error', function() {
|
||||||
log.error('Error connecting to ' + explorer.getConnectionInfo());
|
log.error('Error connecting to ' + explorer.getConnectionInfo());
|
||||||
});
|
});
|
||||||
socket.on('tx', _.bind(self._handleIncommingTx, self));
|
socket.on('tx', _.bind(self._handleIncomingTx, self, coin, network));
|
||||||
socket.on('block', _.bind(self._handleNewBlock, self, explorer.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;
|
var self = this;
|
||||||
if (!data || !data.txid) return;
|
if (!data || !data.txid) return;
|
||||||
|
|
||||||
|
@ -103,7 +123,7 @@ BlockchainMonitor.prototype._handleTxId = function(data, processIt) {
|
||||||
|
|
||||||
if (!processIt) {
|
if (!processIt) {
|
||||||
log.info('Detected broadcast ' + data.txid + ' of an accepted txp [' + txp.id + '] for wallet ' + walletId + ' [' + txp.amount + 'sat ]');
|
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 ]');
|
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._handleIncomingPayments = function(coin, network, data) {
|
||||||
|
|
||||||
BlockchainMonitor.prototype._handleTxOuts = function(data) {
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (!data || !data.vout) return;
|
if (!data || !data.vout) return;
|
||||||
|
|
||||||
var outs = _.compact(_.map(data.vout, function(v) {
|
var outs = _.compact(_.map(data.vout, function(v) {
|
||||||
var addr = _.keys(v)[0];
|
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 {
|
return {
|
||||||
address: addr,
|
address: addr,
|
||||||
amount: +v[addr]
|
amount: amount,
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
if (_.isEmpty(outs)) return;
|
if (_.isEmpty(outs)) return;
|
||||||
|
|
||||||
async.each(outs, function(out, next) {
|
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) {
|
if (err) {
|
||||||
log.error('Could not fetch addresses from the db');
|
log.error('Could not fetch addresses from the db');
|
||||||
return next(err);
|
return next(err);
|
||||||
|
@ -160,18 +185,30 @@ BlockchainMonitor.prototype._handleTxOuts = function(data) {
|
||||||
var walletId = address.walletId;
|
var walletId = address.walletId;
|
||||||
log.info('Incoming tx for wallet ' + walletId + ' [' + out.amount + 'sat -> ' + out.address + ']');
|
log.info('Incoming tx for wallet ' + walletId + ' [' + out.amount + 'sat -> ' + out.address + ']');
|
||||||
|
|
||||||
var notification = Notification.create({
|
var fromTs = Date.now() - 24 * 3600 * 1000;
|
||||||
type: 'NewIncomingTx',
|
self.storage.fetchNotifications(walletId, null, fromTs, function(err, notifications) {
|
||||||
data: {
|
if (err) return next(err);
|
||||||
txid: data.txid,
|
var alreadyNotified = _.any(notifications, function(n) {
|
||||||
address: out.address,
|
return n.type == 'NewIncomingTx' && n.data && n.data.txid == data.txid;
|
||||||
amount: out.amount,
|
});
|
||||||
},
|
if (alreadyNotified) {
|
||||||
walletId: walletId,
|
log.info('The incoming tx ' + data.txid + ' was already notified');
|
||||||
});
|
return next();
|
||||||
self.storage.softResetTxHistoryCache(walletId, function() {
|
}
|
||||||
self._updateActiveAddresses(address, function() {
|
|
||||||
self._storeAndBroadcastNotification(notification, next);
|
var notification = Notification.create({
|
||||||
|
type: 'NewIncomingTx',
|
||||||
|
data: {
|
||||||
|
txid: data.txid,
|
||||||
|
address: out.address,
|
||||||
|
amount: out.amount,
|
||||||
|
},
|
||||||
|
walletId: walletId,
|
||||||
|
});
|
||||||
|
self.storage.softResetTxHistoryCache(walletId, function() {
|
||||||
|
self._updateAddressesWithBalance(address, function() {
|
||||||
|
self._storeAndBroadcastNotification(notification, next);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -180,31 +217,47 @@ BlockchainMonitor.prototype._handleTxOuts = function(data) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockchainMonitor.prototype._updateActiveAddresses = function(address, cb) {
|
BlockchainMonitor.prototype._updateAddressesWithBalance = function(address, cb) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.storage.storeActiveAddresses(address.walletId, address.address, function(err) {
|
self.storage.fetchAddressesWithBalance(address.walletId, function(err, result) {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.warn('Could not update wallet cache', err);
|
log.warn('Could not update wallet cache', err);
|
||||||
|
return cb(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) {
|
BlockchainMonitor.prototype._handleIncomingTx = function(coin, network, data) {
|
||||||
this._handleTxId(data);
|
this._handleThirdPartyBroadcasts(data);
|
||||||
this._handleTxOuts(data);
|
this._handleIncomingPayments(coin, network, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockchainMonitor.prototype._handleNewBlock = function(network, hash) {
|
BlockchainMonitor.prototype._notifyNewBlock = function(coin, network, hash) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
log.info('New ' + network + ' block: ', hash);
|
log.info('New ' + network + ' block: ' + hash);
|
||||||
var notification = Notification.create({
|
var notification = Notification.create({
|
||||||
type: 'NewBlock',
|
type: 'NewBlock',
|
||||||
walletId: network, // use network name as wallet id for global notifications
|
walletId: network, // use network name as wallet id for global notifications
|
||||||
data: {
|
data: {
|
||||||
hash: hash,
|
hash: hash,
|
||||||
|
coin: coin,
|
||||||
network: network,
|
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) {
|
BlockchainMonitor.prototype._storeAndBroadcastNotification = function(notification, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,18 @@
|
||||||
|
|
||||||
var Constants = {};
|
var Constants = {};
|
||||||
|
|
||||||
|
Constants.COINS = {
|
||||||
|
BTC: 'btc',
|
||||||
|
BCH: 'bch',
|
||||||
|
};
|
||||||
|
|
||||||
Constants.NETWORKS = {
|
Constants.NETWORKS = {
|
||||||
LIVENET: 'livenet',
|
LIVENET: 'livenet',
|
||||||
TESTNET: 'testnet',
|
TESTNET: 'testnet',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Constants.ADDRESS_FORMATS = ['copay', 'cashaddr', 'legacy'];
|
||||||
|
|
||||||
Constants.SCRIPT_TYPES = {
|
Constants.SCRIPT_TYPES = {
|
||||||
P2SH: 'P2SH',
|
P2SH: 'P2SH',
|
||||||
P2PKH: 'P2PKH',
|
P2PKH: 'P2PKH',
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
var Defaults = {};
|
var Defaults = {};
|
||||||
|
|
||||||
Defaults.MIN_FEE_PER_KB = 0;
|
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.MIN_TX_FEE = 0;
|
||||||
Defaults.MAX_TX_FEE = 0.1 * 1e8;
|
Defaults.MAX_TX_FEE = 0.1 * 1e8;
|
||||||
Defaults.MAX_TX_SIZE_IN_KB = 100;
|
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
|
// TODO: should allow different gap sizes for external/internal chains
|
||||||
Defaults.SCAN_ADDRESS_GAP = Defaults.MAX_MAIN_ADDRESS_GAP + 20;
|
Defaults.SCAN_ADDRESS_GAP = Defaults.MAX_MAIN_ADDRESS_GAP + 20;
|
||||||
|
|
||||||
Defaults.FEE_LEVELS = [{
|
Defaults.FEE_LEVELS = {
|
||||||
name: 'priority',
|
btc: [{
|
||||||
nbBlocks: 2,
|
name: 'urgent',
|
||||||
defaultValue: 50000
|
nbBlocks: 2,
|
||||||
}, {
|
multiplier: 1.5,
|
||||||
name: 'normal',
|
defaultValue: 150000,
|
||||||
nbBlocks: 3,
|
}, {
|
||||||
defaultValue: 40000
|
name: 'priority',
|
||||||
}, {
|
nbBlocks: 2,
|
||||||
name: 'economy',
|
defaultValue: 100000
|
||||||
nbBlocks: 6,
|
}, {
|
||||||
defaultValue: 25000
|
name: 'normal',
|
||||||
}, {
|
nbBlocks: 3,
|
||||||
name: 'superEconomy',
|
defaultValue: 80000
|
||||||
nbBlocks: 24,
|
}, {
|
||||||
defaultValue: 10000
|
name: 'economy',
|
||||||
}];
|
nbBlocks: 6,
|
||||||
|
defaultValue: 50000
|
||||||
Defaults.DEFAULT_FEE_PER_KB = Defaults.FEE_LEVELS[1].defaultValue;
|
}, {
|
||||||
|
name: 'superEconomy',
|
||||||
|
nbBlocks: 24,
|
||||||
|
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
|
// How many levels to fallback to if the value returned by the network for a given nbBlocks is -1
|
||||||
Defaults.FEE_LEVELS_FALLBACK = 2;
|
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
|
// Minimum nb of addresses a wallet must have to start using 2-step balance optimization
|
||||||
Defaults.TWO_STEP_BALANCE_THRESHOLD = 100;
|
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_PROVIDER = 'BitPay';
|
||||||
Defaults.FIAT_RATE_FETCH_INTERVAL = 10; // In minutes
|
Defaults.FIAT_RATE_FETCH_INTERVAL = 10; // In minutes
|
||||||
Defaults.FIAT_RATE_MAX_LOOK_BACK_TIME = 120; // In minutes
|
Defaults.FIAT_RATE_MAX_LOOK_BACK_TIME = 120; // In minutes
|
||||||
|
@ -73,13 +89,19 @@ Defaults.UTXO_SELECTION_MAX_FEE_VS_SINGLE_UTXO_FEE_FACTOR = 5;
|
||||||
// Minimum allowed amount for tx outputs (including change) in SAT
|
// Minimum allowed amount for tx outputs (including change) in SAT
|
||||||
Defaults.MIN_OUTPUT_AMOUNT = 5000;
|
Defaults.MIN_OUTPUT_AMOUNT = 5000;
|
||||||
|
|
||||||
// Number of confirmations from which tx in history will be cached
|
// Number of confirmations from which tx in history will be cached
|
||||||
// (ie we consider them inmutables)
|
// (ie we consider them inmutables)
|
||||||
Defaults.CONFIRMATIONS_TO_START_CACHING = 6 * 6; // ~ 6hrs
|
Defaults.CONFIRMATIONS_TO_START_CACHING = 6 * 6; // ~ 6hrs
|
||||||
|
|
||||||
// Number of addresses from which tx history is enabled in a wallet
|
// Number of addresses from which tx history is enabled in a wallet
|
||||||
Defaults.HISTORY_CACHE_ADDRESS_THRESOLD = 100;
|
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)
|
// Cache time for blockchain height (in seconds)
|
||||||
Defaults.BLOCKHEIGHT_CACHE_TIME = 10 * 60;
|
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.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;
|
module.exports = Defaults;
|
||||||
|
|
|
@ -7,6 +7,13 @@ var encoding = bitcore.encoding;
|
||||||
var secp256k1 = require('secp256k1');
|
var secp256k1 = require('secp256k1');
|
||||||
|
|
||||||
var Utils = {};
|
var Utils = {};
|
||||||
|
var Bitcore = require('bitcore-lib');
|
||||||
|
var Bitcore_ = {
|
||||||
|
btc: Bitcore,
|
||||||
|
bch: require('bitcore-lib-cash')
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Utils.getMissingFields = function(obj, args) {
|
Utils.getMissingFields = function(obj, args) {
|
||||||
args = [].concat(args);
|
args = [].concat(args);
|
||||||
|
@ -64,7 +71,7 @@ Utils._tryImportPublicKey = function(publicKey) {
|
||||||
publicKeyBuffer = new Buffer(publicKey, 'hex');
|
publicKeyBuffer = new Buffer(publicKey, 'hex');
|
||||||
}
|
}
|
||||||
return publicKeyBuffer;
|
return publicKeyBuffer;
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -76,7 +83,7 @@ Utils._tryImportSignature = function(signature) {
|
||||||
signatureBuffer = new Buffer(signature, 'hex');
|
signatureBuffer = new Buffer(signature, 'hex');
|
||||||
}
|
}
|
||||||
return secp256k1.signatureImport(signatureBuffer);
|
return secp256k1.signatureImport(signatureBuffer);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -84,7 +91,7 @@ Utils._tryImportSignature = function(signature) {
|
||||||
Utils._tryVerifyMessage = function(hash, sig, publicKeyBuffer) {
|
Utils._tryVerifyMessage = function(hash, sig, publicKeyBuffer) {
|
||||||
try {
|
try {
|
||||||
return secp256k1.verify(hash, sig, publicKeyBuffer);
|
return secp256k1.verify(hash, sig, publicKeyBuffer);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -105,7 +112,12 @@ Utils.formatAmount = function(satoshis, unit, opts) {
|
||||||
toSatoshis: 1,
|
toSatoshis: 1,
|
||||||
maxDecimals: 0,
|
maxDecimals: 0,
|
||||||
minDecimals: 0,
|
minDecimals: 0,
|
||||||
}
|
},
|
||||||
|
bch: {
|
||||||
|
toSatoshis: 100000000,
|
||||||
|
maxDecimals: 6,
|
||||||
|
minDecimals: 2,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
$.shouldBeNumber(satoshis);
|
$.shouldBeNumber(satoshis);
|
||||||
|
@ -176,5 +188,35 @@ Utils.parseVersion = function(version) {
|
||||||
return v;
|
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;
|
module.exports = Utils;
|
||||||
|
|
|
@ -11,6 +11,7 @@ var path = require('path');
|
||||||
var nodemailer = require('nodemailer');
|
var nodemailer = require('nodemailer');
|
||||||
|
|
||||||
var Utils = require('./common/utils');
|
var Utils = require('./common/utils');
|
||||||
|
var Defaults = require('./common/defaults');
|
||||||
var Storage = require('./storage');
|
var Storage = require('./storage');
|
||||||
var MessageBroker = require('./messagebroker');
|
var MessageBroker = require('./messagebroker');
|
||||||
var Lock = require('./lock');
|
var Lock = require('./lock');
|
||||||
|
@ -21,26 +22,37 @@ var EMAIL_TYPES = {
|
||||||
'NewCopayer': {
|
'NewCopayer': {
|
||||||
filename: 'new_copayer',
|
filename: 'new_copayer',
|
||||||
notifyDoer: false,
|
notifyDoer: false,
|
||||||
|
notifyOthers: true,
|
||||||
},
|
},
|
||||||
'WalletComplete': {
|
'WalletComplete': {
|
||||||
filename: 'wallet_complete',
|
filename: 'wallet_complete',
|
||||||
notifyDoer: true,
|
notifyDoer: true,
|
||||||
|
notifyOthers: true,
|
||||||
},
|
},
|
||||||
'NewTxProposal': {
|
'NewTxProposal': {
|
||||||
filename: 'new_tx_proposal',
|
filename: 'new_tx_proposal',
|
||||||
notifyDoer: false,
|
notifyDoer: false,
|
||||||
|
notifyOthers: true,
|
||||||
},
|
},
|
||||||
'NewOutgoingTx': {
|
'NewOutgoingTx': {
|
||||||
filename: 'new_outgoing_tx',
|
filename: 'new_outgoing_tx',
|
||||||
notifyDoer: true,
|
notifyDoer: true,
|
||||||
|
notifyOthers: true,
|
||||||
},
|
},
|
||||||
'NewIncomingTx': {
|
'NewIncomingTx': {
|
||||||
filename: 'new_incoming_tx',
|
filename: 'new_incoming_tx',
|
||||||
notifyDoer: true,
|
notifyDoer: true,
|
||||||
|
notifyOthers: true,
|
||||||
},
|
},
|
||||||
'TxProposalFinallyRejected': {
|
'TxProposalFinallyRejected': {
|
||||||
filename: 'txp_finally_rejected',
|
filename: 'txp_finally_rejected',
|
||||||
notifyDoer: false,
|
notifyDoer: false,
|
||||||
|
notifyOthers: true,
|
||||||
|
},
|
||||||
|
'TxConfirmation': {
|
||||||
|
filename: 'tx_confirmation',
|
||||||
|
notifyDoer: true,
|
||||||
|
notifyOthers: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -162,32 +174,44 @@ EmailService.prototype._applyTemplate = function(template, data, cb) {
|
||||||
EmailService.prototype._getRecipientsList = function(notification, emailType, cb) {
|
EmailService.prototype._getRecipientsList = function(notification, emailType, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
|
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (_.isEmpty(preferences)) return cb(null, []);
|
|
||||||
|
|
||||||
var usedEmails = {};
|
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
|
||||||
var recipients = _.compact(_.map(preferences, function(p) {
|
if (err) return cb(err);
|
||||||
if (!p.email || usedEmails[p.email]) return;
|
if (_.isEmpty(preferences)) return cb(null, []);
|
||||||
|
|
||||||
usedEmails[p.email] = true;
|
var usedEmails = {};
|
||||||
if (notification.creatorId == p.copayerId && !emailType.notifyDoer) return;
|
var recipients = _.compact(_.map(preferences, function(p) {
|
||||||
if (!_.contains(self.availableLanguages, p.language)) {
|
if (!p.email || usedEmails[p.email]) return;
|
||||||
if (p.language) {
|
|
||||||
log.warn('Language for email "' + p.language + '" not available.');
|
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.');
|
||||||
|
}
|
||||||
|
p.language = self.defaultLanguage;
|
||||||
}
|
}
|
||||||
p.language = self.defaultLanguage;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
var unit;
|
||||||
copayerId: p.copayerId,
|
if (wallet.coin != Defaults.COIN) {
|
||||||
emailAddress: p.email,
|
unit = wallet.coin;
|
||||||
language: p.language,
|
} else {
|
||||||
unit: p.unit || self.defaultUnit,
|
unit = p.unit || self.defaultUnit;
|
||||||
};
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
return cb(null, recipients);
|
return {
|
||||||
|
copayerId: p.copayerId,
|
||||||
|
emailAddress: p.email,
|
||||||
|
language: p.language,
|
||||||
|
unit: unit,
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
return cb(null, recipients);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -197,7 +221,8 @@ EmailService.prototype._getDataForTemplate = function(notification, recipient, c
|
||||||
// TODO: Declare these in BWU
|
// TODO: Declare these in BWU
|
||||||
var UNIT_LABELS = {
|
var UNIT_LABELS = {
|
||||||
btc: 'BTC',
|
btc: 'BTC',
|
||||||
bit: 'bits'
|
bit: 'bits',
|
||||||
|
bch: 'BCH',
|
||||||
};
|
};
|
||||||
|
|
||||||
var data = _.cloneDeep(notification.data);
|
var data = _.cloneDeep(notification.data);
|
||||||
|
|
|
@ -33,9 +33,10 @@ var errors = {
|
||||||
UPGRADE_NEEDED: 'Client app needs to be upgraded',
|
UPGRADE_NEEDED: 'Client app needs to be upgraded',
|
||||||
WALLET_ALREADY_EXISTS: 'Wallet already exists',
|
WALLET_ALREADY_EXISTS: 'Wallet already exists',
|
||||||
WALLET_FULL: 'Wallet full',
|
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_COMPLETE: 'Wallet is not complete',
|
||||||
WALLET_NOT_FOUND: 'Wallet not found',
|
WALLET_NOT_FOUND: 'Wallet not found',
|
||||||
|
WALLET_NEED_SCAN: 'Wallet needs addresses scan',
|
||||||
};
|
};
|
||||||
|
|
||||||
var errorObjects = _.zipObject(_.map(errors, function(msg, code) {
|
var errorObjects = _.zipObject(_.map(errors, function(msg, code) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ var log = require('npmlog');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var bodyParser = require('body-parser');
|
var bodyParser = require('body-parser');
|
||||||
var compression = require('compression');
|
var compression = require('compression');
|
||||||
|
var RateLimit = require('express-rate-limit');
|
||||||
|
|
||||||
var Common = require('./common');
|
var Common = require('./common');
|
||||||
var Defaults = Common.Defaults;
|
var Defaults = Common.Defaults;
|
||||||
|
@ -16,7 +17,7 @@ var Stats = require('./stats');
|
||||||
|
|
||||||
log.disableColor();
|
log.disableColor();
|
||||||
log.debug = log.verbose;
|
log.debug = log.verbose;
|
||||||
log.level = 'info';
|
log.level = 'verbose';
|
||||||
|
|
||||||
var ExpressApp = function() {
|
var ExpressApp = function() {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
|
@ -38,7 +39,7 @@ ExpressApp.prototype.start = function(opts, cb) {
|
||||||
this.app.use(function(req, res, next) {
|
this.app.use(function(req, res, next) {
|
||||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
|
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());
|
res.setHeader('x-service-version', WalletService.getServiceVersion());
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
@ -53,6 +54,16 @@ ExpressApp.prototype.start = function(opts, cb) {
|
||||||
this.app.use(allowCORS);
|
this.app.use(allowCORS);
|
||||||
this.app.enable('trust proxy');
|
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 */ ;
|
var POST_LIMIT = 1024 * 100 /* Max POST 100 kb */ ;
|
||||||
|
|
||||||
this.app.use(bodyParser.json({
|
this.app.use(bodyParser.json({
|
||||||
|
@ -64,14 +75,10 @@ ExpressApp.prototype.start = function(opts, cb) {
|
||||||
} else {
|
} else {
|
||||||
var morgan = require('morgan');
|
var morgan = require('morgan');
|
||||||
morgan.token('walletId', function getId(req) {
|
morgan.token('walletId', function getId(req) {
|
||||||
return req.walletId
|
return req.walletId ? '<' + req.walletId + '>' : '<>';
|
||||||
});
|
});
|
||||||
|
|
||||||
morgan.token('copayerId', function getId(req) {
|
var logFormat = ':walletId :remote-addr :date[iso] ":method :url" :status :res[content-length] :response-time ":user-agent" ';
|
||||||
return req.copayerId
|
|
||||||
});
|
|
||||||
|
|
||||||
var logFormat = ':remote-addr :date[iso] ":method :url" :status :res[content-length] :response-time ":user-agent" :walletId :copayerId';
|
|
||||||
var logOpts = {
|
var logOpts = {
|
||||||
skip: function(req, res) {
|
skip: function(req, res) {
|
||||||
if (res.statusCode != 200) return false;
|
if (res.statusCode != 200) return false;
|
||||||
|
@ -81,15 +88,15 @@ ExpressApp.prototype.start = function(opts, cb) {
|
||||||
this.app.use(morgan(logFormat, logOpts));
|
this.app.use(morgan(logFormat, logOpts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
|
|
||||||
|
|
||||||
function returnError(err, res, req) {
|
function returnError(err, res, req) {
|
||||||
if (err instanceof WalletService.ClientError) {
|
if (err instanceof WalletService.ClientError) {
|
||||||
|
|
||||||
var status = (err.code == 'NOT_AUTHORIZED') ? 401 : 400;
|
var status = (err.code == 'NOT_AUTHORIZED') ? 401 : 400;
|
||||||
if (!opts.disableLogs)
|
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({
|
res.status(status).json({
|
||||||
code: err.code,
|
code: err.code,
|
||||||
|
@ -154,6 +161,7 @@ ExpressApp.prototype.start = function(opts, cb) {
|
||||||
message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body),
|
message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body),
|
||||||
signature: credentials.signature,
|
signature: credentials.signature,
|
||||||
clientVersion: req.header('x-client-version'),
|
clientVersion: req.header('x-client-version'),
|
||||||
|
walletId: req.header('x-wallet-id'),
|
||||||
};
|
};
|
||||||
if (opts.allowSession) {
|
if (opts.allowSession) {
|
||||||
auth.session = credentials.session;
|
auth.session = credentials.session;
|
||||||
|
@ -161,16 +169,34 @@ ExpressApp.prototype.start = function(opts, cb) {
|
||||||
WalletService.getInstanceWithAuth(auth, function(err, server) {
|
WalletService.getInstanceWithAuth(auth, function(err, server) {
|
||||||
if (err) return returnError(err, res, req);
|
if (err) return returnError(err, res, req);
|
||||||
|
|
||||||
|
if (opts.onlySupportStaff && !server.copayerIsSupportStaff) {
|
||||||
|
return returnError(new WalletService.ClientError({
|
||||||
|
code: 'NOT_AUTHORIZED'
|
||||||
|
}), res, req);
|
||||||
|
}
|
||||||
|
|
||||||
// For logging
|
// For logging
|
||||||
req.walletId = server.walletId;
|
req.walletId = server.walletId;
|
||||||
req.copayerId = server.copayerId;
|
req.copayerId = server.copayerId;
|
||||||
|
|
||||||
return cb(server);
|
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
|
// DEPRECATED
|
||||||
router.post('/v1/wallets/', function(req, res) {
|
router.post('/v1/wallets/', createWalletLimiter, function(req, res) {
|
||||||
logDeprecated(req);
|
logDeprecated(req);
|
||||||
var server;
|
var server;
|
||||||
try {
|
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;
|
var server;
|
||||||
try {
|
try {
|
||||||
server = getServer(req, res);
|
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) {
|
router.get('/v1/preferences/', function(req, res) {
|
||||||
getServerWithAuth(req, res, function(server) {
|
getServerWithAuth(req, res, function(server) {
|
||||||
server.getPreferences({}, function(err, preferences) {
|
server.getPreferences({}, function(err, preferences) {
|
||||||
|
@ -368,6 +417,7 @@ ExpressApp.prototype.start = function(opts, cb) {
|
||||||
router.get('/v1/balance/', function(req, res) {
|
router.get('/v1/balance/', function(req, res) {
|
||||||
getServerWithAuth(req, res, function(server) {
|
getServerWithAuth(req, res, function(server) {
|
||||||
var opts = {};
|
var opts = {};
|
||||||
|
if (req.query.coin) opts.coin = req.query.coin;
|
||||||
if (req.query.twoStep == '1') opts.twoStep = true;
|
if (req.query.twoStep == '1') opts.twoStep = true;
|
||||||
server.getBalance(opts, function(err, balance) {
|
server.getBalance(opts, function(err, balance) {
|
||||||
if (err) return returnError(err, res, req);
|
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
|
// DEPRECATED
|
||||||
router.get('/v1/feelevels/', function(req, res) {
|
router.get('/v1/feelevels/', estimateFeeLimiter, function(req, res) {
|
||||||
logDeprecated(req);
|
logDeprecated(req);
|
||||||
var opts = {};
|
var opts = {};
|
||||||
if (req.query.network) opts.network = req.query.network;
|
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 = {};
|
var opts = {};
|
||||||
|
if (req.query.coin) opts.coin = req.query.coin;
|
||||||
if (req.query.network) opts.network = req.query.network;
|
if (req.query.network) opts.network = req.query.network;
|
||||||
|
|
||||||
var server;
|
var server;
|
||||||
try {
|
try {
|
||||||
server = getServer(req, res);
|
server = getServer(req, res);
|
||||||
|
@ -546,6 +611,7 @@ ExpressApp.prototype.start = function(opts, cb) {
|
||||||
router.get('/v1/stats/', function(req, res) {
|
router.get('/v1/stats/', function(req, res) {
|
||||||
var opts = {};
|
var opts = {};
|
||||||
if (req.query.network) opts.network = req.query.network;
|
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.from) opts.from = req.query.from;
|
||||||
if (req.query.to) opts.to = req.query.to;
|
if (req.query.to) opts.to = req.query.to;
|
||||||
|
|
||||||
|
@ -635,16 +701,20 @@ ExpressApp.prototype.start = function(opts, cb) {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/v1/fiatrates/:code/', function(req, res) {
|
router.get('/v1/fiatrates/:code/', function(req, res) {
|
||||||
getServerWithAuth(req, res, function(server) {
|
var server;
|
||||||
var opts = {
|
var opts = {
|
||||||
code: req.params['code'],
|
code: req.params['code'],
|
||||||
source: req.query.source,
|
provider: req.query.provider,
|
||||||
ts: +req.query.ts,
|
ts: +req.query.ts,
|
||||||
};
|
};
|
||||||
server.getFiatRate(opts, function(err, rates) {
|
try {
|
||||||
if (err) return returnError(err, res, req);
|
server = getServer(req, res);
|
||||||
res.json(rates);
|
} catch (ex) {
|
||||||
});
|
return returnError(ex, res, req);
|
||||||
|
}
|
||||||
|
server.getFiatRate(opts, function(err, rates) {
|
||||||
|
if (err) return returnError(err, res, req);
|
||||||
|
res.json(rates);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -657,9 +727,47 @@ ExpressApp.prototype.start = function(opts, cb) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// DEPRECATED
|
||||||
router.delete('/v1/pushnotifications/subscriptions/', function(req, res) {
|
router.delete('/v1/pushnotifications/subscriptions/', function(req, res) {
|
||||||
|
logDeprecated(req);
|
||||||
getServerWithAuth(req, res, function(server) {
|
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);
|
if (err) return returnError(err, res, req);
|
||||||
res.json(response);
|
res.json(response);
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,11 +27,13 @@ function Lock(opts) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Lock.prototype.runLocked = function(token, cb, task) {
|
Lock.prototype.runLocked = function(token, cb, task, waitTime) {
|
||||||
$.shouldBeDefined(token);
|
$.shouldBeDefined(token);
|
||||||
|
|
||||||
this.lock.locked(token, 5 * 1000, 5 * 60 * 1000, function(err, release) {
|
waitTime = waitTime || 5 * 1000;
|
||||||
if (err) return cb(Errors.WALLET_LOCKED);
|
|
||||||
|
this.lock.locked(token, waitTime , 5 * 60 * 1000, function(err, release) {
|
||||||
|
if (err) return cb(Errors.WALLET_BUSY);
|
||||||
var _cb = function() {
|
var _cb = function() {
|
||||||
cb.apply(null, arguments);
|
cb.apply(null, arguments);
|
||||||
release();
|
release();
|
||||||
|
|
|
@ -3,8 +3,14 @@
|
||||||
var $ = require('preconditions').singleton();
|
var $ = require('preconditions').singleton();
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
var Bitcore = require('bitcore-lib');
|
var Bitcore = {
|
||||||
var Constants = require('../common/constants');
|
'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() {};
|
function Address() {};
|
||||||
|
|
||||||
|
@ -13,6 +19,8 @@ Address.create = function(opts) {
|
||||||
|
|
||||||
var x = new Address();
|
var x = new Address();
|
||||||
|
|
||||||
|
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
|
||||||
|
|
||||||
x.version = '1.0.0';
|
x.version = '1.0.0';
|
||||||
x.createdOn = Math.floor(Date.now() / 1000);
|
x.createdOn = Math.floor(Date.now() / 1000);
|
||||||
x.address = opts.address;
|
x.address = opts.address;
|
||||||
|
@ -20,7 +28,8 @@ Address.create = function(opts) {
|
||||||
x.isChange = opts.isChange;
|
x.isChange = opts.isChange;
|
||||||
x.path = opts.path;
|
x.path = opts.path;
|
||||||
x.publicKeys = opts.publicKeys;
|
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.type = opts.type || Constants.SCRIPT_TYPES.P2SH;
|
||||||
x.hasActivity = undefined;
|
x.hasActivity = undefined;
|
||||||
return x;
|
return x;
|
||||||
|
@ -33,6 +42,7 @@ Address.fromObj = function(obj) {
|
||||||
x.createdOn = obj.createdOn;
|
x.createdOn = obj.createdOn;
|
||||||
x.address = obj.address;
|
x.address = obj.address;
|
||||||
x.walletId = obj.walletId;
|
x.walletId = obj.walletId;
|
||||||
|
x.coin = obj.coin || Defaults.COIN;
|
||||||
x.network = obj.network;
|
x.network = obj.network;
|
||||||
x.isChange = obj.isChange;
|
x.isChange = obj.isChange;
|
||||||
x.path = obj.path;
|
x.path = obj.path;
|
||||||
|
@ -42,22 +52,22 @@ Address.fromObj = function(obj) {
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
||||||
Address._deriveAddress = function(scriptType, publicKeyRing, path, m, network) {
|
Address._deriveAddress = function(scriptType, publicKeyRing, path, m, coin, network) {
|
||||||
$.checkArgument(_.contains(_.values(Constants.SCRIPT_TYPES), scriptType));
|
$.checkArgument(Utils.checkValueInCollection(scriptType, Constants.SCRIPT_TYPES));
|
||||||
|
|
||||||
var publicKeys = _.map(publicKeyRing, function(item) {
|
var publicKeys = _.map(publicKeyRing, function(item) {
|
||||||
var xpub = new Bitcore.HDPublicKey(item.xPubKey);
|
var xpub = new Bitcore[coin].HDPublicKey(item.xPubKey);
|
||||||
return xpub.derive(path).publicKey;
|
return xpub.deriveChild(path).publicKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
var bitcoreAddress;
|
var bitcoreAddress;
|
||||||
switch (scriptType) {
|
switch (scriptType) {
|
||||||
case Constants.SCRIPT_TYPES.P2SH:
|
case Constants.SCRIPT_TYPES.P2SH:
|
||||||
bitcoreAddress = Bitcore.Address.createMultisig(publicKeys, m, network);
|
bitcoreAddress = Bitcore[coin].Address.createMultisig(publicKeys, m, network);
|
||||||
break;
|
break;
|
||||||
case Constants.SCRIPT_TYPES.P2PKH:
|
case Constants.SCRIPT_TYPES.P2PKH:
|
||||||
$.checkState(_.isArray(publicKeys) && publicKeys.length == 1);
|
$.checkState(_.isArray(publicKeys) && publicKeys.length == 1);
|
||||||
bitcoreAddress = Bitcore.Address.fromPublicKey(publicKeys[0], network);
|
bitcoreAddress = Bitcore[coin].Address.fromPublicKey(publicKeys[0], network);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,9 +78,10 @@ Address._deriveAddress = function(scriptType, publicKeyRing, path, m, network) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Address.derive = function(walletId, scriptType, publicKeyRing, path, m, network, isChange) {
|
Address.derive = function(walletId, scriptType, publicKeyRing, path, m, coin, network, isChange) {
|
||||||
var raw = Address._deriveAddress(scriptType, publicKeyRing, path, m, network);
|
var raw = Address._deriveAddress(scriptType, publicKeyRing, path, m, coin, network);
|
||||||
return Address.create(_.extend(raw, {
|
return Address.create(_.extend(raw, {
|
||||||
|
coin: coin,
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
type: scriptType,
|
type: scriptType,
|
||||||
isChange: isChange,
|
isChange: isChange,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var $ = require('preconditions').singleton();
|
var $ = require('preconditions').singleton();
|
||||||
|
|
||||||
var Bitcore = require('bitcore-lib');
|
|
||||||
var Constants = require('../common/constants');
|
var Constants = require('../common/constants');
|
||||||
|
var Utils = require('../common/utils');
|
||||||
|
|
||||||
function AddressManager() {};
|
function AddressManager() {};
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ AddressManager.create = function(opts) {
|
||||||
|
|
||||||
x.version = 2;
|
x.version = 2;
|
||||||
x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
|
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.receiveAddressIndex = 0;
|
||||||
x.changeAddressIndex = 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) {
|
AddressManager.prototype.getCurrentAddressPath = function(isChange) {
|
||||||
return 'm/' +
|
return 'm/' +
|
||||||
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +
|
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +
|
||||||
|
|
|
@ -10,12 +10,16 @@ var Address = require('./address');
|
||||||
var AddressManager = require('./addressmanager');
|
var AddressManager = require('./addressmanager');
|
||||||
var Bitcore = require('bitcore-lib');
|
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() {};
|
function Copayer() {};
|
||||||
|
|
||||||
Copayer._xPubToCopayerId = function(xpub) {
|
Copayer._xPubToCopayerId = function(coin, xpub) {
|
||||||
var hash = sjcl.hash.sha256.hash(xpub);
|
var str = coin == Defaults.COIN ? xpub : coin + xpub;
|
||||||
|
var hash = sjcl.hash.sha256.hash(str);
|
||||||
return sjcl.codec.hex.fromBits(hash);
|
return sjcl.codec.hex.fromBits(hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,14 +29,17 @@ Copayer.create = function(opts) {
|
||||||
.checkArgument(opts.requestPubKey, 'Missing copayer request public key')
|
.checkArgument(opts.requestPubKey, 'Missing copayer request public key')
|
||||||
.checkArgument(opts.signature, 'Missing copayer request public key signature');
|
.checkArgument(opts.signature, 'Missing copayer request public key signature');
|
||||||
|
|
||||||
|
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
|
||||||
|
|
||||||
opts.copayerIndex = opts.copayerIndex || 0;
|
opts.copayerIndex = opts.copayerIndex || 0;
|
||||||
|
|
||||||
var x = new Copayer();
|
var x = new Copayer();
|
||||||
|
|
||||||
x.version = 2;
|
x.version = 2;
|
||||||
x.createdOn = Math.floor(Date.now() / 1000);
|
x.createdOn = Math.floor(Date.now() / 1000);
|
||||||
|
x.coin = opts.coin;
|
||||||
x.xPubKey = opts.xPubKey;
|
x.xPubKey = opts.xPubKey;
|
||||||
x.id = Copayer._xPubToCopayerId(x.xPubKey);
|
x.id = Copayer._xPubToCopayerId(opts.coin, x.xPubKey);
|
||||||
x.name = opts.name;
|
x.name = opts.name;
|
||||||
x.requestPubKey = opts.requestPubKey;
|
x.requestPubKey = opts.requestPubKey;
|
||||||
x.signature = opts.signature;
|
x.signature = opts.signature;
|
||||||
|
@ -59,6 +66,7 @@ Copayer.fromObj = function(obj) {
|
||||||
|
|
||||||
x.version = obj.version;
|
x.version = obj.version;
|
||||||
x.createdOn = obj.createdOn;
|
x.createdOn = obj.createdOn;
|
||||||
|
x.coin = obj.coin || Defaults.COIN;
|
||||||
x.id = obj.id;
|
x.id = obj.id;
|
||||||
x.name = obj.name;
|
x.name = obj.name;
|
||||||
x.xPubKey = obj.xPubKey;
|
x.xPubKey = obj.xPubKey;
|
||||||
|
@ -87,7 +95,7 @@ Copayer.prototype.createAddress = function(wallet, isChange) {
|
||||||
$.checkState(wallet.isComplete());
|
$.checkState(wallet.isComplete());
|
||||||
|
|
||||||
var path = this.addressManager.getNewAddressPath(isChange);
|
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;
|
return address;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,7 @@ Model.Preferences = require('./preferences');
|
||||||
Model.Email = require('./email');
|
Model.Email = require('./email');
|
||||||
Model.TxNote = require('./txnote');
|
Model.TxNote = require('./txnote');
|
||||||
Model.Session = require('./session');
|
Model.Session = require('./session');
|
||||||
|
Model.PushNotificationSub = require('./pushnotificationsub');
|
||||||
|
Model.TxConfirmationSub = require('./txconfirmationsub');
|
||||||
|
|
||||||
module.exports = Model;
|
module.exports = Model;
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -7,11 +7,15 @@ var log = require('npmlog');
|
||||||
log.debug = log.verbose;
|
log.debug = log.verbose;
|
||||||
log.disableColor();
|
log.disableColor();
|
||||||
|
|
||||||
var Bitcore = require('bitcore-lib');
|
var Bitcore = {
|
||||||
|
'btc': require('bitcore-lib'),
|
||||||
|
'bch': require('bitcore-lib-cash'),
|
||||||
|
};
|
||||||
|
|
||||||
var Common = require('../common');
|
var Common = require('../common');
|
||||||
var Constants = Common.Constants;
|
var Constants = Common.Constants,
|
||||||
var Defaults = Common.Defaults;
|
Defaults = Common.Defaults,
|
||||||
|
Utils = Common.Utils;
|
||||||
|
|
||||||
var TxProposalLegacy = require('./txproposal_legacy');
|
var TxProposalLegacy = require('./txproposal_legacy');
|
||||||
var TxProposalAction = require('./txproposalaction');
|
var TxProposalAction = require('./txproposalaction');
|
||||||
|
@ -21,6 +25,9 @@ function TxProposal() {};
|
||||||
TxProposal.create = function(opts) {
|
TxProposal.create = function(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
|
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
|
||||||
|
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
|
||||||
|
|
||||||
var x = new TxProposal();
|
var x = new TxProposal();
|
||||||
|
|
||||||
x.version = 3;
|
x.version = 3;
|
||||||
|
@ -30,6 +37,8 @@ TxProposal.create = function(opts) {
|
||||||
x.id = opts.id || Uuid.v4();
|
x.id = opts.id || Uuid.v4();
|
||||||
x.walletId = opts.walletId;
|
x.walletId = opts.walletId;
|
||||||
x.creatorId = opts.creatorId;
|
x.creatorId = opts.creatorId;
|
||||||
|
x.coin = opts.coin;
|
||||||
|
x.network = opts.network;
|
||||||
x.message = opts.message;
|
x.message = opts.message;
|
||||||
x.payProUrl = opts.payProUrl;
|
x.payProUrl = opts.payProUrl;
|
||||||
x.changeAddress = opts.changeAddress;
|
x.changeAddress = opts.changeAddress;
|
||||||
|
@ -51,15 +60,11 @@ TxProposal.create = function(opts) {
|
||||||
x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos;
|
x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos;
|
||||||
|
|
||||||
x.addressType = opts.addressType || (x.walletN > 1 ? Constants.SCRIPT_TYPES.P2SH : Constants.SCRIPT_TYPES.P2PKH);
|
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.customData = opts.customData;
|
||||||
|
|
||||||
x.amount = x.getTotalAmount();
|
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.setInputs(opts.inputs);
|
||||||
x.fee = opts.fee;
|
x.fee = opts.fee;
|
||||||
|
@ -79,6 +84,7 @@ TxProposal.fromObj = function(obj) {
|
||||||
x.id = obj.id;
|
x.id = obj.id;
|
||||||
x.walletId = obj.walletId;
|
x.walletId = obj.walletId;
|
||||||
x.creatorId = obj.creatorId;
|
x.creatorId = obj.creatorId;
|
||||||
|
x.coin = obj.coin || Defaults.COIN;
|
||||||
x.network = obj.network;
|
x.network = obj.network;
|
||||||
x.outputs = obj.outputs;
|
x.outputs = obj.outputs;
|
||||||
x.amount = obj.amount;
|
x.amount = obj.amount;
|
||||||
|
@ -136,9 +142,9 @@ TxProposal.prototype._updateStatus = function() {
|
||||||
TxProposal.prototype._buildTx = function() {
|
TxProposal.prototype._buildTx = function() {
|
||||||
var self = this;
|
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) {
|
switch (self.addressType) {
|
||||||
case Constants.SCRIPT_TYPES.P2SH:
|
case Constants.SCRIPT_TYPES.P2SH:
|
||||||
|
@ -155,7 +161,7 @@ TxProposal.prototype._buildTx = function() {
|
||||||
_.each(self.outputs, function(o) {
|
_.each(self.outputs, function(o) {
|
||||||
$.checkState(o.script || o.toAddress, 'Output should have either toAddress or script specified');
|
$.checkState(o.script || o.toAddress, 'Output should have either toAddress or script specified');
|
||||||
if (o.script) {
|
if (o.script) {
|
||||||
t.addOutput(new Bitcore.Transaction.Output({
|
t.addOutput(new Bitcore[self.coin].Transaction.Output({
|
||||||
script: o.script,
|
script: o.script,
|
||||||
satoshis: o.amount
|
satoshis: o.amount
|
||||||
}));
|
}));
|
||||||
|
@ -220,10 +226,6 @@ TxProposal.prototype.getBitcoreTx = function() {
|
||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.getNetworkName = function() {
|
|
||||||
return this.network;
|
|
||||||
};
|
|
||||||
|
|
||||||
TxProposal.prototype.getRawTx = function() {
|
TxProposal.prototype.getRawTx = function() {
|
||||||
var t = this.getBitcoreTx();
|
var t = this.getBitcoreTx();
|
||||||
|
|
||||||
|
@ -323,21 +325,23 @@ TxProposal.prototype.addAction = function(copayerId, type, comment, signatures,
|
||||||
TxProposal.prototype._addSignaturesToBitcoreTx = function(tx, signatures, xpub) {
|
TxProposal.prototype._addSignaturesToBitcoreTx = function(tx, signatures, xpub) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
var bitcore = Bitcore[self.coin];
|
||||||
|
|
||||||
if (signatures.length != this.inputs.length)
|
if (signatures.length != this.inputs.length)
|
||||||
throw new Error('Number of signatures does not match number of inputs');
|
throw new Error('Number of signatures does not match number of inputs');
|
||||||
|
|
||||||
var i = 0,
|
var i = 0,
|
||||||
x = new Bitcore.HDPublicKey(xpub);
|
x = new bitcore.HDPublicKey(xpub);
|
||||||
|
|
||||||
_.each(signatures, function(signatureHex) {
|
_.each(signatures, function(signatureHex) {
|
||||||
var input = self.inputs[i];
|
var input = self.inputs[i];
|
||||||
try {
|
try {
|
||||||
var signature = Bitcore.crypto.Signature.fromString(signatureHex);
|
var signature = bitcore.crypto.Signature.fromString(signatureHex);
|
||||||
var pub = x.derive(self.inputPaths[i]).publicKey;
|
var pub = x.deriveChild(self.inputPaths[i]).publicKey;
|
||||||
var s = {
|
var s = {
|
||||||
inputIndex: i,
|
inputIndex: i,
|
||||||
signature: signature,
|
signature: signature,
|
||||||
sigtype: Bitcore.crypto.Signature.SIGHASH_ALL,
|
sigtype: bitcore.crypto.Signature.SIGHASH_ALL | bitcore.crypto.Signature.SIGHASH_FORKID,
|
||||||
publicKey: pub,
|
publicKey: pub,
|
||||||
};
|
};
|
||||||
tx.inputs[i].addSignature(tx, s);
|
tx.inputs[i].addSignature(tx, s);
|
||||||
|
|
|
@ -54,6 +54,7 @@ TxProposal.fromObj = function(obj) {
|
||||||
return TxProposalAction.fromObj(action);
|
return TxProposalAction.fromObj(action);
|
||||||
});
|
});
|
||||||
x.outputOrder = obj.outputOrder;
|
x.outputOrder = obj.outputOrder;
|
||||||
|
x.coin = obj.coin || Defaults.COIN;
|
||||||
x.network = obj.network;
|
x.network = obj.network;
|
||||||
x.fee = obj.fee;
|
x.fee = obj.fee;
|
||||||
x.feePerKb = obj.feePerKb;
|
x.feePerKb = obj.feePerKb;
|
||||||
|
@ -93,10 +94,6 @@ TxProposal.prototype.getBitcoreTx = function() {
|
||||||
throwUnsupportedError();
|
throwUnsupportedError();
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.getNetworkName = function() {
|
|
||||||
return Bitcore.Address(this.changeAddress.address).toObject().network;
|
|
||||||
};
|
|
||||||
|
|
||||||
TxProposal.prototype.getRawTx = function() {
|
TxProposal.prototype.getRawTx = function() {
|
||||||
throwUnsupportedError();
|
throwUnsupportedError();
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
var log = require('npmlog');
|
||||||
var $ = require('preconditions').singleton();
|
var $ = require('preconditions').singleton();
|
||||||
var Uuid = require('uuid');
|
var Uuid = require('uuid');
|
||||||
|
|
||||||
|
@ -9,7 +10,10 @@ var Address = require('./address');
|
||||||
var Copayer = require('./copayer');
|
var Copayer = require('./copayer');
|
||||||
var AddressManager = require('./addressmanager');
|
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() {};
|
function Wallet() {};
|
||||||
|
|
||||||
|
@ -20,6 +24,8 @@ Wallet.create = function(opts) {
|
||||||
|
|
||||||
$.shouldBeNumber(opts.m);
|
$.shouldBeNumber(opts.m);
|
||||||
$.shouldBeNumber(opts.n);
|
$.shouldBeNumber(opts.n);
|
||||||
|
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
|
||||||
|
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
|
||||||
|
|
||||||
x.version = '1.0.0';
|
x.version = '1.0.0';
|
||||||
x.createdOn = Math.floor(Date.now() / 1000);
|
x.createdOn = Math.floor(Date.now() / 1000);
|
||||||
|
@ -33,6 +39,7 @@ Wallet.create = function(opts) {
|
||||||
x.addressIndex = 0;
|
x.addressIndex = 0;
|
||||||
x.copayers = [];
|
x.copayers = [];
|
||||||
x.pubKey = opts.pubKey;
|
x.pubKey = opts.pubKey;
|
||||||
|
x.coin = opts.coin;
|
||||||
x.network = opts.network;
|
x.network = opts.network;
|
||||||
x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
|
x.derivationStrategy = opts.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
|
||||||
x.addressType = opts.addressType || Constants.SCRIPT_TYPES.P2SH;
|
x.addressType = opts.addressType || Constants.SCRIPT_TYPES.P2SH;
|
||||||
|
@ -64,6 +71,7 @@ Wallet.fromObj = function(obj) {
|
||||||
return Copayer.fromObj(copayer);
|
return Copayer.fromObj(copayer);
|
||||||
});
|
});
|
||||||
x.pubKey = obj.pubKey;
|
x.pubKey = obj.pubKey;
|
||||||
|
x.coin = obj.coin || Defaults.COIN;
|
||||||
x.network = obj.network;
|
x.network = obj.network;
|
||||||
x.derivationStrategy = obj.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
|
x.derivationStrategy = obj.derivationStrategy || Constants.DERIVATION_STRATEGIES.BIP45;
|
||||||
x.addressType = obj.addressType || Constants.SCRIPT_TYPES.P2SH;
|
x.addressType = obj.addressType || Constants.SCRIPT_TYPES.P2SH;
|
||||||
|
@ -105,6 +113,7 @@ Wallet.prototype._updatePublicKeyRing = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
Wallet.prototype.addCopayer = function(copayer) {
|
Wallet.prototype.addCopayer = function(copayer) {
|
||||||
|
$.checkState(copayer.coin == this.coin);
|
||||||
|
|
||||||
this.copayers.push(copayer);
|
this.copayers.push(copayer);
|
||||||
if (this.copayers.length < this.n) return;
|
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() {
|
Wallet.prototype.isComplete = function() {
|
||||||
return this.status == 'complete';
|
return this.status == 'complete';
|
||||||
};
|
};
|
||||||
|
@ -152,7 +157,8 @@ Wallet.prototype.createAddress = function(isChange) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var path = this.addressManager.getNewAddressPath(isChange);
|
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;
|
return address;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ var Storage = require('./storage');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var Utils = require('./common/utils');
|
var Utils = require('./common/utils');
|
||||||
|
var Defaults = require('./common/defaults');
|
||||||
var Model = require('./model');
|
var Model = require('./model');
|
||||||
var sjcl = require('sjcl');
|
var sjcl = require('sjcl');
|
||||||
var log = require('npmlog');
|
var log = require('npmlog');
|
||||||
|
@ -33,6 +34,10 @@ var PUSHNOTIFICATIONS_TYPES = {
|
||||||
'TxProposalFinallyRejected': {
|
'TxProposalFinallyRejected': {
|
||||||
filename: 'txp_finally_rejected',
|
filename: 'txp_finally_rejected',
|
||||||
},
|
},
|
||||||
|
'TxConfirmation': {
|
||||||
|
filename: 'tx_confirmation',
|
||||||
|
notifyCreatorOnly: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function PushNotificationsService() {};
|
function PushNotificationsService() {};
|
||||||
|
@ -60,6 +65,10 @@ PushNotificationsService.prototype.start = function(opts, cb) {
|
||||||
self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc';
|
self.defaultUnit = opts.pushNotificationsOpts.defaultUnit || 'btc';
|
||||||
self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || '';
|
self.subjectPrefix = opts.pushNotificationsOpts.subjectPrefix || '';
|
||||||
self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl;
|
self.pushServerUrl = opts.pushNotificationsOpts.pushServerUrl;
|
||||||
|
self.authorizationKey = opts.pushNotificationsOpts.authorizationKey;
|
||||||
|
|
||||||
|
if (!self.authorizationKey) return cb(new Error('Missing authorizationKey attribute in configuration.'))
|
||||||
|
|
||||||
async.parallel([
|
async.parallel([
|
||||||
|
|
||||||
function(done) {
|
function(done) {
|
||||||
|
@ -107,7 +116,7 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio
|
||||||
log.debug('Should send notification: ', should);
|
log.debug('Should send notification: ', should);
|
||||||
if (!should) return cb();
|
if (!should) return cb();
|
||||||
|
|
||||||
self._getRecipientsList(notification, function(err, recipientsList) {
|
self._getRecipientsList(notification, notifType, function(err, recipientsList) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
|
@ -117,34 +126,40 @@ PushNotificationsService.prototype._sendPushNotifications = function(notificatio
|
||||||
},
|
},
|
||||||
function(contents, next) {
|
function(contents, next) {
|
||||||
async.map(recipientsList, function(recipient, next) {
|
async.map(recipientsList, function(recipient, next) {
|
||||||
var opts = {};
|
|
||||||
var content = contents[recipient.language];
|
var content = contents[recipient.language];
|
||||||
opts.users = [notification.walletId + '$' + recipient.copayerId];
|
|
||||||
opts.android = {
|
self.storage.fetchPushNotificationSubs(recipient.copayerId, function(err, subs) {
|
||||||
"data": {
|
if (err) return next(err);
|
||||||
"title": content.plain.subject,
|
|
||||||
"message": content.plain.body,
|
var notifications = _.map(subs, function(sub) {
|
||||||
"walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)),
|
return {
|
||||||
"notId": Math.floor(Math.random() * 100000) + 1
|
to: sub.token,
|
||||||
}
|
priority: 'high',
|
||||||
};
|
restricted_package_name: sub.packageName,
|
||||||
opts.ios = {
|
notification: {
|
||||||
"alert": {
|
title: content.plain.subject,
|
||||||
"title": content.plain.subject,
|
body: content.plain.body,
|
||||||
"body": content.plain.body
|
sound: "default",
|
||||||
},
|
click_action: "FCM_PLUGIN_ACTIVITY",
|
||||||
"sound": "default",
|
icon: "fcm_push_icon",
|
||||||
"payload": {
|
},
|
||||||
"walletId": sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId))
|
data: {
|
||||||
}
|
walletId: sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(notification.walletId)),
|
||||||
};
|
copayerId: sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(recipient.copayerId))
|
||||||
return next(err, opts);
|
},
|
||||||
}, next);
|
};
|
||||||
|
});
|
||||||
|
return next(err, notifications);
|
||||||
|
});
|
||||||
|
}, function(err, allNotifications) {
|
||||||
|
if (err) return next(err);
|
||||||
|
return next(null, _.flatten(allNotifications));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
function(optsList, next) {
|
function(notifications, next) {
|
||||||
async.each(optsList,
|
async.each(notifications,
|
||||||
function(opts, next) {
|
function(notification, next) {
|
||||||
self._makeRequest(opts, function(err, response) {
|
self._makeRequest(notification, function(err, response) {
|
||||||
if (err) log.error(err);
|
if (err) log.error(err);
|
||||||
if (response) {
|
if (response) {
|
||||||
log.debug('Request status: ', response.statusCode);
|
log.debug('Request status: ', response.statusCode);
|
||||||
|
@ -174,16 +189,21 @@ PushNotificationsService.prototype._checkShouldSendNotif = function(notification
|
||||||
|
|
||||||
if (notification.type != 'NewTxProposal') return cb(null, true);
|
if (notification.type != 'NewTxProposal') return cb(null, true);
|
||||||
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
|
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;
|
var self = this;
|
||||||
|
|
||||||
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
|
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
var unit;
|
||||||
|
if (wallet.coin != Defaults.COIN) {
|
||||||
|
unit = wallet.coin;
|
||||||
|
}
|
||||||
|
|
||||||
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
|
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
|
||||||
|
|
||||||
if (err) log.error(err);
|
if (err) log.error(err);
|
||||||
|
@ -200,22 +220,23 @@ PushNotificationsService.prototype._getRecipientsList = function(notification, c
|
||||||
return {
|
return {
|
||||||
copayerId: p.copayerId,
|
copayerId: p.copayerId,
|
||||||
language: p.language,
|
language: p.language,
|
||||||
unit: p.unit,
|
unit: unit || p.unit || self.defaultUnit,
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
recipientPreferences = _.indexBy(recipientPreferences, 'copayerId');
|
recipientPreferences = _.indexBy(recipientPreferences, 'copayerId');
|
||||||
|
|
||||||
var recipientsList = _.reject(_.map(wallet.copayers, function(copayer) {
|
var recipientsList = _.compact(_.map(wallet.copayers, function(copayer) {
|
||||||
var p = recipientPreferences[copayer.id] || {};
|
if ((copayer.id == notification.creatorId && notificationType.notifyCreatorOnly) ||
|
||||||
return {
|
(copayer.id != notification.creatorId && !notificationType.notifyCreatorOnly)) {
|
||||||
copayerId: copayer.id,
|
var p = recipientPreferences[copayer.id] || {};
|
||||||
language: p.language || self.defaultLanguage,
|
return {
|
||||||
unit: p.unit || self.defaultUnit,
|
copayerId: copayer.id,
|
||||||
|
language: p.language || self.defaultLanguage,
|
||||||
|
unit: unit || p.unit || self.defaultUnit,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}), {
|
}));
|
||||||
copayerId: notification.creatorId
|
|
||||||
});
|
|
||||||
|
|
||||||
return cb(null, recipientsList);
|
return cb(null, recipientsList);
|
||||||
});
|
});
|
||||||
|
@ -260,7 +281,8 @@ PushNotificationsService.prototype._getDataForTemplate = function(notification,
|
||||||
var self = this;
|
var self = this;
|
||||||
var UNIT_LABELS = {
|
var UNIT_LABELS = {
|
||||||
btc: 'BTC',
|
btc: 'BTC',
|
||||||
bit: 'bits'
|
bit: 'bits',
|
||||||
|
bch: 'BCH',
|
||||||
};
|
};
|
||||||
|
|
||||||
var data = _.cloneDeep(notification.data);
|
var data = _.cloneDeep(notification.data);
|
||||||
|
@ -275,7 +297,7 @@ PushNotificationsService.prototype._getDataForTemplate = function(notification,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
|
self.storage.fetchWallet(notification.walletId, function(err, wallet) {
|
||||||
if (err) return cb(err);
|
if (err || !wallet) return cb(err);
|
||||||
|
|
||||||
data.walletId = wallet.id;
|
data.walletId = wallet.id;
|
||||||
data.walletName = wallet.name;
|
data.walletName = wallet.name;
|
||||||
|
@ -360,10 +382,12 @@ PushNotificationsService.prototype._makeRequest = function(opts, cb) {
|
||||||
url: self.pushServerUrl + '/send',
|
url: self.pushServerUrl + '/send',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
json: true,
|
json: true,
|
||||||
body: opts
|
headers: {
|
||||||
}, function(err, response) {
|
'Content-Type': 'application/json',
|
||||||
return cb(err, response);
|
'Authorization': 'key=' + self.authorizationKey,
|
||||||
});
|
},
|
||||||
|
body: opts,
|
||||||
|
}, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = PushNotificationsService;
|
module.exports = PushNotificationsService;
|
||||||
|
|
1182
lib/server.js
1182
lib/server.js
File diff suppressed because it is too large
Load Diff
15
lib/stats.js
15
lib/stats.js
|
@ -15,12 +15,13 @@ var config = require('../config');
|
||||||
var storage = require('./storage');
|
var storage = require('./storage');
|
||||||
|
|
||||||
|
|
||||||
var INITIAL_DATE = '2015-01-01';
|
var INITIAL_DATE = '2018-01-01';
|
||||||
|
|
||||||
function Stats(opts) {
|
function Stats(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
this.network = opts.network || 'livenet';
|
this.network = opts.network || 'livenet';
|
||||||
|
this.coin = opts.coin || 'btc';
|
||||||
this.from = moment(opts.from || INITIAL_DATE);
|
this.from = moment(opts.from || INITIAL_DATE);
|
||||||
this.to = moment(opts.to);
|
this.to = moment(opts.to);
|
||||||
this.fromTs = this.from.startOf('day').valueOf();
|
this.fromTs = this.from.startOf('day').valueOf();
|
||||||
|
@ -70,13 +71,13 @@ Stats.prototype._getNewWallets = function(cb) {
|
||||||
|
|
||||||
function getLastDate(cb) {
|
function getLastDate(cb) {
|
||||||
self.db.collection('stats_wallets')
|
self.db.collection('stats_wallets')
|
||||||
.find({})
|
.find({'_id.coin': self.coin})
|
||||||
.sort({
|
.sort({
|
||||||
'_id.day': -1
|
'_id.day': -1
|
||||||
})
|
})
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.toArray(function(err, lastRecord) {
|
.toArray(function(err, lastRecord) {
|
||||||
if (_.isEmpty(lastRecord)) return cb(null, moment(INITIAL_DATE));
|
if (_.isEmpty(lastRecord)) return cb(null, moment(INITIAL_DATE));
|
||||||
return cb(null, moment(lastRecord[0]._id.day));
|
return cb(null, moment(lastRecord[0]._id.day));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -91,6 +92,7 @@ Stats.prototype._getNewWallets = function(cb) {
|
||||||
var key = {
|
var key = {
|
||||||
day: +day,
|
day: +day,
|
||||||
network: this.network,
|
network: this.network,
|
||||||
|
coin: this.coin
|
||||||
};
|
};
|
||||||
var value = {
|
var value = {
|
||||||
count: 1
|
count: 1
|
||||||
|
@ -127,6 +129,7 @@ Stats.prototype._getNewWallets = function(cb) {
|
||||||
self.db.collection('stats_wallets')
|
self.db.collection('stats_wallets')
|
||||||
.find({
|
.find({
|
||||||
'_id.network': self.network,
|
'_id.network': self.network,
|
||||||
|
'_id.coin': self.coin,
|
||||||
'_id.day': {
|
'_id.day': {
|
||||||
$gte: self.fromTs,
|
$gte: self.fromTs,
|
||||||
$lte: self.toTs,
|
$lte: self.toTs,
|
||||||
|
@ -142,6 +145,7 @@ Stats.prototype._getNewWallets = function(cb) {
|
||||||
var day = moment(record._id.day).format('YYYYMMDD');
|
var day = moment(record._id.day).format('YYYYMMDD');
|
||||||
return {
|
return {
|
||||||
day: day,
|
day: day,
|
||||||
|
coin: record._id.coin,
|
||||||
count: record.value.count,
|
count: record.value.count,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -181,7 +185,7 @@ Stats.prototype._getTxProposals = function(cb) {
|
||||||
|
|
||||||
function getLastDate(cb) {
|
function getLastDate(cb) {
|
||||||
self.db.collection('stats_txps')
|
self.db.collection('stats_txps')
|
||||||
.find({})
|
.find({'_id.coin': self.coin })
|
||||||
.sort({
|
.sort({
|
||||||
'_id.day': -1
|
'_id.day': -1
|
||||||
})
|
})
|
||||||
|
@ -202,6 +206,7 @@ Stats.prototype._getTxProposals = function(cb) {
|
||||||
var key = {
|
var key = {
|
||||||
day: +day,
|
day: +day,
|
||||||
network: this.network,
|
network: this.network,
|
||||||
|
coin: this.coin
|
||||||
};
|
};
|
||||||
var value = {
|
var value = {
|
||||||
count: 1,
|
count: 1,
|
||||||
|
@ -243,6 +248,7 @@ Stats.prototype._getTxProposals = function(cb) {
|
||||||
self.db.collection('stats_txps')
|
self.db.collection('stats_txps')
|
||||||
.find({
|
.find({
|
||||||
'_id.network': self.network,
|
'_id.network': self.network,
|
||||||
|
'_id.coin': self.coin,
|
||||||
'_id.day': {
|
'_id.day': {
|
||||||
$gte: self.fromTs,
|
$gte: self.fromTs,
|
||||||
$lte: self.toTs,
|
$lte: self.toTs,
|
||||||
|
@ -262,6 +268,7 @@ Stats.prototype._getTxProposals = function(cb) {
|
||||||
var day = moment(record._id.day).format('YYYYMMDD');
|
var day = moment(record._id.day).format('YYYYMMDD');
|
||||||
stats.nbByDay.push({
|
stats.nbByDay.push({
|
||||||
day: day,
|
day: day,
|
||||||
|
coin: record._id.coin,
|
||||||
count: record.value.count,
|
count: record.value.count,
|
||||||
});
|
});
|
||||||
stats.amountByDay.push({
|
stats.amountByDay.push({
|
||||||
|
|
467
lib/storage.js
467
lib/storage.js
|
@ -7,7 +7,7 @@ var log = require('npmlog');
|
||||||
log.debug = log.verbose;
|
log.debug = log.verbose;
|
||||||
log.disableColor();
|
log.disableColor();
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
var Bitcore = require('bitcore-lib');
|
||||||
var mongodb = require('mongodb');
|
var mongodb = require('mongodb');
|
||||||
|
|
||||||
var Model = require('./model');
|
var Model = require('./model');
|
||||||
|
@ -24,6 +24,8 @@ var collections = {
|
||||||
FIAT_RATES: 'fiat_rates',
|
FIAT_RATES: 'fiat_rates',
|
||||||
TX_NOTES: 'tx_notes',
|
TX_NOTES: 'tx_notes',
|
||||||
SESSIONS: 'sessions',
|
SESSIONS: 'sessions',
|
||||||
|
PUSH_NOTIFICATION_SUBS: 'push_notification_subs',
|
||||||
|
TX_CONFIRMATION_SUBS: 'tx_confirmation_subs',
|
||||||
};
|
};
|
||||||
|
|
||||||
var Storage = function(opts) {
|
var Storage = function(opts) {
|
||||||
|
@ -38,6 +40,9 @@ Storage.prototype._createIndexes = function() {
|
||||||
this.db.collection(collections.COPAYERS_LOOKUP).createIndex({
|
this.db.collection(collections.COPAYERS_LOOKUP).createIndex({
|
||||||
copayerId: 1
|
copayerId: 1
|
||||||
});
|
});
|
||||||
|
this.db.collection(collections.COPAYERS_LOOKUP).createIndex({
|
||||||
|
walletId: 1
|
||||||
|
});
|
||||||
this.db.collection(collections.TXS).createIndex({
|
this.db.collection(collections.TXS).createIndex({
|
||||||
walletId: 1,
|
walletId: 1,
|
||||||
id: 1,
|
id: 1,
|
||||||
|
@ -51,6 +56,9 @@ Storage.prototype._createIndexes = function() {
|
||||||
walletId: 1,
|
walletId: 1,
|
||||||
createdOn: -1,
|
createdOn: -1,
|
||||||
});
|
});
|
||||||
|
this.db.collection(collections.TXS).createIndex({
|
||||||
|
txid: 1
|
||||||
|
});
|
||||||
this.db.collection(collections.NOTIFICATIONS).createIndex({
|
this.db.collection(collections.NOTIFICATIONS).createIndex({
|
||||||
walletId: 1,
|
walletId: 1,
|
||||||
id: 1,
|
id: 1,
|
||||||
|
@ -62,6 +70,13 @@ Storage.prototype._createIndexes = function() {
|
||||||
this.db.collection(collections.ADDRESSES).createIndex({
|
this.db.collection(collections.ADDRESSES).createIndex({
|
||||||
address: 1,
|
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({
|
this.db.collection(collections.EMAIL_QUEUE).createIndex({
|
||||||
notificationId: 1,
|
notificationId: 1,
|
||||||
});
|
});
|
||||||
|
@ -74,6 +89,24 @@ Storage.prototype._createIndexes = function() {
|
||||||
walletId: 1,
|
walletId: 1,
|
||||||
txid: 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) {
|
Storage.prototype.connect = function(opts, cb) {
|
||||||
|
@ -107,6 +140,8 @@ Storage.prototype.disconnect = function(cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Storage.prototype.fetchWallet = function(id, cb) {
|
Storage.prototype.fetchWallet = function(id, cb) {
|
||||||
|
if (!this.db) return cb('not ready');
|
||||||
|
|
||||||
this.db.collection(collections.WALLETS).findOne({
|
this.db.collection(collections.WALLETS).findOne({
|
||||||
id: id
|
id: id
|
||||||
}, function(err, result) {
|
}, function(err, result) {
|
||||||
|
@ -195,6 +230,7 @@ Storage.prototype._completeTxData = function(walletId, txs, cb) {
|
||||||
// TODO: remove walletId from signature
|
// TODO: remove walletId from signature
|
||||||
Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
|
Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
if (!this.db) return cb();
|
||||||
|
|
||||||
this.db.collection(collections.TXS).findOne({
|
this.db.collection(collections.TXS).findOne({
|
||||||
id: txProposalId,
|
id: txProposalId,
|
||||||
|
@ -208,6 +244,7 @@ Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
|
||||||
|
|
||||||
Storage.prototype.fetchTxByHash = function(hash, cb) {
|
Storage.prototype.fetchTxByHash = function(hash, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
if (!this.db) return cb();
|
||||||
|
|
||||||
this.db.collection(collections.TXS).findOne({
|
this.db.collection(collections.TXS).findOne({
|
||||||
txid: hash,
|
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) {
|
Storage.prototype.countAddresses = function(walletId, cb) {
|
||||||
this.db.collection(collections.ADDRESSES).find({
|
this.db.collection(collections.ADDRESSES).find({
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
|
@ -449,6 +508,7 @@ Storage.prototype.storeAddress = function(address, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.db.collection(collections.ADDRESSES).update({
|
self.db.collection(collections.ADDRESSES).update({
|
||||||
|
walletId: address.walletId,
|
||||||
address: address.address
|
address: address.address
|
||||||
}, address, {
|
}, address, {
|
||||||
w: 1,
|
w: 1,
|
||||||
|
@ -460,37 +520,21 @@ Storage.prototype.storeAddressAndWallet = function(wallet, addresses, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var addresses = [].concat(addresses);
|
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).insert(addresses, {
|
||||||
self.db.collection(collections.ADDRESSES).findOne({
|
w: 1
|
||||||
address: address.address,
|
}, function(err) {
|
||||||
}, {
|
if (err) return cb(err);
|
||||||
walletId: true,
|
self.storeWallet(wallet, cb);
|
||||||
}, 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, {
|
|
||||||
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;
|
var self = this;
|
||||||
|
|
||||||
this.db.collection(collections.ADDRESSES).findOne({
|
this.db.collection(collections.ADDRESSES).findOne({
|
||||||
|
walletId: walletId,
|
||||||
address: address,
|
address: address,
|
||||||
}, function(err, result) {
|
}, function(err, result) {
|
||||||
if (err) return cb(err);
|
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) {
|
Storage.prototype.fetchPreferences = function(walletId, copayerId, cb) {
|
||||||
this.db.collection(collections.PREFERENCES).find({
|
this.db.collection(collections.PREFERENCES).find({
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
|
@ -563,53 +629,93 @@ Storage.prototype.fetchEmailByNotification = function(notificationId, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Storage.prototype.cleanActiveAddresses = function(walletId, cb) {
|
Storage.prototype.storeTwoStepCache = function(walletId, cacheStatus, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
self.db.collection(collections.CACHE).update( {
|
||||||
async.series([
|
walletId: walletId,
|
||||||
|
type: 'twoStep',
|
||||||
function(next) {
|
key: null,
|
||||||
self.db.collection(collections.CACHE).remove({
|
}, {
|
||||||
walletId: walletId,
|
"$set":
|
||||||
type: 'activeAddresses',
|
{
|
||||||
}, {
|
addressCount: cacheStatus.addressCount,
|
||||||
w: 1
|
lastEmpty: cacheStatus.lastEmpty,
|
||||||
}, next);
|
}
|
||||||
},
|
}, {
|
||||||
function(next) {
|
w: 1,
|
||||||
self.db.collection(collections.CACHE).insert({
|
upsert: true,
|
||||||
walletId: walletId,
|
|
||||||
type: 'activeAddresses',
|
|
||||||
key: null
|
|
||||||
}, {
|
|
||||||
w: 1
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
], cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
Storage.prototype.storeActiveAddresses = function(walletId, addresses, cb) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
async.each(addresses, function(address, next) {
|
|
||||||
var record = {
|
|
||||||
walletId: walletId,
|
|
||||||
type: 'activeAddresses',
|
|
||||||
key: address,
|
|
||||||
};
|
|
||||||
self.db.collection(collections.CACHE).update({
|
|
||||||
walletId: record.walletId,
|
|
||||||
type: record.type,
|
|
||||||
key: record.key,
|
|
||||||
}, record, {
|
|
||||||
w: 1,
|
|
||||||
upsert: true,
|
|
||||||
}, next);
|
|
||||||
}, cb);
|
}, 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
|
// -------- --------------------------- Total
|
||||||
// > Time >
|
// > Time >
|
||||||
// ^to <= ^from
|
// ^to <= ^from
|
||||||
// ^fwdIndex => ^end
|
// ^fwdIndex => ^end
|
||||||
Storage.prototype.getTxHistoryCache = function(walletId, from, to, cb) {
|
Storage.prototype.getTxHistoryCache = function(walletId, from, to, cb) {
|
||||||
|
@ -658,7 +764,7 @@ Storage.prototype.getTxHistoryCache = function(walletId, from, to, cb) {
|
||||||
return cb();
|
return cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
var txs = _.pluck(result, 'tx');
|
var txs = _.map(result, 'tx');
|
||||||
return cb(null, txs);
|
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) {
|
Storage.prototype.storeFiatRate = function(providerName, rates, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
@ -890,6 +979,79 @@ Storage.prototype.storeSession = function(session, cb) {
|
||||||
}, 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) {
|
Storage.prototype._dump = function(cb, fn) {
|
||||||
fn = fn || console.log;
|
fn = fn || console.log;
|
||||||
cb = cb || function() {};
|
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;
|
Storage.collections = collections;
|
||||||
module.exports = Storage;
|
module.exports = Storage;
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}New copayer
|
{{subjectPrefix}}New copayer
|
||||||
A new copayer just joined your wallet {{walletName}}.
|
A new copayer just joined your wallet.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}New payment received
|
{{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.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Payment sent
|
{{subjectPrefix}}Payment sent
|
||||||
A Payment of {{amount}} has been sent from your wallet {{walletName}}.
|
A Payment of {{amount}} has been sent from your wallet.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}New payment proposal
|
{{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.
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
{{subjectPrefix}} Transaction confirmed
|
||||||
|
The transaction you were waiting for has been confirmed.
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Payment proposal rejected
|
{{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.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Wallet complete
|
{{subjectPrefix}}Wallet complete
|
||||||
Your wallet {{walletName}} is complete.
|
Your wallet is complete.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Nuevo copayer
|
{{subjectPrefix}}Nuevo copayer
|
||||||
Un nuevo copayer ha ingresado a su monedero {{walletName}}.
|
Un nuevo copayer ha ingresado a su billetera.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Nuevo pago recibido
|
{{subjectPrefix}}Nuevo pago recibido
|
||||||
Un pago de {{amount}} fue recibido en su monedero {{walletName}}.
|
Un pago de {{amount}} fue recibido en su billetera.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Pago enviado
|
{{subjectPrefix}}Pago enviado
|
||||||
Un pago de {{amount}} ha sido enviado de su monedero {{walletName}}.
|
Un pago de {{amount}} ha sido enviado de su billetera.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Nueva propuesta de pago
|
{{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.
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
{{subjectPrefix}} Transacción confirmada
|
||||||
|
La transacción que estabas esperando se ha confirmado.
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Propuesta de pago rechazada
|
{{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.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Monedero completo
|
{{subjectPrefix}}Billetera completa
|
||||||
Su monedero {{walletName}} está completo.
|
Su billetera está completa.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Nouveau copayer
|
{{subjectPrefix}}Nouveau copayer
|
||||||
Un nouveau copayer vient de rejoindre votre portefeuille {{walletName}}.
|
Un nouveau copayer vient de rejoindre votre portefeuille.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Nouveau paiement reçu
|
{{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.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Paiement envoyé
|
{{subjectPrefix}}Paiement envoyé
|
||||||
Un paiement de {{amount}} a été envoyé de votre portefeuille {{walletName}}.
|
Un paiement de {{amount}} a été envoyé de votre portefeuille.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Nouvelle proposition de paiement
|
{{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.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Proposition de paiement rejetée
|
{{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.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
{{subjectPrefix}}Portefeuille terminé
|
{{subjectPrefix}}Portefeuille terminé
|
||||||
Votre portefeuille {{walletName}} est terminé.
|
Votre portefeuille est terminé.
|
||||||
|
|
39
package.json
39
package.json
|
@ -2,7 +2,8 @@
|
||||||
"name": "bitcore-wallet-service",
|
"name": "bitcore-wallet-service",
|
||||||
"description": "A service for Mutisig HD Bitcoin Wallets",
|
"description": "A service for Mutisig HD Bitcoin Wallets",
|
||||||
"author": "BitPay Inc",
|
"author": "BitPay Inc",
|
||||||
"version": "1.13.0",
|
"version": "2.4.0",
|
||||||
|
"licence": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
"copay",
|
"copay",
|
||||||
|
@ -12,20 +13,22 @@
|
||||||
"BWS"
|
"BWS"
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "git@github.com:bitpay/bitcore-wallet-service.git",
|
"url": "git@github.com:BTCPrivate/bitcore-wallet-service.git",
|
||||||
"type": "git"
|
"type": "git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/bitpay/bitcore-wallet-service/issues"
|
"url": "https://github.com/BTCPrivate/bitcore-wallet-service/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^0.9.2",
|
"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",
|
"body-parser": "^1.11.0",
|
||||||
"compression": "^1.6.2",
|
"compression": "^1.6.2",
|
||||||
"coveralls": "^2.11.2",
|
"coveralls": "^2.11.2",
|
||||||
"email-validator": "^1.0.1",
|
"email-validator": "^1.0.1",
|
||||||
"express": "^4.10.0",
|
"express": "^4.10.0",
|
||||||
|
"express-rate-limit": "^2.6.0",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
"json-stable-stringify": "^1.0.0",
|
"json-stable-stringify": "^1.0.0",
|
||||||
"locker": "^0.1.0",
|
"locker": "^0.1.0",
|
||||||
|
@ -52,13 +55,13 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "^1.9.1",
|
"chai": "^1.9.1",
|
||||||
"istanbul": "*",
|
"istanbul": "*",
|
||||||
"jsdoc": "^3.3.0-beta1",
|
"jsdoc": "^3.5.5",
|
||||||
"memdown": "^1.0.0",
|
"memdown": "^1.0.0",
|
||||||
"mocha": "^1.18.2",
|
"mocha": "^1.18.2",
|
||||||
"proxyquire": "^1.7.2",
|
"proxyquire": "^1.7.2",
|
||||||
"sinon": "1.10.3",
|
"sinon": "1.10.3",
|
||||||
"supertest": "*",
|
"supertest": "*",
|
||||||
"tingodb": "^0.3.4"
|
"tingodb": "^0.5.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "./start.sh",
|
"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"
|
"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",
|
"bitcoreNode": "./bitcorenode",
|
||||||
"contributors": [{
|
"contributors": [
|
||||||
"name": "Braydon Fuller",
|
{
|
||||||
"email": "braydon@bitpay.com"
|
"name": "Braydon Fuller",
|
||||||
}, {
|
"email": "braydon@bitpay.com"
|
||||||
"name": "Ivan Socolsky",
|
},
|
||||||
"email": "ivan@bitpay.com"
|
{
|
||||||
}, {
|
"name": "Ivan Socolsky",
|
||||||
"name": "Matias Alejo Garcia",
|
"email": "ivan@bitpay.com"
|
||||||
"email": "ematiu@gmail.com"
|
},
|
||||||
}]
|
{
|
||||||
|
"name": "Matias Alejo Garcia",
|
||||||
|
"email": "ematiu@gmail.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }});
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -98,11 +98,8 @@ describe('Email notifications', function() {
|
||||||
var one = emails[0];
|
var one = emails[0];
|
||||||
one.from.should.equal('bws@dummy.net');
|
one.from.should.equal('bws@dummy.net');
|
||||||
one.subject.should.contain('New payment proposal');
|
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);
|
should.exist(one.html);
|
||||||
one.html.indexOf('<html>').should.equal(0);
|
one.html.indexOf('<html>').should.equal(0);
|
||||||
one.html.should.contain(wallet.name);
|
|
||||||
server.storage.fetchUnsentEmails(function(err, unsent) {
|
server.storage.fetchUnsentEmails(function(err, unsent) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
unsent.should.be.empty;
|
unsent.should.be.empty;
|
||||||
|
@ -172,7 +169,7 @@ describe('Email notifications', function() {
|
||||||
txp = t;
|
txp = t;
|
||||||
async.eachSeries(_.range(2), function(i, next) {
|
async.eachSeries(_.range(2), function(i, next) {
|
||||||
var copayer = TestData.copayers[i];
|
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);
|
var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H);
|
||||||
server.signTx({
|
server.signTx({
|
||||||
txProposalId: txp.id,
|
txProposalId: txp.id,
|
||||||
|
@ -202,7 +199,6 @@ describe('Email notifications', function() {
|
||||||
var one = emails[0];
|
var one = emails[0];
|
||||||
one.from.should.equal('bws@dummy.net');
|
one.from.should.equal('bws@dummy.net');
|
||||||
one.subject.should.contain('Payment sent');
|
one.subject.should.contain('Payment sent');
|
||||||
one.text.should.contain(wallet.name);
|
|
||||||
one.text.should.contain('800,000');
|
one.text.should.contain('800,000');
|
||||||
should.exist(one.html);
|
should.exist(one.html);
|
||||||
one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid);
|
one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid);
|
||||||
|
@ -239,7 +235,7 @@ describe('Email notifications', function() {
|
||||||
txpId = txp.id;
|
txpId = txp.id;
|
||||||
async.eachSeries(_.range(1, 3), function(i, next) {
|
async.eachSeries(_.range(1, 3), function(i, next) {
|
||||||
var copayer = TestData.copayers[i];
|
var copayer = TestData.copayers[i];
|
||||||
helpers.getAuthServer(copayer.id44, function(server) {
|
helpers.getAuthServer(copayer.id44btc, function(server) {
|
||||||
server.rejectTx({
|
server.rejectTx({
|
||||||
txProposalId: txp.id,
|
txProposalId: txp.id,
|
||||||
}, next);
|
}, next);
|
||||||
|
@ -258,9 +254,6 @@ describe('Email notifications', function() {
|
||||||
var one = emails[0];
|
var one = emails[0];
|
||||||
one.from.should.equal('bws@dummy.net');
|
one.from.should.equal('bws@dummy.net');
|
||||||
one.subject.should.contain('Payment proposal rejected');
|
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) {
|
server.storage.fetchUnsentEmails(function(err, unsent) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
unsent.should.be.empty;
|
unsent.should.be.empty;
|
||||||
|
@ -291,7 +284,6 @@ describe('Email notifications', function() {
|
||||||
var one = emails[0];
|
var one = emails[0];
|
||||||
one.from.should.equal('bws@dummy.net');
|
one.from.should.equal('bws@dummy.net');
|
||||||
one.subject.should.contain('New payment received');
|
one.subject.should.contain('New payment received');
|
||||||
one.text.should.contain(wallet.name);
|
|
||||||
one.text.should.contain('123,000');
|
one.text.should.contain('123,000');
|
||||||
server.storage.fetchUnsentEmails(function(err, unsent) {
|
server.storage.fetchUnsentEmails(function(err, unsent) {
|
||||||
should.not.exist(err);
|
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) {
|
it('should notify each email address only once', function(done) {
|
||||||
// Set same email address for copayer1 and copayer2
|
// Set same email address for copayer1 and copayer2
|
||||||
server.savePreferences({
|
server.savePreferences({
|
||||||
|
@ -327,7 +351,6 @@ describe('Email notifications', function() {
|
||||||
var one = emails[0];
|
var one = emails[0];
|
||||||
one.from.should.equal('bws@dummy.net');
|
one.from.should.equal('bws@dummy.net');
|
||||||
one.subject.should.contain('New payment received');
|
one.subject.should.contain('New payment received');
|
||||||
one.text.should.contain(wallet.name);
|
|
||||||
one.text.should.contain('123,000');
|
one.text.should.contain('123,000');
|
||||||
server.storage.fetchUnsentEmails(function(err, unsent) {
|
server.storage.fetchUnsentEmails(function(err, unsent) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -367,14 +390,12 @@ describe('Email notifications', function() {
|
||||||
});
|
});
|
||||||
spanish.from.should.equal('bws@dummy.net');
|
spanish.from.should.equal('bws@dummy.net');
|
||||||
spanish.subject.should.contain('Nuevo pago recibido');
|
spanish.subject.should.contain('Nuevo pago recibido');
|
||||||
spanish.text.should.contain(wallet.name);
|
|
||||||
spanish.text.should.contain('0.123 BTC');
|
spanish.text.should.contain('0.123 BTC');
|
||||||
var english = _.find(emails, {
|
var english = _.find(emails, {
|
||||||
to: 'copayer2@domain.com'
|
to: 'copayer2@domain.com'
|
||||||
});
|
});
|
||||||
english.from.should.equal('bws@dummy.net');
|
english.from.should.equal('bws@dummy.net');
|
||||||
english.subject.should.contain('New payment received');
|
english.subject.should.contain('New payment received');
|
||||||
english.text.should.contain(wallet.name);
|
|
||||||
english.text.should.contain('123,000 bits');
|
english.text.should.contain('123,000 bits');
|
||||||
done();
|
done();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
|
@ -13,6 +13,10 @@ var tingodb = require('tingodb')({
|
||||||
});
|
});
|
||||||
|
|
||||||
var Bitcore = require('bitcore-lib');
|
var Bitcore = require('bitcore-lib');
|
||||||
|
var Bitcore_ = {
|
||||||
|
btc: Bitcore,
|
||||||
|
bch: require('bitcore-lib-cash')
|
||||||
|
};
|
||||||
|
|
||||||
var Common = require('../../lib/common');
|
var Common = require('../../lib/common');
|
||||||
var Utils = Common.Utils;
|
var Utils = Common.Utils;
|
||||||
|
@ -88,13 +92,14 @@ helpers.signMessage = function(text, privKey) {
|
||||||
};
|
};
|
||||||
|
|
||||||
helpers.signRequestPubKey = function(requestPubKey, xPrivKey) {
|
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);
|
return helpers.signMessage(requestPubKey, priv);
|
||||||
};
|
};
|
||||||
|
|
||||||
helpers.getAuthServer = function(copayerId, cb) {
|
helpers.getAuthServer = function(copayerId, cb) {
|
||||||
var verifyStub = sinon.stub(WalletService.prototype, '_verifySignature');
|
var verifyStub = sinon.stub(WalletService.prototype, '_verifySignature');
|
||||||
verifyStub.returns(true);
|
verifyStub.returns(true);
|
||||||
|
|
||||||
WalletService.getInstanceWithAuth({
|
WalletService.getInstanceWithAuth({
|
||||||
copayerId: copayerId,
|
copayerId: copayerId,
|
||||||
message: 'dummy',
|
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 = [');
|
console.log('var copayers = [');
|
||||||
_.each(_.range(n), function(c) {
|
_.each(xPrivKeys, function(xPrivKeyStr, c) {
|
||||||
var xpriv = new Bitcore.HDPrivateKey();
|
var xpriv = Bitcore.HDPrivateKey(xPrivKeyStr);
|
||||||
var xpub = Bitcore.HDPublicKey(xpriv);
|
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 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 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 xpub_1H = Bitcore.HDPublicKey(xpriv_1H);
|
||||||
var priv = xpriv_1H.derive(0).privateKey;
|
var priv = xpriv_1H.deriveChild(0).privateKey;
|
||||||
var pub = xpub_1H.derive(0).publicKey;
|
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('id45: ', "'" + id45 + "',");
|
||||||
console.log('xPrivKey: ', "'" + xpriv.toString() + "',");
|
console.log('xPrivKey: ', "'" + xpriv.toString() + "',");
|
||||||
console.log('xPubKey: ', "'" + xpub.toString() + "',");
|
console.log('xPubKey: ', "'" + xpub.toString() + "',");
|
||||||
|
@ -165,6 +184,8 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
|
||||||
n: n,
|
n: n,
|
||||||
pubKey: TestData.keyPair.pub,
|
pubKey: TestData.keyPair.pub,
|
||||||
singleAddress: !!opts.singleAddress,
|
singleAddress: !!opts.singleAddress,
|
||||||
|
coin: opts.coin || 'btc',
|
||||||
|
network: opts.network || 'livenet',
|
||||||
};
|
};
|
||||||
if (_.isBoolean(opts.supportBIP44AndP2PKH))
|
if (_.isBoolean(opts.supportBIP44AndP2PKH))
|
||||||
walletOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH;
|
walletOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH;
|
||||||
|
@ -174,10 +195,18 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
|
||||||
|
|
||||||
async.each(_.range(n), function(i, cb) {
|
async.each(_.range(n), function(i, cb) {
|
||||||
var copayerData = TestData.copayers[i + offset];
|
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({
|
var copayerOpts = helpers.getSignedCopayerOpts({
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
|
coin: opts.coin,
|
||||||
name: 'copayer ' + (i + 1),
|
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,
|
requestPubKey: copayerData.pubKey_1H_0,
|
||||||
customData: 'custom data ' + (i + 1),
|
customData: 'custom data ' + (i + 1),
|
||||||
});
|
});
|
||||||
|
@ -185,6 +214,7 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
|
||||||
copayerOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH;
|
copayerOpts.supportBIP44AndP2PKH = opts.supportBIP44AndP2PKH;
|
||||||
|
|
||||||
server.joinWallet(copayerOpts, function(err, result) {
|
server.joinWallet(copayerOpts, function(err, result) {
|
||||||
|
if (err) console.log(err);
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
copayerIds.push(result.copayerId);
|
copayerIds.push(result.copayerId);
|
||||||
return cb(err);
|
return cb(err);
|
||||||
|
@ -256,6 +286,8 @@ helpers.stubUtxos = function(server, wallet, amounts, opts, cb) {
|
||||||
|
|
||||||
if (!helpers._utxos) helpers._utxos = {};
|
if (!helpers._utxos) helpers._utxos = {};
|
||||||
|
|
||||||
|
var S = Bitcore_[wallet.coin].Script;
|
||||||
|
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
|
|
||||||
function(next) {
|
function(next) {
|
||||||
|
@ -277,10 +309,10 @@ helpers.stubUtxos = function(server, wallet, amounts, opts, cb) {
|
||||||
var scriptPubKey;
|
var scriptPubKey;
|
||||||
switch (wallet.addressType) {
|
switch (wallet.addressType) {
|
||||||
case Constants.SCRIPT_TYPES.P2SH:
|
case Constants.SCRIPT_TYPES.P2SH:
|
||||||
scriptPubKey = Bitcore.Script.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut();
|
scriptPubKey = S.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut();
|
||||||
break;
|
break;
|
||||||
case Constants.SCRIPT_TYPES.P2PKH:
|
case Constants.SCRIPT_TYPES.P2PKH:
|
||||||
scriptPubKey = Bitcore.Script.buildPublicKeyHashOut(address.address);
|
scriptPubKey = S.buildPublicKeyHashOut(address.address);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
should.exist(scriptPubKey);
|
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) {
|
blockchainExplorer.getAddressActivity = function(address, cb) {
|
||||||
|
if (stubAddressActivityFailsOnCount === stubAddressActivityFailsOn)
|
||||||
|
return cb('failed on request');
|
||||||
|
|
||||||
|
stubAddressActivityFailsOnCount++;
|
||||||
|
|
||||||
return cb(null, _.contains(activeAddresses, address));
|
return cb(null, _.contains(activeAddresses, address));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -374,7 +420,7 @@ helpers.clientSign = function(txp, derivedXPrivKey) {
|
||||||
|
|
||||||
_.each(txp.inputs, function(i) {
|
_.each(txp.inputs, function(i) {
|
||||||
if (!derived[i.path]) {
|
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]);
|
privs.push(derived[i.path]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,6 +10,8 @@ var log = require('npmlog');
|
||||||
log.debug = log.verbose;
|
log.debug = log.verbose;
|
||||||
log.level = 'info';
|
log.level = 'info';
|
||||||
|
|
||||||
|
var sjcl = require('sjcl');
|
||||||
|
|
||||||
var WalletService = require('../../lib/server');
|
var WalletService = require('../../lib/server');
|
||||||
var PushNotificationsService = require('../../lib/pushnotificationsservice');
|
var PushNotificationsService = require('../../lib/pushnotificationsservice');
|
||||||
|
|
||||||
|
@ -36,11 +38,24 @@ describe('Push notifications', function() {
|
||||||
var i = 0;
|
var i = 0;
|
||||||
async.eachSeries(w.copayers, function(copayer, next) {
|
async.eachSeries(w.copayers, function(copayer, next) {
|
||||||
helpers.getAuthServer(copayer.id, function(server) {
|
helpers.getAuthServer(copayer.id, function(server) {
|
||||||
server.savePreferences({
|
async.parallel([
|
||||||
email: 'copayer' + (++i) + '@domain.com',
|
|
||||||
language: 'en',
|
function(done) {
|
||||||
unit: 'bit',
|
server.savePreferences({
|
||||||
}, next);
|
email: 'copayer' + (++i) + '@domain.com',
|
||||||
|
language: 'en',
|
||||||
|
unit: 'bit',
|
||||||
|
}, done);
|
||||||
|
},
|
||||||
|
function(done) {
|
||||||
|
server.pushNotificationsSubscribe({
|
||||||
|
token: '1234',
|
||||||
|
packageName: 'com.wallet',
|
||||||
|
platform: 'Android',
|
||||||
|
}, done);
|
||||||
|
},
|
||||||
|
], next);
|
||||||
|
|
||||||
});
|
});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -59,8 +74,8 @@ describe('Push notifications', function() {
|
||||||
defaultLanguage: 'en',
|
defaultLanguage: 'en',
|
||||||
defaultUnit: 'btc',
|
defaultUnit: 'btc',
|
||||||
subjectPrefix: '',
|
subjectPrefix: '',
|
||||||
|
pushServerUrl: 'http://localhost:8000',
|
||||||
pushServerUrl: 'http://localhost:8000/send',
|
authorizationKey: 'secret',
|
||||||
},
|
},
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -93,8 +108,9 @@ describe('Push notifications', function() {
|
||||||
return c.args[0];
|
return c.args[0];
|
||||||
});
|
});
|
||||||
calls.length.should.equal(1);
|
calls.length.should.equal(1);
|
||||||
args[0].body.android.data.title.should.contain('New payment received');
|
args[0].body.notification.title.should.contain('New payment received');
|
||||||
args[0].body.android.data.message.should.contain('123,000');
|
args[0].body.notification.body.should.contain('123,000');
|
||||||
|
args[0].body.notification.body.should.contain('bits');
|
||||||
done();
|
done();
|
||||||
}, 100);
|
}, 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() {
|
describe('Shared wallet', function() {
|
||||||
|
@ -154,11 +193,24 @@ describe('Push notifications', function() {
|
||||||
var i = 0;
|
var i = 0;
|
||||||
async.eachSeries(w.copayers, function(copayer, next) {
|
async.eachSeries(w.copayers, function(copayer, next) {
|
||||||
helpers.getAuthServer(copayer.id, function(server) {
|
helpers.getAuthServer(copayer.id, function(server) {
|
||||||
server.savePreferences({
|
async.parallel([
|
||||||
email: 'copayer' + (++i) + '@domain.com',
|
|
||||||
language: 'en',
|
function(done) {
|
||||||
unit: 'bit',
|
server.savePreferences({
|
||||||
}, next);
|
email: 'copayer' + (++i) + '@domain.com',
|
||||||
|
language: 'en',
|
||||||
|
unit: 'bit',
|
||||||
|
}, done);
|
||||||
|
},
|
||||||
|
function(done) {
|
||||||
|
server.pushNotificationsSubscribe({
|
||||||
|
token: '1234',
|
||||||
|
packageName: 'com.wallet',
|
||||||
|
platform: 'Android',
|
||||||
|
}, done);
|
||||||
|
},
|
||||||
|
], next);
|
||||||
|
|
||||||
});
|
});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -177,8 +229,8 @@ describe('Push notifications', function() {
|
||||||
defaultLanguage: 'en',
|
defaultLanguage: 'en',
|
||||||
defaultUnit: 'btc',
|
defaultUnit: 'btc',
|
||||||
subjectPrefix: '',
|
subjectPrefix: '',
|
||||||
|
pushServerUrl: 'http://localhost:8000',
|
||||||
pushServerUrl: 'http://localhost:8000/send',
|
authorizationKey: 'secret',
|
||||||
},
|
},
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -214,14 +266,14 @@ describe('Push notifications', function() {
|
||||||
|
|
||||||
calls.length.should.equal(3);
|
calls.length.should.equal(3);
|
||||||
|
|
||||||
args[0].body.android.data.title.should.contain('Nuevo pago recibido');
|
args[0].body.notification.title.should.contain('Nuevo pago recibido');
|
||||||
args[0].body.android.data.message.should.contain('0.123');
|
args[0].body.notification.body.should.contain('0.123');
|
||||||
|
|
||||||
args[1].body.android.data.title.should.contain('New payment received');
|
args[1].body.notification.title.should.contain('New payment received');
|
||||||
args[1].body.android.data.message.should.contain('123,000');
|
args[1].body.notification.body.should.contain('123,000');
|
||||||
|
|
||||||
args[2].body.android.data.title.should.contain('New payment received');
|
args[2].body.notification.title.should.contain('New payment received');
|
||||||
args[2].body.android.data.message.should.contain('123,000');
|
args[2].body.notification.body.should.contain('123,000');
|
||||||
done();
|
done();
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
@ -317,7 +369,7 @@ describe('Push notifications', function() {
|
||||||
txpId = txp.id;
|
txpId = txp.id;
|
||||||
async.eachSeries(_.range(1, 3), function(i, next) {
|
async.eachSeries(_.range(1, 3), function(i, next) {
|
||||||
var copayer = TestData.copayers[i];
|
var copayer = TestData.copayers[i];
|
||||||
helpers.getAuthServer(copayer.id44, function(server) {
|
helpers.getAuthServer(copayer.id44btc, function(server) {
|
||||||
server.rejectTx({
|
server.rejectTx({
|
||||||
txProposalId: txp.id,
|
txProposalId: txp.id,
|
||||||
}, next);
|
}, next);
|
||||||
|
@ -333,9 +385,7 @@ describe('Push notifications', function() {
|
||||||
return c.args[0];
|
return c.args[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
args[0].body.android.data.title.should.contain('Payment proposal rejected');
|
args[0].body.notification.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');
|
|
||||||
done();
|
done();
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
@ -364,7 +414,7 @@ describe('Push notifications', function() {
|
||||||
txp = t;
|
txp = t;
|
||||||
async.eachSeries(_.range(1, 3), function(i, next) {
|
async.eachSeries(_.range(1, 3), function(i, next) {
|
||||||
var copayer = TestData.copayers[i];
|
var copayer = TestData.copayers[i];
|
||||||
helpers.getAuthServer(copayer.id44, function(s) {
|
helpers.getAuthServer(copayer.id44btc, function(s) {
|
||||||
server = s;
|
server = s;
|
||||||
var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H);
|
var signatures = helpers.clientSign(txp, copayer.xPrivKey_44H_0H_0H);
|
||||||
server.signTx({
|
server.signTx({
|
||||||
|
@ -392,11 +442,11 @@ describe('Push notifications', function() {
|
||||||
return c.args[0];
|
return c.args[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
args[0].body.android.data.title.should.contain('Payment sent');
|
args[0].body.notification.title.should.contain('Payment sent');
|
||||||
args[1].body.android.data.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]);
|
sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(server.copayerId)).should.not.equal(args[0].body.data.copayerId);
|
||||||
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[1].body.data.copayerId);
|
||||||
done();
|
done();
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
@ -432,7 +482,8 @@ describe('Push notifications', function() {
|
||||||
defaultLanguage: 'en',
|
defaultLanguage: 'en',
|
||||||
defaultUnit: 'btc',
|
defaultUnit: 'btc',
|
||||||
subjectPrefix: '',
|
subjectPrefix: '',
|
||||||
pushServerUrl: 'http://localhost:8000/send',
|
pushServerUrl: 'http://localhost:8000',
|
||||||
|
authorizationKey: 'secret',
|
||||||
},
|
},
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -452,44 +503,54 @@ describe('Push notifications', function() {
|
||||||
customData: 'custom data ' + (i + 1),
|
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) {
|
}, function(err) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
var calls = requestStub.getCalls();
|
var calls = requestStub.getCalls();
|
||||||
var args = _.map(calls, function(c) {
|
var args = _.filter(_.map(calls, function(call) {
|
||||||
return c.args[0];
|
return call.args[0];
|
||||||
|
}), function(arg) {
|
||||||
|
return arg.body.notification.title == 'New copayer';
|
||||||
});
|
});
|
||||||
|
|
||||||
var argu = _.compact(_.map(args, function(a) {
|
server.getWallet(null, function(err, wallet) {
|
||||||
if (a.body.android.data.title == 'New copayer')
|
|
||||||
return a;
|
|
||||||
}));
|
|
||||||
|
|
||||||
server.getWallet(null, function(err, w) {
|
|
||||||
/*
|
/*
|
||||||
First call - copayer2 joined
|
First call - copayer2 joined
|
||||||
copayer2 should notify to copayer1
|
copayer2 should notify to copayer1
|
||||||
copayer2 should NOT be notifyed
|
copayer2 should NOT be notifyed
|
||||||
*/
|
*/
|
||||||
w.copayers[0].id.should.contain((argu[0].body.users[0]).split('$')[1]);
|
var hashedCopayerIds = _.map(wallet.copayers, function(copayer) {
|
||||||
w.copayers[1].id.should.not.contain((argu[0].body.users[0]).split('$')[1]);
|
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
|
Second call - copayer3 joined
|
||||||
copayer3 should notify to copayer1
|
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
|
Third call - copayer3 joined
|
||||||
copayer3 should notify to copayer2
|
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
|
// copayer3 should NOT notify any other copayer
|
||||||
w.copayers[2].id.should.not.contain((argu[1].body.users[0]).split('$')[1]);
|
hashedCopayerIds[2].should.not.equal((args[1].body.data.copayerId));
|
||||||
w.copayers[2].id.should.not.contain((argu[2].body.users[0]).split('$')[1]);
|
hashedCopayerIds[2].should.not.equal((args[2].body.data.copayerId));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,6 +12,7 @@ describe('Address', function() {
|
||||||
it('should create livenet address', function() {
|
it('should create livenet address', function() {
|
||||||
var x = Address.create({
|
var x = Address.create({
|
||||||
address: '3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg',
|
address: '3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg',
|
||||||
|
coin: 'btc',
|
||||||
walletId: '123',
|
walletId: '123',
|
||||||
isChange: false,
|
isChange: false,
|
||||||
path: 'm/0/1',
|
path: 'm/0/1',
|
||||||
|
@ -23,6 +24,7 @@ describe('Address', function() {
|
||||||
it('should create testnet address', function() {
|
it('should create testnet address', function() {
|
||||||
var x = Address.create({
|
var x = Address.create({
|
||||||
address: 'mp5xaa4uBj16DJt1fuA3D9fejHuCzeb7hj',
|
address: 'mp5xaa4uBj16DJt1fuA3D9fejHuCzeb7hj',
|
||||||
|
coin: 'btc',
|
||||||
walletId: '123',
|
walletId: '123',
|
||||||
isChange: false,
|
isChange: false,
|
||||||
path: 'm/0/1',
|
path: 'm/0/1',
|
||||||
|
@ -39,7 +41,7 @@ describe('Address', function() {
|
||||||
}, {
|
}, {
|
||||||
xPubKey: 'xpub68tpbrfk747AvDUCdtEUgK2yDPmtGKf7YXzEcUUqnF3jmAMeZgcpoZqgXwwoi8CpwDkyzVX6wxUktTw2wh9EhhVjh5S71MLL3FkZDGF5GeY'
|
xPubKey: 'xpub68tpbrfk747AvDUCdtEUgK2yDPmtGKf7YXzEcUUqnF3jmAMeZgcpoZqgXwwoi8CpwDkyzVX6wxUktTw2wh9EhhVjh5S71MLL3FkZDGF5GeY'
|
||||||
// PubKey(xPubKey/0/0) -> 03162179906dbe6a67979d4f8f46ee1db6ff81715f465e6615a4f5969478ad2171
|
// PubKey(xPubKey/0/0) -> 03162179906dbe6a67979d4f8f46ee1db6ff81715f465e6615a4f5969478ad2171
|
||||||
}], 'm/0/0', 1, 'livenet', false);
|
}], 'm/0/0', 1, 'btc', 'livenet', false);
|
||||||
should.exist(address);
|
should.exist(address);
|
||||||
address.walletId.should.equal('wallet-id');
|
address.walletId.should.equal('wallet-id');
|
||||||
address.address.should.equal('3QN2CiSxcUsFuRxZJwXMNDQ2esnr5RXTvw');
|
address.address.should.equal('3QN2CiSxcUsFuRxZJwXMNDQ2esnr5RXTvw');
|
||||||
|
@ -52,7 +54,7 @@ describe('Address', function() {
|
||||||
var address = Address.derive('wallet-id', 'P2SH', [{
|
var address = Address.derive('wallet-id', 'P2SH', [{
|
||||||
xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1'
|
xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1'
|
||||||
// PubKey(xPubKey/0/0) -> 03fe466ea829aa4c9a1c289f9ba61ebc26a61816500860c8d23f94aad9af152ecd
|
// PubKey(xPubKey/0/0) -> 03fe466ea829aa4c9a1c289f9ba61ebc26a61816500860c8d23f94aad9af152ecd
|
||||||
}], 'm/0/0', 1, 'livenet', false);
|
}], 'm/0/0', 1, 'btc', 'livenet', false);
|
||||||
should.exist(address);
|
should.exist(address);
|
||||||
address.walletId.should.equal('wallet-id');
|
address.walletId.should.equal('wallet-id');
|
||||||
address.address.should.equal('3BY4K8dfsHryhWh2MJ6XHxxsRfcvPAyseH');
|
address.address.should.equal('3BY4K8dfsHryhWh2MJ6XHxxsRfcvPAyseH');
|
||||||
|
@ -65,7 +67,7 @@ describe('Address', function() {
|
||||||
var address = Address.derive('wallet-id', 'P2PKH', [{
|
var address = Address.derive('wallet-id', 'P2PKH', [{
|
||||||
xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1'
|
xPubKey: 'xpub686v8eJUJEqxzAtkWPyQ9nvpBHfucVsB8Q8HQHw5mxYPQtBact2rmA8wRXFYaVESK8f7WrxeU4ayALaEhicdXCX5ZHktNeRFnvFeffztiY1'
|
||||||
// PubKey(xPubKey/1/2) -> 0232c09a6edd8e2189628132d530c038e0b15b414cf3984e532358cbcfb83a7bd7
|
// PubKey(xPubKey/1/2) -> 0232c09a6edd8e2189628132d530c038e0b15b414cf3984e532358cbcfb83a7bd7
|
||||||
}], 'm/1/2', 1, 'livenet', true);
|
}], 'm/1/2', 1, 'btc', 'livenet', true);
|
||||||
should.exist(address);
|
should.exist(address);
|
||||||
address.walletId.should.equal('wallet-id');
|
address.walletId.should.equal('wallet-id');
|
||||||
address.address.should.equal('1G4wgi9YzmSSwQaQVLXQ5HUVquQDgJf8oT');
|
address.address.should.equal('1G4wgi9YzmSSwQaQVLXQ5HUVquQDgJf8oT');
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe('Copayer', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#createAddress', function() {
|
describe('#createAddress', function() {
|
||||||
it('create an address', function() {
|
it('should create an address', function() {
|
||||||
var w = Wallet.fromObj(testWallet);
|
var w = Wallet.fromObj(testWallet);
|
||||||
var c = Copayer.fromObj(testWallet.copayers[2]);
|
var c = Copayer.fromObj(testWallet.copayers[2]);
|
||||||
should.exist(c.requestPubKeys);
|
should.exist(c.requestPubKeys);
|
||||||
|
@ -42,6 +42,7 @@ var testWallet = {
|
||||||
createdOn: 1422904188,
|
createdOn: 1422904188,
|
||||||
id: '123',
|
id: '123',
|
||||||
name: '123 wallet',
|
name: '123 wallet',
|
||||||
|
network: 'livenet',
|
||||||
m: 2,
|
m: 2,
|
||||||
n: 3,
|
n: 3,
|
||||||
status: 'complete',
|
status: 'complete',
|
||||||
|
|
|
@ -24,6 +24,11 @@ describe('TxProposal', function() {
|
||||||
should.exist(txp);
|
should.exist(txp);
|
||||||
txp.amount.should.equal(aTXP().amount);
|
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() {
|
describe('#getBitcoreTx', function() {
|
||||||
|
@ -108,8 +113,10 @@ var theXPub = 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2
|
||||||
var theSignatures = ['304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6'];
|
var theSignatures = ['304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6'];
|
||||||
var theRawTx = '0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e08000000910047304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0380969800000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac002d3101000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70f62b040000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000';
|
var theRawTx = '0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e08000000910047304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0380969800000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac002d3101000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70f62b040000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000';
|
||||||
|
|
||||||
var aTxpOpts = function(type) {
|
var aTxpOpts = function() {
|
||||||
var opts = {
|
var opts = {
|
||||||
|
coin: 'btc',
|
||||||
|
network: 'livenet',
|
||||||
message: 'some message'
|
message: 'some message'
|
||||||
};
|
};
|
||||||
opts.outputs = [{
|
opts.outputs = [{
|
||||||
|
@ -125,7 +132,7 @@ var aTxpOpts = function(type) {
|
||||||
return opts;
|
return opts;
|
||||||
};
|
};
|
||||||
|
|
||||||
var aTXP = function(type) {
|
var aTXP = function() {
|
||||||
var txp = {
|
var txp = {
|
||||||
"version": 3,
|
"version": 3,
|
||||||
"createdOn": 1423146231,
|
"createdOn": 1423146231,
|
||||||
|
|
|
@ -54,6 +54,8 @@ describe('Storage', function() {
|
||||||
name: 'my wallet',
|
name: 'my wallet',
|
||||||
m: 2,
|
m: 2,
|
||||||
n: 3,
|
n: 3,
|
||||||
|
coin: 'btc',
|
||||||
|
network: 'livenet',
|
||||||
});
|
});
|
||||||
should.exist(wallet);
|
should.exist(wallet);
|
||||||
storage.storeWallet(wallet, function(err) {
|
storage.storeWallet(wallet, function(err) {
|
||||||
|
@ -85,9 +87,12 @@ describe('Storage', function() {
|
||||||
name: 'my wallet',
|
name: 'my wallet',
|
||||||
m: 2,
|
m: 2,
|
||||||
n: 3,
|
n: 3,
|
||||||
|
coin: 'btc',
|
||||||
|
network: 'livenet',
|
||||||
});
|
});
|
||||||
_.each(_.range(3), function(i) {
|
_.each(_.range(3), function(i) {
|
||||||
var copayer = Model.Copayer.create({
|
var copayer = Model.Copayer.create({
|
||||||
|
coin: 'btc',
|
||||||
name: 'copayer ' + i,
|
name: 'copayer ' + i,
|
||||||
xPubKey: 'xPubKey ' + i,
|
xPubKey: 'xPubKey ' + i,
|
||||||
requestPubKey: 'requestPubKey ' + i,
|
requestPubKey: 'requestPubKey ' + i,
|
||||||
|
@ -127,9 +132,12 @@ describe('Storage', function() {
|
||||||
name: 'my wallet',
|
name: 'my wallet',
|
||||||
m: 2,
|
m: 2,
|
||||||
n: 3,
|
n: 3,
|
||||||
|
coin: 'btc',
|
||||||
|
network: 'livenet',
|
||||||
});
|
});
|
||||||
_.each(_.range(3), function(i) {
|
_.each(_.range(3), function(i) {
|
||||||
var copayer = Model.Copayer.create({
|
var copayer = Model.Copayer.create({
|
||||||
|
coin: 'btc',
|
||||||
name: 'copayer ' + i,
|
name: 'copayer ' + i,
|
||||||
xPubKey: 'xPubKey ' + i,
|
xPubKey: 'xPubKey ' + i,
|
||||||
requestPubKey: 'requestPubKey ' + i,
|
requestPubKey: 'requestPubKey ' + i,
|
||||||
|
@ -144,6 +152,8 @@ describe('Storage', function() {
|
||||||
proposals = _.map(_.range(4), function(i) {
|
proposals = _.map(_.range(4), function(i) {
|
||||||
var tx = Model.TxProposal.create({
|
var tx = Model.TxProposal.create({
|
||||||
walletId: '123',
|
walletId: '123',
|
||||||
|
coin: 'btc',
|
||||||
|
network: 'livenet',
|
||||||
outputs: [{
|
outputs: [{
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: i + 100,
|
amount: i + 100,
|
||||||
|
|
|
@ -4,7 +4,8 @@ var keyPair = {
|
||||||
};
|
};
|
||||||
|
|
||||||
var copayers = [{
|
var copayers = [{
|
||||||
id44: '626452e5e0e35df4d9ae4d3e60653c9ae9a814f00c84dc40f5887069b18e2110',
|
id44btc: '626452e5e0e35df4d9ae4d3e60653c9ae9a814f00c84dc40f5887069b18e2110',
|
||||||
|
id44bch: '671fee02a6c1c4de2e2609f9f9a6180dc03acfff6b759fe0b13a616ed4880065',
|
||||||
id45: 'e7467366d5754be2b7d386c9737ab87214c26314bdc3489702e09c719be1bdb7',
|
id45: 'e7467366d5754be2b7d386c9737ab87214c26314bdc3489702e09c719be1bdb7',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K2n4rV4AtAJFptEmd1tNMKCcSyQBCSuN5eq1dCUhcv6KQJS49joRxu8NNdFxy8yuwTtzCPNYUZvVGC7EPRm2st2cvE7oyTbB',
|
xPrivKey: 'xprv9s21ZrQH143K2n4rV4AtAJFptEmd1tNMKCcSyQBCSuN5eq1dCUhcv6KQJS49joRxu8NNdFxy8yuwTtzCPNYUZvVGC7EPRm2st2cvE7oyTbB',
|
||||||
xPubKey: 'xpub661MyMwAqRbcFG9Kb5htXSCZSGc7RM6CgRY3mnap1Eu4XdLmk21sTtdt9iWAiL64KazU3QWrYEYSRAKgLvkMRe8JMxffDvt4AhCDzyMsnsT',
|
xPubKey: 'xpub661MyMwAqRbcFG9Kb5htXSCZSGc7RM6CgRY3mnap1Eu4XdLmk21sTtdt9iWAiL64KazU3QWrYEYSRAKgLvkMRe8JMxffDvt4AhCDzyMsnsT',
|
||||||
|
@ -12,12 +13,18 @@ var copayers = [{
|
||||||
xPubKey_45H: 'xpub68pKcb8jHWqWuTgPz2czjFSJJBJTsTNdd87Mgh5bVz4sNFBJBus5KyptGBWgA4V6LGCi12s4Mw4S1JC2GkqX4NJ4kfQ47XqRZLbyM2DY9Jd',
|
xPubKey_45H: 'xpub68pKcb8jHWqWuTgPz2czjFSJJBJTsTNdd87Mgh5bVz4sNFBJBus5KyptGBWgA4V6LGCi12s4Mw4S1JC2GkqX4NJ4kfQ47XqRZLbyM2DY9Jd',
|
||||||
xPrivKey_44H_0H_0H: 'xprv9zWRZ7CXrC4z9xA9RRBFXohmPKbyCajWaCNTHPtwNeJwTnysHG5QK7WMqpNLVtvqGxts7WNcNtqBLfdaFdCGknDPXjLKt2E2BUrPaFDqrLh',
|
xPrivKey_44H_0H_0H: 'xprv9zWRZ7CXrC4z9xA9RRBFXohmPKbyCajWaCNTHPtwNeJwTnysHG5QK7WMqpNLVtvqGxts7WNcNtqBLfdaFdCGknDPXjLKt2E2BUrPaFDqrLh',
|
||||||
xPubKey_44H_0H_0H: 'xpub6DVmxcjRgZdHNSEcXSiFtweVwMSTc3TMwRJ45nJYvyqvLbK1poPerupqh87rSoz27wvckb1CKnGZoLmLXSZyNGZtVd7neqSvdwJL6fceQpe',
|
xPubKey_44H_0H_0H: 'xpub6DVmxcjRgZdHNSEcXSiFtweVwMSTc3TMwRJ45nJYvyqvLbK1poPerupqh87rSoz27wvckb1CKnGZoLmLXSZyNGZtVd7neqSvdwJL6fceQpe',
|
||||||
|
|
||||||
|
|
||||||
|
xPrivKey_44H_0H_0Ht: 'tprv8ZgxMBicQKsPcxUEtgtQ2wKpkmuNKS6R2w3UmFTUHHURv4PKGE2aGkkbQEcQs9gGsoW4zPr7VM98xdbjQuWc3cZ6bkEyKy1sywhV9gLUcUi',
|
||||||
|
xPubKey_44H_0H_0Ht: 'tpubD6NzVbkrYhZ4WRW2nLYzSLywKoRJUmHKcEeG3mVmhZGpkYe5tcrATFNTaQRAWM3dzL2QyXoctpjkaAXruDXyc6xkF4EDGu3eQdwZXFzoFSW',
|
||||||
|
|
||||||
xPrivKey_1H: 'xprv9upyD5bqT9HBkWym7TvTH3njEzTnjrtkLB2sg3DD2CxxA5hZKGee1sYJUtD8C4QaeATLXQ33TirRzRhuTGDBA6XRoYDMwfXAj1KSmGyNBio',
|
xPrivKey_1H: 'xprv9upyD5bqT9HBkWym7TvTH3njEzTnjrtkLB2sg3DD2CxxA5hZKGee1sYJUtD8C4QaeATLXQ33TirRzRhuTGDBA6XRoYDMwfXAj1KSmGyNBio',
|
||||||
xPubKey_1H: 'xpub68pKcb8jHWqUy14EDVTTeBjTo2JH9KcbhPxUURcpaYVw2t2hroxtZfrnLBw1bWzBrHbEJA48QmZ8DB9gTvhphKSitC15SiYx9k2ncGh55Hq',
|
xPubKey_1H: 'xpub68pKcb8jHWqUy14EDVTTeBjTo2JH9KcbhPxUURcpaYVw2t2hroxtZfrnLBw1bWzBrHbEJA48QmZ8DB9gTvhphKSitC15SiYx9k2ncGh55Hq',
|
||||||
privKey_1H_0: 'a710be25950738a7d13637e2e09affd7f579a3479fd7cc024bd9459f8fba6659',
|
privKey_1H_0: 'a710be25950738a7d13637e2e09affd7f579a3479fd7cc024bd9459f8fba6659',
|
||||||
pubKey_1H_0: '026e3020913420a5b9425952627f0a074c9235e7a329869b322061f786e997ae0d'
|
pubKey_1H_0: '026e3020913420a5b9425952627f0a074c9235e7a329869b322061f786e997ae0d'
|
||||||
}, {
|
}, {
|
||||||
id44: '842c048066e7d10ae1bbf67edccf69f2e5ff9a754d0c2b5524f0d01a87d6acbb',
|
id44btc: '842c048066e7d10ae1bbf67edccf69f2e5ff9a754d0c2b5524f0d01a87d6acbb',
|
||||||
|
id44bch: '0d8f0c0ebfb11ad589002fd4539075c6fb625fb1725406ca442726c6bc6746b1',
|
||||||
id45: 'ee75154b646277c8d0d256fc1a0aa0470e4c3435497f208092c865737040b55b',
|
id45: 'ee75154b646277c8d0d256fc1a0aa0470e4c3435497f208092c865737040b55b',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K3BwkLceWNLUsgES15JoZuv8BZfnmDRcCGtDooUAPhY8KovhCWcRLXUun5AYL5vVtUNRrmPEibtfk9ongxAGLXZzEHifpvwZ',
|
xPrivKey: 'xprv9s21ZrQH143K3BwkLceWNLUsgES15JoZuv8BZfnmDRcCGtDooUAPhY8KovhCWcRLXUun5AYL5vVtUNRrmPEibtfk9ongxAGLXZzEHifpvwZ',
|
||||||
xPubKey: 'xpub661MyMwAqRbcFg2DSeBWjURcEGGVUmXRH93nN4CNmm9B9gYxM1UeFLSofD6gtMnRYeucgPjfrWNxaAEhiT4di6HLty8Un6aheCKev4REvhZ',
|
xPubKey: 'xpub661MyMwAqRbcFg2DSeBWjURcEGGVUmXRH93nN4CNmm9B9gYxM1UeFLSofD6gtMnRYeucgPjfrWNxaAEhiT4di6HLty8Un6aheCKev4REvhZ',
|
||||||
|
@ -30,7 +37,8 @@ var copayers = [{
|
||||||
privKey_1H_0: 'ee062ce6dc5ece50e8110646b5e858c98dba9315cdfdd19da85ab0d33dcac74a',
|
privKey_1H_0: 'ee062ce6dc5ece50e8110646b5e858c98dba9315cdfdd19da85ab0d33dcac74a',
|
||||||
pubKey_1H_0: '02c679bf169233a273dec87fae5a1830481866c4e96a350d56346ac267808c905d'
|
pubKey_1H_0: '02c679bf169233a273dec87fae5a1830481866c4e96a350d56346ac267808c905d'
|
||||||
}, {
|
}, {
|
||||||
id44: '719f4ee61c691fbf0ebefa34e2151a1a3dbe39cf2fa4a498cb6af53600d30d1a',
|
id44btc: '719f4ee61c691fbf0ebefa34e2151a1a3dbe39cf2fa4a498cb6af53600d30d1a',
|
||||||
|
id44bch: '56ed2c8d04c4aa29e9d6408724197c27d1fa0b71e2c2a6b91a4cf9710f09eb0a',
|
||||||
id45: 'acd666d7c677d9f2c85b55a5fad1610fe272eac46ef7a577c7aeeab0b1474e43',
|
id45: 'acd666d7c677d9f2c85b55a5fad1610fe272eac46ef7a577c7aeeab0b1474e43',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K3xgLzxd6SuWqG5Zp1iUmyGgSsJVhdQNeTzAqBFvXXLZqZzFZqocTx4HD9vUVYU27At5i8q46LmBXXL97fo4H9C3tHm4BnjY',
|
xPrivKey: 'xprv9s21ZrQH143K3xgLzxd6SuWqG5Zp1iUmyGgSsJVhdQNeTzAqBFvXXLZqZzFZqocTx4HD9vUVYU27At5i8q46LmBXXL97fo4H9C3tHm4BnjY',
|
||||||
xPubKey: 'xpub661MyMwAqRbcGSkp6zA6p3TZp7QJRBCdLVc3fguKBjudLnVyioEn58tKRFPmGMkdGJWMX69mgZWHKrKmpQ3fwBXeFjLc5Sd2rnxcQthSW42',
|
xPubKey: 'xpub661MyMwAqRbcGSkp6zA6p3TZp7QJRBCdLVc3fguKBjudLnVyioEn58tKRFPmGMkdGJWMX69mgZWHKrKmpQ3fwBXeFjLc5Sd2rnxcQthSW42',
|
||||||
|
@ -43,7 +51,8 @@ var copayers = [{
|
||||||
privKey_1H_0: '5009c8488e9a364fc24a999d99a81ae955271de1d06d46c2f2f09e20c6281b04',
|
privKey_1H_0: '5009c8488e9a364fc24a999d99a81ae955271de1d06d46c2f2f09e20c6281b04',
|
||||||
pubKey_1H_0: '03338a3b7c08e9d9832e1baff0758e08f9cc691497dd6e91d4c191cd960fb2f043'
|
pubKey_1H_0: '03338a3b7c08e9d9832e1baff0758e08f9cc691497dd6e91d4c191cd960fb2f043'
|
||||||
}, {
|
}, {
|
||||||
id44: 'e225a29864060823df67b98432b070a40aad1bf9af517005b0b5fe09c96e29c9',
|
id44btc: 'e225a29864060823df67b98432b070a40aad1bf9af517005b0b5fe09c96e29c9',
|
||||||
|
id44bch: '2baf290be693407fd9c32597608b6fd90ba60f65b2b81b58e9fe9c960938de11',
|
||||||
id45: 'c65a89f64794cb7e1886c7010a32dd6fa362d3e81710bac32e97e325b9109fd8',
|
id45: 'c65a89f64794cb7e1886c7010a32dd6fa362d3e81710bac32e97e325b9109fd8',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K48nfuK14gKJtML7eQzV2dAH1RaqAMj8v2zs79uaavA9UTWMxpBdgbMH2mhJLeKGq8AFA6GDnFyWP4rLmknqZAfgFFV718vo',
|
xPrivKey: 'xprv9s21ZrQH143K48nfuK14gKJtML7eQzV2dAH1RaqAMj8v2zs79uaavA9UTWMxpBdgbMH2mhJLeKGq8AFA6GDnFyWP4rLmknqZAfgFFV718vo',
|
||||||
xPubKey: 'xpub661MyMwAqRbcGcs91LY53TFcuMx8pTCszPCcDyEmv4ftuoCFhStqTxTxJoy35yjp2H3qQtxDYGe1gtkZu4T7mR7ARK1MLYte2fptZVt6hkD',
|
xPubKey: 'xpub661MyMwAqRbcGcs91LY53TFcuMx8pTCszPCcDyEmv4ftuoCFhStqTxTxJoy35yjp2H3qQtxDYGe1gtkZu4T7mR7ARK1MLYte2fptZVt6hkD',
|
||||||
|
@ -56,7 +65,8 @@ var copayers = [{
|
||||||
privKey_1H_0: '460ee692f05de66b5d8e2fa1d005a8b6bdb1442e2ce6b3facfcee2f9012c9474',
|
privKey_1H_0: '460ee692f05de66b5d8e2fa1d005a8b6bdb1442e2ce6b3facfcee2f9012c9474',
|
||||||
pubKey_1H_0: '03d0e0c526619b158aac9a8de8082f439df43d389ec50cb54386c3d87cfde4c99b'
|
pubKey_1H_0: '03d0e0c526619b158aac9a8de8082f439df43d389ec50cb54386c3d87cfde4c99b'
|
||||||
}, {
|
}, {
|
||||||
id44: '120416cd4c427a7e4d94213cebe242f56a06bc6dd5c5c6cae27dc920a0ddf1fb',
|
id44btc: '120416cd4c427a7e4d94213cebe242f56a06bc6dd5c5c6cae27dc920a0ddf1fb',
|
||||||
|
id44bch: '4abc36e7731c08e0a93483691c3cb451013463ccee1b676e4a20d98cd1de8af3',
|
||||||
id45: '65ae087eb9efdc7e0ada3a7ef954285e9e5ba4b8c7ab2d36747ddd286f7a334f',
|
id45: '65ae087eb9efdc7e0ada3a7ef954285e9e5ba4b8c7ab2d36747ddd286f7a334f',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K44Bb9G3EVNmLfAUKjTBAA2YtKxF4zc8SLV1o15JBoddhGHE9PGLXePMbEsSjCCvTvP3fUv6yMXZrnHigBboRBn2DmNoJkJg',
|
xPrivKey: 'xprv9s21ZrQH143K44Bb9G3EVNmLfAUKjTBAA2YtKxF4zc8SLV1o15JBoddhGHE9PGLXePMbEsSjCCvTvP3fUv6yMXZrnHigBboRBn2DmNoJkJg',
|
||||||
xPubKey: 'xpub661MyMwAqRbcGYG4FHaErWi5DCJp8uu1XFUV8LegYwfRDHLwYccSMRxB7Z3L1NgKychKdXQvbVEyDhSwNnNnnNKh9mBEAdQ5tv2guK8ywKU',
|
xPubKey: 'xpub661MyMwAqRbcGYG4FHaErWi5DCJp8uu1XFUV8LegYwfRDHLwYccSMRxB7Z3L1NgKychKdXQvbVEyDhSwNnNnnNKh9mBEAdQ5tv2guK8ywKU',
|
||||||
|
@ -69,7 +79,8 @@ var copayers = [{
|
||||||
privKey_1H_0: '7a5158b92d9ed4cb9644ddbd472b43428832a5f3bb91a481532a081908e62b2e',
|
privKey_1H_0: '7a5158b92d9ed4cb9644ddbd472b43428832a5f3bb91a481532a081908e62b2e',
|
||||||
pubKey_1H_0: '02b47d5c977c93c883f369165ebc2b564d14a52712ec6892f7097fa99e0d36ca20'
|
pubKey_1H_0: '02b47d5c977c93c883f369165ebc2b564d14a52712ec6892f7097fa99e0d36ca20'
|
||||||
}, {
|
}, {
|
||||||
id44: '85de9f025ee190fab7cb1bd9b6772c64df26188ce705d4f258c5adaf7bc610f9',
|
id44btc: '85de9f025ee190fab7cb1bd9b6772c64df26188ce705d4f258c5adaf7bc610f9',
|
||||||
|
id44bch: '0845739e508fb8f7b28e10bed9d827968a12d2dbd6ecbac3303305fcaf535bfe',
|
||||||
id45: 'dacc5c350cef4449a3ca12939711c7449d0d6189e5e7f33cff60095a7a29b0f9',
|
id45: 'dacc5c350cef4449a3ca12939711c7449d0d6189e5e7f33cff60095a7a29b0f9',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K48PpVxrh71KdViTFhAaiDSVtNFkmbWNYjwwwPbTrcqoVXsgBfue3Gq9b71hQeEbk67JgtTBcpYgKLF8pTwVnGz56f1BaCYt',
|
xPrivKey: 'xprv9s21ZrQH143K48PpVxrh71KdViTFhAaiDSVtNFkmbWNYjwwwPbTrcqoVXsgBfue3Gq9b71hQeEbk67JgtTBcpYgKLF8pTwVnGz56f1BaCYt',
|
||||||
xPubKey: 'xpub661MyMwAqRbcGcUHbzPhU9GN3kHk6dJZafRVAeAP9quXckH5w8n7Ae7yP8e2Zh6SPPKFn2K6oE3GBpcz9QzfJTNRWXbY7w1L3nGLE5beZL1',
|
xPubKey: 'xpub661MyMwAqRbcGcUHbzPhU9GN3kHk6dJZafRVAeAP9quXckH5w8n7Ae7yP8e2Zh6SPPKFn2K6oE3GBpcz9QzfJTNRWXbY7w1L3nGLE5beZL1',
|
||||||
|
@ -82,7 +93,8 @@ var copayers = [{
|
||||||
privKey_1H_0: '3c49816d4e83d8758f89e8e104e3566a8a61426a9b7d4945b34212fbbb8e8290',
|
privKey_1H_0: '3c49816d4e83d8758f89e8e104e3566a8a61426a9b7d4945b34212fbbb8e8290',
|
||||||
pubKey_1H_0: '0307ab8c0d8eea1fe3c3781050a69e71f9e7c8cc8476a77103e08a461506a0e780'
|
pubKey_1H_0: '0307ab8c0d8eea1fe3c3781050a69e71f9e7c8cc8476a77103e08a461506a0e780'
|
||||||
}, {
|
}, {
|
||||||
id44: '4d0c1eaab0aafc08aea7328f9ed1d3fc2812791ad2ebb9cbc1a8537b51b18afa',
|
id44btc: '4d0c1eaab0aafc08aea7328f9ed1d3fc2812791ad2ebb9cbc1a8537b51b18afa',
|
||||||
|
id44bch: '63ed91d8b7c4f06028d4a795cbb30d91772d93c99e7cc612d9f0b33a4fa215de',
|
||||||
id45: '9129a0454adcf659f4f9d65a9b4dc4f9793bd1f59664268b56a7ef73f29f1b8a',
|
id45: '9129a0454adcf659f4f9d65a9b4dc4f9793bd1f59664268b56a7ef73f29f1b8a',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K3pgRcRBRnmcxNkNNLmJrpneMkEXY6o5TWBuJLMfdRpAWdb2cG3yxbL4DxfpUnQpjfQUmwPdVrRGoDJmtAf5u8cyqKCoDV97',
|
xPrivKey: 'xprv9s21ZrQH143K3pgRcRBRnmcxNkNNLmJrpneMkEXY6o5TWBuJLMfdRpAWdb2cG3yxbL4DxfpUnQpjfQUmwPdVrRGoDJmtAf5u8cyqKCoDV97',
|
||||||
xPubKey: 'xpub661MyMwAqRbcGJktiSiS9uZgvnCrkE2iC1ZxYcw9f8cSNzESstysycUzUsDCU6KnnjR29VZ1eRAXDgEXfYxGw1B9E7VLSAcHa9UuifSozmy',
|
xPubKey: 'xpub661MyMwAqRbcGJktiSiS9uZgvnCrkE2iC1ZxYcw9f8cSNzESstysycUzUsDCU6KnnjR29VZ1eRAXDgEXfYxGw1B9E7VLSAcHa9UuifSozmy',
|
||||||
|
@ -95,7 +107,8 @@ var copayers = [{
|
||||||
privKey_1H_0: '87f8a2b92dd04d2782c3d40a34f09f2ab42076bd02b81fbe4a4a72f87ad2e6df',
|
privKey_1H_0: '87f8a2b92dd04d2782c3d40a34f09f2ab42076bd02b81fbe4a4a72f87ad2e6df',
|
||||||
pubKey_1H_0: '02a0370d6f1213ab3390ac666585614ad71146f3f28ec326e2e779f999c1a497eb'
|
pubKey_1H_0: '02a0370d6f1213ab3390ac666585614ad71146f3f28ec326e2e779f999c1a497eb'
|
||||||
}, {
|
}, {
|
||||||
id44: '5ae7b75deb3b4d7e251f1fc5613904c9ef8548af7601d93ef668299be4f75ddd',
|
id44btc: '5ae7b75deb3b4d7e251f1fc5613904c9ef8548af7601d93ef668299be4f75ddd',
|
||||||
|
id44bch: '375a87b5614473ad359fee0385e9ffcb01d78c7880b34987e59da06eeac8029a',
|
||||||
id45: '37b81e2544b43ce7f37a132a748426e1566ecbb758564d4d7d07b716fbe1b368',
|
id45: '37b81e2544b43ce7f37a132a748426e1566ecbb758564d4d7d07b716fbe1b368',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K3nvcmdjDDDZbDJHpfWZCUiunwraZdcamYcafHvUnZfV51fivH9FPyfo12NyKH5JDxGLsQePyWKtTiJx3pkEaiwxsMLkVapp',
|
xPrivKey: 'xprv9s21ZrQH143K3nvcmdjDDDZbDJHpfWZCUiunwraZdcamYcafHvUnZfV51fivH9FPyfo12NyKH5JDxGLsQePyWKtTiJx3pkEaiwxsMLkVapp',
|
||||||
xPubKey: 'xpub661MyMwAqRbcGH15sfGDaMWKmL8K4yH3qwqPkEzBBx7kRQuoqTo37ToYrvLJh7JpV5FQSverERMcdF4HcP1UCiie2ayeMXRq67zr75PzMKs',
|
xPubKey: 'xpub661MyMwAqRbcGH15sfGDaMWKmL8K4yH3qwqPkEzBBx7kRQuoqTo37ToYrvLJh7JpV5FQSverERMcdF4HcP1UCiie2ayeMXRq67zr75PzMKs',
|
||||||
|
@ -108,7 +121,8 @@ var copayers = [{
|
||||||
privKey_1H_0: '66230b6b8b65725162ea43313fcc233f4f0dd135cea00d04b73a84d3f681ef25',
|
privKey_1H_0: '66230b6b8b65725162ea43313fcc233f4f0dd135cea00d04b73a84d3f681ef25',
|
||||||
pubKey_1H_0: '03f148bde0784c80051acd159b28a30022e685aca56418f8f50100d9f8a0192c37'
|
pubKey_1H_0: '03f148bde0784c80051acd159b28a30022e685aca56418f8f50100d9f8a0192c37'
|
||||||
}, {
|
}, {
|
||||||
id44: '98e78a9cb2ab340a245c5082897eadb28c367319f97b93e7b51b4d5ca5cdc68e',
|
id44btc: '98e78a9cb2ab340a245c5082897eadb28c367319f97b93e7b51b4d5ca5cdc68e',
|
||||||
|
id44bch: 'f390e03140593c0c724e0d3a2a9cf39d63319edc833024a149a72efacb368737',
|
||||||
id45: 'e1557d3421a8884fe007674f3f0b6f0feafa76289a0edcc5ec736161b4d02257',
|
id45: 'e1557d3421a8884fe007674f3f0b6f0feafa76289a0edcc5ec736161b4d02257',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K2uYgqtYtphEQkFAgiWSqahFUWjgCdKykJagiNDz6Lf7xRVQdtZ7MvkhX9V3pEcK3xTAWZ6Y6ecJqrXnCpzrH9GSHn8wyrT5',
|
xPrivKey: 'xprv9s21ZrQH143K2uYgqtYtphEQkFAgiWSqahFUWjgCdKykJagiNDz6Lf7xRVQdtZ7MvkhX9V3pEcK3xTAWZ6Y6ecJqrXnCpzrH9GSHn8wyrT5',
|
||||||
xPubKey: 'xpub661MyMwAqRbcFPd9wv5uBqB9JH1B7yAgwvB5K85pBfWjBP1rumJLtTSSGnCdsJSXfwmTyexsRjbUhzB4J6LWfL8mC2Ka117JrnXetyCzk3r',
|
xPubKey: 'xpub661MyMwAqRbcFPd9wv5uBqB9JH1B7yAgwvB5K85pBfWjBP1rumJLtTSSGnCdsJSXfwmTyexsRjbUhzB4J6LWfL8mC2Ka117JrnXetyCzk3r',
|
||||||
|
@ -121,7 +135,8 @@ var copayers = [{
|
||||||
privKey_1H_0: '9e215580c8e5876215ad101ded325bcacc5ab9d97b26e8fdfab89ef5bb6e0ab7',
|
privKey_1H_0: '9e215580c8e5876215ad101ded325bcacc5ab9d97b26e8fdfab89ef5bb6e0ab7',
|
||||||
pubKey_1H_0: '0265d33caaa128a77cc38ab8751c7d730e0274a212f1f65b73f637eddb3a3fb151'
|
pubKey_1H_0: '0265d33caaa128a77cc38ab8751c7d730e0274a212f1f65b73f637eddb3a3fb151'
|
||||||
}, {
|
}, {
|
||||||
id44: 'f716dbeec58e44c698b34c2d81bae4699ed5a5a522281733ec50aa03caf76a19',
|
id44btc: 'f716dbeec58e44c698b34c2d81bae4699ed5a5a522281733ec50aa03caf76a19',
|
||||||
|
id44bch: 'da39b3d560d2d99d9557a5a70ca3dc4561c3930e2850748fa80bdcecb650a9bf',
|
||||||
id45: '8a6d840580549a34422c9b150dbd1e96e369c5db69ee736caab95616f8abb22b',
|
id45: '8a6d840580549a34422c9b150dbd1e96e369c5db69ee736caab95616f8abb22b',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K2wcRMP75tAEL5JnUx4xU2AbUBQzVVUDP7DHZJkjF3kaRE7tcnPLLLL9PGjYTWTJmCQPaQ4GGzgWEUFJ6snwJG9YnQHBFRNR',
|
xPrivKey: 'xprv9s21ZrQH143K2wcRMP75tAEL5JnUx4xU2AbUBQzVVUDP7DHZJkjF3kaRE7tcnPLLLL9PGjYTWTJmCQPaQ4GGzgWEUFJ6snwJG9YnQHBFRNR',
|
||||||
xPubKey: 'xpub661MyMwAqRbcFRgtTQe6FJB4dLcyMXgKPPX4yoQ73okMz1chrJ3VbYtu5PRTxMBGuXt6eyqwAuG2BEBzQPLc1x8gnSQiATS3GRzKi1BuQAR',
|
xPubKey: 'xpub661MyMwAqRbcFRgtTQe6FJB4dLcyMXgKPPX4yoQ73okMz1chrJ3VbYtu5PRTxMBGuXt6eyqwAuG2BEBzQPLc1x8gnSQiATS3GRzKi1BuQAR',
|
||||||
|
@ -135,8 +150,8 @@ var copayers = [{
|
||||||
pubKey_1H_0: '0266cdb57b8a4d7c1b5b20ddeea43705420c6e3aef2c2979a3768b7b585839a0d3'
|
pubKey_1H_0: '0266cdb57b8a4d7c1b5b20ddeea43705420c6e3aef2c2979a3768b7b585839a0d3'
|
||||||
}, ];
|
}, ];
|
||||||
|
|
||||||
var history = [
|
|
||||||
{
|
var history = [{
|
||||||
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
|
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
|
||||||
vin: [{
|
vin: [{
|
||||||
txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d",
|
txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d",
|
||||||
|
@ -175,8 +190,7 @@ var history = [
|
||||||
valueOut: 0.01345753,
|
valueOut: 0.01345753,
|
||||||
valueIn: 0.01371235,
|
valueIn: 0.01371235,
|
||||||
fees: 0.00025482
|
fees: 0.00025482
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
|
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
|
||||||
vin: [{
|
vin: [{
|
||||||
txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d",
|
txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d",
|
||||||
|
|
|
@ -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');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue