Merge pull request #772 from matiu/feat/bch-addresses-formats

Feat/bch addresses formats
This commit is contained in:
Matias Alejo Garcia 2018-03-13 16:33:24 -03:00 committed by GitHub
commit 9f27a33f0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 102 additions and 3738 deletions

View File

@ -55,12 +55,12 @@ var config = {
provider: 'insight',
//url: 'https://cashexplorer.bitcoin.com',
url: 'https://bch-insight.bitpay.com:443',
translateAddresses: true,
addressFormat: 'cashaddr', // copay, cashaddr, or legacy
},
testnet: {
provider: 'insight',
url: 'https://test-bch-insight.bitpay.com:443',
translateAddresses: true,
addressFormat: 'cashaddr', // copay, cashaddr, or legacy
},
},

View File

@ -1,60 +0,0 @@
var Bitcore_ = {
btc: require('bitcore-lib'),
bch: require('bitcore-lib-cash')
};
var _ = require('lodash');
function AddressTranslator() {
};
AddressTranslator.getAddressCoin = function(address) {
try {
new Bitcore_['btc'].Address(address);
return 'btc';
} catch (e) {
try {
new Bitcore_['bch'].Address(address);
return 'bch';
} catch (e) {
return;
}
}
};
AddressTranslator.translate = function(addresses, coin, origCoin) {
var wasArray = true;
if (!_.isArray(addresses)) {
wasArray = false;
addresses = [addresses];
}
origCoin = origCoin || AddressTranslator.getAddressCoin(addresses[0]);
var ret = _.map(addresses, function(x) {
var orig = new Bitcore_[origCoin].Address(x).toObject();
if (coin == 'bch') {
return Bitcore_[coin].Address.fromObject(orig).toCashAddress(true);
} else {
return Bitcore_[coin].Address.fromObject(orig).toString();
}
});
if (wasArray)
return ret;
else
return ret[0];
};
AddressTranslator.translateInput = function(addresses) {
return this.translate(addresses, 'btc', 'bch');
}
AddressTranslator.translateOutput = function(addresses) {
return this.translate(addresses, 'bch', 'btc');
}
module.exports = AddressTranslator;

View File

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

View File

@ -37,6 +37,14 @@ function BlockChainExplorer(opts) {
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) {
case 'insight':
return new Insight({
@ -45,7 +53,7 @@ function BlockChainExplorer(opts) {
url: url,
apiPrefix: opts.apiPrefix,
userAgent: opts.userAgent,
translateAddresses: opts.translateAddresses,
addressFormat: opts.addressFormat,
});
default:
throw new Error('Provider ' + provider + ' not supported.');

View File

@ -8,7 +8,7 @@ log.debug = log.verbose;
var io = require('socket.io-client');
var requestList = require('./request-list');
var Common = require('../common');
var AddressTranslator = require('../addresstranslator');
var BCHAddressTranslator = require('../bchaddresstranslator');
var Constants = Common.Constants,
Defaults = Common.Defaults,
Utils = Common.Utils;
@ -24,7 +24,11 @@ function Insight(opts) {
this.network = opts.network || 'livenet';
this.hosts = opts.url;
this.userAgent = opts.userAgent || 'bws';
this.shouldTranslateAddresses = _.isUndefined(opts.translateAddresses) ? this.coin == 'bch' : opts.translateAddresses;
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);
@ -42,37 +46,33 @@ var _parseErr = function(err, res) {
// Translate Request Address query
Insight.prototype.translateQueryAddresses = function(addresses) {
if (!this.shouldTranslateAddresses) return addresses;
// It is called 'translatedInput' from Cxxx to 1xxx because the
// module was created for insight-api
return AddressTranslator.translateInput(addresses);
if (!this.addressFormat) return addresses;
return BCHAddressTranslator.translate(addresses, this.addressFormat, 'copay');
};
// Translate Result Address
Insight.prototype.translateResultAddresses = function(addresses) {
if (!this.shouldTranslateAddresses) return addresses;
if (!this.addressFormat) return addresses;
// It is called 'translateOutput' from 1xxx to Cxxx because the
// module was created for insight-api
return AddressTranslator.translateOutput(addresses);
return BCHAddressTranslator.translate(addresses, 'copay', this.addressFormat);
};
Insight.prototype.translateTx = function(tx) {
if (!this.shouldTranslateAddresses) return tx;
var self = this;
if (!this.addressFormat) return tx;
_.each(tx.vin, function(x){
if (x.addr) {
x.addr = AddressTranslator.translateOutput(x.addr);
x.addr = self.translateResultAddresses(x.addr);
}
});
_.each(tx.vout, function(x){
if (x.scriptPubKey && x.scriptPubKey.addresses) {
x.scriptPubKey.addresses = AddressTranslator.translateOutput(x.scriptPubKey.addresses);
x.scriptPubKey.addresses = self.translateResultAddresses(x.scriptPubKey.addresses);
}
});
@ -117,7 +117,8 @@ Insight.prototype.getUtxos = function(addresses, cb) {
this.requestQueue.push(args, function(err, res, unspent) {
if (err || res.statusCode !== 200) return cb(_parseErr(err, res));
if (self.shouldTranslateAddresses) {
if (self.addressFormat) {
_.each(unspent, function(x) {
x.address = self.translateResultAddresses(x.address);
});
@ -201,7 +202,7 @@ Insight.prototype.getTransactions = function(addresses, from, to, cb) {
// NOTE: Whenever Insight breaks communication with bitcoind, it returns invalid data but no error code.
if (!_.isArray(txs) || (txs.length != _.compact(txs).length)) return cb(new Error('Could not retrieve transactions from blockchain. Request was:' + JSON.stringify(args)));
if (self.shouldTranslateAddresses) {
if (self.addressFormat) {
_.each(txs, function(tx){
self.translateTx(tx);

View File

@ -12,6 +12,8 @@ Constants.NETWORKS = {
TESTNET: 'testnet',
};
Constants.ADDRESS_FORMATS = ['copay', 'cashaddr', 'legacy'];
Constants.SCRIPT_TYPES = {
P2SH: 'P2SH',
P2PKH: 'P2PKH',

View File

@ -1189,6 +1189,12 @@ WalletService.prototype._getUtxosForCurrentWallet = function(opts, cb) {
if (!opts.coin) return next();
coin = opts.coin;
if (coin != 'bch') return next();
if (Utils.getAddressCoin(addressStrs[0]) == 'bch')
return next();
// because some old BCH walelts could have legacy addresses?
addressStrs = _.map(addressStrs, function(a) {
return Utils.translateAddress(a, coin);
});
@ -1245,6 +1251,10 @@ WalletService.prototype._getUtxosForCurrentWallet = function(opts, cb) {
// Needed for the clients to sign UTXOs
var addressToPath = _.indexBy(allAddresses, 'address');
_.each(allUtxos, function(utxo) {
if (!addressToPath[utxo.address]) {
log.warn('Ignored UTXO!: ' + utxo.address);
return;
}
utxo.path = addressToPath[utxo.address].path;
utxo.publicKeys = addressToPath[utxo.address].publicKeys;
});

3605
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "bitcore-wallet-service",
"description": "A service for Mutisig HD Bitcoin Wallets",
"author": "BitPay Inc",
"version": "2.3.0",
"version": "2.4.0",
"licence": "MIT",
"keywords": [
"bitcoin",

View File

@ -1,53 +0,0 @@
var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var assert = require('assert');
var should = chai.should;
var AddressTranslator = require('../lib/addresstranslator');
describe('#AddressTranslator', function() {
it('should translate address from btc to bch', function() {
var res = AddressTranslator.translate('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', 'bch');
assert( res == 'qrvcdmgpk73zyfd8pmdl9wnuld36zh9n4gms8s0u59');
});
it('should translate address from bch to btc', function() {
var res = AddressTranslator.translateInput('HBf8isgS8EXG1r3X6GP89FmooUmiJ42wHS');
assert(res=='36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r');
});
it('should keep the address if there is nothing to do (bch)', function() {
var res = AddressTranslator.translate('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz', 'bch');
assert(res=='qrvcdmgpk73zyfd8pmdl9wnuld36zh9n4gms8s0u59');
});
it('should keep the address if there is nothing to do (btc)', function() {
var res = AddressTranslator.translate('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', 'btc');
assert(res=='1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA');
});
it('should support 3 params NOK', function() {
var a;
try {
var res = AddressTranslator.translate('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', 'btc', 'bch');
} catch (e) {
a=e.toString();
assert(a.match(/Address has mismatched network type/));
};
});
it('should support 3 params OK', function() {
var res = AddressTranslator.translate('HBf8isgS8EXG1r3X6GP89FmooUmiJ42wHS', 'btc', 'bch');
assert(res=='36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r');
});
it('should work with arrays also', function() {
var res = AddressTranslator.translateOutput(['1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', '37YHiaQnMjy73GS1UpiE8p2Ju6MyrrDw3J', '1DuPdCpGzVX73kBYaAbu5XDNDgE2Lza5Ed']);
assert(res[0] == 'qrvcdmgpk73zyfd8pmdl9wnuld36zh9n4gms8s0u59');
assert(res[1] == 'ppqz5v08kssnuupe0ckqtw4ss3qt460fcqugqzq2me');
assert(res[2] == 'qzxc5pnsfs8pmgfprhzc4l4vzf3zxz8p85nc6kfh8l');
});
});