mirror of https://github.com/BTCPrivate/copay.git
updates from eordano comments. Better jsdocs some more tests. Still WIP
This commit is contained in:
parent
6462968d5e
commit
abd19e5a96
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');
|
|
||||||
|
|
|
@ -260,7 +260,7 @@ Network.prototype._setupConnectionHandlers = function(opts, cb) {
|
||||||
if (fromTs) {
|
if (fromTs) {
|
||||||
self.ignoreMessageFromTs = fromTs;
|
self.ignoreMessageFromTs = fromTs;
|
||||||
}
|
}
|
||||||
log.info('Async: syncing from: ', fromTs);
|
log.info('Async: synchronizing from: ', fromTs);
|
||||||
self.socket.emit('sync', fromTs);
|
self.socket.emit('sync', fromTs);
|
||||||
self.started = true;
|
self.started = true;
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,8 +36,7 @@ var TX_MAX_INS = 70;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc
|
* @desc
|
||||||
* Wallet manages a private key for Copay, network, storage of the wallet for
|
* Wallet manages a private key for Copay, network and blockchain information.
|
||||||
* persistance, and blockchain information.
|
|
||||||
*
|
*
|
||||||
* @TODO: Split this leviathan.
|
* @TODO: Split this leviathan.
|
||||||
*
|
*
|
||||||
|
@ -133,13 +132,13 @@ Wallet.prototype.emitAndKeepAlive = function(args) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @TODO: Document this. Its usage is kind of weird
|
* @desc Fixed & Forced TransactionBuilder options, for genereration transactions.
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
* @property lockTime
|
* @property lockTime null
|
||||||
* @property signhash
|
* @property signhash SIGHASH
|
||||||
* @property fee
|
* @property fee null (automatic)
|
||||||
* @property feeSat
|
* @property feeSat null
|
||||||
*/
|
*/
|
||||||
Wallet.builderOpts = {
|
Wallet.builderOpts = {
|
||||||
lockTime: null,
|
lockTime: null,
|
||||||
|
@ -468,6 +467,15 @@ Wallet.prototype._checkIfTxProposalIsSent = function(ntxid, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _processTxProposalPayPro
|
||||||
|
*
|
||||||
|
* @desc Process and incoming PayPro TX Proposal. Fetchs the payment request
|
||||||
|
* from the merchant.
|
||||||
|
*
|
||||||
|
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
|
||||||
|
* @return {fetchPaymentRequestCallback}
|
||||||
|
*/
|
||||||
Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) {
|
Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var txp = mergeInfo.txp;
|
var txp = mergeInfo.txp;
|
||||||
|
@ -494,6 +502,14 @@ Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _processIncomingTxProposal
|
||||||
|
*
|
||||||
|
* @desc Process an incoming transaction proposal. Runs safety and sanity checks on it.
|
||||||
|
*
|
||||||
|
* @param mergeInfo Proposals merge information, as returned by TxProposals.merge
|
||||||
|
* @return {errCallback}
|
||||||
|
*/
|
||||||
Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) {
|
Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) {
|
||||||
if (!mergeInfo) return cb();
|
if (!mergeInfo) return cb();
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -617,7 +633,8 @@ Wallet.prototype._onAddressBook = function(senderId, data) {
|
||||||
if (!data.addressBook || !_.isObject(data.addressBook))
|
if (!data.addressBook || !_.isObject(data.addressBook))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var self = this, hasChange;
|
var self = this,
|
||||||
|
hasChange;
|
||||||
_.each(data.addressBook, function(value, key) {
|
_.each(data.addressBook, function(value, key) {
|
||||||
if (!self.addressBook[key] && Address.validate(key)) {
|
if (!self.addressBook[key] && Address.validate(key)) {
|
||||||
|
|
||||||
|
@ -629,7 +646,6 @@ Wallet.prototype._onAddressBook = function(senderId, data) {
|
||||||
hasChange = true;
|
hasChange = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('[Wallet.js.635:hasChange:]',hasChange); //TODO
|
|
||||||
|
|
||||||
if (hasChange) {
|
if (hasChange) {
|
||||||
this.emitAndKeepAlive('addressBookUpdated');
|
this.emitAndKeepAlive('addressBookUpdated');
|
||||||
|
@ -675,7 +691,6 @@ Wallet.prototype._onNoMessages = function() {
|
||||||
* @emits corrupt
|
* @emits corrupt
|
||||||
*/
|
*/
|
||||||
Wallet.prototype._onData = function(senderId, data, ts) {
|
Wallet.prototype._onData = function(senderId, data, ts) {
|
||||||
console.log('[Wallet.js.533]0', this.txProposals.txps); //TODO
|
|
||||||
preconditions.checkArgument(senderId);
|
preconditions.checkArgument(senderId);
|
||||||
preconditions.checkArgument(data);
|
preconditions.checkArgument(data);
|
||||||
preconditions.checkArgument(data.type);
|
preconditions.checkArgument(data.type);
|
||||||
|
@ -1159,13 +1174,13 @@ Wallet.fromObj = function(o, readOpts) {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc sendToPeers a message to other peers
|
* @desc sends a message to peers
|
||||||
* @param {string[]} recipients - the pubkey of the recipients of the message
|
* @param {string[]} recipients - the pubkey of the recipients of the message. Null for sending to all peers.
|
||||||
* @param {Object} obj - the data to be sent to them
|
* @param {Object} obj - the data to be sent to them.
|
||||||
|
* @param {String} obj.type - Type of the message to be send
|
||||||
*/
|
*/
|
||||||
Wallet.prototype._sendToPeers = function(recipients, obj) {
|
Wallet.prototype._sendToPeers = function(recipients, obj) {
|
||||||
if (!this.isShared()) return;
|
if (!this.isShared()) return;
|
||||||
|
|
||||||
log.info('Wallet:' + this.getName() + ' ### Sending ' + obj.type);
|
log.info('Wallet:' + this.getName() + ' ### Sending ' + obj.type);
|
||||||
log.debug('Sending obj', obj);
|
log.debug('Sending obj', obj);
|
||||||
|
|
||||||
|
@ -1173,7 +1188,7 @@ Wallet.prototype._sendToPeers = function(recipients, obj) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Send the set of TxProposals to some peers
|
* @desc Send the set of TxProposals to peers
|
||||||
* @param {string[]} recipients - the pubkeys of the recipients
|
* @param {string[]} recipients - the pubkeys of the recipients
|
||||||
*/
|
*/
|
||||||
Wallet.prototype.sendAllTxProposals = function(recipients, sinceTs) {
|
Wallet.prototype.sendAllTxProposals = function(recipients, sinceTs) {
|
||||||
|
@ -1243,8 +1258,6 @@ Wallet.prototype.sendWalletReady = function(recipients, sinceTs) {
|
||||||
* @param {string[]} [recipients] - the pubkeys of the recipients
|
* @param {string[]} [recipients] - the pubkeys of the recipients
|
||||||
*/
|
*/
|
||||||
Wallet.prototype.sendWalletId = function(recipients) {
|
Wallet.prototype.sendWalletId = function(recipients) {
|
||||||
log.debug('Wallet:' + this.id + ' ### SENDING walletId TO:', recipients || 'All', this.id);
|
|
||||||
|
|
||||||
this._sendToPeers(recipients, {
|
this._sendToPeers(recipients, {
|
||||||
type: 'walletId',
|
type: 'walletId',
|
||||||
walletId: this.id,
|
walletId: this.id,
|
||||||
|
@ -1454,12 +1467,25 @@ Wallet.prototype.reject = function(ntxid) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Sign a proposal
|
* @callback signCallback
|
||||||
|
* @param {Error} error if any
|
||||||
|
* @param {number} Transaction ID or Transaction Proposal ID
|
||||||
|
* @param {status} Wallet.TX_* Status:
|
||||||
|
*
|
||||||
|
* TX_BROADCASTED
|
||||||
|
* TX_SIGNED
|
||||||
|
* TX_PROPOSAL_SENT
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Signs a proposal
|
||||||
* @param {string} ntxid the id of the transaction proposal to sign
|
* @param {string} ntxid the id of the transaction proposal to sign
|
||||||
* @emits txProposalsUpdated
|
* @emits txProposalsUpdated
|
||||||
* @throws {Error} Could not sign proposal
|
* @throws {Error} Could not sign proposal
|
||||||
* @throws {Error} Bad payment request
|
* @throws {Error} Bad payment request
|
||||||
* @return {boolean} true if signing actually incremented the number of signatures
|
* @return {boolean} true if signing actually incremented the number of signatures
|
||||||
|
* @emits txProposalsUpdated
|
||||||
*/
|
*/
|
||||||
Wallet.prototype.sign = function(ntxid) {
|
Wallet.prototype.sign = function(ntxid) {
|
||||||
preconditions.checkState(!_.isUndefined(this.getMyCopayerId()));
|
preconditions.checkState(!_.isUndefined(this.getMyCopayerId()));
|
||||||
|
@ -1476,6 +1502,17 @@ Wallet.prototype.sign = function(ntxid) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @desc signs and send or broadcast a transaction.
|
||||||
|
* In m-n wallets,
|
||||||
|
* if m==1 it will broadcast it to the Bitcoin Network
|
||||||
|
* if n>1 it will send the proposal to the peers
|
||||||
|
*
|
||||||
|
* @param ntxid Transaction Proposal Id
|
||||||
|
* @param {signCallback} cb
|
||||||
|
* @throws {Error} Could not sign proposal
|
||||||
|
*/
|
||||||
Wallet.prototype.signAndSend = function(ntxid, cb) {
|
Wallet.prototype.signAndSend = function(ntxid, cb) {
|
||||||
if (this.sign(ntxid)) {
|
if (this.sign(ntxid)) {
|
||||||
var txp = this.txProposals.get(ntxid);
|
var txp = this.txProposals.get(ntxid);
|
||||||
|
@ -1483,7 +1520,7 @@ Wallet.prototype.signAndSend = function(ntxid, cb) {
|
||||||
return this.broadcastTx(ntxid, cb);
|
return this.broadcastTx(ntxid, cb);
|
||||||
} else {
|
} else {
|
||||||
this.sendTxProposal(ntxid);
|
this.sendTxProposal(ntxid);
|
||||||
return cb(null, ntxid, Wallet.TX_SIGNED );
|
return cb(null, ntxid, Wallet.TX_SIGNED);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return cb(new Error('Could not sign the proposal'));
|
return cb(new Error('Could not sign the proposal'));
|
||||||
|
@ -1493,9 +1530,8 @@ Wallet.prototype.signAndSend = function(ntxid, cb) {
|
||||||
/**
|
/**
|
||||||
* @desc Broadcasts a transaction to the blockchain
|
* @desc Broadcasts a transaction to the blockchain
|
||||||
* @param {string} ntxid - the transaction proposal id
|
* @param {string} ntxid - the transaction proposal id
|
||||||
* @param {broadcastCallback} cb
|
|
||||||
* @callback broadcastCallback
|
|
||||||
* @param {string} txid - the transaction id on the blockchain
|
* @param {string} txid - the transaction id on the blockchain
|
||||||
|
* @param {signCallback} cb
|
||||||
*/
|
*/
|
||||||
Wallet.prototype.broadcastTx = function(ntxid, cb) {
|
Wallet.prototype.broadcastTx = function(ntxid, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -1505,32 +1541,36 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) {
|
||||||
if (!tx.isComplete())
|
if (!tx.isComplete())
|
||||||
throw new Error('Tx is not complete. Can not broadcast');
|
throw new Error('Tx is not complete. Can not broadcast');
|
||||||
|
|
||||||
|
|
||||||
var serializedTx = tx.serialize();
|
|
||||||
|
|
||||||
log.info('Wallet:' + this.id + ' Broadcasting Transaction ntxid:' + ntxid);
|
log.info('Wallet:' + this.id + ' Broadcasting Transaction ntxid:' + ntxid);
|
||||||
|
|
||||||
var txHex = serializedTx.toString('hex');
|
var txHex = tx.serialize().toString('hex');
|
||||||
log.debug('\tRaw transaction: ', txHex);
|
log.debug('\tRaw transaction: ', txHex);
|
||||||
|
|
||||||
this.blockchain.broadcast(txHex, function(err, txid) {
|
this.blockchain.broadcast(txHex, function(err, txid) {
|
||||||
if (err)
|
if (err) {
|
||||||
log.error('Error sending TX:', err);
|
log.error('Error sending TX:' + err);
|
||||||
|
return cb(err);;
|
||||||
|
}
|
||||||
|
|
||||||
if (txid) {
|
if (txid) {
|
||||||
log.debug('Wallet:' + self.getName() + ' broadcasted a TX. BITCOIND txid:', txid);
|
log.debug('Wallet:' + self.getName() + ' broadcasted a TX. BITCOIND txid:', txid);
|
||||||
|
|
||||||
txp.setSent(txid);
|
txp.setSent(txid);
|
||||||
self.sendTxProposal(ntxid);
|
|
||||||
self.emitAndKeepAlive('txProposalsUpdated');
|
|
||||||
|
|
||||||
// PAYPRO: Payment message is optional, only if payment_url is set
|
// PAYPRO: Payment message is optional, only if payment_url is set
|
||||||
// This is async. and will notify and update txp async.
|
// This is async. and will notify and update txp async.
|
||||||
if (txp.merchant && txp.merchant.pr.pd.payment_url) {
|
if (txp.merchant && txp.merchant.pr.pd.payment_url) {
|
||||||
self.sendPaymentTx(ntxid, serializedTx);
|
var data = this.createPayProPayment(txp);
|
||||||
|
self.sendPayProPayment(txp, data, function(err, data) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
self.onPayProPaymentAck(ntxid, data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.sendTxProposal(ntxid);
|
||||||
|
self.emitAndKeepAlive('txProposalsUpdated');
|
||||||
return cb(null, txid, Wallet.TX_BROADCASTED);
|
return cb(null, txid, Wallet.TX_BROADCASTED);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already');
|
log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already');
|
||||||
self._checkIfTxProposalIsSent(ntxid, cb);
|
self._checkIfTxProposalIsSent(ntxid, cb);
|
||||||
|
@ -1538,11 +1578,18 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Create a Payment Protocol transaction
|
* @callback {fetchPaymentRequestCallback}
|
||||||
|
* @param {string=} err - an error, if any
|
||||||
|
* @param {Object} merchantData - object representing the payment request. Add described on BIP70 merchant_data
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Creates a Payment Protocol transaction
|
||||||
* @param {Object|string} options - if it's a string, parse it as the url
|
* @param {Object|string} options - if it's a string, parse it as the url
|
||||||
* @param {string} options.url the url for the transaction
|
* @param {string} options.url the url for the transaction
|
||||||
* @param {Function} cb
|
* @return {fetchPaymentRequestCallback} cb
|
||||||
*/
|
*/
|
||||||
Wallet.prototype.fetchPaymentRequest = function(options, cb) {
|
Wallet.prototype.fetchPaymentRequest = function(options, cb) {
|
||||||
preconditions.checkArgument(_.isObject(options));
|
preconditions.checkArgument(_.isObject(options));
|
||||||
|
@ -1578,24 +1625,18 @@ Wallet.prototype.fetchPaymentRequest = function(options, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* addOutputsToMerchantData
|
|
||||||
*
|
|
||||||
* NOTE: We use to: set the TX scripts with the payment request scripts:
|
|
||||||
* but this is a hack around transaction builder, so we dont do it anymore.
|
|
||||||
* See Readme.md. For now we only support p2scripthash or p2pubkeyhash
|
|
||||||
merchantData.pr.pd.outputs.forEach(function(output, i) {
|
|
||||||
var script = {
|
|
||||||
offset: output.script.offset,
|
|
||||||
limit: output.script.limit,
|
|
||||||
buffer: new Buffer(output.script.buffer, 'hex')
|
|
||||||
};
|
|
||||||
var s = script.buffer.slice(script.offset, script.limit);
|
|
||||||
b.tx.outs[i].s = s;
|
|
||||||
});
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _addOutputsToMerchantData
|
||||||
|
*
|
||||||
|
* @desc parses merchant_data internal output representation and stores
|
||||||
|
* the result in merchant_data.outs = [{address: xx, amountSatStr: xx}],
|
||||||
|
* to be compatible with TransactionBuilder.
|
||||||
|
*`
|
||||||
|
* @param merchantData BIP70 merchant_data (from the payment request)
|
||||||
|
* @throws {Error} PayPro: Unsupported inputs
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
Wallet.prototype._addOutputsToMerchantData = function(merchantData) {
|
Wallet.prototype._addOutputsToMerchantData = function(merchantData) {
|
||||||
|
|
||||||
var total = bignum(0);
|
var total = bignum(0);
|
||||||
|
@ -1737,7 +1778,7 @@ Wallet.prototype.parsePaymentRequest = function(options, rawData) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _getPayProRefundOutputs
|
* _getPayProRefundOutputs
|
||||||
* Create refund address for PayPro.
|
* Create refund outputs for a PayPro Payment Message
|
||||||
* Uses current transaction's change address.
|
* Uses current transaction's change address.
|
||||||
*
|
*
|
||||||
* @param txp
|
* @param txp
|
||||||
|
@ -1750,13 +1791,23 @@ Wallet.prototype._getPayProRefundOutputs = function(txp) {
|
||||||
|
|
||||||
var output = new PayPro.Output();
|
var output = new PayPro.Output();
|
||||||
var script = pkr.getScriptPubKeyHex(index.changeIndex, true, this.pubkey);
|
var script = pkr.getScriptPubKeyHex(index.changeIndex, true, this.pubkey);
|
||||||
output.set('script',new Buffer(script, 'hex'));
|
output.set('script', new Buffer(script, 'hex'));
|
||||||
output.set('amount', amount);
|
output.set('amount', amount);
|
||||||
return [output];
|
return [output];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Wallet.prototype._createPaymentTx = function(txp, txHex) {
|
/**
|
||||||
|
*
|
||||||
|
* @desc Creates a Payment Protocol Payment message for the given TX Proposal
|
||||||
|
* @param txp Transaction Proposal
|
||||||
|
* @param txHex
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
Wallet.prototype.createPayProPayment = function(txp) {
|
||||||
|
|
||||||
|
var tx = txp.builder.build();
|
||||||
|
var txBuf = tx.serialize();
|
||||||
|
|
||||||
var refund_outputs = this._getPayProRefundOutputs(txp);
|
var refund_outputs = this._getPayProRefundOutputs(txp);
|
||||||
|
|
||||||
|
@ -1770,7 +1821,7 @@ Wallet.prototype._createPaymentTx = function(txp, txHex) {
|
||||||
pay.set('merchant_data', merchant_data);
|
pay.set('merchant_data', merchant_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
pay.set('transactions', [txHex]);
|
pay.set('transactions', [txBuf]);
|
||||||
pay.set('refund_to', refund_outputs);
|
pay.set('refund_to', refund_outputs);
|
||||||
|
|
||||||
// Unused for now
|
// Unused for now
|
||||||
|
@ -1787,18 +1838,39 @@ Wallet.prototype._createPaymentTx = function(txp, txHex) {
|
||||||
return view;
|
return view;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Send a payment transaction to a server, complying with BIP70
|
* onPayProPaymentAck
|
||||||
*
|
*
|
||||||
* @param {string} ntxid - the transaction proposal id
|
* @desc parse and process a Payment Protocol Payment Ack. Updates
|
||||||
* @param {Function} txHex
|
* given TX Proposal with merchant's memo and send it to copayers
|
||||||
*
|
*
|
||||||
* emits paymentACK(server's memo)
|
* @param ntxid ID of the Transaction Proposal
|
||||||
|
* @param rawData of the Payment Ack
|
||||||
|
* @emits paymentACK - (merchants's memo)
|
||||||
*/
|
*/
|
||||||
Wallet.prototype.sendPaymentTx = function(ntxid, txHex) {
|
Wallet.prototype.onPayProPaymentAck = function(ntxid, rawData) {
|
||||||
|
var data = PayPro.PaymentACK.decode(rawData);
|
||||||
|
var paypro = new PayPro();
|
||||||
|
var ack = paypro.makePaymentACK(data);
|
||||||
|
var memo = ack.get('memo');
|
||||||
|
log.debug('Payment Acknowledged!: %s', memo);
|
||||||
|
txp.paymentAckMemo = memo;
|
||||||
|
self.sendTxProposal(ntxid);
|
||||||
|
self.emitAndKeepAlive('paymentACK', memo);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Send a payment transaction to a merchant, complying with BIP70
|
||||||
|
* on Acknoledge, updates the TX Proposal with server's memo and send it
|
||||||
|
* to peers
|
||||||
|
*
|
||||||
|
* @param {string} ntxid - the transaction proposal ID for with the
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Wallet.prototype.sendPayProPayment = function(txp, data, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var txp = this.txProposals.get(ntxid);
|
|
||||||
var data = this._createPaymentTx(txp, txHex);
|
|
||||||
|
|
||||||
log.debug('Sending Payment Message to merchant server');
|
log.debug('Sending Payment Message to merchant server');
|
||||||
var postInfo = {
|
var postInfo = {
|
||||||
|
@ -1818,19 +1890,13 @@ Wallet.prototype.sendPaymentTx = function(ntxid, txHex) {
|
||||||
responseType: 'arraybuffer'
|
responseType: 'arraybuffer'
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.httpUtil.request(postInfo)
|
this.httpUtil.request(postInfo)
|
||||||
.success(function(rawData) {
|
.success(function(rawData) {
|
||||||
var data = PayPro.PaymentACK.decode(rawData);
|
return cb(null, rawData);
|
||||||
var paypro = new PayPro();
|
|
||||||
var ack = paypro.makePaymentACK(data);
|
|
||||||
var memo = ack.get('memo');
|
|
||||||
log.debug('Payment Acknowledged!: %s', memo);
|
|
||||||
txp.paymentAckMemo = memo;
|
|
||||||
self.sendTxProposal(ntxid);
|
|
||||||
self.emitAndKeepAlive('paymentACK', memo);
|
|
||||||
})
|
})
|
||||||
.error(function(data, status) {
|
.error(function(data, status) {
|
||||||
log.error('Sending payment notification: XHR status: ' + status);
|
log.error('Sending payment notification: XHR status: ' + status);
|
||||||
|
return cb(new Error(status));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1899,7 +1965,7 @@ Wallet.prototype.addressIsOwn = function(addrStr) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimate a tx fee in satoshis given its input count
|
* Estimate a tx fee in satoshis given its input count
|
||||||
* only for spending all wallet funds
|
* (only used when spending all wallet funds)
|
||||||
*/
|
*/
|
||||||
Wallet.estimatedFee = function(unspentCount) {
|
Wallet.estimatedFee = function(unspentCount) {
|
||||||
preconditions.checkArgument(_.isNumber(unspentCount));
|
preconditions.checkArgument(_.isNumber(unspentCount));
|
||||||
|
@ -2071,6 +2137,7 @@ Wallet.prototype.spend = function(opts, cb) {
|
||||||
var comment = opts.comment;
|
var comment = opts.comment;
|
||||||
var url = opts.url;
|
var url = opts.url;
|
||||||
|
|
||||||
|
// PayPro? Fetch payment data and recurse
|
||||||
if (url && !opts.merchantData) {
|
if (url && !opts.merchantData) {
|
||||||
return self.fetchPaymentRequest({
|
return self.fetchPaymentRequest({
|
||||||
url: url,
|
url: url,
|
||||||
|
@ -2083,7 +2150,8 @@ Wallet.prototype.spend = function(opts, cb) {
|
||||||
opts.amountSat = parseInt(merchantData.outs[0].amountSatStr);
|
opts.amountSat = parseInt(merchantData.outs[0].amountSatStr);
|
||||||
return self.spend(opts, cb);
|
return self.spend(opts, cb);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
preconditions.checkArgument(amountSat, 'no amount');
|
preconditions.checkArgument(amountSat, 'no amount');
|
||||||
preconditions.checkArgument(toAddress, 'no address');
|
preconditions.checkArgument(toAddress, 'no address');
|
||||||
|
|
||||||
|
@ -2213,7 +2281,7 @@ Wallet.prototype._createTxProposal = function(toAddress, amountSat, comment, utx
|
||||||
var tx = b.build();
|
var tx = b.build();
|
||||||
var myId = this.getMyCopayerId();
|
var myId = this.getMyCopayerId();
|
||||||
var keys = priv.getForPaths(inputChainPaths);
|
var keys = priv.getForPaths(inputChainPaths);
|
||||||
return new TxProposal({
|
return new TxProposal({
|
||||||
inputChainPaths: inputChainPaths,
|
inputChainPaths: inputChainPaths,
|
||||||
comment: comment,
|
comment: comment,
|
||||||
builder: b,
|
builder: b,
|
||||||
|
|
259
test/Wallet.js
259
test/Wallet.js
|
@ -767,7 +767,10 @@ describe('Wallet model', function() {
|
||||||
'confirmations': 10,
|
'confirmations': 10,
|
||||||
'confirmationsFromCache': false
|
'confirmationsFromCache': false
|
||||||
}];
|
}];
|
||||||
|
sinon.stub(w, 'sendIndexes');
|
||||||
var addr = w.generateAddress().toString();
|
var addr = w.generateAddress().toString();
|
||||||
|
w.sendIndexes.restore();
|
||||||
|
|
||||||
utxo[0].address = addr;
|
utxo[0].address = addr;
|
||||||
utxo[0].scriptPubKey = (new bitcore.Address(addr)).getScriptPubKey().serialize().toString('hex');
|
utxo[0].scriptPubKey = (new bitcore.Address(addr)).getScriptPubKey().serialize().toString('hex');
|
||||||
return utxo;
|
return utxo;
|
||||||
|
@ -775,124 +778,154 @@ describe('Wallet model', function() {
|
||||||
var toAddress = 'mjfAe7YrzFujFf8ub5aUrCaN5GfSABdqjh';
|
var toAddress = 'mjfAe7YrzFujFf8ub5aUrCaN5GfSABdqjh';
|
||||||
var amountSatStr = '10000';
|
var amountSatStr = '10000';
|
||||||
|
|
||||||
it('should create transaction', function(done) {
|
describe('#spend', function() {
|
||||||
var w = cachedCreateW2();
|
it('should create transaction', function(done) {
|
||||||
var utxo = createUTXO(w);
|
var w = cachedCreateW2();
|
||||||
w.blockchain.fixUnspent(utxo);
|
var utxo = createUTXO(w);
|
||||||
w.spend({
|
w.blockchain.fixUnspent(utxo);
|
||||||
toAddress: toAddress,
|
w.spend({
|
||||||
amountSat: amountSatStr,
|
toAddress: toAddress,
|
||||||
}, function(err, ntxid) {
|
amountSat: amountSatStr,
|
||||||
ntxid.length.should.equal(64);
|
}, function(err, ntxid) {
|
||||||
done();
|
ntxid.length.should.equal(64);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create & sign transaction from received funds', function(done) {
|
|
||||||
var k2 = new PrivateKey({
|
|
||||||
networkName: walletConfig.networkName
|
|
||||||
});
|
|
||||||
|
|
||||||
var w = createW2([k2]);
|
|
||||||
var utxo = createUTXO(w);
|
|
||||||
w.blockchain.fixUnspent(utxo);
|
|
||||||
w.spend({
|
|
||||||
toAddress: toAddress,
|
|
||||||
amountSat: amountSatStr,
|
|
||||||
}, function(err, ntxid) {
|
|
||||||
w.on('txProposalsUpdated', function() {
|
|
||||||
w.getTxProposals()[0].signedByUs.should.equal(true);
|
|
||||||
w.getTxProposals()[0].rejectedByUs.should.equal(false);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
w.privateKey = k2;
|
|
||||||
w.sign(ntxid).should.equal.true;
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
it('should fail to reject a signed transaction', function() {
|
it('should create & sign transaction from received funds', function(done) {
|
||||||
var w = cachedCreateW2();
|
var k2 = new PrivateKey({
|
||||||
var utxo = createUTXO(w);
|
networkName: walletConfig.networkName
|
||||||
w.blockchain.fixUnspent(utxo);
|
});
|
||||||
w.spend({
|
|
||||||
toAddress: toAddress,
|
var w = createW2([k2]);
|
||||||
amountSat: amountSatStr,
|
var utxo = createUTXO(w);
|
||||||
}, function(err, ntxid) {
|
w.blockchain.fixUnspent(utxo);
|
||||||
(function() {
|
w.spend({
|
||||||
|
toAddress: toAddress,
|
||||||
|
amountSat: amountSatStr,
|
||||||
|
}, function(err, ntxid) {
|
||||||
|
w.on('txProposalsUpdated', function() {
|
||||||
|
w.getTxProposals()[0].signedByUs.should.equal(true);
|
||||||
|
w.getTxProposals()[0].rejectedByUs.should.equal(false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
w.privateKey = k2;
|
||||||
|
w.sign(ntxid).should.equal.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should fail to reject a signed transaction', function() {
|
||||||
|
var w = cachedCreateW2();
|
||||||
|
var utxo = createUTXO(w);
|
||||||
|
w.blockchain.fixUnspent(utxo);
|
||||||
|
w.spend({
|
||||||
|
toAddress: toAddress,
|
||||||
|
amountSat: amountSatStr,
|
||||||
|
}, function(err, ntxid) {
|
||||||
|
(function() {
|
||||||
|
w.reject(ntxid);
|
||||||
|
}).should.throw('reject a signed');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create & reject transaction', function(done) {
|
||||||
|
var w = cachedCreateW2();
|
||||||
|
var oldK = w.privateKey;
|
||||||
|
var utxo = createUTXO(w);
|
||||||
|
w.blockchain.fixUnspent(utxo);
|
||||||
|
w.spend({
|
||||||
|
toAddress: toAddress,
|
||||||
|
amountSat: amountSatStr,
|
||||||
|
}, function(err, ntxid) {
|
||||||
|
var s = sinon.stub(w, 'getMyCopayerId').returns('213');
|
||||||
|
Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(0);
|
||||||
w.reject(ntxid);
|
w.reject(ntxid);
|
||||||
}).should.throw('reject a signed');
|
Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(1);
|
||||||
|
w.txProposals.get(ntxid).rejectedBy['213'].should.gt(1);
|
||||||
|
s.restore();
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
it('should fail to send incomplete transaction', function(done) {
|
||||||
|
var w = createW2(null, 1);
|
||||||
|
var utxo = createUTXO(w);
|
||||||
|
w.blockchain.fixUnspent(utxo);
|
||||||
|
|
||||||
it('should create & reject transaction', function(done) {
|
// TODO in this test, txp should be created with createTxProposal
|
||||||
var w = cachedCreateW2();
|
w.spend({
|
||||||
var oldK = w.privateKey;
|
toAddress: toAddress,
|
||||||
var utxo = createUTXO(w);
|
amountSat: amountSatStr,
|
||||||
w.blockchain.fixUnspent(utxo);
|
}, function(err, ntxid) {
|
||||||
w.spend({
|
var txp = w.txProposals.get(ntxid);
|
||||||
toAddress: toAddress,
|
// Assign fake builder
|
||||||
amountSat: amountSatStr,
|
txp.builder = new Builder();
|
||||||
}, function(err, ntxid) {
|
sinon.stub(txp.builder, 'build').returns({
|
||||||
var s = sinon.stub(w, 'getMyCopayerId').returns('213');
|
isComplete: function() {
|
||||||
Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(0);
|
return false;
|
||||||
w.reject(ntxid);
|
}
|
||||||
Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(1);
|
});
|
||||||
w.txProposals.get(ntxid).rejectedBy['213'].should.gt(1);
|
(function() {
|
||||||
s.restore();
|
w.broadcastTx(ntxid);
|
||||||
done();
|
}).should.throw('Tx is not complete. Can not broadcast');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
it('should send a TX proposal to peers if incomplete', function(done) {
|
||||||
it('should create & sign & send a transaction', function(done) {
|
var w = createW2(null, 1);
|
||||||
var w = createW2(null, 1);
|
var utxo = createUTXO(w);
|
||||||
var utxo = createUTXO(w);
|
w.blockchain.fixUnspent(utxo);
|
||||||
w.blockchain.fixUnspent(utxo);
|
|
||||||
w.spend({
|
sinon.spy(w, 'sendIndexes');
|
||||||
toAddress: toAddress,
|
sinon.spy(w, 'sendTxProposal');
|
||||||
amountSat: amountSatStr,
|
w.spend({
|
||||||
}, function(err, ntxid) {
|
toAddress: toAddress,
|
||||||
w.broadcastTx(ntxid, function(err, txid, status) {
|
amountSat: amountSatStr,
|
||||||
|
}, function(err, id, status) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
txid.length.should.equal(64);
|
should.exist(id);
|
||||||
|
status.should.equal(Wallet.TX_PROPOSAL_SENT);
|
||||||
|
w.sendTxProposal.calledOnce.should.equal(true);
|
||||||
|
w.sendIndexes.calledOnce.should.equal(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should broadcast a TX if complete', function(done) {
|
||||||
|
var w = createW2(null, 1);
|
||||||
|
var utxo = createUTXO(w);
|
||||||
|
w.blockchain.fixUnspent(utxo);
|
||||||
|
sinon.spy(w, 'sendIndexes');
|
||||||
|
sinon.spy(w, 'sendTxProposal');
|
||||||
|
sinon.spy(w, 'broadcastTx');
|
||||||
|
sinon.stub(w, 'requiresMultipleSignatures').returns(false);
|
||||||
|
w.spend({
|
||||||
|
toAddress: toAddress,
|
||||||
|
amountSat: amountSatStr,
|
||||||
|
}, function(err, id, status) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(id);
|
||||||
status.should.equal(Wallet.TX_BROADCASTED);
|
status.should.equal(Wallet.TX_BROADCASTED);
|
||||||
|
w.sendTxProposal.calledOnce.should.equal(true);
|
||||||
|
w.sendIndexes.calledOnce.should.equal(true);
|
||||||
|
w.broadcastTx.calledOnce.should.equal(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should fail to send incomplete transaction', function(done) {
|
|
||||||
|
it('should return error if failing to send', function(done) {
|
||||||
var w = createW2(null, 1);
|
var w = createW2(null, 1);
|
||||||
var utxo = createUTXO(w);
|
var utxo = createUTXO(w);
|
||||||
w.blockchain.fixUnspent(utxo);
|
w.blockchain.fixUnspent(utxo);
|
||||||
|
sinon.stub(w, 'requiresMultipleSignatures').returns(false);
|
||||||
|
sinon.spy(w, 'sendIndexes');
|
||||||
|
sinon.spy(w, 'sendTxProposal');
|
||||||
|
sinon.stub(w.blockchain, 'broadcast').yields('error');
|
||||||
w.spend({
|
w.spend({
|
||||||
toAddress: toAddress,
|
toAddress: toAddress,
|
||||||
amountSat: amountSatStr,
|
amountSat: amountSatStr,
|
||||||
}, function(err, ntxid) {
|
}, function(err, id, status) {
|
||||||
var txp = w.txProposals.get(ntxid);
|
err.should.equal('error');
|
||||||
// Assign fake builder
|
w.sendTxProposal.calledOnce.should.equal(false);
|
||||||
txp.builder = new Builder();
|
w.sendIndexes.calledOnce.should.equal(true);
|
||||||
sinon.stub(txp.builder, 'build').returns({
|
|
||||||
isComplete: function() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
(function() {
|
|
||||||
w.broadcastTx(ntxid);
|
|
||||||
}).should.throw('Tx is not complete. Can not broadcast');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('should check if transaction already sent when failing to send', function(done) {
|
|
||||||
var w = createW2(null, 1);
|
|
||||||
var utxo = createUTXO(w);
|
|
||||||
w.blockchain.fixUnspent(utxo);
|
|
||||||
w.spend({
|
|
||||||
toAddress: toAddress,
|
|
||||||
amountSat: amountSatStr,
|
|
||||||
}, function(err, ntxid) {
|
|
||||||
sinon.stub(w.blockchain, 'broadcast').yields({
|
|
||||||
statusCode: 303
|
|
||||||
});
|
|
||||||
var spyCheckSentTx = sinon.spy(w, '_checkSentTx');
|
|
||||||
w.broadcastTx(ntxid, function() {});
|
|
||||||
chai.expect(spyCheckSentTx.calledOnce).to.be.true;
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -940,19 +973,19 @@ describe('Wallet model', function() {
|
||||||
url: 'http://xxx',
|
url: 'http://xxx',
|
||||||
};
|
};
|
||||||
|
|
||||||
var rawData ='wqer';
|
var rawData = 'wqer';
|
||||||
var e = sinon.stub();
|
var e = sinon.stub();
|
||||||
e.error = sinon.stub();
|
e.error = sinon.stub();
|
||||||
|
|
||||||
var s = sinon.stub();
|
var s = sinon.stub();
|
||||||
s.success = sinon.stub().yields(rawData).returns(e);
|
s.success = sinon.stub().yields(rawData).returns(e);
|
||||||
|
|
||||||
sinon.stub(w.httpUtil,'request').returns(s);
|
sinon.stub(w.httpUtil, 'request').returns(s);
|
||||||
|
|
||||||
w.fetchPaymentRequest(opts, function(err, merchantData){
|
w.fetchPaymentRequest(opts, function(err, merchantData) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(merchantData);
|
should.exist(merchantData);
|
||||||
w.parsePaymentRequest.firstCall.args.should.deep.equal([opts,rawData]);
|
w.parsePaymentRequest.firstCall.args.should.deep.equal([opts, rawData]);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -964,14 +997,14 @@ describe('Wallet model', function() {
|
||||||
url: 'http://xxx',
|
url: 'http://xxx',
|
||||||
};
|
};
|
||||||
|
|
||||||
var rawData ='wqer';
|
var rawData = 'wqer';
|
||||||
var e = sinon.stub();
|
var e = sinon.stub();
|
||||||
e.error = sinon.stub().yields(null, 'status');
|
e.error = sinon.stub().yields(null, 'status');
|
||||||
|
|
||||||
var s = sinon.stub();
|
var s = sinon.stub();
|
||||||
s.success = sinon.stub().returns(e);
|
s.success = sinon.stub().returns(e);
|
||||||
sinon.stub(w.httpUtil,'request').returns(s);
|
sinon.stub(w.httpUtil, 'request').returns(s);
|
||||||
w.fetchPaymentRequest(opts, function(err, merchantData){
|
w.fetchPaymentRequest(opts, function(err, merchantData) {
|
||||||
err.toString().should.contain('status');
|
err.toString().should.contain('status');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -983,13 +1016,13 @@ describe('Wallet model', function() {
|
||||||
// FakePayProServer.getRequest should be parametrizable
|
// FakePayProServer.getRequest should be parametrizable
|
||||||
describe('#parsePaymentRequest', function() {
|
describe('#parsePaymentRequest', function() {
|
||||||
it('should parse a Payment Request', function() {
|
it('should parse a Payment Request', function() {
|
||||||
var now = Date.now()/1000;
|
var now = Date.now() / 1000;
|
||||||
var w = cachedCreateW2();
|
var w = cachedCreateW2();
|
||||||
var opts = {
|
var opts = {
|
||||||
url: 'http://xxx',
|
url: 'http://xxx',
|
||||||
};
|
};
|
||||||
var data = FakePayProServer.getRequest();
|
var data = FakePayProServer.getRequest();
|
||||||
var md = w.parsePaymentRequest(opts,data);
|
var md = w.parsePaymentRequest(opts, data);
|
||||||
md.outs.should.deep.equal(FakePayProServer.outs);
|
md.outs.should.deep.equal(FakePayProServer.outs);
|
||||||
md.request_url.should.equal(opts.url);
|
md.request_url.should.equal(opts.url);
|
||||||
md.pr.untrusted.should.equal(true);
|
md.pr.untrusted.should.equal(true);
|
||||||
|
@ -1158,7 +1191,9 @@ describe('Wallet model', function() {
|
||||||
var w = cachedCreateW2();
|
var w = cachedCreateW2();
|
||||||
var save = w.network.send;
|
var save = w.network.send;
|
||||||
w.network.send = sinon.spy();
|
w.network.send = sinon.spy();
|
||||||
w._sendToPeers(null, {type:'hola'});
|
w._sendToPeers(null, {
|
||||||
|
type: 'hola'
|
||||||
|
});
|
||||||
w.network.send.calledOnce.should.equal(true);
|
w.network.send.calledOnce.should.equal(true);
|
||||||
w.network.send = save;
|
w.network.send = save;
|
||||||
});
|
});
|
||||||
|
@ -2389,6 +2424,12 @@ describe('Wallet model', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe.skip('#onPayProPaymentAck', function() {
|
||||||
|
it('should emit', function() {
|
||||||
|
var w = cachedCreateW2();
|
||||||
|
w.onPayProPaymentAck('id', 'data');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe.skip('#read', function() {
|
describe.skip('#read', function() {
|
||||||
var network, blockchain;
|
var network, blockchain;
|
||||||
|
|
|
@ -131,9 +131,6 @@ var createBundle = function(opts) {
|
||||||
b.require('./test/mocks/FakePayProServer', {
|
b.require('./test/mocks/FakePayProServer', {
|
||||||
expose: './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'
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue