Browse Source

refactor proposal hash

activeAddress
Ivan Socolsky 10 years ago
parent
commit
dee4a83ded
  1. 37
      lib/model/txproposal.js
  2. 14
      lib/server.js
  3. 36
      test/integration/server.js
  4. 104
      test/models/txproposal.js

37
lib/model/txproposal.js

@ -18,7 +18,7 @@ TxProposal.Types = {
}; };
TxProposal.isTypeSupported = function(type) { TxProposal.isTypeSupported = function(type) {
return !type || _.contains(_.values(TxProposal.Types), type); return _.contains(_.values(TxProposal.Types), type);
}; };
TxProposal._create = {}; TxProposal._create = {};
@ -27,7 +27,9 @@ TxProposal._create.simple = function(txp, opts) {
txp.toAddress = opts.toAddress; txp.toAddress = opts.toAddress;
txp.amount = opts.amount; txp.amount = opts.amount;
txp.outputOrder = _.shuffle(_.range(2)); txp.outputOrder = _.shuffle(_.range(2));
txp.network = Bitcore.Address(txp.toAddress).toObject().network; try {
txp.network = Bitcore.Address(txp.toAddress).toObject().network;
} catch (ex) {}
}; };
TxProposal._create.undefined = TxProposal._create.simple; TxProposal._create.undefined = TxProposal._create.simple;
@ -35,7 +37,9 @@ TxProposal._create.undefined = TxProposal._create.simple;
TxProposal._create.multiple_outputs = function(txp, opts) { TxProposal._create.multiple_outputs = function(txp, opts) {
txp.outputs = opts.outputs; txp.outputs = opts.outputs;
txp.outputOrder = _.shuffle(_.range(txp.outputs.length + 1)); txp.outputOrder = _.shuffle(_.range(txp.outputs.length + 1));
txp.network = Bitcore.Address(txp.outputs[0].toAddress).toObject().network; try {
txp.network = Bitcore.Address(txp.outputs[0].toAddress).toObject().network;
} catch (ex) {}
}; };
TxProposal.create = function(opts) { TxProposal.create = function(opts) {
@ -43,10 +47,8 @@ TxProposal.create = function(opts) {
var x = new TxProposal(); var x = new TxProposal();
if (opts.version == '1.0.0' && !opts.type) { x.type = opts.type || TxProposal.Types.SIMPLE;
opts.type = TxProposal.Types.SIMPLE;
}
x.type = opts.type;
var now = Date.now(); var now = Date.now();
x.createdOn = Math.floor(now / 1000); x.createdOn = Math.floor(now / 1000);
x.id = _.padLeft(now, 14, '0') + Uuid.v4(); x.id = _.padLeft(now, 14, '0') + Uuid.v4();
@ -65,7 +67,7 @@ TxProposal.create = function(opts) {
x.fee = null; x.fee = null;
x.feePerKb = opts.feePerKb; x.feePerKb = opts.feePerKb;
if (typeof TxProposal._create[x.type] == 'function') { if (_.isFunction(TxProposal._create[x.type])) {
TxProposal._create[x.type](x, opts); TxProposal._create[x.type](x, opts);
} }
@ -75,10 +77,11 @@ TxProposal.create = function(opts) {
TxProposal.fromObj = function(obj) { TxProposal.fromObj = function(obj) {
var x = new TxProposal(); var x = new TxProposal();
if (obj.version == '1.0.0' && !obj.type) { if (obj.version == '1.0.0') {
obj.type = TxProposal.Types.SIMPLE; x.type = TxProposal.Types.SIMPLE;
} else {
x.type = obj.type;
} }
x.type = obj.type;
x.version = obj.version; x.version = obj.version;
x.createdOn = obj.createdOn; x.createdOn = obj.createdOn;
x.id = obj.id; x.id = obj.id;
@ -168,13 +171,13 @@ TxProposal.prototype.getRawTx = function() {
*/ */
TxProposal.prototype.getHeader = function() { TxProposal.prototype.getHeader = function() {
if (this.type == TxProposal.Types.MULTIPLEOUTPUTS) { if (this.type == TxProposal.Types.MULTIPLEOUTPUTS) {
return [ { return [{
outputs: this.outputs, outputs: this.outputs,
message: this.message, message: this.message,
payProUrl: this.payProUrl payProUrl: this.payProUrl
} ]; }];
} else { } else {
return [ this.toAddress, this.amount, this.message, this.payProUrl ]; return [this.toAddress, this.amount, this.message, this.payProUrl];
} }
}; };
@ -185,8 +188,10 @@ TxProposal.prototype.getHeader = function() {
*/ */
TxProposal.prototype.getTotalAmount = function() { TxProposal.prototype.getTotalAmount = function() {
if (this.type == TxProposal.Types.MULTIPLEOUTPUTS) { if (this.type == TxProposal.Types.MULTIPLEOUTPUTS) {
return _.map(this.outputs, function(o) { return o.amount }) return _.pluck(this.outputs, 'amount')
.reduce(function(total, n) { return total + n; }); .reduce(function(total, n) {
return total + n;
});
} else { } else {
return this.amount; return this.amount;
} }

14
lib/server.js

@ -830,6 +830,7 @@ WalletService.prototype.createTx = function(opts, cb) {
if (!Utils.checkRequired(opts, ['proposalSignature'])) if (!Utils.checkRequired(opts, ['proposalSignature']))
return cb(new ClientError('Required argument missing')); return cb(new ClientError('Required argument missing'));
opts.type = opts.type || Model.TxProposal.Types.SIMPLE;
if (!Model.TxProposal.isTypeSupported(opts.type)) if (!Model.TxProposal.isTypeSupported(opts.type))
return cb(new ClientError('Invalid proposal type')); return cb(new ClientError('Invalid proposal type'));
@ -853,7 +854,9 @@ WalletService.prototype.createTx = function(opts, cb) {
if (!o.valid) return false; if (!o.valid) return false;
}); });
if (_.any(opts.outputs, 'valid', false)) return; if (_.any(opts.outputs, 'valid', false)) return;
_.each(opts.outputs, function(o) { delete o.valid; }); _.each(opts.outputs, function(o) {
delete o.valid;
});
} else { } else {
if (!Utils.checkRequired(opts, ['toAddress', 'amount'])) if (!Utils.checkRequired(opts, ['toAddress', 'amount']))
@ -877,13 +880,14 @@ WalletService.prototype.createTx = function(opts, cb) {
var copayer = wallet.getCopayer(self.copayerId); var copayer = wallet.getCopayer(self.copayerId);
var proposalHeader = Model.TxProposal.create(opts).getHeader(); var proposalHeader = Model.TxProposal.create(opts).getHeader();
var hash = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader); var hash = WalletUtils.getProposalHash(proposalHeader);
if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey)) if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey))
return cb(new ClientError('Invalid proposal signature')); return cb(new ClientError('Invalid proposal signature'));
var outputs = (opts.type == Model.TxProposal.Types.MULTIPLEOUTPUTS) var outputs = (opts.type == Model.TxProposal.Types.MULTIPLEOUTPUTS) ? opts.outputs : [{
? opts.outputs toAddress: opts.toAddress,
: [ { toAddress: opts.toAddress, amount: opts.amount } ]; amount: opts.amount
}];
_.each(outputs, function(output) { _.each(outputs, function(output) {
output.valid = false; output.valid = false;
var toAddress = {}; var toAddress = {};

36
test/integration/server.js

@ -193,7 +193,10 @@ helpers.createProposalOpts = function(toAddress, amount, message, signingKey, fe
}; };
if (feePerKb) opts.feePerKb = feePerKb; if (feePerKb) opts.feePerKb = feePerKb;
var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message); var txp = Model.TxProposal.create(opts);
var proposalHeader = txp.getHeader();
var hash = WalletUtils.getProposalHash(proposalHeader);
try { try {
opts.proposalSignature = WalletUtils.signMessage(hash, signingKey); opts.proposalSignature = WalletUtils.signMessage(hash, signingKey);
} catch (ex) {} } catch (ex) {}
@ -221,7 +224,7 @@ helpers.createProposalOptsByType = function(type, outputs, message, signingKey,
var txp = Model.TxProposal.create(opts); var txp = Model.TxProposal.create(opts);
var proposalHeader = txp.getHeader(); var proposalHeader = txp.getHeader();
var hash = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader); var hash = WalletUtils.getProposalHash(proposalHeader);
try { try {
opts.proposalSignature = WalletUtils.signMessage(hash, signingKey); opts.proposalSignature = WalletUtils.signMessage(hash, signingKey);
@ -1399,8 +1402,8 @@ describe('Wallet service', function() {
var txOpts = helpers.createProposalOpts('invalid address', 80, null, TestData.copayers[0].privKey_1H_0); var txOpts = helpers.createProposalOpts('invalid address', 80, null, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) { server.createTx(txOpts, function(err, tx) {
should.not.exist(tx);
should.exist(err); should.exist(err);
should.not.exist(tx);
// may fail due to Non-base58 character, or Checksum mismatch, or other // may fail due to Non-base58 character, or Checksum mismatch, or other
done(); done();
}); });
@ -1664,10 +1667,15 @@ describe('Wallet service', function() {
it('should create tx for type multiple_outputs', function(done) { it('should create tx for type multiple_outputs', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() { helpers.stubUtxos(server, wallet, [100, 200], function() {
var outputs = [ var outputs = [{
{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 75, message: 'message #1' }, toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 75, message: 'message #2' } amount: 75,
]; message: 'message #1'
}, {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: 75,
message: 'message #2'
}];
var txOpts = helpers.createProposalOptsByType(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0); var txOpts = helpers.createProposalOptsByType(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) { server.createTx(txOpts, function(err, tx) {
should.not.exist(err); should.not.exist(err);
@ -1679,10 +1687,16 @@ describe('Wallet service', function() {
it('should fail to create tx for type multiple_outputs with invalid output argument', function(done) { it('should fail to create tx for type multiple_outputs with invalid output argument', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() { helpers.stubUtxos(server, wallet, [100, 200], function() {
var outputs = [ var outputs = [{
{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 80, message: 'message #1', foo: 'bar' }, toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 90, message: 'message #2' } amount: 80,
]; message: 'message #1',
foo: 'bar'
}, {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: 90,
message: 'message #2'
}];
var txOpts = helpers.createProposalOptsByType(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0); var txOpts = helpers.createProposalOptsByType(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) { server.createTx(txOpts, function(err, tx) {
should.exist(err); should.exist(err);

104
test/models/txproposal.js

@ -4,21 +4,21 @@ var _ = require('lodash');
var chai = require('chai'); var chai = require('chai');
var sinon = require('sinon'); var sinon = require('sinon');
var should = chai.should(); var should = chai.should();
var TXP = require('../../lib/model/txproposal'); var TxProposal = require('../../lib/model/txproposal');
var Bitcore = require('bitcore-wallet-utils').Bitcore; var Bitcore = require('bitcore-wallet-utils').Bitcore;
var WalletUtils = require('bitcore-wallet-utils'); var WalletUtils = require('bitcore-wallet-utils');
describe('TXProposal', function() { describe('TXProposal', function() {
describe('#create', function() { describe('#create', function() {
it('should create a TXP', function() { it('should create a TxProposal', function() {
var txp = TXP.create(aTxpOpts()); var txp = TxProposal.create(aTxpOpts());
should.exist(txp); should.exist(txp);
should.exist(txp.toAddress); should.exist(txp.toAddress);
should.not.exist(txp.outputs); should.not.exist(txp.outputs);
}); });
it('should create a multiple-outputs TXP', function() { it('should create a multiple-outputs TxProposal', function() {
var txp = TXP.create(aTxpOpts(TXP.Types.MULTIPLEOUTPUTS)); var txp = TxProposal.create(aTxpOpts(TxProposal.Types.MULTIPLEOUTPUTS));
should.exist(txp); should.exist(txp);
should.not.exist(txp.toAddress); should.not.exist(txp.toAddress);
should.exist(txp.outputs); should.exist(txp.outputs);
@ -26,26 +26,26 @@ describe('TXProposal', function() {
}); });
describe('#fromObj', function() { describe('#fromObj', function() {
it('should copy a TXP', function() { it('should copy a TxProposal', function() {
var txp = TXP.fromObj(aTXP()); var txp = TxProposal.fromObj(aTXP());
should.exist(txp); should.exist(txp);
txp.toAddress.should.equal(aTXP().toAddress); txp.toAddress.should.equal(aTXP().toAddress);
}); });
it('should copy a multiple-outputs TXP', function() { it('should copy a multiple-outputs TxProposal', function() {
var txp = TXP.fromObj(aTXP(TXP.Types.MULTIPLEOUTPUTS)); var txp = TxProposal.fromObj(aTXP(TxProposal.Types.MULTIPLEOUTPUTS));
should.exist(txp); should.exist(txp);
txp.outputs.should.deep.equal(aTXP(TXP.Types.MULTIPLEOUTPUTS).outputs); txp.outputs.should.deep.equal(aTXP(TxProposal.Types.MULTIPLEOUTPUTS).outputs);
}); });
}); });
describe('#getBitcoreTx', function() { describe('#getBitcoreTx', function() {
it('should create a valid bitcore TX', function() { it('should create a valid bitcore TX', function() {
var txp = TXP.fromObj(aTXP()); var txp = TxProposal.fromObj(aTXP());
var t = txp.getBitcoreTx(); var t = txp.getBitcoreTx();
should.exist(t); should.exist(t);
}); });
it('should order outputs as specified by outputOrder', function() { it('should order outputs as specified by outputOrder', function() {
var txp = TXP.fromObj(aTXP()); var txp = TxProposal.fromObj(aTXP());
txp.outputOrder = [0, 1]; txp.outputOrder = [0, 1];
var t = txp.getBitcoreTx(); var t = txp.getBitcoreTx();
@ -56,7 +56,7 @@ describe('TXProposal', function() {
t.getChangeOutput().should.deep.equal(t.outputs[0]); t.getChangeOutput().should.deep.equal(t.outputs[0]);
}); });
it('should create a bitcore TX with multiple outputs', function() { it('should create a bitcore TX with multiple outputs', function() {
var txp = TXP.fromObj(aTXP(TXP.Types.MULTIPLEOUTPUTS)); var txp = TxProposal.fromObj(aTXP(TxProposal.Types.MULTIPLEOUTPUTS));
txp.outputOrder = [0, 1, 2]; txp.outputOrder = [0, 1, 2];
var t = txp.getBitcoreTx(); var t = txp.getBitcoreTx();
t.getChangeOutput().should.deep.equal(t.outputs[2]); t.getChangeOutput().should.deep.equal(t.outputs[2]);
@ -65,14 +65,14 @@ describe('TXProposal', function() {
describe('#getHeader', function() { describe('#getHeader', function() {
it('should be compatible with simple proposal legacy header', function() { it('should be compatible with simple proposal legacy header', function() {
var x = TXP.fromObj(aTXP()); var x = TxProposal.fromObj(aTXP());
var proposalHeader = x.getHeader(); var proposalHeader = x.getHeader();
var pH = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader); var pH = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader);
var uH = WalletUtils.getProposalHash(x.toAddress, x.amount, x.message, x.payProUrl); var uH = WalletUtils.getProposalHash(x.toAddress, x.amount, x.message, x.payProUrl);
pH.should.equal(uH); pH.should.equal(uH);
}); });
it('should handle multiple-outputs', function() { it('should handle multiple-outputs', function() {
var x = TXP.fromObj(aTXP(TXP.Types.MULTIPLEOUTPUTS)); var x = TxProposal.fromObj(aTXP(TxProposal.Types.MULTIPLEOUTPUTS));
var proposalHeader = x.getHeader(); var proposalHeader = x.getHeader();
should.exist(proposalHeader); should.exist(proposalHeader);
var pH = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader); var pH = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader);
@ -82,21 +82,23 @@ describe('TXProposal', function() {
describe('#getTotalAmount', function() { describe('#getTotalAmount', function() {
it('should be compatible with simple proposal legacy amount', function() { it('should be compatible with simple proposal legacy amount', function() {
var x = TXP.fromObj(aTXP()); var x = TxProposal.fromObj(aTXP());
var total = x.getTotalAmount(); var total = x.getTotalAmount();
total.should.equal(x.amount); total.should.equal(x.amount);
}); });
it('should handle multiple-outputs', function() { it('should handle multiple-outputs', function() {
var x = TXP.fromObj(aTXP(TXP.Types.MULTIPLEOUTPUTS)); var x = TxProposal.fromObj(aTXP(TxProposal.Types.MULTIPLEOUTPUTS));
var totalOutput = 0; var totalOutput = 0;
_.each(x.outputs, function(o) { totalOutput += o.amount }); _.each(x.outputs, function(o) {
totalOutput += o.amount
});
x.getTotalAmount().should.equal(totalOutput); x.getTotalAmount().should.equal(totalOutput);
}); });
}); });
describe('#sign', function() { describe('#sign', function() {
it('should sign 2-2', function() { it('should sign 2-2', function() {
var txp = TXP.fromObj(aTXP()); var txp = TxProposal.fromObj(aTXP());
txp.sign('1', theSignatures, theXPub); txp.sign('1', theSignatures, theXPub);
txp.isAccepted().should.equal(false); txp.isAccepted().should.equal(false);
txp.isRejected().should.equal(false); txp.isRejected().should.equal(false);
@ -108,7 +110,7 @@ describe('TXProposal', function() {
describe('#getRawTx', function() { describe('#getRawTx', function() {
it('should generate correct raw transaction for signed 2-2', function() { it('should generate correct raw transaction for signed 2-2', function() {
var txp = TXP.fromObj(aTXP()); var txp = TxProposal.fromObj(aTXP());
txp.sign('1', theSignatures, theXPub); txp.sign('1', theSignatures, theXPub);
txp.getRawTx().should.equal('0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e080000009200483045022100896aeb8db75fec22fddb5facf791927a996eb3aee23ee6deaa15471ea46047de02204c0c33f42a9d3ff93d62738712a8c8a5ecd21b45393fdd144e7b01b5a186f1f9014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0280f0fa02000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70c9fa020000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000'); txp.getRawTx().should.equal('0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e080000009200483045022100896aeb8db75fec22fddb5facf791927a996eb3aee23ee6deaa15471ea46047de02204c0c33f42a9d3ff93d62738712a8c8a5ecd21b45393fdd144e7b01b5a186f1f9014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0280f0fa02000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70c9fa020000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000');
}); });
@ -118,7 +120,7 @@ describe('TXProposal', function() {
describe('#reject', function() { describe('#reject', function() {
it('should reject 2-2', function() { it('should reject 2-2', function() {
var txp = TXP.fromObj(aTXP()); var txp = TxProposal.fromObj(aTXP());
txp.reject('1'); txp.reject('1');
txp.isAccepted().should.equal(false); txp.isAccepted().should.equal(false);
txp.isRejected().should.equal(true); txp.isRejected().should.equal(true);
@ -128,7 +130,7 @@ describe('TXProposal', function() {
describe('#reject & #sign', function() { describe('#reject & #sign', function() {
it('should finally reject', function() { it('should finally reject', function() {
var txp = TXP.fromObj(aTXP()); var txp = TxProposal.fromObj(aTXP());
txp.sign('1', theSignatures); txp.sign('1', theSignatures);
txp.isAccepted().should.equal(false); txp.isAccepted().should.equal(false);
txp.isRejected().should.equal(false); txp.isRejected().should.equal(false);
@ -151,19 +153,16 @@ var aTxpOpts = function(type) {
amount: 50000000, amount: 50000000,
message: 'some message' message: 'some message'
}; };
if (type == TXP.Types.MULTIPLEOUTPUTS) { if (type == TxProposal.Types.MULTIPLEOUTPUTS) {
opts.outputs = [ opts.outputs = [{
{ toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7",
toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", amount: 10000000,
amount: 10000000, message: "first message"
message: "first message" }, {
}, toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7",
{ amount: 20000000,
toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", message: "second message"
amount: 20000000, }, ];
message: "second message"
},
];
delete opts.toAddress; delete opts.toAddress;
delete opts.amount; delete opts.amount;
} }
@ -171,8 +170,18 @@ var aTxpOpts = function(type) {
}; };
var aTXP = function(type) { var aTXP = function(type) {
var version;
switch (type) {
case TxProposal.Types.MULTIPLEOUTPUTS:
version = '1.0.1';
break;
default:
version = '1.0.0';
break
}
var txp = { var txp = {
"version": "1.0.0", "version": version,
"type": type, "type": type,
"createdOn": 1423146231, "createdOn": 1423146231,
"id": "75c34f49-1ed6-255f-e9fd-0c71ae75ed1e", "id": "75c34f49-1ed6-255f-e9fd-0c71ae75ed1e",
@ -209,19 +218,16 @@ var aTXP = function(type) {
"actions": [], "actions": [],
"outputOrder": [0, 1], "outputOrder": [0, 1],
}; };
if (type == TXP.Types.MULTIPLEOUTPUTS) { if (type == TxProposal.Types.MULTIPLEOUTPUTS) {
txp.outputs = [ txp.outputs = [{
{ toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7",
toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", amount: 10000000,
amount: 10000000, message: "first message"
message: "first message" }, {
}, toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7",
{ amount: 20000000,
toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", message: "second message"
amount: 20000000, }, ];
message: "second message"
},
];
txp.outputOrder = [0, 1, 2]; txp.outputOrder = [0, 1, 2];
delete txp.toAddress; delete txp.toAddress;
delete txp.amount; delete txp.amount;

Loading…
Cancel
Save