mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 12:15:51 +00:00
Add multisignature support
This commit is contained in:
@@ -54,6 +54,7 @@
|
||||
- [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation)
|
||||
- [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution)
|
||||
- [sign](#sign)
|
||||
- [combine](#combine)
|
||||
- [submit](#submit)
|
||||
- [generateAddress](#generateaddress)
|
||||
- [computeLedgerHash](#computeledgerhash)
|
||||
@@ -269,7 +270,7 @@ Executing a transaction with `RippleAPI` requires the following four steps:
|
||||
* [prepareSuspendedPaymentCreation](#preparesuspendedpaymentcreation)
|
||||
* [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation)
|
||||
* [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution)
|
||||
2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place.
|
||||
2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place. For multisignature transactions, the `signedTransaction` fields returned by `sign` must be collected and passed to the [combine](#combine) method.
|
||||
3. [Submit](#submit) - Submit the transaction to the connected server.
|
||||
4. Verify - Verify that the transaction got validated by querying with [getTransaction](#gettransaction). This is necessary because transactions may fail even if they were successfully submitted.
|
||||
|
||||
@@ -480,6 +481,12 @@ passwordSpent | boolean | *Optional* Indicates that the account has used its fre
|
||||
regularKey | [address](#ripple-address),null | *Optional* The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key.
|
||||
requireAuthorization | boolean | *Optional* If set, this account must individually approve other users in order for those users to hold this account’s issuances.
|
||||
requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag.
|
||||
signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.
|
||||
*signers.* threshold | integer | *Optional* A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`.
|
||||
*signers.* weights | array | *Optional* Weights of signatures for each signer.
|
||||
*signers.* weights[] | object | An association of an address and a weight.
|
||||
*signers.weights[].* address | [address](#ripple-address) | A Ripple account address
|
||||
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
|
||||
transferRate | number,null | *Optional* The fee to charge when users transfer this account’s issuances, represented as billionths of a unit. Use `null` to set no fee.
|
||||
|
||||
### Example
|
||||
@@ -2644,6 +2651,12 @@ passwordSpent | boolean | *Optional* Indicates that the account has used its fre
|
||||
regularKey | [address](#ripple-address),null | *Optional* The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key.
|
||||
requireAuthorization | boolean | *Optional* If set, this account must individually approve other users in order for those users to hold this account’s issuances.
|
||||
requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag.
|
||||
signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.
|
||||
*signers.* threshold | integer | *Optional* A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`.
|
||||
*signers.* weights | array | *Optional* Weights of signatures for each signer.
|
||||
*signers.* weights[] | object | An association of an address and a weight.
|
||||
*signers.weights[].* address | [address](#ripple-address) | A Ripple account address
|
||||
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
|
||||
transferRate | number,null | *Optional* The fee to charge when users transfer this account’s issuances, represented as billionths of a unit. Use `null` to set no fee.
|
||||
|
||||
### Example
|
||||
@@ -3282,7 +3295,7 @@ return api.prepareSuspendedPaymentExecution(address, suspendedPaymentExecution).
|
||||
|
||||
## sign
|
||||
|
||||
`sign(txJSON: string, secret: string): {signedTransaction: string, id: string}`
|
||||
`sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}`
|
||||
|
||||
Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit).
|
||||
|
||||
@@ -3292,6 +3305,8 @@ Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
txJSON | string | Transaction represented as a JSON string in rippled format.
|
||||
secret | secret string | The secret of the account that is initiating the transaction.
|
||||
options | object | *Optional* Options that control the type of signature that will be generated.
|
||||
*options.* signAs | [address](#ripple-address) | *Optional* The account that the signature should count for in multisigning.
|
||||
|
||||
### Return Value
|
||||
|
||||
@@ -3319,6 +3334,44 @@ return api.sign(txJSON, secret);
|
||||
```
|
||||
|
||||
|
||||
## combine
|
||||
|
||||
`combine(signedTransactions: Array<string>): {signedTransaction: string, id: string}`
|
||||
|
||||
Combines signed transactions from multiple accounts for a multisignature transaction. The signed transaction must subsequently be [submitted](#submit).
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
signedTransactions | array\<string\> | An array of signed transactions (from the output of [sign](#sign)) to combine.
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
signedTransaction | string | The signed transaction represented as an uppercase hexadecimal string.
|
||||
id | [id](#transaction-id) | The [Transaction ID](#transaction-id) of the signed transaction.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const signedTransactions = [ "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1",
|
||||
"12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1F1" ];
|
||||
return api.combine(signedTransactions);
|
||||
```
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"signedTransaction": "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1",
|
||||
"id": "8A3BFD2214B4C8271ED62648FCE9ADE4EE82EF01827CF7D1F7ED497549A368CC"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## submit
|
||||
|
||||
`submit(signedTransaction: string): Promise<Object>`
|
||||
|
||||
24
docs/src/combine.md.ejs
Normal file
24
docs/src/combine.md.ejs
Normal file
@@ -0,0 +1,24 @@
|
||||
## combine
|
||||
|
||||
`combine(signedTransactions: Array<string>): {signedTransaction: string, id: string}`
|
||||
|
||||
Combines signed transactions from multiple accounts for a multisignature transaction. The signed transaction must subsequently be [submitted](#submit).
|
||||
|
||||
### Parameters
|
||||
|
||||
<%- renderSchema("input/combine.json") %>
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
|
||||
<%- renderSchema("output/sign.json") %>
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const signedTransactions = <%- importFile('test/fixtures/requests/combine.json') %>;
|
||||
return api.combine(signedTransactions);
|
||||
```
|
||||
|
||||
<%- renderFixture("responses/combine.json") %>
|
||||
@@ -31,6 +31,7 @@
|
||||
<% include prepareSuspendedPaymentCancellation.md.ejs %>
|
||||
<% include prepareSuspendedPaymentExecution.md.ejs %>
|
||||
<% include sign.md.ejs %>
|
||||
<% include combine.md.ejs %>
|
||||
<% include submit.md.ejs %>
|
||||
<% include generateAddress.md.ejs %>
|
||||
<% include computeLedgerHash.md.ejs %>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## sign
|
||||
|
||||
`sign(txJSON: string, secret: string): {signedTransaction: string, id: string}`
|
||||
`sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}`
|
||||
|
||||
Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit).
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Executing a transaction with `RippleAPI` requires the following four steps:
|
||||
* [prepareSuspendedPaymentCreation](#preparesuspendedpaymentcreation)
|
||||
* [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation)
|
||||
* [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution)
|
||||
2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place.
|
||||
2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place. For multisignature transactions, the `signedTransaction` fields returned by `sign` must be collected and passed to the [combine](#combine) method.
|
||||
3. [Submit](#submit) - Submit the transaction to the connected server.
|
||||
4. Verify - Verify that the transaction got validated by querying with [getTransaction](#gettransaction). This is necessary because transactions may fail even if they were successfully submitted.
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ const prepareSuspendedPaymentCancellation =
|
||||
require('./transaction/suspended-payment-cancellation');
|
||||
const prepareSettings = require('./transaction/settings');
|
||||
const sign = require('./transaction/sign');
|
||||
const combine = require('./transaction/combine');
|
||||
const submit = require('./transaction/submit');
|
||||
const errors = require('./common').errors;
|
||||
const generateAddress =
|
||||
@@ -125,6 +126,7 @@ _.assign(RippleAPI.prototype, {
|
||||
prepareSuspendedPaymentCancellation,
|
||||
prepareSettings,
|
||||
sign,
|
||||
combine,
|
||||
submit,
|
||||
|
||||
generateAddress,
|
||||
|
||||
@@ -94,7 +94,8 @@ function loadSchemas() {
|
||||
require('./schemas/input/compute-ledger-hash'),
|
||||
require('./schemas/input/sign.json'),
|
||||
require('./schemas/input/submit.json'),
|
||||
require('./schemas/input/generate-address.json')
|
||||
require('./schemas/input/generate-address.json'),
|
||||
require('./schemas/input/combine.json')
|
||||
];
|
||||
const titles = _.map(schemas, schema => schema.title);
|
||||
const duplicates = _.keys(_.pick(_.countBy(titles), count => count > 1));
|
||||
|
||||
19
src/common/schemas/input/combine.json
Normal file
19
src/common/schemas/input/combine.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "combineParameters",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"signedTransactions": {
|
||||
"type": "array",
|
||||
"description": "An array of signed transactions (from the output of [sign](#sign)) to combine.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-F0-9]+$",
|
||||
"description": "A single-signed transaction represented as an uppercase hexadecimal string (from the output of [sign](#sign))"
|
||||
},
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["signedTransactions"]
|
||||
}
|
||||
@@ -11,6 +11,17 @@
|
||||
"type": "string",
|
||||
"format": "secret",
|
||||
"description": "The secret of the account that is initiating the transaction."
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options that control the type of signature that will be generated.",
|
||||
"properties": {
|
||||
"signAs": {
|
||||
"$ref": "address",
|
||||
"description": "The account that the signature should count for in multisigning."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -68,6 +68,35 @@
|
||||
],
|
||||
"description": "The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key."
|
||||
},
|
||||
"signers": {
|
||||
"type": "object",
|
||||
"description": "Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.",
|
||||
"properties": {
|
||||
"threshold": {
|
||||
"$ref": "uint32",
|
||||
"description": "A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`."
|
||||
},
|
||||
"weights": {
|
||||
"type": "array",
|
||||
"description": "Weights of signatures for each signer.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "An association of an address and a weight.",
|
||||
"properties": {
|
||||
"address": {"$ref": "address"},
|
||||
"weight": {
|
||||
"$ref": "uint32",
|
||||
"description": "The weight that the signature of this account counts as towards the threshold."
|
||||
}
|
||||
},
|
||||
"required": ["address", "weight"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
"memos": {"$ref": "memos"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -47,6 +47,7 @@ module.exports = {
|
||||
prepareSuspendedPaymentExecution: _.partial(schemaValidate,
|
||||
'prepareSuspendedPaymentExecutionParameters'),
|
||||
sign: _.partial(schemaValidate, 'signParameters'),
|
||||
combine: _.partial(schemaValidate, 'combineParameters'),
|
||||
submit: _.partial(schemaValidate, 'submitParameters'),
|
||||
computeLedgerHash: _.partial(schemaValidate, 'computeLedgerHashParameters'),
|
||||
generateAddress: _.partial(schemaValidate, 'generateAddressParameters'),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const BigNumber = require('bignumber.js');
|
||||
const AccountFields = require('./utils').constants.AccountFields;
|
||||
|
||||
@@ -22,6 +23,26 @@ function parseFields(data: Object): Object {
|
||||
settings[info.name] = parseField(info, fieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.RegularKey) {
|
||||
settings.regularKey = data.RegularKey;
|
||||
}
|
||||
|
||||
// TODO: this isn't implemented in rippled yet, may have to change this later
|
||||
if (data.SignerQuorum || data.SignerEntries) {
|
||||
settings.signers = {};
|
||||
if (data.SignerQuorum) {
|
||||
settings.signers.threshold = data.SignerQuorum;
|
||||
}
|
||||
if (data.SignerEntries) {
|
||||
settings.signers.weights = _.map(data.SignerEntries, entry => {
|
||||
return {
|
||||
address: entry.SignerEntry.Account,
|
||||
weight: entry.SignerEntry.SignerWeight
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,10 +52,10 @@ function parseFlags(tx: Object) {
|
||||
|
||||
function parseSettings(tx: Object) {
|
||||
const txType = tx.TransactionType;
|
||||
assert(txType === 'AccountSet' || txType === 'SetRegularKey');
|
||||
assert(txType === 'AccountSet' || txType === 'SetRegularKey' ||
|
||||
txType === 'SignerListSet');
|
||||
|
||||
const regularKey = tx.RegularKey ? {regularKey: tx.RegularKey} : {};
|
||||
return _.assign(regularKey, parseFlags(tx), parseFields(tx));
|
||||
return _.assign({}, parseFlags(tx), parseFields(tx));
|
||||
}
|
||||
|
||||
module.exports = parseSettings;
|
||||
|
||||
@@ -22,7 +22,8 @@ function parseTransactionType(type) {
|
||||
SetRegularKey: 'settings',
|
||||
SuspendedPaymentCreate: 'suspendedPaymentCreation',
|
||||
SuspendedPaymentFinish: 'suspendedPaymentExecution',
|
||||
SuspendedPaymentCancel: 'suspendedPaymentCancellation'
|
||||
SuspendedPaymentCancel: 'suspendedPaymentCancellation',
|
||||
SignerListSet: 'settings'
|
||||
};
|
||||
return mapping[type] || null;
|
||||
}
|
||||
|
||||
39
src/transaction/combine.js
Normal file
39
src/transaction/combine.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const binary = require('ripple-binary-codec');
|
||||
const utils = require('./utils');
|
||||
const BigNumber = require('bignumber.js');
|
||||
const {decodeAddress} = require('ripple-address-codec');
|
||||
const {validate} = utils.common;
|
||||
const {computeBinaryTransactionHash} = require('ripple-hashes');
|
||||
|
||||
function addressToBigNumber(address) {
|
||||
const hex = (new Buffer(decodeAddress(address))).toString('hex');
|
||||
return new BigNumber(hex, 16);
|
||||
}
|
||||
|
||||
function compareSigners(a, b) {
|
||||
return addressToBigNumber(a.Signer.Account)
|
||||
.comparedTo(addressToBigNumber(b.Signer.Account));
|
||||
}
|
||||
|
||||
function combine(signedTransactions: Array<string>): Object {
|
||||
validate.combine({signedTransactions});
|
||||
|
||||
const txs = _.map(signedTransactions, binary.decode);
|
||||
const tx = _.omit(txs[0], 'Signers');
|
||||
if (!_.every(txs, _tx => _.isEqual(tx, _.omit(_tx, 'Signers')))) {
|
||||
throw new utils.common.errors.ValidationError(
|
||||
'txJSON is not the same for all signedTransactions');
|
||||
}
|
||||
const unsortedSigners = _.reduce(txs, (accumulator, _tx) =>
|
||||
accumulator.concat(_tx.Signers || []), []);
|
||||
const signers = unsortedSigners.sort(compareSigners);
|
||||
const signedTx = _.assign({}, tx, {Signers: signers});
|
||||
const signedTransaction = binary.encode(signedTx);
|
||||
const id = computeBinaryTransactionHash(signedTransaction);
|
||||
return {signedTransaction, id};
|
||||
}
|
||||
|
||||
module.exports = combine;
|
||||
@@ -70,7 +70,17 @@ function convertTransferRate(transferRate: number | string): number | string {
|
||||
return (new BigNumber(transferRate)).shift(9).toNumber();
|
||||
}
|
||||
|
||||
function createSettingsTransaction(account: string, settings: Settings
|
||||
function formatSignerEntry(signer: Object): Object {
|
||||
return {
|
||||
SignerEntry: {
|
||||
Account: signer.address,
|
||||
SignerWeight: signer.weight
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createSettingsTransactionWithoutMemos(
|
||||
account: string, settings: Settings
|
||||
): Object {
|
||||
if (settings.regularKey !== undefined) {
|
||||
const removeRegularKey = {
|
||||
@@ -83,15 +93,20 @@ function createSettingsTransaction(account: string, settings: Settings
|
||||
return _.assign({}, removeRegularKey, {RegularKey: settings.regularKey});
|
||||
}
|
||||
|
||||
if (settings.signers !== undefined) {
|
||||
return {
|
||||
TransactionType: 'SignerListSet',
|
||||
Account: account,
|
||||
SignerQuorum: settings.signers.threshold,
|
||||
SignerEntries: _.map(settings.signers.weights, formatSignerEntry)
|
||||
};
|
||||
}
|
||||
|
||||
const txJSON: Object = {
|
||||
TransactionType: 'AccountSet',
|
||||
Account: account
|
||||
};
|
||||
|
||||
if (settings.memos !== undefined) {
|
||||
txJSON.Memos = _.map(settings.memos, utils.convertMemo);
|
||||
}
|
||||
|
||||
setTransactionFlags(txJSON, _.omit(settings, 'memos'));
|
||||
setTransactionFields(txJSON, settings);
|
||||
|
||||
@@ -101,6 +116,15 @@ function createSettingsTransaction(account: string, settings: Settings
|
||||
return txJSON;
|
||||
}
|
||||
|
||||
function createSettingsTransaction(account: string, settings: Settings
|
||||
): Object {
|
||||
const txJSON = createSettingsTransactionWithoutMemos(account, settings);
|
||||
if (settings.memos !== undefined) {
|
||||
txJSON.Memos = _.map(settings.memos, utils.convertMemo);
|
||||
}
|
||||
return txJSON;
|
||||
}
|
||||
|
||||
function prepareSettings(address: string, settings: Settings,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
|
||||
@@ -6,23 +6,38 @@ const binary = require('ripple-binary-codec');
|
||||
const {computeBinaryTransactionHash} = require('ripple-hashes');
|
||||
const validate = utils.common.validate;
|
||||
|
||||
function computeSignature(txJSON, privateKey) {
|
||||
const signingData = binary.encodeForSigning(txJSON);
|
||||
function computeSignature(tx: Object, privateKey: string, signAs: ?string) {
|
||||
const signingData = signAs ?
|
||||
binary.encodeForMultisigning(tx, signAs) : binary.encodeForSigning(tx);
|
||||
return keypairs.sign(signingData, privateKey);
|
||||
}
|
||||
|
||||
function sign(txJSON: string, secret: string
|
||||
function sign(txJSON: string, secret: string, options: Object = {}
|
||||
): {signedTransaction: string; id: string} {
|
||||
validate.sign({txJSON, secret});
|
||||
// we can't validate that the secret matches the account because
|
||||
// the secret could correspond to the regular key
|
||||
|
||||
const tx = JSON.parse(txJSON);
|
||||
const keypair = keypairs.deriveKeypair(secret);
|
||||
if (tx.SigningPubKey === undefined) {
|
||||
tx.SigningPubKey = keypair.publicKey;
|
||||
if (tx.TxnSignature || tx.Signers) {
|
||||
throw new utils.common.errors.ValidationError(
|
||||
'txJSON must not contain "TxnSignature" or "Signers" properties');
|
||||
}
|
||||
tx.TxnSignature = computeSignature(tx, keypair.privateKey);
|
||||
|
||||
const keypair = keypairs.deriveKeypair(secret);
|
||||
tx.SigningPubKey = options.signAs ? '' : keypair.publicKey;
|
||||
|
||||
if (options.signAs) {
|
||||
const signer = {
|
||||
Account: options.signAs,
|
||||
SigningPubKey: keypair.publicKey,
|
||||
TxnSignature: computeSignature(tx, keypair.privateKey, options.signAs)
|
||||
};
|
||||
tx.Signers = [{Signer: signer}];
|
||||
} else {
|
||||
tx.TxnSignature = computeSignature(tx, keypair.privateKey);
|
||||
}
|
||||
|
||||
const serialized = binary.encode(tx);
|
||||
return {
|
||||
signedTransaction: serialized,
|
||||
|
||||
@@ -3,15 +3,7 @@
|
||||
const _ = require('lodash');
|
||||
const utils = require('./utils');
|
||||
const {validate} = utils.common;
|
||||
|
||||
type Submit = {
|
||||
success: boolean,
|
||||
engineResult: string,
|
||||
engineResultCode: number,
|
||||
engineResultMessage?: string,
|
||||
txBlob?: string,
|
||||
txJson?: Object
|
||||
}
|
||||
import type {Submit} from './types.js';
|
||||
|
||||
function isImmediateRejection(engineResult: string): boolean {
|
||||
// note: "tel" errors mean the local server refused to process the
|
||||
@@ -23,7 +15,7 @@ function isImmediateRejection(engineResult: string): boolean {
|
||||
return _.startsWith(engineResult, 'tem') || _.startsWith(engineResult, 'tej');
|
||||
}
|
||||
|
||||
function formatResponse(response) {
|
||||
function formatSubmitResponse(response) {
|
||||
const data = {
|
||||
resultCode: response.engine_result,
|
||||
resultMessage: response.engine_result_message
|
||||
@@ -36,11 +28,12 @@ function formatResponse(response) {
|
||||
|
||||
function submit(signedTransaction: string): Promise<Submit> {
|
||||
validate.submit({signedTransaction});
|
||||
|
||||
const request = {
|
||||
command: 'submit',
|
||||
tx_blob: signedTransaction
|
||||
};
|
||||
return this.connection.request(request).then(formatResponse);
|
||||
return this.connection.request(request).then(formatSubmitResponse);
|
||||
}
|
||||
|
||||
module.exports = submit;
|
||||
|
||||
@@ -17,3 +17,12 @@ export type Prepare = {
|
||||
maxLedgerVersion?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type Submit = {
|
||||
success: boolean,
|
||||
engineResult: string,
|
||||
engineResultCode: number,
|
||||
engineResultMessage?: string,
|
||||
txBlob?: string,
|
||||
txJson?: Object
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ function setCanonicalFlag(txJSON) {
|
||||
txJSON.Flags = txJSON.Flags >>> 0;
|
||||
}
|
||||
|
||||
function scaleValue(value, multiplier) {
|
||||
return (new BigNumber(value)).times(multiplier).toString();
|
||||
}
|
||||
|
||||
function prepareTransaction(txJSON: Object, api: Object,
|
||||
instructions: Instructions
|
||||
): Promise<Prepare> {
|
||||
@@ -51,8 +55,9 @@ function prepareTransaction(txJSON: Object, api: Object,
|
||||
}
|
||||
|
||||
function prepareFee(): Promise<Object> {
|
||||
const multiplier = (txJSON.Signers || []).length + 1;
|
||||
if (instructions.fee !== undefined) {
|
||||
txJSON.Fee = common.xrpToDrops(instructions.fee);
|
||||
txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier);
|
||||
return Promise.resolve(txJSON);
|
||||
}
|
||||
const cushion = api._feeCushion;
|
||||
@@ -60,9 +65,10 @@ function prepareTransaction(txJSON: Object, api: Object,
|
||||
const feeDrops = common.xrpToDrops(fee);
|
||||
if (instructions.maxFee !== undefined) {
|
||||
const maxFeeDrops = common.xrpToDrops(instructions.maxFee);
|
||||
txJSON.Fee = BigNumber.min(feeDrops, maxFeeDrops).toString();
|
||||
const normalFee = BigNumber.min(feeDrops, maxFeeDrops).toString();
|
||||
txJSON.Fee = scaleValue(normalFee, multiplier);
|
||||
} else {
|
||||
txJSON.Fee = feeDrops;
|
||||
txJSON.Fee = scaleValue(feeDrops, multiplier);
|
||||
}
|
||||
return txJSON;
|
||||
});
|
||||
|
||||
@@ -183,20 +183,20 @@ describe('RippleAPI', function() {
|
||||
|
||||
it('prepareSettings', function() {
|
||||
return this.api.prepareSettings(
|
||||
address, requests.prepareSettings, instructions).then(
|
||||
address, requests.prepareSettings.domain, instructions).then(
|
||||
_.partial(checkResult, responses.prepareSettings.flags, 'prepare'));
|
||||
});
|
||||
|
||||
it('prepareSettings - no maxLedgerVersion', function() {
|
||||
return this.api.prepareSettings(
|
||||
address, requests.prepareSettings, {maxLedgerVersion: null}).then(
|
||||
address, requests.prepareSettings.domain, {maxLedgerVersion: null}).then(
|
||||
_.partial(checkResult, responses.prepareSettings.noMaxLedgerVersion,
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareSettings - no instructions', function() {
|
||||
return this.api.prepareSettings(
|
||||
address, requests.prepareSettings).then(
|
||||
address, requests.prepareSettings.domain).then(
|
||||
_.partial(
|
||||
checkResult,
|
||||
responses.prepareSettings.noInstructions,
|
||||
@@ -244,6 +244,13 @@ describe('RippleAPI', function() {
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareSettings - set signers', function() {
|
||||
const settings = requests.prepareSettings.signers;
|
||||
return this.api.prepareSettings(address, settings, instructions).then(
|
||||
_.partial(checkResult, responses.prepareSettings.signers,
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareSuspendedPaymentCreation', function() {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
@@ -312,6 +319,14 @@ describe('RippleAPI', function() {
|
||||
schemaValidator.schemaValidate('sign', result);
|
||||
});
|
||||
|
||||
it('sign - signAs', function() {
|
||||
const txJSON = requests.sign.signAs;
|
||||
const secret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb';
|
||||
const signature = this.api.sign(JSON.stringify(txJSON), secret,
|
||||
{signAs: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'});
|
||||
assert.deepEqual(signature, responses.sign.signAs);
|
||||
});
|
||||
|
||||
it('submit', function() {
|
||||
return this.api.submit(responses.sign.normal.signedTransaction).then(
|
||||
_.partial(checkResult, responses.submit, 'submit'));
|
||||
@@ -326,6 +341,11 @@ describe('RippleAPI', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('combine', function() {
|
||||
const combined = this.api.combine(requests.combine.setDomain);
|
||||
checkResult(responses.combine.single, 'sign', combined);
|
||||
});
|
||||
|
||||
describe('RippleAPI', function() {
|
||||
|
||||
it('getBalances', function() {
|
||||
@@ -1255,7 +1275,7 @@ describe('RippleAPI - offline', function() {
|
||||
it('prepareSettings and sign', function() {
|
||||
const api = new RippleAPI();
|
||||
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
||||
const settings = requests.prepareSettings;
|
||||
const settings = requests.prepareSettings.domain;
|
||||
const instructions = {
|
||||
sequence: 23,
|
||||
maxLedgerVersion: 8820051,
|
||||
|
||||
2
test/fixtures/requests/combine.json
vendored
Normal file
2
test/fixtures/requests/combine.json
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[ "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1",
|
||||
"12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1F1" ]
|
||||
11
test/fixtures/requests/index.js
vendored
11
test/fixtures/requests/index.js
vendored
@@ -20,7 +20,10 @@ module.exports = {
|
||||
allOptions: require('./prepare-payment-all-options'),
|
||||
noCounterparty: require('./prepare-payment-no-counterparty')
|
||||
},
|
||||
prepareSettings: require('./prepare-settings'),
|
||||
prepareSettings: {
|
||||
domain: require('./prepare-settings'),
|
||||
signers: require('./prepare-settings-signers')
|
||||
},
|
||||
prepareSuspendedPaymentCreation: {
|
||||
normal: require('./prepare-suspended-payment-creation'),
|
||||
full: require('./prepare-suspended-payment-creation-full')
|
||||
@@ -40,7 +43,8 @@ module.exports = {
|
||||
},
|
||||
sign: {
|
||||
normal: require('./sign'),
|
||||
suspended: require('./sign-suspended.json')
|
||||
suspended: require('./sign-suspended.json'),
|
||||
signAs: require('./sign-as')
|
||||
},
|
||||
getPaths: {
|
||||
normal: require('./getpaths/normal'),
|
||||
@@ -61,5 +65,8 @@ module.exports = {
|
||||
computeLedgerHash: {
|
||||
header: require('./compute-ledger-hash'),
|
||||
transactions: require('./compute-ledger-hash-transactions')
|
||||
},
|
||||
combine: {
|
||||
setDomain: require('./combine.json')
|
||||
}
|
||||
};
|
||||
|
||||
19
test/fixtures/requests/prepare-settings-signers.json
vendored
Normal file
19
test/fixtures/requests/prepare-settings-signers.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"signers": {
|
||||
"threshold": 2,
|
||||
"weights": [
|
||||
{
|
||||
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"address": "rwBYyfufTzk77zUSKEu4MvixfarC35av1J",
|
||||
"weight": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
8
test/fixtures/requests/sign-as.json
vendored
Normal file
8
test/fixtures/requests/sign-as.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"Amount": "1000000000",
|
||||
"Destination": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
"Fee": "50",
|
||||
"Sequence": 2,
|
||||
"TransactionType": "Payment"
|
||||
}
|
||||
4
test/fixtures/responses/combine.json
vendored
Normal file
4
test/fixtures/responses/combine.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"signedTransaction": "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1",
|
||||
"id": "8A3BFD2214B4C8271ED62648FCE9ADE4EE82EF01827CF7D1F7ED497549A368CC"
|
||||
}
|
||||
9
test/fixtures/responses/index.js
vendored
9
test/fixtures/responses/index.js
vendored
@@ -87,7 +87,8 @@ module.exports = {
|
||||
fieldClear: require('./prepare-settings-field-clear.json'),
|
||||
noInstructions: require('./prepare-settings-no-instructions.json'),
|
||||
signed: require('./prepare-settings-signed.json'),
|
||||
noMaxLedgerVersion: require('./prepare-settings-no-maxledgerversion.json')
|
||||
noMaxLedgerVersion: require('./prepare-settings-no-maxledgerversion.json'),
|
||||
signers: require('./prepare-settings-signers.json')
|
||||
},
|
||||
prepareSuspendedPaymentCreation: {
|
||||
normal: require('./prepare-suspended-payment-creation'),
|
||||
@@ -108,7 +109,11 @@ module.exports = {
|
||||
},
|
||||
sign: {
|
||||
normal: require('./sign.json'),
|
||||
suspended: require('./sign-suspended.json')
|
||||
suspended: require('./sign-suspended.json'),
|
||||
signAs: require('./sign-as')
|
||||
},
|
||||
combine: {
|
||||
single: require('./combine.json')
|
||||
},
|
||||
submit: require('./submit.json'),
|
||||
ledgerEvent: require('./ledger-event.json')
|
||||
|
||||
8
test/fixtures/responses/prepare-settings-signers.json
vendored
Normal file
8
test/fixtures/responses/prepare-settings-signers.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"txJSON": "{\"TransactionType\":\"SignerListSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SignerQuorum\":2,\"SignerEntries\":[{\"SignerEntry\":{\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SignerWeight\":1}},{\"SignerEntry\":{\"Account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"SignerWeight\":1}},{\"SignerEntry\":{\"Account\":\"rwBYyfufTzk77zUSKEu4MvixfarC35av1J\",\"SignerWeight\":1}}],\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
|
||||
"instructions": {
|
||||
"fee": "0.000012",
|
||||
"sequence": 23,
|
||||
"maxLedgerVersion": 8820051
|
||||
}
|
||||
}
|
||||
4
test/fixtures/responses/sign-as.json
vendored
Normal file
4
test/fixtures/responses/sign-as.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"signedTransaction": "120000240000000261400000003B9ACA00684000000000000032730081142E244E6F20104E57C0C60BD823CB312BF10928C78314B5F762798A53D543A014CAF8B297CFF8F2F937E8F3E01073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100BB6FC77F26BC88587204CAA79B2230C420D7EC937B8AC3A0CF9B0BE988BAB0D002203BF86893BA3B764375FFFAD9D54A4AAEDABD07C4D72ADB9C1B20C10B4DD712898114B5F762798A53D543A014CAF8B297CFF8F2F937E8E1F1",
|
||||
"id": "AB7632D7C07E591658635CED6A5DDE832B22CA066907CB131DEFAAA925B98185"
|
||||
}
|
||||
@@ -110,7 +110,7 @@ describe('http server integration tests', function() {
|
||||
'prepareSettings',
|
||||
[
|
||||
{address},
|
||||
{settings: apiRequests.prepareSettings},
|
||||
{settings: apiRequests.prepareSettings.domain},
|
||||
{instructions: {
|
||||
maxFee: '0.000012',
|
||||
sequence: 23,
|
||||
|
||||
@@ -14,36 +14,45 @@ const {isValidSecret} = require('../../src/common');
|
||||
const TIMEOUT = 30000; // how long before each test case times out
|
||||
const INTERVAL = 1000; // how long to wait between checks for validated ledger
|
||||
|
||||
function acceptLedger(api) {
|
||||
return api.connection.request({command: 'ledger_accept'});
|
||||
}
|
||||
|
||||
function verifyTransaction(testcase, hash, type, options, txData) {
|
||||
function verifyTransaction(testcase, hash, type, options, txData, address) {
|
||||
console.log('VERIFY...');
|
||||
return testcase.api.getTransaction(hash, options).then(data => {
|
||||
assert(data && data.outcome);
|
||||
assert.strictEqual(data.type, type);
|
||||
assert.strictEqual(data.address, wallet.getAddress());
|
||||
assert.strictEqual(data.address, address);
|
||||
assert.strictEqual(data.outcome.result, 'tesSUCCESS');
|
||||
testcase.transactions.push(hash);
|
||||
if (testcase.transactions !== undefined) {
|
||||
testcase.transactions.push(hash);
|
||||
}
|
||||
return {txJSON: JSON.stringify(txData), id: hash, tx: data};
|
||||
}).catch(error => {
|
||||
if (error instanceof errors.PendingLedgerVersionError) {
|
||||
console.log('NOT VALIDATED YET...');
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => verifyTransaction(testcase, hash, type,
|
||||
options, txData).then(resolve, reject), INTERVAL);
|
||||
options, txData, address).then(resolve, reject), INTERVAL);
|
||||
});
|
||||
}
|
||||
console.log(error.stack);
|
||||
assert(false, 'Transaction not successful: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function testTransaction(testcase, type, lastClosedLedgerVersion, prepared) {
|
||||
function testTransaction(testcase, type, lastClosedLedgerVersion, prepared,
|
||||
address = wallet.getAddress(), secret = wallet.getSecret()) {
|
||||
const txJSON = prepared.txJSON;
|
||||
assert(txJSON, 'missing txJSON');
|
||||
const txData = JSON.parse(txJSON);
|
||||
assert.strictEqual(txData.Account, wallet.getAddress());
|
||||
const signedData = testcase.api.sign(txJSON, wallet.getSecret());
|
||||
assert.strictEqual(txData.Account, address);
|
||||
const signedData = testcase.api.sign(txJSON, secret);
|
||||
console.log('PREPARED...');
|
||||
return testcase.api.submit(signedData.signedTransaction).then(data => {
|
||||
return testcase.api.submit(signedData.signedTransaction)
|
||||
.then(data => testcase.test.title.indexOf('multisign') !== -1 ?
|
||||
acceptLedger(testcase.api).then(() => data) : data).then(data => {
|
||||
console.log('SUBMITTED...');
|
||||
assert.strictEqual(data.resultCode, 'tesSUCCESS');
|
||||
const options = {
|
||||
@@ -52,13 +61,13 @@ function testTransaction(testcase, type, lastClosedLedgerVersion, prepared) {
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => verifyTransaction(testcase, signedData.id, type,
|
||||
options, txData).then(resolve, reject), INTERVAL);
|
||||
options, txData, address).then(resolve, reject), INTERVAL);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setup() {
|
||||
this.api = new RippleAPI({server: 'wss://s1.ripple.com'});
|
||||
function setup(server = 'wss://s1.ripple.com') {
|
||||
this.api = new RippleAPI({server});
|
||||
console.log('CONNECTING...');
|
||||
return this.api.connect().then(() => {
|
||||
console.log('CONNECTED...');
|
||||
@@ -91,7 +100,7 @@ describe('integration tests', function() {
|
||||
it('settings', function() {
|
||||
return this.api.getLedgerVersion().then(ledgerVersion => {
|
||||
return this.api.prepareSettings(address,
|
||||
requests.prepareSettings, instructions).then(prepared =>
|
||||
requests.prepareSettings.domain, instructions).then(prepared =>
|
||||
testTransaction(this, 'settings', ledgerVersion, prepared));
|
||||
});
|
||||
});
|
||||
@@ -232,7 +241,7 @@ describe('integration tests', function() {
|
||||
it('getSettings', function() {
|
||||
return this.api.getSettings(address).then(data => {
|
||||
assert(data);
|
||||
assert.strictEqual(data.domain, requests.prepareSettings.domain);
|
||||
assert.strictEqual(data.domain, requests.prepareSettings.domain.domain);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -313,3 +322,80 @@ describe('integration tests', function() {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function createAccount(api, address) {
|
||||
const root = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
const secret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb';
|
||||
const amount = {
|
||||
currency: 'XRP',
|
||||
value: '10000'
|
||||
};
|
||||
return api.preparePayment(root, {
|
||||
source: {address: root, maxAmount: amount},
|
||||
destination: {address, amount}
|
||||
}).then(prepared => {
|
||||
return api.submit(api.sign(prepared.txJSON, secret).signedTransaction);
|
||||
}).then(() => {
|
||||
return acceptLedger(api);
|
||||
});
|
||||
}
|
||||
|
||||
describe.skip('integration tests - standalone rippled', function() {
|
||||
const instructions = {maxLedgerVersionOffset: 10, fee: '1'};
|
||||
this.timeout(TIMEOUT);
|
||||
|
||||
const url = 'ws://127.0.0.1:6006';
|
||||
// const url = 'wss://s.altnet.rippletest.net:51233';
|
||||
beforeEach(_.partial(setup, url));
|
||||
afterEach(teardown);
|
||||
const address = 'r5nx8ZkwEbFztnc8Qyi22DE9JYjRzNmvs';
|
||||
const secret = 'ss6F8381Br6wwpy9p582H8sBt19J3';
|
||||
const signer1address = 'rQDhz2ZNXmhxzCYwxU6qAbdxsHA4HV45Y2';
|
||||
const signer1secret = 'shK6YXzwYfnFVn3YZSaMh5zuAddKx';
|
||||
const signer2address = 'r3RtUvGw9nMoJ5FuHxuoVJvcENhKtuF9ud';
|
||||
const signer2secret = 'shUHQnL4EH27V4EiBrj6EfhWvZngF';
|
||||
|
||||
it('submit multisigned transaction', function() {
|
||||
const signers = {
|
||||
threshold: 2,
|
||||
weights: [
|
||||
{address: signer1address, weight: 1},
|
||||
{address: signer2address, weight: 1}
|
||||
]
|
||||
};
|
||||
let minLedgerVersion = null;
|
||||
return createAccount(this.api, address).then(() => {
|
||||
return this.api.getLedgerVersion().then(ledgerVersion => {
|
||||
minLedgerVersion = ledgerVersion;
|
||||
return this.api.prepareSettings(address, {signers}, instructions)
|
||||
.then(prepared => {
|
||||
return testTransaction(this, 'settings', ledgerVersion, prepared,
|
||||
address, secret);
|
||||
});
|
||||
});
|
||||
}).then(() => {
|
||||
return this.api.prepareSettings(
|
||||
address, {domain: 'example.com'}, instructions)
|
||||
.then(prepared => {
|
||||
const signed1 = this.api.sign(
|
||||
prepared.txJSON, signer1secret, {signAs: signer1address});
|
||||
const signed2 = this.api.sign(
|
||||
prepared.txJSON, signer2secret, {signAs: signer2address});
|
||||
const combined = this.api.combine([
|
||||
signed1.signedTransaction, signed2.signedTransaction
|
||||
]);
|
||||
return this.api.submit(combined.signedTransaction)
|
||||
.then(response => acceptLedger(this.api).then(() => response))
|
||||
.then(response => {
|
||||
assert.strictEqual(response.resultCode, 'tesSUCCESS');
|
||||
const options = {minLedgerVersion};
|
||||
return verifyTransaction(this, combined.id, 'settings',
|
||||
options, {}, address);
|
||||
}).catch(error => {
|
||||
console.log(error.message);
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -249,6 +249,11 @@ module.exports = function(port) {
|
||||
}
|
||||
});
|
||||
|
||||
mock.on('request_submit_multisigned', function(request, conn) {
|
||||
assert.strictEqual(request.command, 'submit_multisigned');
|
||||
conn.send(createResponse(request, fixtures.submit.success));
|
||||
});
|
||||
|
||||
mock.on('request_account_lines', function(request, conn) {
|
||||
if (request.account === addresses.ACCOUNT) {
|
||||
conn.send(accountLinesResponse.normal(request));
|
||||
|
||||
Reference in New Issue
Block a user