commit
538c9eae99
22 changed files with 1710 additions and 2775 deletions
13
README.md
13
README.md
|
|
@ -196,4 +196,17 @@ Copay uses the Javascript library Bitcore for Bitcoin related functions. Bitcore
|
||||||
var cmd = `node util/build_bitcore.js`
|
var cmd = `node util/build_bitcore.js`
|
||||||
cd <BITCORE_HOME>
|
cd <BITCORE_HOME>
|
||||||
node $cmd
|
node $cmd
|
||||||
|
|
||||||
|
Payment Protocol
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Copay support BIP70 (Payment Protocol), with the following current limitations:
|
||||||
|
|
||||||
|
* Only one output is allowed. Payment requests is more that one output are not supported.
|
||||||
|
* Only standard Pay-to-pubkeyhash and Pay-to-scripthash scripts are supported (on payment requests). Other script types will cause the entire payment request to be rejected.
|
||||||
|
* Memos from the custormer to the server are not supported (i.e. there is no place to write messages to the server in the current UX)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
3
copay.js
3
copay.js
|
|
@ -20,6 +20,3 @@ module.exports.Compatibility = require('./js/models/Compatibility');
|
||||||
module.exports.PluginManager = require('./js/models/PluginManager');
|
module.exports.PluginManager = require('./js/models/PluginManager');
|
||||||
module.exports.version = require('./version').version;
|
module.exports.version = require('./version').version;
|
||||||
module.exports.commitHash = require('./version').commitHash;
|
module.exports.commitHash = require('./version').commitHash;
|
||||||
|
|
||||||
// test hack :s, will fix
|
|
||||||
module.exports.FakePayProServer = require('./test/mocks/FakePayProServer');
|
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,8 @@ angular.module('copayApp.controllers').controller('HistoryController',
|
||||||
_.each(items, function(r) {
|
_.each(items, function(r) {
|
||||||
r.ts = r.minedTs || r.sentTs;
|
r.ts = r.minedTs || r.sentTs;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$scope.blockchain_txs = w.cached_txs = items;
|
$scope.blockchain_txs = w.cached_txs = items;
|
||||||
$scope.nbPages = res.nbPages;
|
$scope.nbPages = res.nbPages;
|
||||||
$scope.totalItems = res.nbItems;
|
$scope.totalItems = res.nbItems;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
$scope.isRateAvailable = false;
|
$scope.isRateAvailable = false;
|
||||||
$scope.rateService = rateService;
|
$scope.rateService = rateService;
|
||||||
|
$scope.showScanner = false;
|
||||||
|
|
||||||
|
|
||||||
rateService.whenAvailable(function() {
|
rateService.whenAvailable(function() {
|
||||||
|
|
@ -100,6 +101,24 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
if (!window.cordova && !navigator.getUserMedia)
|
if (!window.cordova && !navigator.getUserMedia)
|
||||||
$scope.disableScanner = 1;
|
$scope.disableScanner = 1;
|
||||||
|
|
||||||
|
$scope._showError = function(err) {
|
||||||
|
copay.logger.error(err);
|
||||||
|
|
||||||
|
var msg = err.toString();
|
||||||
|
if (msg.match('BIG'))
|
||||||
|
msg = 'The transaction have too many inputs. Try creating many transactions for smaller amounts'
|
||||||
|
|
||||||
|
if (msg.match('totalNeededAmount'))
|
||||||
|
msg = 'Not enough funds'
|
||||||
|
|
||||||
|
var message = 'The transaction' + (w.isShared() ? ' proposal' : '') +
|
||||||
|
' could not be created: ' + msg;
|
||||||
|
|
||||||
|
$scope.error = message;
|
||||||
|
$scope.loading = false;
|
||||||
|
$scope.loadTxs();
|
||||||
|
};
|
||||||
|
|
||||||
$scope.submitForm = function(form) {
|
$scope.submitForm = function(form) {
|
||||||
if (form.$invalid) {
|
if (form.$invalid) {
|
||||||
$scope.error = 'Unable to send transaction proposal';
|
$scope.error = 'Unable to send transaction proposal';
|
||||||
|
|
@ -112,92 +131,37 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
var amount = parseInt((form.amount.$modelValue * w.settings.unitToSatoshi).toFixed(0));
|
var amount = parseInt((form.amount.$modelValue * w.settings.unitToSatoshi).toFixed(0));
|
||||||
var commentText = form.comment.$modelValue;
|
var commentText = form.comment.$modelValue;
|
||||||
|
|
||||||
function done(err, ntxid, merchantData) {
|
|
||||||
if (err) {
|
|
||||||
copay.logger.error(err);
|
|
||||||
|
|
||||||
var msg = err.toString();
|
var payInfo;
|
||||||
|
|
||||||
if (msg.match('BIG'))
|
|
||||||
msg = 'The transaction have too many inputs. Try creating many transactions for smaller amounts.'
|
|
||||||
|
|
||||||
if (msg.match('totalNeededAmount'))
|
|
||||||
msg = 'Not enough funds.'
|
|
||||||
|
|
||||||
var message = 'The transaction' + (w.isShared() ? ' proposal' : '') + ' could not be created: ' + msg;
|
|
||||||
$scope.error = message;
|
|
||||||
$scope.loading = false;
|
|
||||||
$scope.loadTxs();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user is granted the privilege of choosing
|
|
||||||
// their own amount, add it to the tx.
|
|
||||||
if (merchantData && +merchantData.total === 0) {
|
|
||||||
var txp = w.txProposals.get(ntxid);
|
|
||||||
var tx = txp.builder.tx = txp.builder.tx || txp.builder.build();
|
|
||||||
tx.outs[0].v = bitcore.Bignum(amount + '', 10).toBuffer({
|
|
||||||
// XXX This may not work in node due
|
|
||||||
// to the bignum only-big endian bug:
|
|
||||||
endian: 'little',
|
|
||||||
size: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (w.requiresMultipleSignatures()) {
|
|
||||||
$scope.loading = false;
|
|
||||||
notification.success('Success', 'The transaction proposal created');
|
|
||||||
$scope.loadTxs();
|
|
||||||
} else {
|
|
||||||
w.sendTx(ntxid, function(txid, merchantData) {
|
|
||||||
if (txid) {
|
|
||||||
var message = 'Transaction id: ' + txid;
|
|
||||||
if (merchantData) {
|
|
||||||
if (merchantData.pr.ca) {
|
|
||||||
message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.';
|
|
||||||
}
|
|
||||||
message += merchantData.pr.pd.memo;
|
|
||||||
message += ' Merchant: ' + merchantData.pr.pd.payment_url;
|
|
||||||
}
|
|
||||||
$scope.success = 'Transaction broadcasted' + message;
|
|
||||||
} else {
|
|
||||||
$scope.error = 'There was an error sending the transaction';
|
|
||||||
}
|
|
||||||
$scope.loading = false;
|
|
||||||
$scope.loadTxs();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$rootScope.pendingPayment = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var uri;
|
|
||||||
if (address.indexOf('bitcoin:') === 0) {
|
if (address.indexOf('bitcoin:') === 0) {
|
||||||
uri = new bitcore.BIP21(address).data;
|
payInfo = (new bitcore.BIP21(address)).data;
|
||||||
} else if (/^https?:\/\//.test(address)) {
|
} else if (/^https?:\/\//.test(address)) {
|
||||||
uri = {
|
payInfo = {
|
||||||
merchant: address
|
merchant: address
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're setting the domain, ignore the change.
|
// If we're setting the domain, ignore the change.
|
||||||
if ($rootScope.merchant && $rootScope.merchant.domain && address === $rootScope.merchant.domain) {
|
if ($rootScope.merchant && $rootScope.merchant.domain && address === $rootScope.merchant.domain) {
|
||||||
uri = {
|
payInfo = {
|
||||||
merchant: $rootScope.merchant.request_url
|
merchant: $rootScope.merchant.request_url
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
w.spend({
|
||||||
|
toAddress: address,
|
||||||
|
amountSat: amount,
|
||||||
|
comment: commentText,
|
||||||
|
url: (payInfo && payInfo.merchant) ? payInfo.merchant : null,
|
||||||
|
}, function(err, txid, status) {
|
||||||
|
// reset fields
|
||||||
|
$scope.address = $scope.amount = $scope.commentText = null;
|
||||||
|
form.address.$pristine = form.amount.$pristine = true;
|
||||||
|
$rootScope.pendingPayment = null;
|
||||||
|
if (err) return $scope._showError(err);
|
||||||
|
|
||||||
if (uri && uri.merchant) {
|
$scope.notifyStatus(status);
|
||||||
w.createPaymentTx({
|
$scope.loadTxs();
|
||||||
uri: uri.merchant,
|
});
|
||||||
memo: commentText
|
|
||||||
}, done);
|
|
||||||
} else {
|
|
||||||
w.createTx(address, amount, commentText, done);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset fields
|
|
||||||
$scope.address = $scope.amount = $scope.commentText = null;
|
|
||||||
form.address.$pristine = form.amount.$pristine = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// QR code Scanner
|
// QR code Scanner
|
||||||
|
|
@ -294,7 +258,6 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
$scope.openScanner = function() {
|
$scope.openScanner = function() {
|
||||||
if (window.cordova) return $scope.scannerIntent();
|
if (window.cordova) return $scope.scannerIntent();
|
||||||
|
|
||||||
$scope.showScanner = true;
|
$scope.showScanner = true;
|
||||||
|
|
||||||
// Wait a moment until the canvas shows
|
// Wait a moment until the canvas shows
|
||||||
|
|
@ -399,28 +362,24 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
$scope.amount = $rootScope.topAmount;
|
$scope.amount = $rootScope.topAmount;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.notifyStatus = function(status) {
|
||||||
|
if (status == copay.Wallet.TX_BROADCASTED)
|
||||||
|
notification.success('Success', 'Transaction broadcasted!');
|
||||||
|
else if (status == copay.Wallet.TX_PROPOSAL_SENT)
|
||||||
|
notification.success('Success', 'Transaction proposal created');
|
||||||
|
else if (status == copay.Wallet.TX_SIGNED)
|
||||||
|
notification.success('Success', 'Transaction proposal was signed');
|
||||||
|
else
|
||||||
|
notification.error('Error', 'Unknown error occured');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
$scope.send = function(ntxid, cb) {
|
$scope.send = function(ntxid, cb) {
|
||||||
$scope.error = $scope.success = null;
|
$scope.error = $scope.success = null;
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
$rootScope.txAlertCount = 0;
|
$rootScope.txAlertCount = 0;
|
||||||
w.sendTx(ntxid, function(txid, merchantData) {
|
w.broadcastTx(ntxid, function(err, txid, status) {
|
||||||
if (!txid) {
|
$scope.notifyStatus(status);
|
||||||
notification.error('Error', 'There was an error sending the transaction');
|
|
||||||
} else {
|
|
||||||
if (!merchantData) {
|
|
||||||
notification.success('Success', 'Transaction broadcasted!');
|
|
||||||
} else {
|
|
||||||
var message = 'Transaction ID: ' + txid;
|
|
||||||
if (merchantData.pr.ca) {
|
|
||||||
message += ' This payment protocol transaction' + ' has been verified through ' + merchantData.pr.ca + '.';
|
|
||||||
}
|
|
||||||
message += ' Message from server: ' + merchantData.ack.memo;
|
|
||||||
message += ' For merchant: ' + merchantData.pr.pd.payment_url;
|
|
||||||
notification.success('Success', 'Transaction sent' + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cb) return cb();
|
if (cb) return cb();
|
||||||
else $scope.loadTxs();
|
else $scope.loadTxs();
|
||||||
});
|
});
|
||||||
|
|
@ -430,22 +389,10 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
$scope.error = $scope.success = null;
|
$scope.error = $scope.success = null;
|
||||||
|
|
||||||
try {
|
w.signAndSend(ntxid, function(err, id, status) {
|
||||||
w.sign(ntxid);
|
$scope.notifyStatus(status);
|
||||||
} catch (e) {
|
|
||||||
notification.error('Error','There was an error signing the transaction');
|
|
||||||
$scope.loadTxs();
|
$scope.loadTxs();
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
var p = w.txProposals.getTxProposal(ntxid);
|
|
||||||
if (p.builder.isFullySigned()) {
|
|
||||||
$scope.send(ntxid, function() {
|
|
||||||
$scope.loadTxs();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$scope.loadTxs();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.reject = function(ntxid) {
|
$scope.reject = function(ntxid) {
|
||||||
|
|
@ -453,7 +400,6 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
$rootScope.txAlertCount = 0;
|
$rootScope.txAlertCount = 0;
|
||||||
w.reject(ntxid);
|
w.reject(ntxid);
|
||||||
notification.warning('Transaction rejected', 'You rejected the transaction successfully');
|
notification.warning('Transaction rejected', 'You rejected the transaction successfully');
|
||||||
$scope.loading = false;
|
|
||||||
$scope.loadTxs();
|
$scope.loadTxs();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -546,7 +492,9 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
}, 10 * 1000);
|
}, 10 * 1000);
|
||||||
|
|
||||||
// Payment Protocol URI (BIP-72)
|
// Payment Protocol URI (BIP-72)
|
||||||
$scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) {
|
$scope.wallet.fetchPaymentRequest({
|
||||||
|
url: uri.merchant
|
||||||
|
}, function(err, merchantData) {
|
||||||
if (!timeout) return;
|
if (!timeout) return;
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,6 @@ Network.prototype._onMessage = function(enc) {
|
||||||
log.debug('Ignoring trailing message. Ts:', enc.ts);
|
log.debug('Ignoring trailing message. Ts:', enc.ts);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.debug('Async: receiving ' + JSON.stringify(payload));
|
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
switch (payload.type) {
|
switch (payload.type) {
|
||||||
|
|
@ -239,6 +238,7 @@ Network.prototype._onMessage = function(enc) {
|
||||||
|
|
||||||
Network.prototype._setupConnectionHandlers = function(opts, cb) {
|
Network.prototype._setupConnectionHandlers = function(opts, cb) {
|
||||||
preconditions.checkState(this.socket);
|
preconditions.checkState(this.socket);
|
||||||
|
log.debug('setting up connection', opts);
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.socket.on('connect_error', function(m) {
|
self.socket.on('connect_error', function(m) {
|
||||||
|
|
@ -260,7 +260,7 @@ Network.prototype._setupConnectionHandlers = function(opts, cb) {
|
||||||
if (fromTs) {
|
if (fromTs) {
|
||||||
self.ignoreMessageFromTs = fromTs;
|
self.ignoreMessageFromTs = fromTs;
|
||||||
}
|
}
|
||||||
log.info('Async: synchronizing from: ',fromTs);
|
log.info('Async: synchronizing from: ', fromTs);
|
||||||
self.socket.emit('sync', fromTs);
|
self.socket.emit('sync', fromTs);
|
||||||
self.started = true;
|
self.started = true;
|
||||||
});
|
});
|
||||||
|
|
@ -398,8 +398,6 @@ Network.prototype.send = function(dest, payload, cb) {
|
||||||
if (to == this.copayerId)
|
if (to == this.copayerId)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
log.debug('SEND to: ' + to, this.copayerId, JSON.stringify(payload));
|
|
||||||
|
|
||||||
var message = this.encode(to, payload);
|
var message = this.encode(to, payload);
|
||||||
this.socket.emit('message', message);
|
this.socket.emit('message', message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,8 @@ Identity.prototype.storeWallet = function(wallet, cb) {
|
||||||
|
|
||||||
this.storage.setItem(key, val, function(err) {
|
this.storage.setItem(key, val, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.debug('Wallet:' + wallet.getName() + ' couldnt be stored:', err);
|
log.error('Wallet:' + wallet.getName() + ' couldnt be stored:', err);
|
||||||
|
log.error('Wallet:' + wallet.getName() + ' Size:', JSON.stringify(wallet.sizes()));
|
||||||
}
|
}
|
||||||
if (cb)
|
if (cb)
|
||||||
return cb(err);
|
return cb(err);
|
||||||
|
|
@ -402,6 +403,9 @@ Identity.prototype.bindWallet = function(w) {
|
||||||
w.on('txProposalsUpdated', function() {
|
w.on('txProposalsUpdated', function() {
|
||||||
Identity.storeWalletDebounced(self, w);
|
Identity.storeWalletDebounced(self, w);
|
||||||
});
|
});
|
||||||
|
w.on('paymentAck', function() {
|
||||||
|
Identity.storeWalletDebounced(self, w);
|
||||||
|
});
|
||||||
w.on('newAddresses', function() {
|
w.on('newAddresses', function() {
|
||||||
Identity.storeWalletDebounced(self, w);
|
Identity.storeWalletDebounced(self, w);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -224,9 +224,7 @@ Insight.prototype.subscribe = function(addresses) {
|
||||||
|
|
||||||
s.emit('subscribe', address);
|
s.emit('subscribe', address);
|
||||||
s.on(address, handler);
|
s.on(address, handler);
|
||||||
} else {
|
}
|
||||||
log.debug('Already subcribed to: ', address);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ var Key = bitcore.Key;
|
||||||
var buffertools = bitcore.buffertools;
|
var buffertools = bitcore.buffertools;
|
||||||
var preconditions = require('preconditions').instance();
|
var preconditions = require('preconditions').instance();
|
||||||
|
|
||||||
|
var TX_MAX_SIZE_KB = 50;
|
||||||
var VERSION = 1;
|
var VERSION = 1;
|
||||||
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment', 'paymentProtocolURL'];
|
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment', 'paymentProtocolURL', 'paymentAckMemo'];
|
||||||
|
|
||||||
|
|
||||||
function TxProposal(opts) {
|
function TxProposal(opts) {
|
||||||
|
|
@ -37,10 +38,72 @@ function TxProposal(opts) {
|
||||||
this.comment = opts.comment || null;
|
this.comment = opts.comment || null;
|
||||||
this.readonly = opts.readonly || null;
|
this.readonly = opts.readonly || null;
|
||||||
this.merchant = opts.merchant || null;
|
this.merchant = opts.merchant || null;
|
||||||
|
this.paymentAckMemo = null;
|
||||||
this.paymentProtocolURL = opts.paymentProtocolURL || null;
|
this.paymentProtocolURL = opts.paymentProtocolURL || null;
|
||||||
|
|
||||||
|
if (opts.creator) {
|
||||||
|
var now = Date.now();
|
||||||
|
var me = {};
|
||||||
|
me[opts.creator] = now;
|
||||||
|
|
||||||
|
this.seenBy = me;
|
||||||
|
this.signedBy = {};
|
||||||
|
this.creator = opts.creator;
|
||||||
|
this.createdTs = now;
|
||||||
|
if (opts.signWith) {
|
||||||
|
if (!this.sign(opts.signWith, opts.creator))
|
||||||
|
throw new Error('Could not sign generated tx');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._sync();
|
this._sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TxProposal.prototype._checkPayPro = function() {
|
||||||
|
if (!this.merchant) return;
|
||||||
|
|
||||||
|
if (this.paymentProtocolURL !== this.merchant.request_url)
|
||||||
|
throw new Error('PayPro: Mismatch on Payment URLs');
|
||||||
|
|
||||||
|
if (!this.merchant.outs || this.merchant.outs.length !== 1)
|
||||||
|
throw new Error('PayPro: Unsopported number of outputs');
|
||||||
|
|
||||||
|
if (this.merchant.expires < (this.getSent() || Date.now() / 1000.))
|
||||||
|
throw new Error('PayPro: Request expired');
|
||||||
|
|
||||||
|
if (!this.merchant.total || !this.merchant.outs[0].amountSatStr || !this.merchant.outs[0].address)
|
||||||
|
throw new Error('PayPro: Missing amount');
|
||||||
|
|
||||||
|
var outs = JSON.parse(this.builder.vanilla.outs);
|
||||||
|
if (_.size(outs) != 1)
|
||||||
|
throw new Error('PayPro: Wrong outs in Tx');
|
||||||
|
|
||||||
|
var ppOut = this.merchant.outs[0];
|
||||||
|
var txOut = outs[0];
|
||||||
|
|
||||||
|
if (ppOut.address !== txOut.address)
|
||||||
|
throw new Error('PayPro: Wrong out address in Tx');
|
||||||
|
|
||||||
|
if (ppOut.amountSatStr !== txOut.amountSatStr + '')
|
||||||
|
throw new Error('PayPro: Wrong amount in Tx');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TxProposal.prototype.isFullySigned = function() {
|
||||||
|
return this.builder && this.builder.isFullySigned();
|
||||||
|
};
|
||||||
|
|
||||||
|
TxProposal.prototype.sign = function(keys, signerId) {
|
||||||
|
var before = this.countSignatures();
|
||||||
|
this.builder.sign(keys);
|
||||||
|
|
||||||
|
var signaturesAdded = this.countSignatures() > before;
|
||||||
|
if (signaturesAdded){
|
||||||
|
this.signedBy[signerId] = Date.now();
|
||||||
|
}
|
||||||
|
return signaturesAdded;
|
||||||
|
};
|
||||||
|
|
||||||
TxProposal.prototype._check = function() {
|
TxProposal.prototype._check = function() {
|
||||||
|
|
||||||
|
|
@ -49,6 +112,11 @@ TxProposal.prototype._check = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var tx = this.builder.build();
|
var tx = this.builder.build();
|
||||||
|
|
||||||
|
var txSize = tx.getSize();
|
||||||
|
if (txSize / 1024 > TX_MAX_SIZE_KB)
|
||||||
|
throw new Error('BIG: Invalid TX proposal. Too big: ' + txSize + ' bytes');
|
||||||
|
|
||||||
if (!tx.ins.length)
|
if (!tx.ins.length)
|
||||||
throw new Error('Invalid tx proposal: no ins');
|
throw new Error('Invalid tx proposal: no ins');
|
||||||
|
|
||||||
|
|
@ -61,6 +129,28 @@ TxProposal.prototype._check = function() {
|
||||||
if (hashType && hashType !== Transaction.SIGHASH_ALL)
|
if (hashType && hashType !== Transaction.SIGHASH_ALL)
|
||||||
throw new Error('Invalid tx proposal: bad signatures');
|
throw new Error('Invalid tx proposal: bad signatures');
|
||||||
});
|
});
|
||||||
|
this._checkPayPro();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TxProposal.prototype.trimForStorage = function() {
|
||||||
|
// TODO (remove builder / builderObj. utxos, etc)
|
||||||
|
//
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
TxProposal.prototype.addMerchantData = function(merchantData) {
|
||||||
|
preconditions.checkArgument(merchantData.pr);
|
||||||
|
preconditions.checkArgument(merchantData.request_url);
|
||||||
|
var m = _.clone(merchantData);
|
||||||
|
|
||||||
|
if (!this.paymentProtocolURL)
|
||||||
|
this.paymentProtocolURL = m.request_url;
|
||||||
|
|
||||||
|
// remove unneeded data
|
||||||
|
m.raw = m.pr.pki_data = m.pr.signature = undefined;
|
||||||
|
this.merchant = m;
|
||||||
|
this._checkPayPro();
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.rejectCount = function() {
|
TxProposal.prototype.rejectCount = function() {
|
||||||
|
|
@ -101,7 +191,6 @@ TxProposal.prototype._sync = function() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TxProposal.prototype.getId = function() {
|
TxProposal.prototype.getId = function() {
|
||||||
preconditions.checkState(this.builder);
|
preconditions.checkState(this.builder);
|
||||||
return this.builder.build().getNormalizedHash().toString('hex');
|
return this.builder.build().getNormalizedHash().toString('hex');
|
||||||
|
|
@ -221,17 +310,6 @@ TxProposal._infoFromRedeemScript = function(s) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.mergeBuilder = function(incoming) {
|
|
||||||
var b0 = this.builder;
|
|
||||||
var b1 = incoming.builder;
|
|
||||||
|
|
||||||
var before = JSON.stringify(b0.toObj());
|
|
||||||
b0.merge(b1);
|
|
||||||
var after = JSON.stringify(b0.toObj());
|
|
||||||
return after !== before;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
TxProposal.prototype.getSeen = function(copayerId) {
|
TxProposal.prototype.getSeen = function(copayerId) {
|
||||||
return this.seenBy[copayerId];
|
return this.seenBy[copayerId];
|
||||||
};
|
};
|
||||||
|
|
@ -248,11 +326,14 @@ TxProposal.prototype.setRejected = function(copayerId) {
|
||||||
|
|
||||||
if (!this.rejectedBy[copayerId])
|
if (!this.rejectedBy[copayerId])
|
||||||
this.rejectedBy[copayerId] = Date.now();
|
this.rejectedBy[copayerId] = Date.now();
|
||||||
|
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.setSent = function(sentTxid) {
|
TxProposal.prototype.setSent = function(sentTxid) {
|
||||||
this.sentTxid = sentTxid;
|
this.sentTxid = sentTxid;
|
||||||
this.sentTs = Date.now();
|
this.sentTs = Date.now();
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.getSent = function() {
|
TxProposal.prototype.getSent = function() {
|
||||||
|
|
@ -338,9 +419,17 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
||||||
|
|
||||||
// merge will not merge any metadata.
|
// merge will not merge any metadata.
|
||||||
TxProposal.prototype.merge = function(incoming) {
|
TxProposal.prototype.merge = function(incoming) {
|
||||||
var hasChanged = this.mergeBuilder(incoming);
|
preconditions.checkArgument(_.isFunction(incoming._sync));
|
||||||
|
incoming._sync();
|
||||||
|
|
||||||
|
// Note that all inputs must have the same number of signatures, so checking
|
||||||
|
// one (0) is OK.
|
||||||
|
var before = this._inputSigners[0].length;
|
||||||
|
this.builder.merge(incoming.builder);
|
||||||
this._sync();
|
this._sync();
|
||||||
return hasChanged;
|
|
||||||
|
var after = this._inputSigners[0].length;
|
||||||
|
return after !== before;
|
||||||
};
|
};
|
||||||
|
|
||||||
//This should be on bitcore / Transaction
|
//This should be on bitcore / Transaction
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,6 @@ TxProposals.prototype.toObj = function() {
|
||||||
|
|
||||||
TxProposals.prototype.merge = function(inObj, builderOpts) {
|
TxProposals.prototype.merge = function(inObj, builderOpts) {
|
||||||
var incomingTx = TxProposal.fromUntrustedObj(inObj, builderOpts);
|
var incomingTx = TxProposal.fromUntrustedObj(inObj, builderOpts);
|
||||||
incomingTx._sync();
|
|
||||||
|
|
||||||
var myTxps = this.txps;
|
var myTxps = this.txps;
|
||||||
var ntxid = incomingTx.getId();
|
var ntxid = incomingTx.getId();
|
||||||
|
|
@ -178,16 +177,6 @@ TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
TxProposals.prototype.reject = function(ntxid, copayerId) {
|
|
||||||
var txp = this.get(ntxid);
|
|
||||||
txp.setRejected(copayerId);
|
|
||||||
};
|
|
||||||
|
|
||||||
TxProposals.prototype.seen = function(ntxid, copayerId) {
|
|
||||||
var txp = this.get(ntxid);
|
|
||||||
txp.setSeen(copayerId);
|
|
||||||
};
|
|
||||||
|
|
||||||
//returns the unspent txid-vout used in PENDING Txs
|
//returns the unspent txid-vout used in PENDING Txs
|
||||||
TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
||||||
var ret = {};
|
var ret = {};
|
||||||
|
|
@ -205,4 +194,22 @@ TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* purge
|
||||||
|
*
|
||||||
|
* @param deleteAll
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
TxProposals.prototype.purge = function(deleteAll, maxRejectCount) {
|
||||||
|
var m = _.size(this.txps);
|
||||||
|
|
||||||
|
if (deleteAll) {
|
||||||
|
this.deleteAll();
|
||||||
|
} else {
|
||||||
|
this.deletePending(maxRejectCount);
|
||||||
|
}
|
||||||
|
var n = _.size(this.txps);
|
||||||
|
return m - n;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = TxProposals;
|
module.exports = TxProposals;
|
||||||
|
|
|
||||||
1637
js/models/Wallet.js
1637
js/models/Wallet.js
File diff suppressed because it is too large
Load diff
|
|
@ -156,6 +156,8 @@ InsightStorage.prototype.setItem = function(name, value, callback) {
|
||||||
var passphrase = this.getPassphrase();
|
var passphrase = this.getPassphrase();
|
||||||
var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64');
|
var authHeader = new buffers.Buffer(this.email + ':' + passphrase).toString('base64');
|
||||||
var registerUrl = this.storeUrl + '/save';
|
var registerUrl = this.storeUrl + '/save';
|
||||||
|
|
||||||
|
log.debug('setItem ' + name + ' size:'+ (value.length/1024).toFixed(1) + 'kb' );
|
||||||
this.request.post({
|
this.request.post({
|
||||||
url: registerUrl,
|
url: registerUrl,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ angular.module('copayApp.services')
|
||||||
};
|
};
|
||||||
|
|
||||||
root.onError = function(scope) {
|
root.onError = function(scope) {
|
||||||
if (scope) {
|
if (scope) {
|
||||||
scope.loading = false;
|
scope.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,12 +64,6 @@ angular.module('copayApp.services')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
root.updateTxsAndBalance = function(w) {
|
|
||||||
root.updateTxs(w);
|
|
||||||
root.updateBalance(w, function() {
|
|
||||||
$rootScope.$digest();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
root.installWalletHandlers = function($scope, w) {
|
root.installWalletHandlers = function($scope, w) {
|
||||||
|
|
||||||
|
|
@ -129,35 +123,44 @@ angular.module('copayApp.services')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
w.on('newAddresses', function() {
|
w.on('newAddresses', function() {
|
||||||
root.updateTxsAndBalance(w);
|
root.updateBalance(w);
|
||||||
});
|
});
|
||||||
|
|
||||||
w.on('txProposalsUpdated', function() {
|
w.on('txProposalsUpdated', function() {
|
||||||
root.updateTxsAndBalance(w);
|
if (root.isFocusedWallet(wid)) {
|
||||||
|
root.updateTxs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
w.on('paymentACK', function(memo) {
|
||||||
|
notification.success('Payment Acknowledged', memo);
|
||||||
});
|
});
|
||||||
|
|
||||||
w.on('txProposalEvent', function(e) {
|
w.on('txProposalEvent', function(e) {
|
||||||
|
|
||||||
root.updateTxsAndBalance(w);
|
if (root.isFocusedWallet(wid)) {
|
||||||
|
root.updateTxs();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: add wallet name notification
|
// TODO: add wallet name notification
|
||||||
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
|
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
|
||||||
var name = w.getName();
|
var name = w.getName();
|
||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
case 'new':
|
case 'new':
|
||||||
notification.info('['+ name +'] New Transaction',
|
notification.info('[' + name + '] New Transaction',
|
||||||
$filter('translate')('You received a transaction proposal from') + ' ' + user);
|
$filter('translate')('You received a transaction proposal from') + ' ' + user);
|
||||||
break;
|
break;
|
||||||
case 'signed':
|
case 'signed':
|
||||||
notification.info('['+ name +'] Transaction Signed',
|
notification.info('[' + name + '] Transaction Signed',
|
||||||
$filter('translate')('A transaction was signed by') + ' ' + user);
|
$filter('translate')('A transaction was signed by') + ' ' + user);
|
||||||
break;
|
break;
|
||||||
case 'rejected':
|
case 'rejected':
|
||||||
notification.info('['+ name +'] Transaction Rejected',
|
notification.info('[' + name + '] Transaction Rejected',
|
||||||
$filter('translate')('A transaction was rejected by') + ' ' + user);
|
$filter('translate')('A transaction was rejected by') + ' ' + user);
|
||||||
break;
|
break;
|
||||||
case 'corrupt':
|
case 'corrupt':
|
||||||
notification.error('['+ name +'] Transaction Error',
|
notification.error('[' + name + '] Transaction Error',
|
||||||
$filter('translate')('Received corrupt transaction from') + ' ' + user);
|
$filter('translate')('Received corrupt transaction from') + ' ' + user);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
|
|
@ -181,20 +184,12 @@ angular.module('copayApp.services')
|
||||||
notification.enableHtml5Mode(); // for chrome: if support, enable it
|
notification.enableHtml5Mode(); // for chrome: if support, enable it
|
||||||
uriHandler.register();
|
uriHandler.register();
|
||||||
$rootScope.unitName = config.unitName;
|
$rootScope.unitName = config.unitName;
|
||||||
$rootScope.txAlertCount = 0;
|
$rootScope.pendingTxCount = 0;
|
||||||
$rootScope.initialConnection = true;
|
$rootScope.initialConnection = true;
|
||||||
$rootScope.reconnecting = false;
|
$rootScope.reconnecting = false;
|
||||||
$rootScope.isCollapsed = true;
|
$rootScope.isCollapsed = true;
|
||||||
|
|
||||||
$rootScope.iden = iden;
|
$rootScope.iden = iden;
|
||||||
|
|
||||||
// TODO
|
|
||||||
// $rootScope.$watch('txAlertCount', function(txAlertCount) {
|
|
||||||
// if (txAlertCount && txAlertCount > 0) {
|
|
||||||
//
|
|
||||||
// notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : $filter('translate')('You have') + ' ' + $rootScope.txAlertCount + ' ' + $filter('translate')('pending transaction proposals'), txAlertCount);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -275,7 +270,7 @@ angular.module('copayApp.services')
|
||||||
r.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN;
|
r.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN;
|
||||||
|
|
||||||
|
|
||||||
if (r.safeUnspentCount){
|
if (r.safeUnspentCount) {
|
||||||
var estimatedFee = copay.Wallet.estimatedFee(r.safeUnspentCount);
|
var estimatedFee = copay.Wallet.estimatedFee(r.safeUnspentCount);
|
||||||
r.topAmount = (((r.availableBalance * w.settings.unitToSatoshi).toFixed(0) - estimatedFee) / w.settings.unitToSatoshi);
|
r.topAmount = (((r.availableBalance * w.settings.unitToSatoshi).toFixed(0) - estimatedFee) / w.settings.unitToSatoshi);
|
||||||
}
|
}
|
||||||
|
|
@ -319,8 +314,6 @@ angular.module('copayApp.services')
|
||||||
w.balanceInfo = {};
|
w.balanceInfo = {};
|
||||||
var scope = root.isFocusedWallet(w.id) && !refreshAll ? $rootScope : w.balanceInfo;
|
var scope = root.isFocusedWallet(w.id) && !refreshAll ? $rootScope : w.balanceInfo;
|
||||||
|
|
||||||
root.updateAddressList();
|
|
||||||
|
|
||||||
var wid = w.getId();
|
var wid = w.getId();
|
||||||
|
|
||||||
if (_balanceCache[wid]) {
|
if (_balanceCache[wid]) {
|
||||||
|
|
@ -345,7 +338,7 @@ angular.module('copayApp.services')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
root.computeAlternativeAmount = function(w, tx, cb) {
|
root.setAlternativeAmount = function(w, tx, cb) {
|
||||||
rateService.whenAvailable(function() {
|
rateService.whenAvailable(function() {
|
||||||
_.each(tx.outs, function(out) {
|
_.each(tx.outs, function(out) {
|
||||||
var valueSat = out.value * w.settings.unitToSatoshi;
|
var valueSat = out.value * w.settings.unitToSatoshi;
|
||||||
|
|
@ -356,12 +349,13 @@ angular.module('copayApp.services')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
root.updateTxs = function(w) {
|
root.updateTxs = function() {
|
||||||
w = w || $rootScope.wallet;
|
var w = $rootScope.wallet;
|
||||||
if (!w) return root.onErrorDigest();
|
if (!w) return;
|
||||||
|
|
||||||
var res = w.getPendingTxProposals();
|
var res = w.getPendingTxProposals();
|
||||||
_.each(res.txs, function(tx) {
|
_.each(res.txs, function(tx) {
|
||||||
root.computeAlternativeAmount(w, tx);
|
root.setAlternativeAmount(w, tx);
|
||||||
if (tx.merchant) {
|
if (tx.merchant) {
|
||||||
var url = tx.merchant.request_url;
|
var url = tx.merchant.request_url;
|
||||||
var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1];
|
var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1];
|
||||||
|
|
@ -369,14 +363,11 @@ angular.module('copayApp.services')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$rootScope.txps = res.txs;
|
$rootScope.txps = res.txs;
|
||||||
if ($rootScope.pendingTxCount < res.pendingForUs) {
|
|
||||||
$rootScope.txAlertCount = res.pendingForUs;
|
|
||||||
}
|
|
||||||
$rootScope.pendingTxCount = res.pendingForUs;
|
$rootScope.pendingTxCount = res.pendingForUs;
|
||||||
};
|
};
|
||||||
|
|
||||||
root.deleteWallet = function($scope, w, cb) {
|
root.deleteWallet = function($scope, w, cb) {
|
||||||
if (!w) return root.onErrorDigest();
|
if (!w) return root.onErrorDigest();
|
||||||
var name = w.getName();
|
var name = w.getName();
|
||||||
$rootScope.iden.deleteWallet(w.id, function() {
|
$rootScope.iden.deleteWallet(w.id, function() {
|
||||||
notification.info(name + ' deleted', $filter('translate')('This wallet was deleted'));
|
notification.info(name + ' deleted', $filter('translate')('This wallet was deleted'));
|
||||||
|
|
|
||||||
75
js/util/HTTP.js
Normal file
75
js/util/HTTP.js
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
module.exports = {
|
||||||
|
request: function(options, callback) {
|
||||||
|
preconditions.checkArgument(_.isObject(options));
|
||||||
|
|
||||||
|
options.method = options.method || 'GET';
|
||||||
|
options.headers = options.headers || {};
|
||||||
|
var ret = {
|
||||||
|
success: function(cb) {
|
||||||
|
this._success = cb;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
error: function(cb) {
|
||||||
|
this._error = cb;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
_success: function() {;
|
||||||
|
},
|
||||||
|
_error: function(_, err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var method = (options.method || 'GET').toUpperCase();
|
||||||
|
var url = options.url;
|
||||||
|
var req = options;
|
||||||
|
|
||||||
|
req.headers = req.headers || {};
|
||||||
|
req.body = req.body || req.data || {};
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open(method, url, true);
|
||||||
|
|
||||||
|
Object.keys(req.headers).forEach(function(key) {
|
||||||
|
var val = req.headers[key];
|
||||||
|
if (key === 'Content-Length') return;
|
||||||
|
if (key === 'Content-Transfer-Encoding') return;
|
||||||
|
xhr.setRequestHeader(key, val);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (req.responseType) {
|
||||||
|
xhr.responseType = req.responseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onload = function(event) {
|
||||||
|
var response = xhr.response;
|
||||||
|
var buf = new Uint8Array(response);
|
||||||
|
var headers = {};
|
||||||
|
(xhr.getAllResponseHeaders() || '').replace(
|
||||||
|
/(?:\r?\n|^)([^:\r\n]+): *([^\r\n]+)/g,
|
||||||
|
function($0, $1, $2) {
|
||||||
|
headers[$1.toLowerCase()] = $2;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return ret._success(buf, xhr.status, headers, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function(event) {
|
||||||
|
var status;
|
||||||
|
if (xhr.status === 0 || !xhr.statusText) {
|
||||||
|
status = 'HTTP Request Error: This endpoint likely does not support cross-origin requests.';
|
||||||
|
} else {
|
||||||
|
status = xhr.statusText;
|
||||||
|
}
|
||||||
|
return ret._error(null, status, null, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (req.body) {
|
||||||
|
xhr.send(req.body);
|
||||||
|
} else {
|
||||||
|
xhr.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -56,7 +56,6 @@ module.exports = function(config) {
|
||||||
'js/init.js',
|
'js/init.js',
|
||||||
|
|
||||||
'test/mocks/FakeBlockchainSocket.js',
|
'test/mocks/FakeBlockchainSocket.js',
|
||||||
'test/mocks/FakePayProServer.js',
|
|
||||||
|
|
||||||
'test/mocha.conf.js',
|
'test/mocha.conf.js',
|
||||||
|
|
||||||
|
|
|
||||||
923
test/PayPro.js
923
test/PayPro.js
|
|
@ -1,923 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var Wallet = copay.Wallet;
|
|
||||||
var PrivateKey = copay.PrivateKey;
|
|
||||||
var Network = requireMock('FakeNetwork');
|
|
||||||
var Blockchain = requireMock('FakeBlockchain');
|
|
||||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
|
||||||
var Transaction = bitcore.Transaction;
|
|
||||||
var Address = bitcore.Address;
|
|
||||||
var PayPro = bitcore.PayPro;
|
|
||||||
var bignum = bitcore.Bignum;
|
|
||||||
var startServer = copay.FakePayProServer; // TODO should be require('./mocks/FakePayProServer');
|
|
||||||
|
|
||||||
var server;
|
|
||||||
|
|
||||||
var walletConfig = {
|
|
||||||
requiredCopayers: 1,
|
|
||||||
totalCopayers: 1,
|
|
||||||
spendUnconfirmed: true,
|
|
||||||
reconnectDelay: 100,
|
|
||||||
networkName: 'testnet'
|
|
||||||
};
|
|
||||||
|
|
||||||
var getNewEpk = function() {
|
|
||||||
return new PrivateKey({
|
|
||||||
networkName: walletConfig.networkName,
|
|
||||||
})
|
|
||||||
.deriveBIP45Branch()
|
|
||||||
.extendedPublicKeyString();
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('PayPro (in Wallet) model', function() {
|
|
||||||
|
|
||||||
if (!is_browser) {
|
|
||||||
var createW = function(N, conf) {
|
|
||||||
var c = JSON.parse(JSON.stringify(conf || walletConfig));
|
|
||||||
if (!N) N = c.totalCopayers;
|
|
||||||
|
|
||||||
var mainPrivateKey = new copay.PrivateKey({
|
|
||||||
networkName: walletConfig.networkName
|
|
||||||
});
|
|
||||||
var mainCopayerEPK = mainPrivateKey.deriveBIP45Branch().extendedPublicKeyString();
|
|
||||||
c.privateKey = mainPrivateKey;
|
|
||||||
|
|
||||||
c.publicKeyRing = new copay.PublicKeyRing({
|
|
||||||
networkName: c.networkName,
|
|
||||||
requiredCopayers: Math.min(N, c.requiredCopayers),
|
|
||||||
totalCopayers: N,
|
|
||||||
});
|
|
||||||
c.publicKeyRing.addCopayer(mainCopayerEPK);
|
|
||||||
|
|
||||||
c.txProposals = new copay.TxProposals({
|
|
||||||
networkName: c.networkName,
|
|
||||||
});
|
|
||||||
|
|
||||||
var network = new Network(walletConfig.network);
|
|
||||||
var blockchain = new Blockchain(walletConfig.blockchain);
|
|
||||||
c.network = network;
|
|
||||||
c.blockchain = blockchain;
|
|
||||||
|
|
||||||
c.addressBook = {
|
|
||||||
'2NFR2kzH9NUdp8vsXTB4wWQtTtzhpKxsyoJ': {
|
|
||||||
label: 'John',
|
|
||||||
copayerId: '026a55261b7c898fff760ebe14fd22a71892295f3b49e0ca66727bc0a0d7f94d03',
|
|
||||||
createdTs: 1403102115,
|
|
||||||
hidden: false
|
|
||||||
},
|
|
||||||
'2MtP8WyiwG7ZdVWM96CVsk2M1N8zyfiVQsY': {
|
|
||||||
label: 'Jennifer',
|
|
||||||
copayerId: '032991f836543a492bd6d0bb112552bfc7c5f3b7d5388fcbcbf2fbb893b44770d7',
|
|
||||||
createdTs: 1403103115,
|
|
||||||
hidden: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
c.networkName = walletConfig.networkName;
|
|
||||||
c.version = '0.0.1';
|
|
||||||
|
|
||||||
c.network = sinon.stub();
|
|
||||||
c.network.setHexNonce = sinon.stub();
|
|
||||||
c.network.setHexNonces = sinon.stub();
|
|
||||||
c.network.getHexNonce = sinon.stub();
|
|
||||||
c.network.getHexNonces = sinon.stub();
|
|
||||||
c.network.send = sinon.stub();
|
|
||||||
|
|
||||||
return new Wallet(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
var unspentTest = [{
|
|
||||||
"address": "dummy",
|
|
||||||
"scriptPubKey": "dummy",
|
|
||||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
|
||||||
"vout": 1,
|
|
||||||
"amount": 10,
|
|
||||||
"confirmations": 7
|
|
||||||
}];
|
|
||||||
|
|
||||||
var createW2 = function(privateKeys, N, conf) {
|
|
||||||
if (!N) N = 3;
|
|
||||||
var w = createW(N, conf);
|
|
||||||
should.exist(w);
|
|
||||||
|
|
||||||
var pkr = w.publicKeyRing;
|
|
||||||
|
|
||||||
for (var i = 0; i < N - 1; i++) {
|
|
||||||
if (privateKeys) {
|
|
||||||
var k = privateKeys[i];
|
|
||||||
pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : getNewEpk());
|
|
||||||
} else {
|
|
||||||
pkr.addCopayer(getNewEpk());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return w;
|
|
||||||
};
|
|
||||||
|
|
||||||
var cachedW2 = null;
|
|
||||||
var cachedW2obj = null;
|
|
||||||
var cachedCreateW2 = function() {
|
|
||||||
if (!cachedW2) {
|
|
||||||
cachedW2 = createW2();
|
|
||||||
cachedW2obj = cachedW2.toObj();
|
|
||||||
cachedW2obj.opts.reconnectDelay = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet._newAsync = sinon.stub().returns(new Network(walletConfig.network));
|
|
||||||
Wallet._newInsight = sinon.stub().returns(new Blockchain(walletConfig.blockchain));
|
|
||||||
|
|
||||||
var w = Wallet.fromObj(cachedW2obj, {
|
|
||||||
blockchainOpts: {},
|
|
||||||
networkOpts: {},
|
|
||||||
});
|
|
||||||
return w;
|
|
||||||
};
|
|
||||||
|
|
||||||
var createWallet = function() {
|
|
||||||
var w = cachedCreateW2();
|
|
||||||
unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString();
|
|
||||||
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey);
|
|
||||||
w.getUnspent = function(cb) {
|
|
||||||
return setTimeout(function() {
|
|
||||||
return cb(null, unspentTest, unspentTest);
|
|
||||||
}, 1);
|
|
||||||
};
|
|
||||||
return w;
|
|
||||||
};
|
|
||||||
|
|
||||||
it('#start the example server', function(done) {
|
|
||||||
startServer(function(err, s) {
|
|
||||||
if (err) return done(err);
|
|
||||||
server = s;
|
|
||||||
server.uri = 'https://localhost:8080/-';
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var pr;
|
|
||||||
var ppw;
|
|
||||||
|
|
||||||
ppw = createWallet();
|
|
||||||
|
|
||||||
it('#retrieve a payment request message via http', function(done) {
|
|
||||||
var w = ppw;
|
|
||||||
should.exist(w);
|
|
||||||
|
|
||||||
var req = {
|
|
||||||
headers: {
|
|
||||||
'Host': 'localhost:8080',
|
|
||||||
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE,
|
|
||||||
'Content-Type': 'application/octet-stream',
|
|
||||||
'Content-Length': '0'
|
|
||||||
},
|
|
||||||
socket: {
|
|
||||||
remoteAddress: 'localhost',
|
|
||||||
remotePort: 8080
|
|
||||||
},
|
|
||||||
body: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(req.headers).forEach(function(key) {
|
|
||||||
req.headers[key.toLowerCase()] = req.headers[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
server.GET['/-/request'](req, function(err, res, body) {
|
|
||||||
var data = PayPro.PaymentRequest.decode(body);
|
|
||||||
pr = new PayPro();
|
|
||||||
pr = pr.makePaymentRequest(data);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#send a payment message via http', function(done) {
|
|
||||||
var w = ppw;
|
|
||||||
should.exist(w);
|
|
||||||
|
|
||||||
var ver = pr.get('payment_details_version');
|
|
||||||
var pki_type = pr.get('pki_type');
|
|
||||||
var pki_data = pr.get('pki_data');
|
|
||||||
var details = pr.get('serialized_payment_details');
|
|
||||||
var sig = pr.get('signature');
|
|
||||||
|
|
||||||
var certs = PayPro.X509Certificates.decode(pki_data);
|
|
||||||
certs = certs.certificate;
|
|
||||||
|
|
||||||
var verified = pr.verify();
|
|
||||||
|
|
||||||
if (!verified) {
|
|
||||||
return done(new Error('Server sent a bad signature.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
details = PayPro.PaymentDetails.decode(details);
|
|
||||||
var pd = new PayPro();
|
|
||||||
pd = pd.makePaymentDetails(details);
|
|
||||||
|
|
||||||
var network = pd.get('network');
|
|
||||||
var outputs = pd.get('outputs');
|
|
||||||
var time = pd.get('time');
|
|
||||||
var expires = pd.get('expires');
|
|
||||||
var memo = pd.get('memo');
|
|
||||||
var payment_url = pd.get('payment_url');
|
|
||||||
var merchant_data = pd.get('merchant_data');
|
|
||||||
|
|
||||||
var priv = w.privateKey;
|
|
||||||
var pkr = w.publicKeyRing;
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
remainderOut: {
|
|
||||||
address: w._doGenerateAddress(true).toString()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var outs = [];
|
|
||||||
outputs.forEach(function(output) {
|
|
||||||
var amount = output.get('amount');
|
|
||||||
var script = {
|
|
||||||
offset: output.get('script').offset,
|
|
||||||
limit: output.get('script').limit,
|
|
||||||
buffer: new Buffer(new Uint8Array(
|
|
||||||
output.get('script').buffer))
|
|
||||||
};
|
|
||||||
|
|
||||||
// big endian
|
|
||||||
var v = new Buffer(8);
|
|
||||||
v[0] = (amount.high >> 24) & 0xff;
|
|
||||||
v[1] = (amount.high >> 16) & 0xff;
|
|
||||||
v[2] = (amount.high >> 8) & 0xff;
|
|
||||||
v[3] = (amount.high >> 0) & 0xff;
|
|
||||||
v[4] = (amount.low >> 24) & 0xff;
|
|
||||||
v[5] = (amount.low >> 16) & 0xff;
|
|
||||||
v[6] = (amount.low >> 8) & 0xff;
|
|
||||||
v[7] = (amount.low >> 0) & 0xff;
|
|
||||||
|
|
||||||
var s = script.buffer.slice(script.offset, script.limit);
|
|
||||||
var net = network === 'main' ? 'livenet' : 'testnet';
|
|
||||||
var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net);
|
|
||||||
|
|
||||||
outs.push({
|
|
||||||
address: addr[0].toString(),
|
|
||||||
amountSatStr: bitcore.Bignum.fromBuffer(v, {
|
|
||||||
// XXX for some reason, endian is ALWAYS 'big'
|
|
||||||
// in node (in the browser it behaves correctly)
|
|
||||||
endian: 'big',
|
|
||||||
size: 1
|
|
||||||
}).toString(10)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var b = new bitcore.TransactionBuilder(opts)
|
|
||||||
.setUnspent(unspentTest)
|
|
||||||
.setOutputs(outs);
|
|
||||||
|
|
||||||
outputs.forEach(function(output, i) {
|
|
||||||
var script = {
|
|
||||||
offset: output.get('script').offset,
|
|
||||||
limit: output.get('script').limit,
|
|
||||||
buffer: new Buffer(new Uint8Array(
|
|
||||||
output.get('script').buffer))
|
|
||||||
};
|
|
||||||
var s = script.buffer.slice(script.offset, script.limit);
|
|
||||||
b.tx.outs[i].s = s;
|
|
||||||
});
|
|
||||||
|
|
||||||
var selectedUtxos = b.getSelectedUnspent();
|
|
||||||
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
|
||||||
return pkr.pathForAddress(utxo.address);
|
|
||||||
});
|
|
||||||
|
|
||||||
b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths));
|
|
||||||
|
|
||||||
if (priv) {
|
|
||||||
var keys = priv.getForPaths(inputChainPaths);
|
|
||||||
var signed = b.sign(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tx = b.build();
|
|
||||||
|
|
||||||
var refund_outputs = [];
|
|
||||||
|
|
||||||
var refund_to = w.publicKeyRing.getPubKeys(0, false, w.getMyCopayerId())[0];
|
|
||||||
|
|
||||||
var total = outputs.reduce(function(total, _, i) {
|
|
||||||
// XXX reverse endianness to work around bignum bug:
|
|
||||||
var txv = tx.outs[i].v;
|
|
||||||
var v = new Buffer(8);
|
|
||||||
for (var j = 0; j < 8; j++) v[j] = txv[7 - j];
|
|
||||||
return total.add(bignum.fromBuffer(v, {
|
|
||||||
endian: 'big',
|
|
||||||
size: 1
|
|
||||||
}));
|
|
||||||
}, bitcore.Bignum('0', 10));
|
|
||||||
|
|
||||||
var rpo = new PayPro();
|
|
||||||
rpo = rpo.makeOutput();
|
|
||||||
|
|
||||||
rpo.set('amount', +total.toString(10));
|
|
||||||
|
|
||||||
rpo.set('script',
|
|
||||||
Buffer.concat([
|
|
||||||
new Buffer([
|
|
||||||
118, // OP_DUP
|
|
||||||
169, // OP_HASH160
|
|
||||||
76, // OP_PUSHDATA1
|
|
||||||
20, // number of bytes
|
|
||||||
]),
|
|
||||||
// needs to be ripesha'd
|
|
||||||
bitcore.util.sha256ripe160(refund_to),
|
|
||||||
new Buffer([
|
|
||||||
136, // OP_EQUALVERIFY
|
|
||||||
172 // OP_CHECKSIG
|
|
||||||
])
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
refund_outputs.push(rpo.message);
|
|
||||||
|
|
||||||
var pay = new PayPro();
|
|
||||||
pay = pay.makePayment();
|
|
||||||
pay.set('merchant_data', new Buffer([0, 1]));
|
|
||||||
pay.set('transactions', [tx.serialize()]);
|
|
||||||
pay.set('refund_to', refund_outputs);
|
|
||||||
pay.set('memo', 'Hi server, I would like to give you some money.');
|
|
||||||
|
|
||||||
pay = pay.serialize();
|
|
||||||
|
|
||||||
var req = {
|
|
||||||
headers: {
|
|
||||||
'Host': 'localhost:8080',
|
|
||||||
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE,
|
|
||||||
'Content-Type': PayPro.PAYMENT_CONTENT_TYPE,
|
|
||||||
'Content-Length': pay.length + ''
|
|
||||||
},
|
|
||||||
socket: {
|
|
||||||
remoteAddress: 'localhost',
|
|
||||||
remotePort: 8080
|
|
||||||
},
|
|
||||||
body: pay,
|
|
||||||
data: pay
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(req.headers).forEach(function(key) {
|
|
||||||
req.headers[key.toLowerCase()] = req.headers[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
server.POST['/-/pay'](req, function(err, res, body) {
|
|
||||||
if (err) return done(err);
|
|
||||||
|
|
||||||
var data = PayPro.PaymentACK.decode(body);
|
|
||||||
var ack = new PayPro();
|
|
||||||
ack = ack.makePaymentACK(data);
|
|
||||||
|
|
||||||
var payment = ack.get('payment');
|
|
||||||
var memo = ack.get('memo');
|
|
||||||
|
|
||||||
payment = PayPro.Payment.decode(payment);
|
|
||||||
var pay = new PayPro();
|
|
||||||
payment = pay.makePayment(payment);
|
|
||||||
|
|
||||||
var tx = payment.message.transactions[0];
|
|
||||||
|
|
||||||
if (!tx) {
|
|
||||||
return done(new Error('No tx in payment ACK.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx.buffer) {
|
|
||||||
tx.buffer = new Buffer(new Uint8Array(tx.buffer));
|
|
||||||
tx.buffer = tx.buffer.slice(tx.offset, tx.limit);
|
|
||||||
var ptx = new bitcore.Transaction();
|
|
||||||
ptx.parse(tx.buffer);
|
|
||||||
tx = ptx;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ackTotal = outputs.reduce(function(total, _, i) {
|
|
||||||
// XXX reverse endianness to work around bignum bug:
|
|
||||||
var txv = tx.outs[i].v;
|
|
||||||
var v = new Buffer(8);
|
|
||||||
for (var j = 0; j < 8; j++) v[j] = txv[7 - j];
|
|
||||||
return total.add(bignum.fromBuffer(v, {
|
|
||||||
endian: 'big',
|
|
||||||
size: 1
|
|
||||||
}));
|
|
||||||
}, bitcore.Bignum('0', 10));
|
|
||||||
|
|
||||||
ackTotal.toString(10).should.equal(total.toString(10));
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#retrieve a payment request message via http', function(done) {
|
|
||||||
var w = ppw;
|
|
||||||
should.exist(w);
|
|
||||||
|
|
||||||
var req = {
|
|
||||||
headers: {
|
|
||||||
'Host': 'localhost:8080',
|
|
||||||
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE,
|
|
||||||
'Content-Type': 'application/octet-stream',
|
|
||||||
'Content-Length': '0'
|
|
||||||
},
|
|
||||||
socket: {
|
|
||||||
remoteAddress: 'localhost',
|
|
||||||
remotePort: 8080
|
|
||||||
},
|
|
||||||
body: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(req.headers).forEach(function(key) {
|
|
||||||
req.headers[key.toLowerCase()] = req.headers[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
server.GET['/-/request'](req, function(err, res, body) {
|
|
||||||
var data = PayPro.PaymentRequest.decode(body);
|
|
||||||
pr = new PayPro();
|
|
||||||
pr = pr.makePaymentRequest(data);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#send a payment message via http', function(done) {
|
|
||||||
var w = ppw;
|
|
||||||
should.exist(w);
|
|
||||||
|
|
||||||
var ver = pr.get('payment_details_version');
|
|
||||||
var pki_type = pr.get('pki_type');
|
|
||||||
var pki_data = pr.get('pki_data');
|
|
||||||
var details = pr.get('serialized_payment_details');
|
|
||||||
var sig = pr.get('signature');
|
|
||||||
|
|
||||||
var certs = PayPro.X509Certificates.decode(pki_data);
|
|
||||||
certs = certs.certificate;
|
|
||||||
|
|
||||||
var verified = pr.verify();
|
|
||||||
|
|
||||||
if (!verified) {
|
|
||||||
return done(new Error('Server sent a bad signature.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
details = PayPro.PaymentDetails.decode(details);
|
|
||||||
var pd = new PayPro();
|
|
||||||
pd = pd.makePaymentDetails(details);
|
|
||||||
|
|
||||||
var network = pd.get('network');
|
|
||||||
var outputs = pd.get('outputs');
|
|
||||||
var time = pd.get('time');
|
|
||||||
var expires = pd.get('expires');
|
|
||||||
var memo = pd.get('memo');
|
|
||||||
var payment_url = pd.get('payment_url');
|
|
||||||
var merchant_data = pd.get('merchant_data');
|
|
||||||
|
|
||||||
var priv = w.privateKey;
|
|
||||||
var pkr = w.publicKeyRing;
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
remainderOut: {
|
|
||||||
address: w._doGenerateAddress(true).toString()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var outs = [];
|
|
||||||
outputs.forEach(function(output) {
|
|
||||||
var amount = output.get('amount');
|
|
||||||
var script = {
|
|
||||||
offset: output.get('script').offset,
|
|
||||||
limit: output.get('script').limit,
|
|
||||||
buffer: new Buffer(new Uint8Array(
|
|
||||||
output.get('script').buffer))
|
|
||||||
};
|
|
||||||
|
|
||||||
// big endian
|
|
||||||
var v = new Buffer(8);
|
|
||||||
v[0] = (amount.high >> 24) & 0xff;
|
|
||||||
v[1] = (amount.high >> 16) & 0xff;
|
|
||||||
v[2] = (amount.high >> 8) & 0xff;
|
|
||||||
v[3] = (amount.high >> 0) & 0xff;
|
|
||||||
v[4] = (amount.low >> 24) & 0xff;
|
|
||||||
v[5] = (amount.low >> 16) & 0xff;
|
|
||||||
v[6] = (amount.low >> 8) & 0xff;
|
|
||||||
v[7] = (amount.low >> 0) & 0xff;
|
|
||||||
|
|
||||||
var s = script.buffer.slice(script.offset, script.limit);
|
|
||||||
var net = network === 'main' ? 'livenet' : 'testnet';
|
|
||||||
var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net);
|
|
||||||
|
|
||||||
outs.push({
|
|
||||||
address: addr[0].toString(),
|
|
||||||
amountSatStr: bitcore.Bignum.fromBuffer(v, {
|
|
||||||
// XXX for some reason, endian is ALWAYS 'big'
|
|
||||||
// in node (in the browser it behaves correctly)
|
|
||||||
endian: 'big',
|
|
||||||
size: 1
|
|
||||||
}).toString(10)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var b = new bitcore.TransactionBuilder(opts)
|
|
||||||
.setUnspent(unspentTest)
|
|
||||||
.setOutputs(outs);
|
|
||||||
|
|
||||||
outputs.forEach(function(output, i) {
|
|
||||||
var script = {
|
|
||||||
offset: output.get('script').offset,
|
|
||||||
limit: output.get('script').limit,
|
|
||||||
buffer: new Buffer(new Uint8Array(
|
|
||||||
output.get('script').buffer))
|
|
||||||
};
|
|
||||||
var s = script.buffer.slice(script.offset, script.limit);
|
|
||||||
b.tx.outs[i].s = s;
|
|
||||||
});
|
|
||||||
|
|
||||||
var selectedUtxos = b.getSelectedUnspent();
|
|
||||||
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
|
||||||
return pkr.pathForAddress(utxo.address);
|
|
||||||
});
|
|
||||||
|
|
||||||
b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths));
|
|
||||||
|
|
||||||
if (priv) {
|
|
||||||
var keys = priv.getForPaths(inputChainPaths);
|
|
||||||
var signed = b.sign(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tx = b.build();
|
|
||||||
|
|
||||||
var refund_outputs = [];
|
|
||||||
|
|
||||||
var refund_to = w.publicKeyRing.getPubKeys(0, false, w.getMyCopayerId())[0];
|
|
||||||
|
|
||||||
var total = outputs.reduce(function(total, _, i) {
|
|
||||||
// XXX reverse endianness to work around bignum bug:
|
|
||||||
var txv = tx.outs[i].v;
|
|
||||||
var v = new Buffer(8);
|
|
||||||
for (var j = 0; j < 8; j++) v[j] = txv[7 - j];
|
|
||||||
return total.add(bignum.fromBuffer(v, {
|
|
||||||
endian: 'big',
|
|
||||||
size: 1
|
|
||||||
}));
|
|
||||||
}, bitcore.Bignum('0', 10));
|
|
||||||
|
|
||||||
var rpo = new PayPro();
|
|
||||||
rpo = rpo.makeOutput();
|
|
||||||
|
|
||||||
rpo.set('amount', +total.toString(10));
|
|
||||||
|
|
||||||
rpo.set('script',
|
|
||||||
Buffer.concat([
|
|
||||||
new Buffer([
|
|
||||||
118, // OP_DUP
|
|
||||||
169, // OP_HASH160
|
|
||||||
76, // OP_PUSHDATA1
|
|
||||||
20, // number of bytes
|
|
||||||
]),
|
|
||||||
// needs to be ripesha'd
|
|
||||||
bitcore.util.sha256ripe160(refund_to),
|
|
||||||
new Buffer([
|
|
||||||
136, // OP_EQUALVERIFY
|
|
||||||
172 // OP_CHECKSIG
|
|
||||||
])
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
refund_outputs.push(rpo.message);
|
|
||||||
|
|
||||||
var pay = new PayPro();
|
|
||||||
pay = pay.makePayment();
|
|
||||||
pay.set('merchant_data', new Buffer([0, 1]));
|
|
||||||
pay.set('transactions', [tx.serialize()]);
|
|
||||||
pay.set('refund_to', refund_outputs);
|
|
||||||
pay.set('memo', 'Hi server, I would like to give you some money.');
|
|
||||||
|
|
||||||
pay = pay.serialize();
|
|
||||||
|
|
||||||
var req = {
|
|
||||||
headers: {
|
|
||||||
'Host': 'localhost:8080',
|
|
||||||
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE,
|
|
||||||
'Content-Type': PayPro.PAYMENT_CONTENT_TYPE,
|
|
||||||
'Content-Length': pay.length + ''
|
|
||||||
},
|
|
||||||
socket: {
|
|
||||||
remoteAddress: 'localhost',
|
|
||||||
remotePort: 8080
|
|
||||||
},
|
|
||||||
body: pay,
|
|
||||||
data: pay
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(req.headers).forEach(function(key) {
|
|
||||||
req.headers[key.toLowerCase()] = req.headers[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
server.POST['/-/pay'](req, function(err, res, body) {
|
|
||||||
if (err) return done(err);
|
|
||||||
|
|
||||||
var data = PayPro.PaymentACK.decode(body);
|
|
||||||
var ack = new PayPro();
|
|
||||||
ack = ack.makePaymentACK(data);
|
|
||||||
|
|
||||||
var payment = ack.get('payment');
|
|
||||||
var memo = ack.get('memo');
|
|
||||||
|
|
||||||
payment = PayPro.Payment.decode(payment);
|
|
||||||
var pay = new PayPro();
|
|
||||||
payment = pay.makePayment(payment);
|
|
||||||
|
|
||||||
var tx = payment.message.transactions[0];
|
|
||||||
|
|
||||||
if (!tx) {
|
|
||||||
return done(new Error('No tx in payment ACK.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx.buffer) {
|
|
||||||
tx.buffer = new Buffer(new Uint8Array(tx.buffer));
|
|
||||||
tx.buffer = tx.buffer.slice(tx.offset, tx.limit);
|
|
||||||
var ptx = new bitcore.Transaction();
|
|
||||||
ptx.parse(tx.buffer);
|
|
||||||
tx = ptx;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ackTotal = outputs.reduce(function(total, _, i) {
|
|
||||||
// XXX reverse endianness to work around bignum bug:
|
|
||||||
var txv = tx.outs[i].v;
|
|
||||||
var v = new Buffer(8);
|
|
||||||
for (var j = 0; j < 8; j++) v[j] = txv[7 - j];
|
|
||||||
return total.add(bignum.fromBuffer(v, {
|
|
||||||
endian: 'big',
|
|
||||||
size: 1
|
|
||||||
}));
|
|
||||||
}, bitcore.Bignum('0', 10));
|
|
||||||
|
|
||||||
ackTotal.toString(10).should.equal(total.toString(10));
|
|
||||||
|
|
||||||
should.exist(ack);
|
|
||||||
memo.should.equal('Thank you for your payment!');
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ppw = createWallet();
|
|
||||||
|
|
||||||
it('#retrieve a payment request message via model', function(done) {
|
|
||||||
var w = ppw;
|
|
||||||
should.exist(w);
|
|
||||||
// Caches Payment Request but does not add TX proposal
|
|
||||||
w.fetchPaymentTx({
|
|
||||||
uri: 'https://localhost:8080/-/request'
|
|
||||||
}, function(err, merchantData) {
|
|
||||||
if (err) return done(err);
|
|
||||||
merchantData.pr.pd.payment_url.should.equal('https://localhost:8080/-/pay');
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#add tx proposal based on payment message via model', function(done) {
|
|
||||||
var w = ppw;
|
|
||||||
should.exist(w);
|
|
||||||
var options = {
|
|
||||||
uri: 'https://localhost:8080/-/request'
|
|
||||||
};
|
|
||||||
var req = w.paymentRequests[options.uri];
|
|
||||||
should.exist(req);
|
|
||||||
delete w.paymentRequests[options.uri];
|
|
||||||
w.receivePaymentRequest(options, req.pr, function(err, ntxid, merchantData) {
|
|
||||||
should.equal(err, null);
|
|
||||||
should.exist(ntxid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
w._ntxid = ntxid;
|
|
||||||
merchantData.pr.pd.payment_url.should.equal('https://localhost:8080/-/pay');
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#add tx proposal based on payment message via model ', function(done) {
|
|
||||||
|
|
||||||
var w = ppw;
|
|
||||||
should.exist(w);
|
|
||||||
|
|
||||||
w.sendPaymentTx(w._ntxid, function(txid, merchantData) {
|
|
||||||
should.exist(txid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
should.exist(merchantData.ack);
|
|
||||||
merchantData.ack.memo.should.equal('Thank you for your payment!');
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#send a payment request using payment api', function(done) {
|
|
||||||
var w = createWallet();
|
|
||||||
should.exist(w);
|
|
||||||
var uri = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
|
|
||||||
var memo = 'Hello, server. I\'d like to make a payment.';
|
|
||||||
w.createPaymentTx({
|
|
||||||
uri: uri,
|
|
||||||
memo: memo
|
|
||||||
}, function(err, ntxid, merchantData) {
|
|
||||||
should.equal(err, null);
|
|
||||||
should.exist(ntxid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
if (w.isShared()) {
|
|
||||||
return done();
|
|
||||||
} else {
|
|
||||||
w.sendPaymentTx(ntxid, {
|
|
||||||
memo: memo
|
|
||||||
}, function(txid, merchantData) {
|
|
||||||
should.exist(txid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#send a payment request with merchant prefix', function(done) {
|
|
||||||
var w = createWallet();
|
|
||||||
should.exist(w);
|
|
||||||
var address = 'Merchant: ' + server.uri + '/request\nMemo: foo';
|
|
||||||
var commentText = 'Hello, server. I\'d like to make a payment.';
|
|
||||||
var uri;
|
|
||||||
|
|
||||||
// Replicates code in controllers/send.js:
|
|
||||||
if (address.indexOf('bitcoin:') === 0) {
|
|
||||||
uri = new bitcore.BIP21(address).data;
|
|
||||||
} else if (address.indexOf('Merchant: ') === 0) {
|
|
||||||
uri = address.split(/\s+/)[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
w.createPaymentTx({
|
|
||||||
uri: uri,
|
|
||||||
memo: commentText
|
|
||||||
}, function(err, ntxid, merchantData) {
|
|
||||||
should.equal(err, null);
|
|
||||||
if (w.isShared()) {
|
|
||||||
should.exist(ntxid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
return done();
|
|
||||||
} else {
|
|
||||||
should.exist(merchantData);
|
|
||||||
w.sendTx(ntxid, function(txid, merchantData) {
|
|
||||||
should.exist(txid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#send a payment request with bitcoin uri', function(done) {
|
|
||||||
var w = createWallet();
|
|
||||||
should.exist(w);
|
|
||||||
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
|
|
||||||
var commentText = 'Hello, server. I\'d like to make a payment.';
|
|
||||||
w.createPaymentTx({
|
|
||||||
uri: address,
|
|
||||||
memo: commentText
|
|
||||||
}, function(err, ntxid, merchantData) {
|
|
||||||
should.equal(err, null);
|
|
||||||
if (w.isShared()) {
|
|
||||||
should.exist(ntxid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
return done();
|
|
||||||
} else {
|
|
||||||
w.sendTx(ntxid, function(txid, merchantData) {
|
|
||||||
should.exist(txid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#try to sign a tampered payment request (raw)', function(done) {
|
|
||||||
var w = createWallet();
|
|
||||||
should.exist(w);
|
|
||||||
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
|
|
||||||
var commentText = 'Hello, server. I\'d like to make a payment.';
|
|
||||||
w.createPaymentTx({
|
|
||||||
uri: address,
|
|
||||||
memo: commentText
|
|
||||||
}, function(err, ntxid, merchantData) {
|
|
||||||
should.equal(err, null);
|
|
||||||
should.exist(ntxid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
|
|
||||||
// Tamper with payment request in its raw form:
|
|
||||||
var data = new Buffer(merchantData.raw, 'hex');
|
|
||||||
data = PayPro.PaymentRequest.decode(data);
|
|
||||||
var pr = new PayPro();
|
|
||||||
pr = pr.makePaymentRequest(data);
|
|
||||||
var details = pr.get('serialized_payment_details');
|
|
||||||
details = PayPro.PaymentDetails.decode(details);
|
|
||||||
var pd = new PayPro();
|
|
||||||
pd = pd.makePaymentDetails(details);
|
|
||||||
var outputs = pd.get('outputs');
|
|
||||||
outputs[outputs.length - 1].set('amount', 1000000000);
|
|
||||||
pd.set('outputs', outputs);
|
|
||||||
pr.set('serialized_payment_details', pd.serialize());
|
|
||||||
merchantData.raw = pr.serialize().toString('hex');
|
|
||||||
|
|
||||||
var myId = w.getMyCopayerId();
|
|
||||||
var txp = w.txProposals.get(ntxid);
|
|
||||||
should.exist(txp);
|
|
||||||
should.exist(txp.signedBy[myId]);
|
|
||||||
should.not.exist(txp.rejectedBy[myId]);
|
|
||||||
|
|
||||||
w.verifyPaymentRequest(ntxid).should.equal(false);
|
|
||||||
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#try to sign a tampered payment request (abstract)', function(done) {
|
|
||||||
var w = createWallet();
|
|
||||||
should.exist(w);
|
|
||||||
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
|
|
||||||
var commentText = 'Hello, server. I\'d like to make a payment.';
|
|
||||||
w.createPaymentTx({
|
|
||||||
uri: address,
|
|
||||||
memo: commentText
|
|
||||||
}, function(err, ntxid, merchantData) {
|
|
||||||
should.equal(err, null);
|
|
||||||
should.exist(ntxid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
|
|
||||||
// Tamper with payment request in its abstract form:
|
|
||||||
var outputs = merchantData.pr.pd.outputs;
|
|
||||||
var output = outputs[outputs.length - 1];
|
|
||||||
var amount = output.amount;
|
|
||||||
amount.low = 2;
|
|
||||||
|
|
||||||
var myId = w.getMyCopayerId();
|
|
||||||
var txp = w.txProposals.get(ntxid);
|
|
||||||
should.exist(txp);
|
|
||||||
should.exist(txp.signedBy[myId]);
|
|
||||||
should.not.exist(txp.rejectedBy[myId]);
|
|
||||||
|
|
||||||
w.verifyPaymentRequest(ntxid).should.equal(false);
|
|
||||||
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#try to sign a tampered txp tx (abstract)', function(done) {
|
|
||||||
var w = createWallet();
|
|
||||||
should.exist(w);
|
|
||||||
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
|
|
||||||
var commentText = 'Hello, server. I\'d like to make a payment.';
|
|
||||||
w.createPaymentTx({
|
|
||||||
uri: address,
|
|
||||||
memo: commentText
|
|
||||||
}, function(err, ntxid, merchantData) {
|
|
||||||
should.equal(err, null);
|
|
||||||
should.exist(ntxid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
|
|
||||||
// Tamper with payment request in its abstract form:
|
|
||||||
var txp = w.txProposals.get(ntxid);
|
|
||||||
var tx = txp.builder.tx || txp.builder.build();
|
|
||||||
tx.outs[0].v = new Buffer([2, 0, 0, 0, 0, 0, 0, 0]);
|
|
||||||
|
|
||||||
var myId = w.getMyCopayerId();
|
|
||||||
var txp = w.txProposals.get(ntxid);
|
|
||||||
should.exist(txp);
|
|
||||||
should.exist(txp.signedBy[myId]);
|
|
||||||
should.not.exist(txp.rejectedBy[myId]);
|
|
||||||
|
|
||||||
w.verifyPaymentRequest(ntxid).should.equal(false);
|
|
||||||
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#sign an untampered payment request', function(done) {
|
|
||||||
var w = createWallet();
|
|
||||||
should.exist(w);
|
|
||||||
var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request';
|
|
||||||
var commentText = 'Hello, server. I\'d like to make a payment.';
|
|
||||||
w.createPaymentTx({
|
|
||||||
uri: address,
|
|
||||||
memo: commentText
|
|
||||||
}, function(err, ntxid, merchantData) {
|
|
||||||
should.equal(err, null);
|
|
||||||
should.exist(ntxid);
|
|
||||||
should.exist(merchantData);
|
|
||||||
|
|
||||||
var myId = w.getMyCopayerId();
|
|
||||||
var txp = w.txProposals.get(ntxid);
|
|
||||||
should.exist(txp);
|
|
||||||
should.exist(txp.signedBy[myId]);
|
|
||||||
should.not.exist(txp.rejectedBy[myId]);
|
|
||||||
|
|
||||||
w.verifyPaymentRequest(ntxid).should.equal(true);
|
|
||||||
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#close payment server', function(done) {
|
|
||||||
server.close(function() {
|
|
||||||
return done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -10,17 +10,23 @@ var util = bitcore.util;
|
||||||
var networks = bitcore.networks;
|
var networks = bitcore.networks;
|
||||||
var FakeBuilder = requireMock('FakeBuilder');
|
var FakeBuilder = requireMock('FakeBuilder');
|
||||||
var TxProposal = copay.TxProposal;
|
var TxProposal = copay.TxProposal;
|
||||||
|
var Buffer = bitcore.Buffer;
|
||||||
var dummyProposal = new TxProposal({
|
|
||||||
creator: 1,
|
|
||||||
createdTs: 1,
|
|
||||||
builder: new FakeBuilder(),
|
|
||||||
inputChainPaths: ['m/1'],
|
|
||||||
});
|
|
||||||
|
|
||||||
var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"];
|
var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"];
|
||||||
|
|
||||||
describe('TxProposal', function() {
|
describe('TxProposal', function() {
|
||||||
|
|
||||||
|
function dummyProposal() {
|
||||||
|
return new TxProposal({
|
||||||
|
creator: 'creator',
|
||||||
|
createdTs: 1,
|
||||||
|
builder: new FakeBuilder(),
|
||||||
|
inputChainPaths: ['m/1'],
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe('new', function() {
|
describe('new', function() {
|
||||||
it('should fail to create an instance with wrong arguments', function() {
|
it('should fail to create an instance with wrong arguments', function() {
|
||||||
|
|
||||||
|
|
@ -208,8 +214,8 @@ describe('TxProposal', function() {
|
||||||
});
|
});
|
||||||
it('#_verifyScriptSig, two signatures', function() {
|
it('#_verifyScriptSig, two signatures', function() {
|
||||||
// Data taken from bitcore's TransactionBuilder test
|
// Data taken from bitcore's TransactionBuilder test
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
var tx = dummyProposal.builder.build();
|
var tx = dummyProposal().builder.build();
|
||||||
var ret = TxProposal._verifySignatures(pubkeys, validScriptSig, tx.hashForSignature());
|
var ret = TxProposal._verifySignatures(pubkeys, validScriptSig, tx.hashForSignature());
|
||||||
ret.should.deep.equal(['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']);
|
ret.should.deep.equal(['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']);
|
||||||
});
|
});
|
||||||
|
|
@ -223,13 +229,13 @@ describe('TxProposal', function() {
|
||||||
Buffer.isBuffer(info.script.getBuffer()).should.equal(true);
|
Buffer.isBuffer(info.script.getBuffer()).should.equal(true);
|
||||||
});
|
});
|
||||||
it('#_updateSignedBy', function() {
|
it('#_updateSignedBy', function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
txp._inputSigners.should.deep.equal([
|
txp._inputSigners.should.deep.equal([
|
||||||
['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']
|
['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
describe('#_check', function() {
|
describe('#_check', function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
var backup = txp.builder.tx.ins;
|
var backup = txp.builder.tx.ins;
|
||||||
|
|
||||||
it('OK', function() {
|
it('OK', function() {
|
||||||
|
|
@ -272,8 +278,96 @@ describe('TxProposal', function() {
|
||||||
txp.builder.tx.ins[0].s = backup;
|
txp.builder.tx.ins[0].s = backup;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#_checkPayPro', function() {
|
||||||
|
var txp, md;
|
||||||
|
beforeEach(function() {
|
||||||
|
txp = dummyProposal();
|
||||||
|
txp.paymentProtocolURL = '123';
|
||||||
|
md = {
|
||||||
|
request_url: '123',
|
||||||
|
pr: {
|
||||||
|
pd: {
|
||||||
|
expires: 123,
|
||||||
|
memo: 'memo',
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
total: '1230',
|
||||||
|
outs: [{
|
||||||
|
address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6',
|
||||||
|
amountSatStr: "123"
|
||||||
|
}],
|
||||||
|
expires: 92345678900,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('OK no merchant data', function() {
|
||||||
|
txp._checkPayPro();
|
||||||
|
});
|
||||||
|
it('OK merchant data', function() {
|
||||||
|
txp.addMerchantData(md);
|
||||||
|
});
|
||||||
|
it('NOK URL', function() {
|
||||||
|
txp.paymentProtocolURL = '1234';
|
||||||
|
(function() {
|
||||||
|
txp.addMerchantData(md);
|
||||||
|
}).should.throw('Mismatch');
|
||||||
|
});
|
||||||
|
it('NOK OUTS', function() {
|
||||||
|
md.outs = [];
|
||||||
|
(function() {
|
||||||
|
txp.addMerchantData(md);
|
||||||
|
}).should.throw('outputs');
|
||||||
|
});
|
||||||
|
it('NOK OUTS (case 2)', function() {
|
||||||
|
md.outs = [{}, {}];
|
||||||
|
(function() {
|
||||||
|
txp.addMerchantData(md);
|
||||||
|
}).should.throw('outputs');
|
||||||
|
});
|
||||||
|
it('NOK OUTS (case 3)', function() {
|
||||||
|
md.outs = [{}, {}];
|
||||||
|
(function() {
|
||||||
|
txp.addMerchantData(md);
|
||||||
|
}).should.throw('outputs');
|
||||||
|
});
|
||||||
|
it('NOK Amount', function() {
|
||||||
|
md.total = undefined;
|
||||||
|
(function() {
|
||||||
|
txp.addMerchantData(md);
|
||||||
|
}).should.throw('amount');
|
||||||
|
});
|
||||||
|
it('NOK Outs case 4', function() {
|
||||||
|
md.outs[0].address = 'aaa';
|
||||||
|
(function() {
|
||||||
|
txp.addMerchantData(md);
|
||||||
|
}).should.throw('address');
|
||||||
|
});
|
||||||
|
it('NOK Outs case 5', function() {
|
||||||
|
md.outs[0].amountSatStr = '432';
|
||||||
|
(function() {
|
||||||
|
txp.addMerchantData(md);
|
||||||
|
}).should.throw('amount');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('NOK Expired', function() {
|
||||||
|
md.expires = 1;
|
||||||
|
(function() {
|
||||||
|
txp.addMerchantData(md);
|
||||||
|
}).should.throw('expired');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('OK Expired but sent', function() {
|
||||||
|
md.expires = 2;
|
||||||
|
txp.sentTs = 1;
|
||||||
|
txp.addMerchantData(md);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('#merge', function() {
|
describe('#merge', function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
var backup = txp.builder.tx.ins;
|
var backup = txp.builder.tx.ins;
|
||||||
it('with self', function() {
|
it('with self', function() {
|
||||||
var hasChanged = txp.merge(txp);
|
var hasChanged = txp.merge(txp);
|
||||||
|
|
@ -283,7 +377,7 @@ describe('TxProposal', function() {
|
||||||
it('with less signatures', function() {
|
it('with less signatures', function() {
|
||||||
var backup = txp.builder.vanilla.scriptSig[0];
|
var backup = txp.builder.vanilla.scriptSig[0];
|
||||||
txp.builder.merge = function() {
|
txp.builder.merge = function() {
|
||||||
// 2 signatures.
|
// Only one signatures.
|
||||||
this.vanilla.scriptSig = ['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'];
|
this.vanilla.scriptSig = ['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'];
|
||||||
this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex');
|
this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex');
|
||||||
};
|
};
|
||||||
|
|
@ -307,7 +401,7 @@ describe('TxProposal', function() {
|
||||||
});
|
});
|
||||||
describe('#setCopayers', function() {
|
describe('#setCopayers', function() {
|
||||||
it("should fails if Tx has no creator", function() {
|
it("should fails if Tx has no creator", function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
txp.signedBy = {
|
txp.signedBy = {
|
||||||
'hugo': 1
|
'hugo': 1
|
||||||
};
|
};
|
||||||
|
|
@ -319,7 +413,7 @@ describe('TxProposal', function() {
|
||||||
}).should.throw('no creator');
|
}).should.throw('no creator');
|
||||||
});
|
});
|
||||||
it("should fails if Tx is not signed by creator", function() {
|
it("should fails if Tx is not signed by creator", function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
txp.creator = 'creator';
|
txp.creator = 'creator';
|
||||||
txp.signedBy = {
|
txp.signedBy = {
|
||||||
'hugo': 1
|
'hugo': 1
|
||||||
|
|
@ -336,7 +430,7 @@ describe('TxProposal', function() {
|
||||||
|
|
||||||
|
|
||||||
it("should fails if Tx has unmapped signatures", function() {
|
it("should fails if Tx has unmapped signatures", function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
txp.creator = 'creator';
|
txp.creator = 'creator';
|
||||||
txp.signedBy = {
|
txp.signedBy = {
|
||||||
creator: 1
|
creator: 1
|
||||||
|
|
@ -353,7 +447,7 @@ describe('TxProposal', function() {
|
||||||
|
|
||||||
// This was disabled. Unnecessary to check this.
|
// This was disabled. Unnecessary to check this.
|
||||||
it.skip("should be signed by sender", function() {
|
it.skip("should be signed by sender", function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
var ts = Date.now();
|
var ts = Date.now();
|
||||||
txp._inputSigners = [
|
txp._inputSigners = [
|
||||||
['pk1', 'pk0']
|
['pk1', 'pk0']
|
||||||
|
|
@ -372,7 +466,7 @@ describe('TxProposal', function() {
|
||||||
|
|
||||||
|
|
||||||
it("should set signedBy (trivial case)", function() {
|
it("should set signedBy (trivial case)", function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
var ts = Date.now();
|
var ts = Date.now();
|
||||||
txp._inputSigners = [
|
txp._inputSigners = [
|
||||||
['pk1', 'pk0']
|
['pk1', 'pk0']
|
||||||
|
|
@ -390,7 +484,7 @@ describe('TxProposal', function() {
|
||||||
txp.signedBy['creator'].should.gte(ts);
|
txp.signedBy['creator'].should.gte(ts);
|
||||||
});
|
});
|
||||||
it("should assign creator", function() {
|
it("should assign creator", function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
var ts = Date.now();
|
var ts = Date.now();
|
||||||
txp._inputSigners = [
|
txp._inputSigners = [
|
||||||
['pk0']
|
['pk0']
|
||||||
|
|
@ -409,7 +503,7 @@ describe('TxProposal', function() {
|
||||||
txp.seenBy['creator'].should.equal(txp.createdTs);
|
txp.seenBy['creator'].should.equal(txp.createdTs);
|
||||||
})
|
})
|
||||||
it("New tx should have only 1 signature", function() {
|
it("New tx should have only 1 signature", function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
var ts = Date.now();
|
var ts = Date.now();
|
||||||
txp.signedBy = {};
|
txp.signedBy = {};
|
||||||
delete txp['creator'];
|
delete txp['creator'];
|
||||||
|
|
@ -431,7 +525,7 @@ describe('TxProposal', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("if signed, should not change ts", function() {
|
it("if signed, should not change ts", function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
var ts = Date.now();
|
var ts = Date.now();
|
||||||
txp._inputSigners = [
|
txp._inputSigners = [
|
||||||
['pk0', 'pk1']
|
['pk0', 'pk1']
|
||||||
|
|
@ -456,25 +550,25 @@ describe('TxProposal', function() {
|
||||||
|
|
||||||
describe('micelaneous functions', function() {
|
describe('micelaneous functions', function() {
|
||||||
it('should report rejectCount', function() {
|
it('should report rejectCount', function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
txp.rejectCount().should.equal(0);
|
txp.rejectCount().should.equal(0);
|
||||||
txp.setRejected(['juan'])
|
txp.setRejected(['juan'])
|
||||||
txp.rejectCount().should.equal(1);
|
txp.rejectCount().should.equal(1);
|
||||||
});
|
});
|
||||||
it('should report isPending 1', function() {
|
it('should report isPending 1', function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
txp.rejectedBy = [];
|
txp.rejectedBy = [];
|
||||||
txp.sentTxid = 1;
|
txp.sentTxid = 1;
|
||||||
txp.isPending(3).should.equal(false);
|
txp.isPending(3).should.equal(false);
|
||||||
});
|
});
|
||||||
it('should report isPending 2', function() {
|
it('should report isPending 2', function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
txp.rejectedBy = [];
|
txp.rejectedBy = [];
|
||||||
txp.sentTxid = null;
|
txp.sentTxid = null;
|
||||||
txp.isPending(3).should.equal(true);
|
txp.isPending(3).should.equal(true);
|
||||||
});
|
});
|
||||||
it('should report isPending 3', function() {
|
it('should report isPending 3', function() {
|
||||||
var txp = dummyProposal;
|
var txp = dummyProposal();
|
||||||
txp.rejectedBy = [1, 2, 3, 4];
|
txp.rejectedBy = [1, 2, 3, 4];
|
||||||
txp.sentTxid = null;
|
txp.sentTxid = null;
|
||||||
txp.isPending(3).should.equal(false);
|
txp.isPending(3).should.equal(false);
|
||||||
|
|
|
||||||
862
test/Wallet.js
862
test/Wallet.js
File diff suppressed because it is too large
Load diff
|
|
@ -2,10 +2,22 @@
|
||||||
var bitcore = bitcore || require('bitcore');
|
var bitcore = bitcore || require('bitcore');
|
||||||
var Script = bitcore.Script;
|
var Script = bitcore.Script;
|
||||||
|
|
||||||
var VALID_SCRIPTSIG_BUF = new Buffer('0048304502200708a381dde585ef7fdfaeaeb5da9b451d3e22b01eac8a5e3d03b959e24a7478022100c90e76e423523a54a9e9c43858337ebcef1a539a7fc685c2698dd8648fcf1b9101473044022030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae','hex');
|
var VALID_SCRIPTSIG_BUF = new Buffer('0048304502200708a381dde585ef7fdfaeaeb5da9b451d3e22b01eac8a5e3d03b959e24a7478022100c90e76e423523a54a9e9c43858337ebcef1a539a7fc685c2698dd8648fcf1b9101473044022030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae', 'hex');
|
||||||
|
|
||||||
function Tx() {
|
function Tx() {
|
||||||
this.ins = [{s: VALID_SCRIPTSIG_BUF }];
|
this.ins = [{
|
||||||
|
s: VALID_SCRIPTSIG_BUF
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Tx.prototype.serialize = function() {
|
||||||
|
return new Buffer('1234','hex');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Tx.prototype.getSize = function() {
|
||||||
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
Tx.prototype.getHashType = function() {
|
Tx.prototype.getHashType = function() {
|
||||||
|
|
@ -23,26 +35,32 @@ function FakeBuilder() {
|
||||||
this.test = 1;
|
this.test = 1;
|
||||||
this.tx = new Tx();
|
this.tx = new Tx();
|
||||||
this.signhash = 1;
|
this.signhash = 1;
|
||||||
this.inputMap = [{ address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6',
|
this.inputMap = [{
|
||||||
|
address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6',
|
||||||
scriptPubKey: new Script(new Buffer('a914dc0623476aefb049066b09b0147a022e6eb8429187', 'hex')),
|
scriptPubKey: new Script(new Buffer('a914dc0623476aefb049066b09b0147a022e6eb8429187', 'hex')),
|
||||||
scriptType: 4,
|
scriptType: 4,
|
||||||
i: 0 }];
|
i: 0
|
||||||
|
}];
|
||||||
|
|
||||||
this.vanilla = {
|
this.vanilla = {
|
||||||
scriptSig: [VALID_SCRIPTSIG_BUF],
|
scriptSig: [VALID_SCRIPTSIG_BUF],
|
||||||
}
|
outs: JSON.stringify([{
|
||||||
|
address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6',
|
||||||
|
amountSatStr: '123',
|
||||||
|
}]),
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FakeBuilder.prototype.merge = function() {
|
FakeBuilder.prototype.merge = function() {};
|
||||||
};
|
|
||||||
|
|
||||||
FakeBuilder.prototype.build = function() {
|
FakeBuilder.prototype.build = function() {
|
||||||
return this.tx;
|
return this.tx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
FakeBuilder.prototype.toObj = function() {
|
FakeBuilder.prototype.toObj = function() {
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
FakeBuilder.VALID_SCRIPTSIG_BUF = VALID_SCRIPTSIG_BUF;
|
FakeBuilder.VALID_SCRIPTSIG_BUF = VALID_SCRIPTSIG_BUF;
|
||||||
|
|
|
||||||
|
|
@ -1,329 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var is_browser = typeof process == 'undefined'
|
|
||||||
|| typeof process.versions === 'undefined';
|
|
||||||
var bitcore = bitcore || require('bitcore');
|
|
||||||
var Buffer = bitcore.Buffer;
|
|
||||||
var PayPro = bitcore.PayPro;
|
|
||||||
if (is_browser) {
|
|
||||||
var copay = require('copay'); //browser
|
|
||||||
} else {
|
|
||||||
var copay = require('../../copay'); //node
|
|
||||||
}
|
|
||||||
var Wallet = copay.Wallet;
|
|
||||||
|
|
||||||
var x509 = {
|
|
||||||
priv: ''
|
|
||||||
+ 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeFRKdUsyYUdM'
|
|
||||||
+ 'bjFkWEpLRGg0TXdQTFVrbDNISTVwR25HNWFjNGwvMGlobXE4Y3dDCitGVlBnWk1TNTlheWtpc0Ir'
|
|
||||||
+ 'ekM3dnR2a0prL2J2K0JTT1g3b3hkSXN1TDNkS1FGcHVYWFZmcmRiOTV3WW40TSsKL25qRWhYTWxo'
|
|
||||||
+ 'Vk1IL09DaUFnOUpLaFRLV0w2R1JXWkFBaEE3bEJSaGdTTkRUaVRDNTFDYmlLN3hBNnBONCt0UQpI'
|
|
||||||
+ 'eG9tSlBYclpSa2JCMmtsT2ZXd2J2OTNZM0oxS0ZEK2kwUE1RSEx3N3JoRXVteEM5MytISFVWWVZI'
|
|
||||||
+ 'N0gxVFBaCkgxYmRVSkowMmdRZXlsSnNzWUNKeWRaUHpOVC96dXRzL0tKV2RSdjVseHdHOXU5dE1O'
|
|
||||||
+ 'TWdoSmJtQWFNa01HaSsKbzdQTkV5UDNxSEZyWXBZaHM1cHFMSE1STkI3OFFNOUllTmpMRndJREFR'
|
|
||||||
+ 'QUJBb0lCQVFERVJyalBiQUdjbmwxaAorZGIrOTczNGZ0aElBUkpWSko1dTRFK1JKcThSRWhGTEVL'
|
|
||||||
+ 'UFlKNW0yUC94dVZBMXpYV2xnYXhaRUZ6d1VRaUpZCjdsOEpLVjlwSHhReVlaQ1M4dndYZzhpWGtz'
|
|
||||||
+ 'dndQaWRvQmN1YW4vd0RWQ1FCZXk2VkxjVXpSYUd1Ui9sTHNYK1YKN2Z0QjBvUnFsSXFrYmNQZE1N'
|
|
||||||
+ 'dnFUeG93UnVoUG11Q3JWVGpPNHBiTnFuU09OUExPaUovRkFYYjJwZnpGZnBCUgpHeCtFTW16d2Ur'
|
|
||||||
+ 'SEZuSkJHRGhIWjk5bm4vVEJmYUp6TlZDcURZLzNid3o1WDdIUU5ZN1QrSnlUVUZzZVE5NHhzCnpy'
|
|
||||||
+ 'a2lidGRmVGNUanB1K1VoWm80c1p6Q3IrZkhHWm9FOUdEUHF0ZDRnQ3ByazRFS0pzbXFCRVN4QlhT'
|
|
||||||
+ 'RGhZZ04KOXBVRDM4c1pBb0dCQU9yZkRqdDZaL0ZDamFuVThXek5GaWYrOVQxQTJ4b013RDVWU2xN'
|
|
||||||
+ 'dVJyWW1HbGZyMEM5TQpmMUVvZ2l2dVRrYnA3cmtnZFRhWVRTYndmTnFaQkt4Y3R5YzdCaGRwWnhE'
|
|
||||||
+ 'RVdKa2Z5cThxVngvem1Cek1JK1ZzCjJLYi9hcHZXcmJlb3NET0NyeUg1YzhKc1VUOXhUWDNYYnhF'
|
|
||||||
+ 'anlPSlFCU1lHRE1qUHlKNkU5czZMQW9HQkFOYnYKd2d0S2Nra0tLbDJhNXZzaGR2RENnNnFLL1Fn'
|
|
||||||
+ 'T20vNktUSlVKRVNqaHoydFIrZlBWUjcwVEg5UmhoVFJscERXQgpCd3oyU2NCc1RRNDIvTGsxRnky'
|
|
||||||
+ 'MFQvck12S3VmSEw1VE1BNGZ6NWRxMUxIbmN6ejZVazVnWEtBT09rUjlVdVhpClR0eTNoREcyQkM4'
|
|
||||||
+ 'Nk1LTVJ4SjUxRWJxam94d0VSMTAwU2FuTVBmTWxBb0dBSUhLY1pyOHNhUHBHMC9XbFBPREEKZE5v'
|
|
||||||
+ 'V1MxWVFidkxnQkR5SVBpR2doejJRV2lFcjY3em53ZkNVdXpqNiszVUtFKzFXQkNyYVRjemZrdHVj'
|
|
||||||
+ 'OTZyLwphcDRPNDJFZWFnU1dNT0ZoZ1AyYWQ4R1JmRGovcEl4N0NlY3pkVUFkVThnc1A1R0lYR3M0'
|
|
||||||
+ 'QU40eUEwL0Y0dUxHCloxbklRT3ZKS2syZnFvWjZNdHd2dEswQ2dZRUFnSjdGTGVDRTkzUmYyZGdD'
|
|
||||||
+ 'ZFRHWGJZZlpKc3M1bEFLNkV0NUwKNmJ1ZFN5dWw1Z0VPWkgyekNsQlJjZFJSMUFNbSt1V1ZoSW8x'
|
|
||||||
+ 'cERLckFlQ2g1MnIvemRmakxLQXNIejkrQWQ3aQpHUEdzVmw0Vm5jaDFTMzQ0bHJKUGUzQklLZ2dj'
|
|
||||||
+ 'L1hncDNTYnNzcHJMY2orT0wyZElrOUpXbzZ1Y3hmMUJmMkwwCjJlbGhBUWtDZ1lCWHN5elZWL1pK'
|
|
||||||
+ 'cVhOcFdDZzU1TDNVRm9UTHlLU3FsVktNM1dpRzVCS240QWF6VkNITCtHUVUKeHd4U2dSOWZRNElu'
|
|
||||||
+ 'dStyUHJOM0lteWswbEtQR0Y5U3pDUlJUaUpGUjcyc05xbE82bDBWOENXUkFQVFBKY2dxVgoxVThO'
|
|
||||||
+ 'SEs4YjNaaUlvR0orbXNOenBkeHJqNjJIM0E2K1krQXNOWTRTbVVUWEg5eWpnK251a2c9PQotLS0t'
|
|
||||||
+ 'LUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=',
|
|
||||||
pub: ''
|
|
||||||
+ 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR'
|
|
||||||
+ 'OEFNSUlCQ2dLQ0FRRUF4VEp1SzJhR0xuMWRYSktEaDRNdwpQTFVrbDNISTVwR25HNWFjNGwvMGlo'
|
|
||||||
+ 'bXE4Y3dDK0ZWUGdaTVM1OWF5a2lzQit6Qzd2dHZrSmsvYnYrQlNPWDdvCnhkSXN1TDNkS1FGcHVY'
|
|
||||||
+ 'WFZmcmRiOTV3WW40TSsvbmpFaFhNbGhWTUgvT0NpQWc5SktoVEtXTDZHUldaQUFoQTcKbEJSaGdT'
|
|
||||||
+ 'TkRUaVRDNTFDYmlLN3hBNnBONCt0UUh4b21KUFhyWlJrYkIya2xPZld3YnY5M1kzSjFLRkQraTBQ'
|
|
||||||
+ 'TQpRSEx3N3JoRXVteEM5MytISFVWWVZIN0gxVFBaSDFiZFVKSjAyZ1FleWxKc3NZQ0p5ZFpQek5U'
|
|
||||||
+ 'L3p1dHMvS0pXCmRSdjVseHdHOXU5dE1OTWdoSmJtQWFNa01HaStvN1BORXlQM3FIRnJZcFloczVw'
|
|
||||||
+ 'cUxITVJOQjc4UU05SWVOakwKRndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==',
|
|
||||||
der: ''
|
|
||||||
+ 'MIIDBjCCAe4CCQDI2qWdA3/VpDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UE'
|
|
||||||
+ 'CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE0MDcx'
|
|
||||||
+ 'NjAxMzM1MVoXDTE1MDcxNjAxMzM1MVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh'
|
|
||||||
+ 'dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD'
|
|
||||||
+ 'ggEPADCCAQoCggEBAMUybitmhi59XVySg4eDMDy1JJdxyOaRpxuWnOJf9IoZqvHMAvhVT4GTEufW'
|
|
||||||
+ 'spIrAfswu77b5CZP27/gUjl+6MXSLLi93SkBabl11X63W/ecGJ+DPv54xIVzJYVTB/zgogIPSSoU'
|
|
||||||
+ 'yli+hkVmQAIQO5QUYYEjQ04kwudQm4iu8QOqTePrUB8aJiT162UZGwdpJTn1sG7/d2NydShQ/otD'
|
|
||||||
+ 'zEBy8O64RLpsQvd/hx1FWFR+x9Uz2R9W3VCSdNoEHspSbLGAicnWT8zU/87rbPyiVnUb+ZccBvbv'
|
|
||||||
+ 'bTDTIISW5gGjJDBovqOzzRMj96hxa2KWIbOaaixzETQe/EDPSHjYyxcCAwEAATANBgkqhkiG9w0B'
|
|
||||||
+ 'AQUFAAOCAQEAL6AMMfC3TlRcmsIgHxjVD4XYtISlldnrn2X9zvFbJKCpNy8XQQosQxrhyfzPHQKj'
|
|
||||||
+ 'lS2L/KCGMnjx9QkYD2Hlp1MJ1uVv9888th/gcZOv3Or3hQyi5K1Sh5xCG+69lUOqUEGu9B4irsqo'
|
|
||||||
+ 'FomQVbQolSy+t4apdJi7kuEDwFDk4gZiVEfsuX+naN5a6pCnWnhX1Vf4fKwfkLobKKXm2zQVsjxl'
|
|
||||||
+ 'wBAqOEmJGDLoRMXH56qJnEZ/dqsczaJOHQSi9mFEHL0r5rsEDTT5AVxdnBfNnyGaCH7/zANEko+F'
|
|
||||||
+ 'GBj1JdJaJgFTXdbxDoyoPTPD+LJqSK5XYToo46y/T0u9CLveNA==',
|
|
||||||
pem: ''
|
|
||||||
+ 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU0Q0NRREkycVdkQTMvVnBEQU5C'
|
|
||||||
+ 'Z2txaGtpRzl3MEJBUVVGQURCRk1Rc3dDUVlEVlFRR0V3SkIKVlRFVE1CRUdBMVVFQ0F3S1UyOXRa'
|
|
||||||
+ 'UzFUZEdGMFpURWhNQjhHQTFVRUNnd1lTVzUwWlhKdVpYUWdWMmxrWjJsMApjeUJRZEhrZ1RIUmtN'
|
|
||||||
+ 'QjRYRFRFME1EY3hOakF4TXpNMU1Wb1hEVEUxTURjeE5qQXhNek0xTVZvd1JURUxNQWtHCkExVUVC'
|
|
||||||
+ 'aE1DUVZVeEV6QVJCZ05WQkFnTUNsTnZiV1V0VTNSaGRHVXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJt'
|
|
||||||
+ 'VjAKSUZkcFpHZHBkSE1nVUhSNUlFeDBaRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFE'
|
|
||||||
+ 'Q0NBUW9DZ2dFQgpBTVV5Yml0bWhpNTlYVnlTZzRlRE1EeTFKSmR4eU9hUnB4dVduT0pmOUlvWnF2'
|
|
||||||
+ 'SE1BdmhWVDRHVEV1ZldzcElyCkFmc3d1NzdiNUNaUDI3L2dVamwrNk1YU0xMaTkzU2tCYWJsMTFY'
|
|
||||||
+ 'NjNXL2VjR0orRFB2NTR4SVZ6SllWVEIvemcKb2dJUFNTb1V5bGkraGtWbVFBSVFPNVFVWVlFalEw'
|
|
||||||
+ 'NGt3dWRRbTRpdThRT3FUZVByVUI4YUppVDE2MlVaR3dkcApKVG4xc0c3L2QyTnlkU2hRL290RHpF'
|
|
||||||
+ 'Qnk4TzY0Ukxwc1F2ZC9oeDFGV0ZSK3g5VXoyUjlXM1ZDU2ROb0VIc3BTCmJMR0FpY25XVDh6VS84'
|
|
||||||
+ 'N3JiUHlpVm5VYitaY2NCdmJ2YlREVElJU1c1Z0dqSkRCb3ZxT3p6Uk1qOTZoeGEyS1cKSWJPYWFp'
|
|
||||||
+ 'eHpFVFFlL0VEUFNIall5eGNDQXdFQUFUQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFMNkFNTWZD'
|
|
||||||
+ 'MwpUbFJjbXNJZ0h4alZENFhZdElTbGxkbnJuMlg5enZGYkpLQ3BOeThYUVFvc1F4cmh5ZnpQSFFL'
|
|
||||||
+ 'amxTMkwvS0NHCk1uang5UWtZRDJIbHAxTUoxdVZ2OTg4OHRoL2djWk92M09yM2hReWk1SzFTaDV4'
|
|
||||||
+ 'Q0crNjlsVU9xVUVHdTlCNGkKcnNxb0ZvbVFWYlFvbFN5K3Q0YXBkSmk3a3VFRHdGRGs0Z1ppVkVm'
|
|
||||||
+ 'c3VYK25hTjVhNnBDblduaFgxVmY0Zkt3ZgprTG9iS0tYbTJ6UVZzanhsd0JBcU9FbUpHRExvUk1Y'
|
|
||||||
+ 'SDU2cUpuRVovZHFzY3phSk9IUVNpOW1GRUhMMHI1cnNFCkRUVDVBVnhkbkJmTm55R2FDSDcvekFO'
|
|
||||||
+ 'RWtvK0ZHQmoxSmRKYUpnRlRYZGJ4RG95b1BUUEQrTEpxU0s1WFlUb28KNDZ5L1QwdTlDTHZlTkE9'
|
|
||||||
+ 'PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=='
|
|
||||||
};
|
|
||||||
|
|
||||||
x509.priv = new Buffer(x509.priv, 'base64');
|
|
||||||
x509.pub = new Buffer(x509.pub, 'base64');
|
|
||||||
x509.der = new Buffer(x509.der, 'base64');
|
|
||||||
x509.pem = new Buffer(x509.pem, 'base64');
|
|
||||||
|
|
||||||
function startServer(cb) {
|
|
||||||
if (Wallet.request._server) {
|
|
||||||
setTimeout(function() {
|
|
||||||
return cb(null, Wallet.request._server);
|
|
||||||
}, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var old = Wallet.request;
|
|
||||||
|
|
||||||
var server = {
|
|
||||||
GET: {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive "I want to pay"
|
|
||||||
*/
|
|
||||||
|
|
||||||
'/-/request': function(req, cb) {
|
|
||||||
var res = {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {},
|
|
||||||
body: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
var uid = 0;
|
|
||||||
|
|
||||||
var outputs = [];
|
|
||||||
|
|
||||||
[2000, 1000].forEach(function(value) {
|
|
||||||
var po = new PayPro();
|
|
||||||
po = po.makeOutput();
|
|
||||||
// number of satoshis to be paid
|
|
||||||
po.set('amount', value);
|
|
||||||
// a TxOut script where the payment should be sent. similar to OP_CHECKSIG
|
|
||||||
po.set('script', new Buffer([
|
|
||||||
118, // OP_DUP
|
|
||||||
169, // OP_HASH160
|
|
||||||
76, // OP_PUSHDATA1
|
|
||||||
20, // number of bytes
|
|
||||||
55,
|
|
||||||
48,
|
|
||||||
254,
|
|
||||||
188,
|
|
||||||
186,
|
|
||||||
4,
|
|
||||||
186,
|
|
||||||
208,
|
|
||||||
205,
|
|
||||||
71,
|
|
||||||
108,
|
|
||||||
251,
|
|
||||||
130,
|
|
||||||
15,
|
|
||||||
156,
|
|
||||||
55,
|
|
||||||
215,
|
|
||||||
70,
|
|
||||||
111,
|
|
||||||
217,
|
|
||||||
136, // OP_EQUALVERIFY
|
|
||||||
172 // OP_CHECKSIG
|
|
||||||
]));
|
|
||||||
outputs.push(po.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Payment Details
|
|
||||||
*/
|
|
||||||
|
|
||||||
var mdata = new Buffer([0]);
|
|
||||||
uid++;
|
|
||||||
if (uid > 0xffff) {
|
|
||||||
throw new Error('UIDs bigger than 0xffff not supported.');
|
|
||||||
} else if (uid > 0xff) {
|
|
||||||
mdata = new Buffer([(uid >> 8) & 0xff, (uid >> 0) & 0xff])
|
|
||||||
} else {
|
|
||||||
mdata = new Buffer([0, uid])
|
|
||||||
}
|
|
||||||
var now = Date.now() / 1000 | 0;
|
|
||||||
var pd = new PayPro();
|
|
||||||
pd = pd.makePaymentDetails();
|
|
||||||
pd.set('network', 'test');
|
|
||||||
pd.set('outputs', outputs);
|
|
||||||
pd.set('time', now);
|
|
||||||
pd.set('expires', now + 60 * 60 * 24);
|
|
||||||
pd.set('memo', 'Hello, this is the server, we would like some money.');
|
|
||||||
var port = +req.headers.host.split(':')[1] || server.port;
|
|
||||||
pd.set('payment_url', 'https://localhost:' + port + '/-/pay');
|
|
||||||
pd.set('merchant_data', mdata);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* PaymentRequest
|
|
||||||
*/
|
|
||||||
|
|
||||||
var cr = new PayPro();
|
|
||||||
cr = cr.makeX509Certificates();
|
|
||||||
cr.set('certificate', [x509.der]);
|
|
||||||
|
|
||||||
// We send the PaymentRequest to the customer
|
|
||||||
var pr = new PayPro();
|
|
||||||
pr = pr.makePaymentRequest();
|
|
||||||
pr.set('payment_details_version', 1);
|
|
||||||
pr.set('pki_type', 'x509+sha256');
|
|
||||||
pr.set('pki_data', cr.serialize());
|
|
||||||
pr.set('serialized_payment_details', pd.serialize());
|
|
||||||
pr.sign(x509.priv);
|
|
||||||
|
|
||||||
pr = pr.serialize();
|
|
||||||
|
|
||||||
// BIP-71 - set the content-type
|
|
||||||
res.headers['Content-Type'] = PayPro.PAYMENT_REQUEST_CONTENT_TYPE;
|
|
||||||
res.headers['Content-Length'] = pr.length + '';
|
|
||||||
res.headers['Content-Transfer-Encoding'] = 'binary';
|
|
||||||
|
|
||||||
res.body = pr;
|
|
||||||
|
|
||||||
return cb(null, res, res.body);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive Payment
|
|
||||||
*/
|
|
||||||
|
|
||||||
POST: {
|
|
||||||
'/-/pay': function(req, cb) {
|
|
||||||
var body = req.body;
|
|
||||||
|
|
||||||
var res = {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {},
|
|
||||||
body: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
body = PayPro.Payment.decode(body);
|
|
||||||
|
|
||||||
var pay = new PayPro();
|
|
||||||
pay = pay.makePayment(body);
|
|
||||||
var merchant_data = pay.get('merchant_data');
|
|
||||||
var transactions = pay.get('transactions');
|
|
||||||
var refund_to = pay.get('refund_to');
|
|
||||||
var memo = pay.get('memo');
|
|
||||||
|
|
||||||
// We send this to the customer after receiving a Payment
|
|
||||||
// Then we propogate the transaction through bitcoin network
|
|
||||||
var ack = new PayPro();
|
|
||||||
ack = ack.makePaymentACK();
|
|
||||||
ack.set('payment', pay.message);
|
|
||||||
ack.set('memo', 'Thank you for your payment!');
|
|
||||||
|
|
||||||
ack = ack.serialize();
|
|
||||||
|
|
||||||
// BIP-71 - set the content-type
|
|
||||||
res.headers['Content-Type'] = PayPro.PAYMENT_ACK_CONTENT_TYPE;
|
|
||||||
res.headers['Content-Length'] = ack.length + '';
|
|
||||||
res.headers['Content-Transfer-Encoding'] = 'binary';
|
|
||||||
|
|
||||||
transactions = transactions.map(function(tx) {
|
|
||||||
tx.buffer = new Buffer(new Uint8Array(tx.buffer));
|
|
||||||
tx.buffer = tx.buffer.slice(tx.offset, tx.limit);
|
|
||||||
var ptx = new bitcore.Transaction();
|
|
||||||
ptx.parse(tx.buffer);
|
|
||||||
return ptx;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.body = ack;
|
|
||||||
|
|
||||||
return cb(null, res, res.body);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
listen: function(port, cb) {
|
|
||||||
if (cb) return cb();
|
|
||||||
},
|
|
||||||
close: function(cb) {
|
|
||||||
Wallet.request = old;
|
|
||||||
return cb();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Wallet.request = function(options) {
|
|
||||||
var ret = {
|
|
||||||
success: function(cb) {
|
|
||||||
this._success = cb;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
error: function(cb) {
|
|
||||||
this._error = cb;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
_success: function() {
|
|
||||||
;
|
|
||||||
},
|
|
||||||
_error: function(_, err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var method = (options.method || 'GET').toUpperCase();
|
|
||||||
var uri = options.uri || options.url;
|
|
||||||
var path = uri.replace(/^https?:\/\/[^\/]+/, '');
|
|
||||||
var req = options;
|
|
||||||
req.headers = req.headers || {};
|
|
||||||
req.body = req.data || req.body || {};
|
|
||||||
req.socket = {
|
|
||||||
remoteAddress: 'localhost'
|
|
||||||
};
|
|
||||||
req.headers['Host'] = 'localhost:8080';
|
|
||||||
Object.keys(req.headers).forEach(function(key) {
|
|
||||||
req.headers[key] = req.headers[key] + '';
|
|
||||||
req.headers[key.toLowerCase()] = req.headers[key] + '';
|
|
||||||
});
|
|
||||||
setTimeout(function() {
|
|
||||||
server[method][path](req, function(err, res, body) {
|
|
||||||
if (err) return ret._error(null, err, null, options);
|
|
||||||
Object.keys(res.headers).forEach(function(key) {
|
|
||||||
res.headers[key] = res.headers[key] + '';
|
|
||||||
res.headers[key.toLowerCase()] = res.headers[key] + '';
|
|
||||||
});
|
|
||||||
return ret._success(body, res.statusCode, res.headers, options);
|
|
||||||
});
|
|
||||||
}, 1);
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
Wallet.request._server = server;
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
return cb(null, server);
|
|
||||||
}, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = startServer;
|
|
||||||
|
|
@ -12,12 +12,16 @@ saveAs = function(blob, filename) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var startServer = require('../../mocks/FakePayProServer');
|
|
||||||
|
|
||||||
describe("Unit: Controllers", function() {
|
describe("Unit: Controllers", function() {
|
||||||
config.plugins.LocalStorage = true;
|
config.plugins.LocalStorage = true;
|
||||||
config.plugins.GoogleDrive = null;
|
config.plugins.GoogleDrive = null;
|
||||||
|
|
||||||
|
var anAddr = 'mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy';
|
||||||
|
var anAmount = 1000;
|
||||||
|
var aComment = 'hola';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var invalidForm = {
|
var invalidForm = {
|
||||||
$invalid: true
|
$invalid: true
|
||||||
};
|
};
|
||||||
|
|
@ -69,24 +73,30 @@ describe("Unit: Controllers", function() {
|
||||||
w.getTransactionHistory = sinon.stub().yields(null);
|
w.getTransactionHistory = sinon.stub().yields(null);
|
||||||
w.getNetworkName = sinon.stub().returns('testnet');
|
w.getNetworkName = sinon.stub().returns('testnet');
|
||||||
|
|
||||||
w.createTx = sinon.stub().yields(null);
|
w.spend = sinon.stub().yields(null);
|
||||||
w.sendTx = sinon.stub().yields(null);
|
w.sendTxProposal = sinon.stub();
|
||||||
|
w.broadcastTx = sinon.stub().yields(null);
|
||||||
w.requiresMultipleSignatures = sinon.stub().returns(true);
|
w.requiresMultipleSignatures = sinon.stub().returns(true);
|
||||||
w.getTxProposals = sinon.stub().returns([1, 2, 3]);
|
w.getTxProposals = sinon.stub().returns([1, 2, 3]);
|
||||||
w.getPendingTxProposals = sinon.stub().returns({
|
w.getPendingTxProposals = sinon.stub().returns({
|
||||||
txs : [{ isPending : true }],
|
txs: [{
|
||||||
|
isPending: true
|
||||||
|
}],
|
||||||
pendingForUs: 1
|
pendingForUs: 1
|
||||||
});
|
});
|
||||||
w.getId = sinon.stub().returns(1234);
|
w.getId = sinon.stub().returns(1234);
|
||||||
w.on = sinon.stub().yields({'e': 'errmsg', 'loading': false});
|
w.on = sinon.stub().yields({
|
||||||
|
'e': 'errmsg',
|
||||||
|
'loading': false
|
||||||
|
});
|
||||||
w.getBalance = sinon.stub().returns(10000);
|
w.getBalance = sinon.stub().returns(10000);
|
||||||
w.publicKeyRing = sinon.stub().yields(null);
|
w.publicKeyRing = sinon.stub().yields(null);
|
||||||
w.publicKeyRing.nicknameForCopayer = sinon.stub().returns('nickcopayer');
|
w.publicKeyRing.nicknameForCopayer = sinon.stub().returns('nickcopayer');
|
||||||
w.updateFocusedTimestamp = sinon.stub().returns(1415804323);
|
w.updateFocusedTimestamp = sinon.stub().returns(1415804323);
|
||||||
w.getAddressesInfo = sinon.stub().returns([
|
w.getAddressesInfo = sinon.stub().returns([{
|
||||||
{ addressStr: "2MxvwvfshZxw4SkkaJZ8NDKLyepa9HLMKtu",
|
addressStr: "2MxvwvfshZxw4SkkaJZ8NDKLyepa9HLMKtu",
|
||||||
isChange: false }
|
isChange: false
|
||||||
]);
|
}]);
|
||||||
|
|
||||||
var iden = {};
|
var iden = {};
|
||||||
iden.deleteWallet = sinon.stub().yields(null);
|
iden.deleteWallet = sinon.stub().yields(null);
|
||||||
|
|
@ -240,31 +250,35 @@ describe("Unit: Controllers", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a transaction proposal with given values', function() {
|
it('should create a transaction proposal with given values', function() {
|
||||||
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
sendForm.address.$setViewValue(anAddr);
|
||||||
sendForm.amount.$setViewValue(1000);
|
sendForm.amount.$setViewValue(anAmount);
|
||||||
|
sendForm.comment.$setViewValue(aComment);
|
||||||
|
|
||||||
scope.loadTxs = sinon.spy();
|
scope.loadTxs = sinon.spy();
|
||||||
|
|
||||||
var w = scope.wallet;
|
var w = scope.wallet;
|
||||||
scope.submitForm(sendForm);
|
scope.submitForm(sendForm);
|
||||||
sinon.assert.callCount(w.createTx, 1);
|
sinon.assert.callCount(w.spend, 1);
|
||||||
sinon.assert.callCount(w.sendTx, 0);
|
sinon.assert.callCount(w.broadcastTx, 0);
|
||||||
sinon.assert.callCount(scope.loadTxs, 1);
|
sinon.assert.callCount(scope.loadTxs, 1);
|
||||||
w.createTx.getCall(0).args[0].should.equal('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
var spendArgs = w.spend.getCall(0).args[0];
|
||||||
w.createTx.getCall(0).args[1].should.equal(1000 * scope.wallet.settings.unitToSatoshi);
|
spendArgs.toAddress.should.equal(anAddr);
|
||||||
(typeof w.createTx.getCall(0).args[2]).should.equal('undefined');
|
spendArgs.amountSat.should.equal(anAmount * scope.wallet.settings.unitToSatoshi);
|
||||||
|
spendArgs.comment.should.equal(aComment);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should handle big values in 100 BTC', function() {
|
it('should handle big values in 100 BTC', function() {
|
||||||
var old = scope.wallet.settings.unitToSatoshi;
|
var old = scope.wallet.settings.unitToSatoshi;
|
||||||
scope.wallet.settings.unitToSatoshi = 100000000;;
|
scope.wallet.settings.unitToSatoshi = 100000000;;
|
||||||
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
sendForm.address.$setViewValue(anAddr);
|
||||||
sendForm.amount.$setViewValue(100);
|
sendForm.amount.$setViewValue(100);
|
||||||
|
sendForm.address.$setViewValue(anAddr);
|
||||||
|
|
||||||
scope.loadTxs = sinon.spy();
|
scope.loadTxs = sinon.spy();
|
||||||
scope.submitForm(sendForm);
|
scope.submitForm(sendForm);
|
||||||
var w = scope.wallet;
|
var w = scope.wallet;
|
||||||
w.createTx.getCall(0).args[1].should.equal(100 * scope.wallet.settings.unitToSatoshi);
|
w.spend.getCall(0).args[0].amountSat.should.equal(100 * scope.wallet.settings.unitToSatoshi);
|
||||||
scope.wallet.settings.unitToSatoshi = old;
|
scope.wallet.settings.unitToSatoshi = old;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -276,11 +290,11 @@ describe("Unit: Controllers", function() {
|
||||||
|
|
||||||
var old = $rootScope.wallet.settings.unitToSatoshi;
|
var old = $rootScope.wallet.settings.unitToSatoshi;
|
||||||
$rootScope.wallet.settings.unitToSatoshi = 100000000;;
|
$rootScope.wallet.settings.unitToSatoshi = 100000000;;
|
||||||
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
sendForm.address.$setViewValue(anAddr);
|
||||||
sendForm.amount.$setViewValue(5000);
|
sendForm.amount.$setViewValue(5000);
|
||||||
scope.submitForm(sendForm);
|
scope.submitForm(sendForm);
|
||||||
|
|
||||||
w.createTx.getCall(0).args[1].should.equal(5000 * $rootScope.wallet.settings.unitToSatoshi);
|
w.spend.getCall(0).args[0].amountSat.should.equal(5000 * $rootScope.wallet.settings.unitToSatoshi);
|
||||||
$rootScope.wallet.settings.unitToSatoshi = old;
|
$rootScope.wallet.settings.unitToSatoshi = old;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -300,39 +314,6 @@ describe("Unit: Controllers", function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create and send a transaction proposal', function() {
|
|
||||||
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
|
||||||
sendForm.amount.$setViewValue(1000);
|
|
||||||
scope.loadTxs = sinon.spy();
|
|
||||||
|
|
||||||
var w = scope.wallet;
|
|
||||||
w.requiresMultipleSignatures = sinon.stub().returns(false);
|
|
||||||
w.totalCopayers = w.requiredCopayers = 1;
|
|
||||||
|
|
||||||
|
|
||||||
scope.submitForm(sendForm);
|
|
||||||
sinon.assert.callCount(w.createTx, 1);
|
|
||||||
sinon.assert.callCount(w.sendTx, 1);
|
|
||||||
sinon.assert.callCount(scope.loadTxs, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not send txp when there is an error at creation', function() {
|
|
||||||
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
|
||||||
sendForm.amount.$setViewValue(1000);
|
|
||||||
scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 1;
|
|
||||||
scope.loadTxs = sinon.spy();
|
|
||||||
var w = scope.wallet;
|
|
||||||
w.createTx.yields('error');
|
|
||||||
w.isShared = sinon.stub().returns(false);
|
|
||||||
|
|
||||||
|
|
||||||
scope.submitForm(sendForm);
|
|
||||||
|
|
||||||
sinon.assert.callCount(w.createTx, 1);
|
|
||||||
sinon.assert.callCount(w.sendTx, 0);
|
|
||||||
sinon.assert.callCount(scope.loadTxs, 1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Unit: Version Controller", function() {
|
describe("Unit: Version Controller", function() {
|
||||||
|
|
|
||||||
|
|
@ -128,12 +128,6 @@ var createBundle = function(opts) {
|
||||||
b.require('./test/mocks/FakeNetwork', {
|
b.require('./test/mocks/FakeNetwork', {
|
||||||
expose: './mocks/FakeNetwork'
|
expose: './mocks/FakeNetwork'
|
||||||
});
|
});
|
||||||
b.require('./test/mocks/FakePayProServer', {
|
|
||||||
expose: './mocks/FakePayProServer'
|
|
||||||
});
|
|
||||||
b.require('./test/mocks/FakePayProServer', {
|
|
||||||
expose: '../../mocks/FakePayProServer'
|
|
||||||
});
|
|
||||||
b.require('./test/mocks/FakeBuilder', {
|
b.require('./test/mocks/FakeBuilder', {
|
||||||
expose: './mocks/FakeBuilder'
|
expose: './mocks/FakeBuilder'
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,11 @@
|
||||||
<div ng-show="btx.showDetails" class="m10t">
|
<div ng-show="btx.showDetails" class="m10t">
|
||||||
<div class="send-note" ng-show="!!btx.merchant">
|
<div class="send-note" ng-show="!!btx.merchant">
|
||||||
<p>
|
<p>
|
||||||
<b>{{btx.merchant.pr.pd.memo}}</b>
|
<b>{{btx.merchant.pr.pd.memo}}</b>
|
||||||
<p>
|
<p>
|
||||||
|
<b>{{btx.paymentAckMemo}}</b>
|
||||||
<span ng-show="tx.merchant.domain">[{{btx.merchant.domain}}]</span>
|
<span ng-show="tx.merchant.domain">[{{btx.merchant.domain}}]</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<table class="last-transactions-content" ng-if="btx.actionList.0">
|
<table class="last-transactions-content" ng-if="btx.actionList.0">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue