txProposal v3
This commit is contained in:
parent
d558156620
commit
63eca6b529
|
@ -2,6 +2,11 @@
|
|||
|
||||
var Constants = {};
|
||||
|
||||
Constants.NETWORKS = {
|
||||
LIVENET: 'livenet',
|
||||
TESTNET: 'testnet',
|
||||
};
|
||||
|
||||
Constants.SCRIPT_TYPES = {
|
||||
P2SH: 'P2SH',
|
||||
P2PKH: 'P2PKH',
|
||||
|
|
|
@ -0,0 +1,401 @@
|
|||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
var $ = require('preconditions').singleton();
|
||||
var Uuid = require('uuid');
|
||||
var log = require('npmlog');
|
||||
log.debug = log.verbose;
|
||||
log.disableColor();
|
||||
|
||||
var Bitcore = require('bitcore-lib');
|
||||
|
||||
var Common = require('../common');
|
||||
var Constants = Common.Constants;
|
||||
var Defaults = Common.Defaults;
|
||||
|
||||
var TxProposalAction = require('./txproposalaction');
|
||||
|
||||
function TxProposalv3() {};
|
||||
|
||||
TxProposalv3.Types = {
|
||||
STANDARD: 'standard',
|
||||
EXTERNAL: 'external'
|
||||
};
|
||||
|
||||
TxProposalv3.isTypeSupported = function(type) {
|
||||
return _.contains(_.values(TxProposalv3.Types), type);
|
||||
};
|
||||
|
||||
TxProposalv3._create = {};
|
||||
|
||||
TxProposalv3._create.standard = function(txp, opts) {
|
||||
txp.outputs = _.map(opts.outputs, function(output) {
|
||||
return _.pick(output, ['amount', 'toAddress', 'message']);
|
||||
});
|
||||
txp.outputOrder = _.shuffle(_.range(txp.outputs.length + 1));
|
||||
};
|
||||
|
||||
TxProposalv3._create.external = function(txp, opts) {
|
||||
txp.setInputs(opts.inputs || []);
|
||||
txp.outputs = opts.outputs;
|
||||
txp.outputOrder = _.range(txp.outputs.length + 1);
|
||||
};
|
||||
|
||||
TxProposalv3.create = function(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
var x = new TxProposalv3();
|
||||
|
||||
x.version = 3;
|
||||
x.type = opts.type || TxProposalv3.Types.STANDARD;
|
||||
|
||||
var now = Date.now();
|
||||
x.createdOn = Math.floor(now / 1000);
|
||||
x.id = _.padLeft(now, 14, '0') + Uuid.v4();
|
||||
x.walletId = opts.walletId;
|
||||
x.creatorId = opts.creatorId;
|
||||
x.message = opts.message;
|
||||
x.payProUrl = opts.payProUrl;
|
||||
x.changeAddress = opts.changeAddress;
|
||||
x.inputs = [];
|
||||
x.inputPaths = [];
|
||||
x.requiredSignatures = opts.requiredSignatures;
|
||||
x.requiredRejections = opts.requiredRejections;
|
||||
x.walletN = opts.walletN;
|
||||
x.status = 'temporary';
|
||||
x.actions = [];
|
||||
x.fee = null;
|
||||
x.feePerKb = opts.feePerKb;
|
||||
x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos;
|
||||
|
||||
x.addressType = opts.addressType || (x.walletN > 1 ? Constants.SCRIPT_TYPES.P2SH : Constants.SCRIPT_TYPES.P2PKH);
|
||||
$.checkState(_.contains(_.values(Constants.SCRIPT_TYPES), x.addressType));
|
||||
|
||||
x.customData = opts.customData;
|
||||
|
||||
if (_.isFunction(TxProposalv3._create[x.type])) {
|
||||
TxProposalv3._create[x.type](x, opts);
|
||||
}
|
||||
|
||||
x.amount = x.getTotalAmount();
|
||||
try {
|
||||
x.network = opts.network || Bitcore.Address(x.outputs[0].toAddress).toObject().network;
|
||||
} catch (ex) {}
|
||||
$.checkState(_.contains(_.values(Constants.NETWORKS), x.network));
|
||||
|
||||
return x;
|
||||
};
|
||||
|
||||
TxProposalv3.fromObj = function(obj) {
|
||||
var x = new TxProposalv3();
|
||||
|
||||
x.version = obj.version;
|
||||
x.type = obj.type;
|
||||
x.createdOn = obj.createdOn;
|
||||
x.id = obj.id;
|
||||
x.walletId = obj.walletId;
|
||||
x.creatorId = obj.creatorId;
|
||||
x.outputs = obj.outputs;
|
||||
x.amount = obj.amount;
|
||||
x.message = obj.message;
|
||||
x.payProUrl = obj.payProUrl;
|
||||
x.changeAddress = obj.changeAddress;
|
||||
x.inputs = obj.inputs;
|
||||
x.requiredSignatures = obj.requiredSignatures;
|
||||
x.requiredRejections = obj.requiredRejections;
|
||||
x.walletN = obj.walletN;
|
||||
x.status = obj.status;
|
||||
x.txid = obj.txid;
|
||||
x.broadcastedOn = obj.broadcastedOn;
|
||||
x.inputPaths = obj.inputPaths;
|
||||
x.actions = _.map(obj.actions, function(action) {
|
||||
return TxProposalAction.fromObj(action);
|
||||
});
|
||||
x.outputOrder = obj.outputOrder;
|
||||
x.fee = obj.fee;
|
||||
x.network = obj.network;
|
||||
x.feePerKb = obj.feePerKb;
|
||||
x.excludeUnconfirmedUtxos = obj.excludeUnconfirmedUtxos;
|
||||
x.addressType = obj.addressType;
|
||||
x.customData = obj.customData;
|
||||
|
||||
x.proposalSignature = obj.proposalSignature;
|
||||
x.proposalSignaturePubKey = obj.proposalSignaturePubKey;
|
||||
x.proposalSignaturePubKeySig = obj.proposalSignaturePubKeySig;
|
||||
|
||||
return x;
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.toObject = function() {
|
||||
var x = _.cloneDeep(this);
|
||||
x.isPending = this.isPending();
|
||||
x.isTemporary = this.isTemporary();
|
||||
return x;
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.setInputs = function(inputs) {
|
||||
this.inputs = inputs;
|
||||
this.inputPaths = _.pluck(inputs, 'path');
|
||||
};
|
||||
|
||||
TxProposalv3.prototype._updateStatus = function() {
|
||||
if (this.status != 'pending') return;
|
||||
|
||||
if (this.isRejected()) {
|
||||
this.status = 'rejected';
|
||||
} else if (this.isAccepted()) {
|
||||
this.status = 'accepted';
|
||||
}
|
||||
};
|
||||
|
||||
TxProposalv3.prototype._buildTx = function() {
|
||||
var self = this;
|
||||
|
||||
var t = new Bitcore.Transaction();
|
||||
|
||||
$.checkState(_.contains(_.values(Constants.SCRIPT_TYPES), self.addressType));
|
||||
|
||||
switch (self.addressType) {
|
||||
case Constants.SCRIPT_TYPES.P2SH:
|
||||
_.each(self.inputs, function(i) {
|
||||
t.from(i, i.publicKeys, self.requiredSignatures);
|
||||
});
|
||||
break;
|
||||
case Constants.SCRIPT_TYPES.P2PKH:
|
||||
t.from(self.inputs);
|
||||
break;
|
||||
}
|
||||
|
||||
if (self.toAddress && self.amount && !self.outputs) {
|
||||
t.to(self.toAddress, self.amount);
|
||||
} else if (self.outputs) {
|
||||
_.each(self.outputs, function(o) {
|
||||
$.checkState(o.script || o.toAddress, 'Output should have either toAddress or script specified');
|
||||
if (o.script) {
|
||||
t.addOutput(new Bitcore.Transaction.Output({
|
||||
script: o.script,
|
||||
satoshis: o.amount
|
||||
}));
|
||||
} else {
|
||||
t.to(o.toAddress, o.amount);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
t.fee(self.fee);
|
||||
t.change(self.changeAddress.address);
|
||||
|
||||
// Shuffle outputs for improved privacy
|
||||
if (t.outputs.length > 1) {
|
||||
var outputOrder = _.reject(self.outputOrder, function(order) {
|
||||
return order >= t.outputs.length;
|
||||
});
|
||||
$.checkState(t.outputs.length == outputOrder.length);
|
||||
t.sortOutputs(function(outputs) {
|
||||
return _.map(outputOrder, function(i) {
|
||||
return outputs[i];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Validate inputs vs outputs independently of Bitcore
|
||||
var totalInputs = _.sum(self.inputs, 'satoshis');
|
||||
var totalOutputs = _.sum(t.outputs, 'satoshis');
|
||||
|
||||
$.checkState(totalInputs - totalOutputs <= Defaults.MAX_TX_FEE);
|
||||
|
||||
return t;
|
||||
};
|
||||
|
||||
|
||||
TxProposalv3.prototype._getCurrentSignatures = function() {
|
||||
var acceptedActions = _.filter(this.actions, {
|
||||
type: 'accept'
|
||||
});
|
||||
|
||||
return _.map(acceptedActions, function(x) {
|
||||
return {
|
||||
signatures: x.signatures,
|
||||
xpub: x.xpub,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.getBitcoreTx = function() {
|
||||
var self = this;
|
||||
|
||||
var t = this._buildTx();
|
||||
|
||||
var sigs = this._getCurrentSignatures();
|
||||
_.each(sigs, function(x) {
|
||||
self._addSignaturesToBitcoreTx(t, x.signatures, x.xpub);
|
||||
});
|
||||
|
||||
return t;
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.getRawTx = function() {
|
||||
var t = this.getBitcoreTx();
|
||||
|
||||
return t.uncheckedSerialize();
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.getEstimatedSize = function() {
|
||||
// Note: found empirically based on all multisig P2SH inputs and within m & n allowed limits.
|
||||
var safetyMargin = 0.05;
|
||||
var walletM = this.requiredSignatures;
|
||||
|
||||
var overhead = 4 + 4 + 9 + 9;
|
||||
var inputSize = walletM * 72 + this.walletN * 36 + 44;
|
||||
var outputSize = 34;
|
||||
var nbInputs = this.inputs.length;
|
||||
var nbOutputs = (_.isArray(this.outputs) ? Math.max(1, this.outputs.length) : 1) + 1;
|
||||
|
||||
var size = overhead + inputSize * nbInputs + outputSize * nbOutputs;
|
||||
|
||||
return parseInt((size * (1 + safetyMargin)).toFixed(0));
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.estimateFee = function() {
|
||||
var fee = this.feePerKb * this.getEstimatedSize() / 1000;
|
||||
this.fee = parseInt(fee.toFixed(0));
|
||||
};
|
||||
|
||||
/**
|
||||
* getTotalAmount
|
||||
*
|
||||
* @return {Number} total amount of all outputs excluding change output
|
||||
*/
|
||||
TxProposalv3.prototype.getTotalAmount = function() {
|
||||
return _.sum(this.outputs, 'amount');
|
||||
};
|
||||
|
||||
/**
|
||||
* getActors
|
||||
*
|
||||
* @return {String[]} copayerIds that performed actions in this proposal (accept / reject)
|
||||
*/
|
||||
TxProposalv3.prototype.getActors = function() {
|
||||
return _.pluck(this.actions, 'copayerId');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* getApprovers
|
||||
*
|
||||
* @return {String[]} copayerIds that approved the tx proposal (accept)
|
||||
*/
|
||||
TxProposalv3.prototype.getApprovers = function() {
|
||||
return _.pluck(
|
||||
_.filter(this.actions, {
|
||||
type: 'accept'
|
||||
}), 'copayerId');
|
||||
};
|
||||
|
||||
/**
|
||||
* getActionBy
|
||||
*
|
||||
* @param {String} copayerId
|
||||
* @return {Object} type / createdOn
|
||||
*/
|
||||
TxProposalv3.prototype.getActionBy = function(copayerId) {
|
||||
return _.find(this.actions, {
|
||||
copayerId: copayerId
|
||||
});
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.addAction = function(copayerId, type, comment, signatures, xpub) {
|
||||
var action = TxProposalAction.create({
|
||||
copayerId: copayerId,
|
||||
type: type,
|
||||
signatures: signatures,
|
||||
xpub: xpub,
|
||||
comment: comment,
|
||||
});
|
||||
this.actions.push(action);
|
||||
this._updateStatus();
|
||||
};
|
||||
|
||||
TxProposalv3.prototype._addSignaturesToBitcoreTx = function(tx, signatures, xpub) {
|
||||
var self = this;
|
||||
|
||||
if (signatures.length != this.inputs.length)
|
||||
throw new Error('Number of signatures does not match number of inputs');
|
||||
|
||||
var i = 0,
|
||||
x = new Bitcore.HDPublicKey(xpub);
|
||||
|
||||
_.each(signatures, function(signatureHex) {
|
||||
var input = self.inputs[i];
|
||||
try {
|
||||
var signature = Bitcore.crypto.Signature.fromString(signatureHex);
|
||||
var pub = x.derive(self.inputPaths[i]).publicKey;
|
||||
var s = {
|
||||
inputIndex: i,
|
||||
signature: signature,
|
||||
sigtype: Bitcore.crypto.Signature.SIGHASH_ALL,
|
||||
publicKey: pub,
|
||||
};
|
||||
tx.inputs[i].addSignature(tx, s);
|
||||
i++;
|
||||
} catch (e) {};
|
||||
});
|
||||
|
||||
if (i != tx.inputs.length)
|
||||
throw new Error('Wrong signatures');
|
||||
};
|
||||
|
||||
|
||||
TxProposalv3.prototype.sign = function(copayerId, signatures, xpub) {
|
||||
try {
|
||||
// Tests signatures are OK
|
||||
var tx = this.getBitcoreTx();
|
||||
this._addSignaturesToBitcoreTx(tx, signatures, xpub);
|
||||
|
||||
this.addAction(copayerId, 'accept', null, signatures, xpub);
|
||||
|
||||
if (this.status == 'accepted') {
|
||||
this.raw = tx.uncheckedSerialize();
|
||||
this.txid = tx.id;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
log.debug(e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.reject = function(copayerId, reason) {
|
||||
this.addAction(copayerId, 'reject', reason);
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.isTemporary = function() {
|
||||
return this.status == 'temporary';
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.isPending = function() {
|
||||
return !_.contains(['broadcasted', 'rejected'], this.status);
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.isAccepted = function() {
|
||||
var votes = _.countBy(this.actions, 'type');
|
||||
return votes['accept'] >= this.requiredSignatures;
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.isRejected = function() {
|
||||
var votes = _.countBy(this.actions, 'type');
|
||||
return votes['reject'] >= this.requiredRejections;
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.isBroadcasted = function() {
|
||||
return this.status == 'broadcasted';
|
||||
};
|
||||
|
||||
TxProposalv3.prototype.setBroadcasted = function() {
|
||||
$.checkState(this.txid);
|
||||
this.status = 'broadcasted';
|
||||
this.broadcastedOn = Math.floor(Date.now() / 1000);
|
||||
};
|
||||
|
||||
module.exports = TxProposalv3;
|
|
@ -0,0 +1,193 @@
|
|||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
var chai = require('chai');
|
||||
var sinon = require('sinon');
|
||||
var should = chai.should();
|
||||
var TxProposal = require('../../lib/model/txproposalv3');
|
||||
var Bitcore = require('bitcore-lib');
|
||||
|
||||
describe('TxProposal v3', function() {
|
||||
describe('#create', function() {
|
||||
it('should create a TxProposal', function() {
|
||||
var txp = TxProposal.create(aTxpOpts());
|
||||
should.exist(txp);
|
||||
txp.outputs.length.should.equal(2);
|
||||
txp.amount.should.equal(30000000);
|
||||
txp.network.should.equal('livenet');
|
||||
});
|
||||
it('should create an external TxProposal', function() {
|
||||
var txp = TxProposal.create(aTxpOpts(TxProposal.Types.EXTERNAL));
|
||||
should.exist(txp);
|
||||
txp.outputs.length.should.equal(2);
|
||||
txp.amount.should.equal(30000000);
|
||||
txp.network.should.equal('livenet');
|
||||
should.exist(txp.inputs);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#fromObj', function() {
|
||||
it('should copy a TxProposal', function() {
|
||||
var txp = TxProposal.fromObj(aTXP());
|
||||
should.exist(txp);
|
||||
txp.amount.should.equal(aTXP().amount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getBitcoreTx', function() {
|
||||
it('should create a valid bitcore TX', function() {
|
||||
var txp = TxProposal.fromObj(aTXP());
|
||||
var t = txp.getBitcoreTx();
|
||||
should.exist(t);
|
||||
});
|
||||
it('should order outputs as specified by outputOrder', function() {
|
||||
var txp = TxProposal.fromObj(aTXP());
|
||||
|
||||
txp.outputOrder = [0, 1, 2];
|
||||
var t = txp.getBitcoreTx();
|
||||
t.getChangeOutput().should.deep.equal(t.outputs[2]);
|
||||
|
||||
txp.outputOrder = [2, 0, 1];
|
||||
var t = txp.getBitcoreTx();
|
||||
t.getChangeOutput().should.deep.equal(t.outputs[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getTotalAmount', function() {
|
||||
it('should compute total amount', function() {
|
||||
var x = TxProposal.fromObj(aTXP());
|
||||
var total = x.getTotalAmount();
|
||||
total.should.equal(x.amount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#sign', function() {
|
||||
it('should sign 2-2', function() {
|
||||
var txp = TxProposal.fromObj(aTXP());
|
||||
txp.sign('1', theSignatures, theXPub);
|
||||
txp.isAccepted().should.equal(false);
|
||||
txp.isRejected().should.equal(false);
|
||||
txp.sign('2', theSignatures, theXPub);
|
||||
txp.isAccepted().should.equal(true);
|
||||
txp.isRejected().should.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getRawTx', function() {
|
||||
it('should generate correct raw transaction for signed 2-2', function() {
|
||||
var txp = TxProposal.fromObj(aTXP());
|
||||
txp.sign('1', theSignatures, theXPub);
|
||||
txp.getRawTx().should.equal(theRawTx);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#reject', function() {
|
||||
it('should reject 2-2', function() {
|
||||
var txp = TxProposal.fromObj(aTXP());
|
||||
txp.reject('1');
|
||||
txp.isAccepted().should.equal(false);
|
||||
txp.isRejected().should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#reject & #sign', function() {
|
||||
it('should finally reject', function() {
|
||||
var txp = TxProposal.fromObj(aTXP());
|
||||
txp.sign('1', theSignatures);
|
||||
txp.isAccepted().should.equal(false);
|
||||
txp.isRejected().should.equal(false);
|
||||
txp.reject('2');
|
||||
txp.isAccepted().should.equal(false);
|
||||
txp.isRejected().should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var theXPriv = 'xprv9s21ZrQH143K2rMHbXTJmWTuFx6ssqn1vyRoZqPkCXYchBSkp5ey8kMJe84sxfXq5uChWH4gk94rWbXZt2opN9kg4ufKGvUM7HQSLjnoh7e';
|
||||
var theXPub = 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2Q9npnVmdTAthYGc3N3uxm5sEdnTpSqBc4YYTAhNnoSxCm9';
|
||||
var theSignatures = ['304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6'];
|
||||
var theRawTx = '0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e08000000910047304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0380969800000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac002d3101000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70f62b040000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000';
|
||||
|
||||
var aTxpOpts = function(type) {
|
||||
var opts = {
|
||||
type: type || TxProposal.Types.STANDARD,
|
||||
message: 'some message'
|
||||
};
|
||||
opts.outputs = [{
|
||||
toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7",
|
||||
amount: 10000000,
|
||||
message: "first message"
|
||||
}, {
|
||||
toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7",
|
||||
amount: 20000000,
|
||||
message: "second message"
|
||||
}, ];
|
||||
|
||||
if (type == TxProposal.Types.EXTERNAL) {
|
||||
opts.inputs = [{
|
||||
"txid": "6ee699846d2d6605f96d20c7cc8230382e5da43342adb11b499bbe73709f06ab",
|
||||
"vout": 8,
|
||||
"satoshis": 100000000,
|
||||
"scriptPubKey": "a914a8a9648754fbda1b6c208ac9d4e252075447f36887",
|
||||
"address": "3H4pNP6J4PW4NnvdrTg37VvZ7h2QWuAwtA",
|
||||
"path": "m/2147483647/0/1",
|
||||
"publicKeys": ["0319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f", "03b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d101301"]
|
||||
}];
|
||||
}
|
||||
return opts;
|
||||
};
|
||||
|
||||
var aTXP = function(type) {
|
||||
var txp = {
|
||||
"version": 3,
|
||||
"type": type || TxProposal.Types.STANDARD,
|
||||
"createdOn": 1423146231,
|
||||
"id": "75c34f49-1ed6-255f-e9fd-0c71ae75ed1e",
|
||||
"walletId": "1",
|
||||
"creatorId": "1",
|
||||
"network": "livenet",
|
||||
"amount": 30000000,
|
||||
"message": 'some message',
|
||||
"proposalSignature": '7035022100896aeb8db75fec22fddb5facf791927a996eb3aee23ee6deaa15471ea46047de02204c0c33f42a9d3ff93d62738712a8c8a5ecd21b45393fdd144e7b01b5a186f1f9',
|
||||
"changeAddress": {
|
||||
"version": '1.0.0',
|
||||
"createdOn": 1424372337,
|
||||
"address": '3CauZ5JUFfmSAx2yANvCRoNXccZ3YSUjXH',
|
||||
"path": 'm/2147483647/1/0',
|
||||
"publicKeys": ['030562cb099e6043dc499eb359dd97c9d500a3586498e4bcf0228a178cc20e6f16',
|
||||
'0367027d17dbdfc27b5e31f8ed70e14d47949f0fa392261e977db0851c8b0d6fac',
|
||||
'0315ae1e8aa866794ae603389fb2b8549153ebf04e7cdf74501dadde5c75ddad11'
|
||||
]
|
||||
},
|
||||
"inputs": [{
|
||||
"txid": "6ee699846d2d6605f96d20c7cc8230382e5da43342adb11b499bbe73709f06ab",
|
||||
"vout": 8,
|
||||
"satoshis": 100000000,
|
||||
"scriptPubKey": "a914a8a9648754fbda1b6c208ac9d4e252075447f36887",
|
||||
"address": "3H4pNP6J4PW4NnvdrTg37VvZ7h2QWuAwtA",
|
||||
"path": "m/2147483647/0/1",
|
||||
"publicKeys": ["0319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f", "03b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d101301"]
|
||||
}],
|
||||
"inputPaths": ["m/2147483647/0/1"],
|
||||
"requiredSignatures": 2,
|
||||
"requiredRejections": 1,
|
||||
"walletN": 2,
|
||||
"addressType": "P2SH",
|
||||
"status": "pending",
|
||||
"actions": [],
|
||||
"fee": 10000,
|
||||
"outputs": [{
|
||||
"toAddress": "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7",
|
||||
"amount": 10000000,
|
||||
"message": "first message"
|
||||
}, {
|
||||
"toAddress": "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7",
|
||||
"amount": 20000000,
|
||||
"message": "second message"
|
||||
}, ],
|
||||
"outputOrder": [0, 1, 2]
|
||||
};
|
||||
|
||||
return txp;
|
||||
};
|
Loading…
Reference in New Issue