create temporary proposal + test
This commit is contained in:
parent
6deb9e77f6
commit
9db456550f
|
@ -129,7 +129,6 @@ TxProposal.fromObj = function(obj) {
|
||||||
TxProposal.prototype.toObject = function() {
|
TxProposal.prototype.toObject = function() {
|
||||||
var x = _.cloneDeep(this);
|
var x = _.cloneDeep(this);
|
||||||
x.isPending = this.isPending();
|
x.isPending = this.isPending();
|
||||||
x.isTemporary = this.isTemporary();
|
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -375,7 +374,7 @@ TxProposal.prototype.isTemporary = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.isPending = function() {
|
TxProposal.prototype.isPending = function() {
|
||||||
return !_.contains(['broadcasted', 'rejected'], this.status);
|
return !_.contains(['temporary', 'broadcasted', 'rejected'], this.status);
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.isAccepted = function() {
|
TxProposal.prototype.isAccepted = function() {
|
||||||
|
|
|
@ -1147,9 +1147,9 @@ WalletService.prototype._selectTxInputs = function(txp, utxosToExclude, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
WalletService.prototype._canCreateTx = function(copayerId, cb) {
|
WalletService.prototype._canCreateTx = function(cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
self.storage.fetchLastTxs(self.walletId, copayerId, 5 + Defaults.BACKOFF_OFFSET, function(err, txs) {
|
self.storage.fetchLastTxs(self.walletId, self.copayerId, 5 + Defaults.BACKOFF_OFFSET, function(err, txs) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
if (!txs.length)
|
if (!txs.length)
|
||||||
|
@ -1277,7 +1277,7 @@ WalletService.prototype.createTx = function(opts, cb) {
|
||||||
if (!signingKey)
|
if (!signingKey)
|
||||||
return cb(new ClientError('Invalid proposal signature'));
|
return cb(new ClientError('Invalid proposal signature'));
|
||||||
|
|
||||||
self._canCreateTx(self.copayerId, function(err, canCreate) {
|
self._canCreateTx(function(err, canCreate) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!canCreate) return cb(Errors.TX_CANNOT_CREATE);
|
if (!canCreate) return cb(Errors.TX_CANNOT_CREATE);
|
||||||
|
|
||||||
|
@ -1345,6 +1345,95 @@ WalletService.prototype.createTx = function(opts, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new transaction proposal.
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {string} opts.type - Proposal type.
|
||||||
|
* @param {Array} opts.outputs - List of outputs.
|
||||||
|
* @param {string} opts.outputs[].toAddress - Destination address.
|
||||||
|
* @param {number} opts.outputs[].amount - Amount to transfer in satoshi.
|
||||||
|
* @param {string} opts.outputs[].message - A message to attach to this output.
|
||||||
|
* @param {string} opts.message - A message to attach to this transaction.
|
||||||
|
* @param {Array} opts.inputs - Optional. Inputs for this TX
|
||||||
|
* @param {string} opts.feePerKb - Optional. Use an alternative fee per KB for this TX
|
||||||
|
* @param {string} opts.payProUrl - Optional. Paypro URL for peers to verify TX
|
||||||
|
* @param {string} opts.excludeUnconfirmedUtxos - Optional. Do not use UTXOs of unconfirmed transactions as inputs (defaults to false)
|
||||||
|
* @returns {TxProposal} Transaction proposal.
|
||||||
|
*/
|
||||||
|
WalletService.prototype.createTx2 = function(opts, cb) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!Utils.checkRequired(opts, ['outputs']))
|
||||||
|
return cb(new ClientError('Required argument missing'));
|
||||||
|
|
||||||
|
var type = opts.type || Model.TxProposal.Types.STANDARD;
|
||||||
|
if (!Model.TxProposal.isTypeSupported(type))
|
||||||
|
return cb(new ClientError('Invalid proposal type'));
|
||||||
|
|
||||||
|
var feePerKb = opts.feePerKb || Defaults.DEFAULT_FEE_PER_KB;
|
||||||
|
if (feePerKb < Defaults.MIN_FEE_PER_KB || feePerKb > Defaults.MAX_FEE_PER_KB)
|
||||||
|
return cb(new ClientError('Invalid fee per KB value'));
|
||||||
|
|
||||||
|
self._runLocked(cb, function(cb) {
|
||||||
|
self.getWallet({}, function(err, wallet) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
if (!wallet.isComplete()) return cb(Errors.WALLET_NOT_COMPLETE);
|
||||||
|
|
||||||
|
self._canCreateTx(function(err, canCreate) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
if (!canCreate) return cb(Errors.TX_CANNOT_CREATE);
|
||||||
|
|
||||||
|
if (type != Model.TxProposal.Types.EXTERNAL) {
|
||||||
|
var validationError = self._validateOutputs(opts, wallet);
|
||||||
|
if (validationError) {
|
||||||
|
return cb(validationError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var txOpts = {
|
||||||
|
version: 3,
|
||||||
|
type: type,
|
||||||
|
walletId: self.walletId,
|
||||||
|
creatorId: self.copayerId,
|
||||||
|
outputs: opts.outputs,
|
||||||
|
inputs: opts.inputs,
|
||||||
|
message: opts.message,
|
||||||
|
changeAddress: wallet.createAddress(true),
|
||||||
|
feePerKb: feePerKb,
|
||||||
|
payProUrl: opts.payProUrl,
|
||||||
|
requiredSignatures: wallet.m,
|
||||||
|
requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1),
|
||||||
|
walletN: wallet.n,
|
||||||
|
excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos,
|
||||||
|
addressType: wallet.addressType,
|
||||||
|
customData: opts.customData,
|
||||||
|
};
|
||||||
|
|
||||||
|
var txp = Model.TxProposal.create(txOpts);
|
||||||
|
|
||||||
|
self._selectTxInputs(txp, opts.utxosToExclude, function(err) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
self.storage.storeAddressAndWallet(wallet, txp.changeAddress, function(err) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
self.storage.storeTx(wallet.id, txp, function(err) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
self._notify('NewTxProposal', {
|
||||||
|
amount: txp.getTotalAmount()
|
||||||
|
}, function() {
|
||||||
|
return cb(null, txp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a tx from storage.
|
* Retrieves a tx from storage.
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
|
|
|
@ -227,7 +227,7 @@ Storage.prototype.fetchPendingTxs = function(walletId, cb) {
|
||||||
|
|
||||||
this.db.collection(collections.TXS).find({
|
this.db.collection(collections.TXS).find({
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
isPending: true
|
isPending: true,
|
||||||
}).sort({
|
}).sort({
|
||||||
createdOn: -1
|
createdOn: -1
|
||||||
}).toArray(function(err, result) {
|
}).toArray(function(err, result) {
|
||||||
|
|
|
@ -371,6 +371,30 @@ helpers.createExternalProposalOpts = function(toAddress, amount, signingKey, mor
|
||||||
return helpers.createProposalOpts(Model.TxProposalLegacy.Types.EXTERNAL, outputs, signingKey, moreOpts, inputs);
|
return helpers.createProposalOpts(Model.TxProposalLegacy.Types.EXTERNAL, outputs, signingKey, moreOpts, inputs);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
helpers.createStandardProposalOpts = function(outputs, moreOpts, inputs) {
|
||||||
|
_.each(outputs, function(output) {
|
||||||
|
output.amount = helpers.toSatoshi(output.amount);
|
||||||
|
});
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
type: Model.TxProposal.Types.STANDARD,
|
||||||
|
outputs: outputs,
|
||||||
|
inputs: inputs || [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (moreOpts) {
|
||||||
|
moreOpts = _.pick(moreOpts, ['feePerKb', 'customData', 'message']);
|
||||||
|
opts = _.assign(opts, moreOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = _.defaults(opts, {
|
||||||
|
message: null
|
||||||
|
});
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
};
|
||||||
|
|
||||||
helpers.createProposalOpts = function(type, outputs, signingKey, moreOpts, inputs) {
|
helpers.createProposalOpts = function(type, outputs, signingKey, moreOpts, inputs) {
|
||||||
_.each(outputs, function(output) {
|
_.each(outputs, function(output) {
|
||||||
output.amount = helpers.toSatoshi(output.amount);
|
output.amount = helpers.toSatoshi(output.amount);
|
||||||
|
@ -383,9 +407,7 @@ helpers.createProposalOpts = function(type, outputs, signingKey, moreOpts, input
|
||||||
};
|
};
|
||||||
|
|
||||||
if (moreOpts) {
|
if (moreOpts) {
|
||||||
moreOpts = _.chain(moreOpts)
|
moreOpts = _.pick(moreOpts, ['feePerKb', 'customData', 'message']);
|
||||||
.pick(['feePerKb', 'customData', 'message'])
|
|
||||||
.value();
|
|
||||||
opts = _.assign(opts, moreOpts);
|
opts = _.assign(opts, moreOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,7 +437,6 @@ helpers.createProposalOpts = function(type, outputs, signingKey, moreOpts, input
|
||||||
|
|
||||||
return opts;
|
return opts;
|
||||||
};
|
};
|
||||||
|
|
||||||
helpers.createAddresses = function(server, wallet, main, change, cb) {
|
helpers.createAddresses = function(server, wallet, main, change, cb) {
|
||||||
var clock = sinon.useFakeTimers(Date.now(), 'Date');
|
var clock = sinon.useFakeTimers(Date.now(), 'Date');
|
||||||
async.map(_.range(main + change), function(i, next) {
|
async.map(_.range(main + change), function(i, next) {
|
||||||
|
|
|
@ -2330,6 +2330,42 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#createTx2', function() {
|
||||||
|
var server, wallet;
|
||||||
|
beforeEach(function(done) {
|
||||||
|
helpers.createAndJoinWallet(2, 3, function(s, w) {
|
||||||
|
server = s;
|
||||||
|
wallet = w;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a tx', function(done) {
|
||||||
|
helpers.stubUtxos(server, wallet, [1, 2], function() {
|
||||||
|
var txOpts = helpers.createStandardProposalOpts([{
|
||||||
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
|
amount: 0.8
|
||||||
|
}], {
|
||||||
|
message: 'some message',
|
||||||
|
customData: 'some custom data',
|
||||||
|
});
|
||||||
|
server.createTx2(txOpts, function(err, tx) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(tx);
|
||||||
|
tx.isAccepted().should.equal.false;
|
||||||
|
tx.isRejected().should.equal.false;
|
||||||
|
tx.isPending().should.equal.true;
|
||||||
|
tx.isTemporary().should.equal.true;
|
||||||
|
tx.amount.should.equal(helpers.toSatoshi(0.8));
|
||||||
|
server.getPendingTxs({}, function(err, txs) {
|
||||||
|
should.not.exist(err);
|
||||||
|
txs.should.be.empty;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('#createTx backoff time', function(done) {
|
describe('#createTx backoff time', function(done) {
|
||||||
var server, wallet, txid;
|
var server, wallet, txid;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue