mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-08 23:05:49 +00:00
4779 lines
190 KiB
JavaScript
4779 lines
190 KiB
JavaScript
/* eslint-disable max-nested-callbacks */
|
|
'use strict'; // eslint-disable-line
|
|
const _ = require('lodash');
|
|
const assert = require('assert-diff');
|
|
const setupAPI = require('./setup-api');
|
|
const RippleAPI = require('ripple-api').RippleAPI;
|
|
const validate = RippleAPI._PRIVATE.validate;
|
|
const fixtures = require('./fixtures');
|
|
const requests = fixtures.requests;
|
|
const responses = fixtures.responses;
|
|
const addresses = require('./fixtures/addresses');
|
|
const hashes = require('./fixtures/hashes');
|
|
const address = addresses.ACCOUNT;
|
|
const utils = RippleAPI._PRIVATE.ledgerUtils;
|
|
const ledgerClosed = require('./fixtures/rippled/ledger-close-newer');
|
|
const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
|
|
const binary = require('ripple-binary-codec');
|
|
const BigNumber = require('bignumber.js');
|
|
assert.options.strict = true;
|
|
|
|
// how long before each test case times out
|
|
const TIMEOUT = 20000;
|
|
|
|
function unused() {
|
|
}
|
|
|
|
function closeLedger(connection) {
|
|
connection._ws.emit('message', JSON.stringify(ledgerClosed));
|
|
}
|
|
|
|
function checkResult(expected, schemaName, response) {
|
|
if (expected.txJSON) {
|
|
assert(response.txJSON);
|
|
assert.deepEqual(JSON.parse(response.txJSON), JSON.parse(expected.txJSON));
|
|
}
|
|
if (expected.tx_json) {
|
|
assert(response.tx_json);
|
|
assert.deepEqual(response.tx_json, expected.tx_json);
|
|
}
|
|
assert.deepEqual(_.omit(response, 'txJSON'), _.omit(expected, 'txJSON'), _.omit(response, 'tx_json'), _.omit(response, 'tx_json'));
|
|
if (schemaName) {
|
|
schemaValidator.schemaValidate(schemaName, response);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
|
|
describe('RippleAPI', function () {
|
|
this.timeout(TIMEOUT);
|
|
const instructionsWithMaxLedgerVersionOffset = { maxLedgerVersionOffset: 100 };
|
|
beforeEach(setupAPI.setup);
|
|
afterEach(setupAPI.teardown);
|
|
|
|
it('error inspect', function () {
|
|
const error = new this.api.errors.RippleError('mess', { data: 1 });
|
|
assert.strictEqual(error.inspect(), '[RippleError(mess, { data: 1 })]');
|
|
});
|
|
|
|
describe('xrpToDrops', function () {
|
|
it('works with a typical amount', function () {
|
|
const drops = this.api.xrpToDrops('2')
|
|
assert.strictEqual(drops, '2000000', '2 XRP equals 2 million drops')
|
|
})
|
|
|
|
it('works with fractions', function () {
|
|
let drops = this.api.xrpToDrops('3.456789')
|
|
assert.strictEqual(drops, '3456789', '3.456789 XRP equals 3,456,789 drops')
|
|
|
|
drops = this.api.xrpToDrops('3.400000')
|
|
assert.strictEqual(drops, '3400000', '3.400000 XRP equals 3,400,000 drops')
|
|
|
|
drops = this.api.xrpToDrops('0.000001')
|
|
assert.strictEqual(drops, '1', '0.000001 XRP equals 1 drop')
|
|
|
|
drops = this.api.xrpToDrops('0.0000010')
|
|
assert.strictEqual(drops, '1', '0.0000010 XRP equals 1 drop')
|
|
})
|
|
|
|
it('works with zero', function () {
|
|
let drops = this.api.xrpToDrops('0')
|
|
assert.strictEqual(drops, '0', '0 XRP equals 0 drops')
|
|
|
|
// negative zero is equivalent to zero
|
|
drops = this.api.xrpToDrops('-0')
|
|
assert.strictEqual(drops, '0', '-0 XRP equals 0 drops')
|
|
|
|
drops = this.api.xrpToDrops('0.000000')
|
|
assert.strictEqual(drops, '0', '0.000000 XRP equals 0 drops')
|
|
|
|
drops = this.api.xrpToDrops('0.0000000')
|
|
assert.strictEqual(drops, '0', '0.0000000 XRP equals 0 drops')
|
|
})
|
|
|
|
it('works with a negative value', function () {
|
|
const drops = this.api.xrpToDrops('-2')
|
|
assert.strictEqual(drops, '-2000000', '-2 XRP equals -2 million drops')
|
|
})
|
|
|
|
it('works with a value ending with a decimal point', function () {
|
|
let drops = this.api.xrpToDrops('2.')
|
|
assert.strictEqual(drops, '2000000', '2. XRP equals 2000000 drops')
|
|
|
|
drops = this.api.xrpToDrops('-2.')
|
|
assert.strictEqual(drops, '-2000000', '-2. XRP equals -2000000 drops')
|
|
})
|
|
|
|
it('works with BigNumber objects', function () {
|
|
let drops = this.api.xrpToDrops(new BigNumber(2))
|
|
assert.strictEqual(drops, '2000000', '(BigNumber) 2 XRP equals 2 million drops')
|
|
|
|
drops = this.api.xrpToDrops(new BigNumber(-2))
|
|
assert.strictEqual(drops, '-2000000', '(BigNumber) -2 XRP equals -2 million drops')
|
|
})
|
|
|
|
it('works with a number', function() {
|
|
// This is not recommended. Use strings or BigNumber objects to avoid precision errors.
|
|
|
|
let drops = this.api.xrpToDrops(2)
|
|
assert.strictEqual(drops, '2000000', '(number) 2 XRP equals 2 million drops')
|
|
|
|
drops = this.api.xrpToDrops(-2)
|
|
assert.strictEqual(drops, '-2000000', '(number) -2 XRP equals -2 million drops')
|
|
})
|
|
|
|
it('throws with an amount with too many decimal places', function () {
|
|
assert.throws(() => {
|
|
this.api.xrpToDrops('1.1234567')
|
|
}, /has too many decimal places/)
|
|
|
|
assert.throws(() => {
|
|
this.api.xrpToDrops('0.0000001')
|
|
}, /has too many decimal places/)
|
|
})
|
|
|
|
it('throws with an invalid value', function () {
|
|
assert.throws(() => {
|
|
this.api.xrpToDrops('FOO')
|
|
}, /invalid value/)
|
|
|
|
assert.throws(() => {
|
|
this.api.xrpToDrops('1e-7')
|
|
}, /invalid value/)
|
|
|
|
assert.throws(() => {
|
|
this.api.xrpToDrops('2,0')
|
|
}, /invalid value/)
|
|
|
|
assert.throws(() => {
|
|
this.api.xrpToDrops('.')
|
|
}, /xrpToDrops\: invalid value '\.', should be a BigNumber or string-encoded number\./)
|
|
})
|
|
|
|
it('throws with an amount more than one decimal point', function () {
|
|
assert.throws(() => {
|
|
this.api.xrpToDrops('1.0.0')
|
|
}, /xrpToDrops:\ invalid\ value\ '1\.0\.0'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
|
|
|
|
assert.throws(() => {
|
|
this.api.xrpToDrops('...')
|
|
}, /xrpToDrops:\ invalid\ value\ '\.\.\.'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
|
|
})
|
|
})
|
|
|
|
describe('dropsToXrp', function () {
|
|
it('works with a typical amount', function () {
|
|
const xrp = this.api.dropsToXrp('2000000')
|
|
assert.strictEqual(xrp, '2', '2 million drops equals 2 XRP')
|
|
})
|
|
|
|
it('works with fractions', function () {
|
|
let xrp = this.api.dropsToXrp('3456789')
|
|
assert.strictEqual(xrp, '3.456789', '3,456,789 drops equals 3.456789 XRP')
|
|
|
|
xrp = this.api.dropsToXrp('3400000')
|
|
assert.strictEqual(xrp, '3.4', '3,400,000 drops equals 3.4 XRP')
|
|
|
|
xrp = this.api.dropsToXrp('1')
|
|
assert.strictEqual(xrp, '0.000001', '1 drop equals 0.000001 XRP')
|
|
|
|
xrp = this.api.dropsToXrp('1.0')
|
|
assert.strictEqual(xrp, '0.000001', '1.0 drops equals 0.000001 XRP')
|
|
|
|
xrp = this.api.dropsToXrp('1.00')
|
|
assert.strictEqual(xrp, '0.000001', '1.00 drops equals 0.000001 XRP')
|
|
})
|
|
|
|
it('works with zero', function () {
|
|
let xrp = this.api.dropsToXrp('0')
|
|
assert.strictEqual(xrp, '0', '0 drops equals 0 XRP')
|
|
|
|
// negative zero is equivalent to zero
|
|
xrp = this.api.dropsToXrp('-0')
|
|
assert.strictEqual(xrp, '0', '-0 drops equals 0 XRP')
|
|
|
|
xrp = this.api.dropsToXrp('0.00')
|
|
assert.strictEqual(xrp, '0', '0.00 drops equals 0 XRP')
|
|
|
|
xrp = this.api.dropsToXrp('000000000')
|
|
assert.strictEqual(xrp, '0', '000000000 drops equals 0 XRP')
|
|
})
|
|
|
|
it('works with a negative value', function () {
|
|
const xrp = this.api.dropsToXrp('-2000000')
|
|
assert.strictEqual(xrp, '-2', '-2 million drops equals -2 XRP')
|
|
})
|
|
|
|
it('works with a value ending with a decimal point', function () {
|
|
let xrp = this.api.dropsToXrp('2000000.')
|
|
assert.strictEqual(xrp, '2', '2000000. drops equals 2 XRP')
|
|
|
|
xrp = this.api.dropsToXrp('-2000000.')
|
|
assert.strictEqual(xrp, '-2', '-2000000. drops equals -2 XRP')
|
|
})
|
|
|
|
it('works with BigNumber objects', function () {
|
|
let xrp = this.api.dropsToXrp(new BigNumber(2000000))
|
|
assert.strictEqual(xrp, '2', '(BigNumber) 2 million drops equals 2 XRP')
|
|
|
|
xrp = this.api.dropsToXrp(new BigNumber(-2000000))
|
|
assert.strictEqual(xrp, '-2', '(BigNumber) -2 million drops equals -2 XRP')
|
|
|
|
xrp = this.api.dropsToXrp(new BigNumber(2345678))
|
|
assert.strictEqual(xrp, '2.345678', '(BigNumber) 2,345,678 drops equals 2.345678 XRP')
|
|
|
|
xrp = this.api.dropsToXrp(new BigNumber(-2345678))
|
|
assert.strictEqual(xrp, '-2.345678', '(BigNumber) -2,345,678 drops equals -2.345678 XRP')
|
|
})
|
|
|
|
it('works with a number', function() {
|
|
// This is not recommended. Use strings or BigNumber objects to avoid precision errors.
|
|
|
|
let xrp = this.api.dropsToXrp(2000000)
|
|
assert.strictEqual(xrp, '2', '(number) 2 million drops equals 2 XRP')
|
|
|
|
xrp = this.api.dropsToXrp(-2000000)
|
|
assert.strictEqual(xrp, '-2', '(number) -2 million drops equals -2 XRP')
|
|
})
|
|
|
|
it('throws with an amount with too many decimal places', function () {
|
|
assert.throws(() => {
|
|
this.api.dropsToXrp('1.2')
|
|
}, /has too many decimal places/)
|
|
|
|
assert.throws(() => {
|
|
this.api.dropsToXrp('0.10')
|
|
}, /has too many decimal places/)
|
|
})
|
|
|
|
it('throws with an invalid value', function () {
|
|
assert.throws(() => {
|
|
this.api.dropsToXrp('FOO')
|
|
}, /invalid value/)
|
|
|
|
assert.throws(() => {
|
|
this.api.dropsToXrp('1e-7')
|
|
}, /invalid value/)
|
|
|
|
assert.throws(() => {
|
|
this.api.dropsToXrp('2,0')
|
|
}, /invalid value/)
|
|
|
|
assert.throws(() => {
|
|
this.api.dropsToXrp('.')
|
|
}, /dropsToXrp\: invalid value '\.', should be a BigNumber or string-encoded number\./)
|
|
})
|
|
|
|
it('throws with an amount more than one decimal point', function () {
|
|
assert.throws(() => {
|
|
this.api.dropsToXrp('1.0.0')
|
|
}, /dropsToXrp:\ invalid\ value\ '1\.0\.0'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
|
|
|
|
assert.throws(() => {
|
|
this.api.dropsToXrp('...')
|
|
}, /dropsToXrp:\ invalid\ value\ '\.\.\.'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
|
|
})
|
|
})
|
|
|
|
describe('isValidAddress', function () {
|
|
it('returns true for valid address', function () {
|
|
assert(this.api.isValidAddress('rLczgQHxPhWtjkaQqn3Q6UM8AbRbbRvs5K'));
|
|
})
|
|
|
|
it('returns false for invalid address', function () {
|
|
assert(!this.api.isValidAddress('foobar'));
|
|
})
|
|
})
|
|
|
|
describe('isValidSecret', function () {
|
|
it('returns true for valid secret', function () {
|
|
assert(this.api.isValidSecret('snsakdSrZSLkYpCXxfRkS4Sh96PMK'));
|
|
})
|
|
|
|
it('returns false for invalid secret', function () {
|
|
assert(!this.api.isValidSecret('foobar'));
|
|
})
|
|
})
|
|
|
|
describe('deriveKeypair', function () {
|
|
it('returns keypair for secret', function () {
|
|
var keypair = this.api.deriveKeypair('snsakdSrZSLkYpCXxfRkS4Sh96PMK');
|
|
assert.equal(keypair.privateKey, '008850736302221AFD59FF9CA1A29D4975F491D726249302EE48A3078A8934D335');
|
|
assert.equal(keypair.publicKey, '035332FBA71D705BD5D97014A833BE2BBB25BEFCD3506198E14AFEA241B98C2D06');
|
|
})
|
|
|
|
it('returns keypair for ed25519 secret', function () {
|
|
var keypair = this.api.deriveKeypair('sEdV9eHWbibBnTj7b1H5kHfPfv7gudx');
|
|
assert.equal(keypair.privateKey, 'ED5C2EF6C2E3200DFA6B72F47935C7F64D35453646EA34919192538F458C7BC30F');
|
|
assert.equal(keypair.publicKey, 'ED0805EC4E728DB87C0CA6C420751F296C57A5F42D02E9E6150CE60694A44593E5');
|
|
})
|
|
|
|
it('throws with an invalid secret', function (){
|
|
assert.throws(() => {
|
|
this.api.deriveKeypair('...');
|
|
}, /^Error\: Non\-base58 character$/)
|
|
})
|
|
})
|
|
|
|
describe('deriveAddress', function () {
|
|
it('returns address for public key', function () {
|
|
var address = this.api.deriveAddress('035332FBA71D705BD5D97014A833BE2BBB25BEFCD3506198E14AFEA241B98C2D06');
|
|
assert.equal(address, 'rLczgQHxPhWtjkaQqn3Q6UM8AbRbbRvs5K');
|
|
})
|
|
})
|
|
|
|
describe('pagination', function () {
|
|
|
|
describe('hasNextPage', function () {
|
|
|
|
it('returns true when there is another page', function () {
|
|
return this.api.request('ledger_data').then(response => {
|
|
assert(this.api.hasNextPage(response));
|
|
}
|
|
);
|
|
});
|
|
|
|
it('returns false when there are no more pages', function () {
|
|
return this.api.request('ledger_data').then(response => {
|
|
return this.api.requestNextPage('ledger_data', {}, response);
|
|
}).then(response => {
|
|
assert(!this.api.hasNextPage(response));
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe('requestNextPage', function () {
|
|
|
|
it('requests the next page', function () {
|
|
return this.api.request('ledger_data').then(response => {
|
|
return this.api.requestNextPage('ledger_data', {}, response);
|
|
}).then(response => {
|
|
assert.equal(response.state[0].index, '000B714B790C3C79FEE00D17C4DEB436B375466F29679447BA64F265FD63D731')
|
|
});
|
|
});
|
|
|
|
it('rejects when there are no more pages', function () {
|
|
return this.api.request('ledger_data').then(response => {
|
|
return this.api.requestNextPage('ledger_data', {}, response);
|
|
}).then(response => {
|
|
assert(!this.api.hasNextPage(response))
|
|
return this.api.requestNextPage('ledger_data', {}, response);
|
|
}).then(() => {
|
|
assert(false, 'Should reject');
|
|
}).catch(error => {
|
|
assert(error instanceof Error);
|
|
assert.equal(error.message, 'response does not have a next page')
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('prepareTransaction - auto-fillable fields', function () {
|
|
|
|
// Fee:
|
|
|
|
it('does not overwrite Fee in txJSON', function () {
|
|
const localInstructions = instructionsWithMaxLedgerVersionOffset
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Fee: '10'
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"10","Sequence":23}',
|
|
instructions: {
|
|
fee: '0.00001', // Notice there are not always 6 digits after the decimal point as trailing zeros are omitted
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
it('does not overwrite Fee in Instructions', function () {
|
|
const localInstructions = _.defaults({
|
|
fee: '0.000014', // CAUTION: This `fee` is specified in XRP, not drops.
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"14","Sequence":23}',
|
|
instructions: {
|
|
fee: '0.000014',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
it('rejects Promise if both are set, even when txJSON.Fee matches instructions.fee', function (done) {
|
|
const localInstructions = _.defaults({
|
|
fee: '0.000016'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Fee: '16'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, '`Fee` in txJSON and `fee` in `instructions` cannot both be set');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise if both are set, when txJSON.Fee does not match instructions.fee', function (done) {
|
|
const localInstructions = _.defaults({
|
|
fee: '0.000018'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Fee: '20'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, '`Fee` in txJSON and `fee` in `instructions` cannot both be set');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when the Fee is capitalized in Instructions', function (done) {
|
|
const localInstructions = _.defaults({
|
|
Fee: '0.000022', // Intentionally capitalized in this test, but the correct field would be `fee`
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance additionalProperty "Fee" exists in instance when not allowed');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when the fee is specified in txJSON', function (done) {
|
|
const localInstructions = instructionsWithMaxLedgerVersionOffset
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
fee: '10'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'txJSON additionalProperty "fee" exists in instance when not allowed');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
// Sequence:
|
|
|
|
it('does not overwrite Sequence in txJSON', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Sequence: 100
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":100}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 100,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
it('does not overwrite Sequence in Instructions', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012',
|
|
sequence: 100
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":100}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 100,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
it('does not overwrite Sequence when same sequence is provided in both txJSON and Instructions', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012',
|
|
sequence: 100
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Sequence: 100
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":100}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 100,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
it('rejects Promise when Sequence in txJSON does not match sequence in Instructions', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012',
|
|
sequence: 100
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Sequence: 101
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, '`Sequence` in txJSON must match `sequence` in `instructions`');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when the Sequence is capitalized in Instructions', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012',
|
|
Sequence: 100 // Intentionally capitalized in this test, but the correct field would be `sequence`
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance additionalProperty "Sequence" exists in instance when not allowed');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
// LastLedgerSequence aka maxLedgerVersion/maxLedgerVersionOffset:
|
|
|
|
it('does not overwrite LastLedgerSequence in txJSON', function () {
|
|
const localInstructions = {}
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Fee: '10',
|
|
LastLedgerSequence: 8880000
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8880000,"Fee":"10","Sequence":23}',
|
|
instructions: {
|
|
fee: '0.00001', // Notice there are not always 6 digits after the decimal point as trailing zeros are omitted
|
|
sequence: 23,
|
|
maxLedgerVersion: 8880000
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
it('does not overwrite maxLedgerVersion in Instructions', function () {
|
|
const localInstructions = {
|
|
"maxLedgerVersion": 8890000
|
|
}
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8890000,"Fee":"12","Sequence":23}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8890000
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
it('does not overwrite maxLedgerVersionOffset in Instructions', function () {
|
|
const localInstructions = _.defaults({
|
|
maxLedgerVersionOffset: 124
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820075,"Fee":"12","Sequence":23}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820075
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
it('rejects Promise if txJSON.LastLedgerSequence and instructions.maxLedgerVersion both are set', function (done) {
|
|
const localInstructions = {
|
|
maxLedgerVersion: 8900000
|
|
}
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Fee: '16',
|
|
LastLedgerSequence: 8900000
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, '`LastLedgerSequence` in txJSON and `maxLedgerVersion` in `instructions` cannot both be set');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise if txJSON.LastLedgerSequence and instructions.maxLedgerVersionOffset both are set', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxLedgerVersionOffset: 123
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Fee: '16',
|
|
LastLedgerSequence: 8900000
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, '`LastLedgerSequence` in txJSON and `maxLedgerVersionOffset` in `instructions` cannot both be set');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise if instructions.maxLedgerVersion and instructions.maxLedgerVersionOffset both are set', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxLedgerVersion: 8900000,
|
|
maxLedgerVersionOffset: 123
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Fee: '16'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance is of prohibited type [object Object]');
|
|
// A better error message would be: '`maxLedgerVersion` in `instructions` and `maxLedgerVersionOffset` in `instructions` cannot both be set'
|
|
// Unfortunately, due to the schema validator, this is not possible without special-casing.
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise if txJSON.LastLedgerSequence and instructions.maxLedgerVersion and instructions.maxLedgerVersionOffset all are set', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxLedgerVersion: 8900000,
|
|
maxLedgerVersionOffset: 123
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Fee: '16',
|
|
LastLedgerSequence: 8900000
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance is of prohibited type [object Object]');
|
|
// A better error message would be: 'At most one of the following can be set: `LastLedgerSequence` in txJSON, `maxLedgerVersion` in `instructions`, `maxLedgerVersionOffset` in `instructions`'
|
|
// Unfortunately, due to the schema validator, this is not possible without special-casing.
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when the maxLedgerVersion is capitalized in Instructions', function (done) {
|
|
const localInstructions = _.defaults({
|
|
MaxLedgerVersion: 8900000, // Intentionally capitalized in this test, but the correct field would be `maxLedgerVersion`
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance additionalProperty "MaxLedgerVersion" exists in instance when not allowed');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when the maxLedgerVersion is specified in txJSON', function (done) {
|
|
const localInstructions = instructionsWithMaxLedgerVersionOffset
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
maxLedgerVersion: 8900000
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'txJSON additionalProperty "maxLedgerVersion" exists in instance when not allowed');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when the maxLedgerVersionOffset is specified in txJSON', function (done) {
|
|
const localInstructions = instructionsWithMaxLedgerVersionOffset
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
maxLedgerVersionOffset: 8900000
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'txJSON additionalProperty "maxLedgerVersionOffset" exists in instance when not allowed');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when the sequence is specified in txJSON', function (done) {
|
|
const localInstructions = instructionsWithMaxLedgerVersionOffset
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
sequence: 8900000
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'txJSON additionalProperty "sequence" exists in instance when not allowed');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
// Paths: is not auto-filled by ripple-lib.
|
|
|
|
// Other errors:
|
|
|
|
it('rejects Promise when an unrecognized field is in Instructions', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012',
|
|
foo: 'bar'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance additionalProperty "foo" exists in instance when not allowed');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
})
|
|
|
|
it('rejects Promise when Account is missing', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
// assert.strictEqual(err.name, 'RippledError');
|
|
// assert.strictEqual(err.message, 'Missing field \'account\'.');
|
|
// assert.strictEqual(err.data.error, 'invalidParams');
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance requires property "Account"');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when Account is not a string', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
Account: 1234,
|
|
TransactionType: 'DepositPreauth',
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance.Account is not of a type(s) string,instance.Account does not conform to the "address" format');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when Account is invalid', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
Account: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xkXXXX', // Invalid checksum
|
|
TransactionType: 'DepositPreauth',
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance.Account does not conform to the "address" format');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when Account is valid but non-existent on the ledger', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
Account: 'rogvkYnY8SWjxkJNgU4ZRVfLeRyt5DR9i',
|
|
TransactionType: 'DepositPreauth',
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'RippledError');
|
|
assert.strictEqual(err.message, 'Account not found.');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
it('rejects Promise when TransactionType is missing', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
// If not caught by ripple-lib validation, the rippled error looks like:
|
|
// { error: 'invalidTransaction',
|
|
// error_exception: 'Field not found',
|
|
// id: 4,
|
|
// request:
|
|
// { command: 'submit',
|
|
// id: 4,
|
|
// tx_blob: '24000000032B7735940068400000000000000C732102E1EA8199F570E7F997A7B34EDFDA0A7D8B38173A17450B121A2EB048FDD16CA97446304402206CE34A79A44AEF15786F23DB25C8420E739C167E66750C0B7999EE4BF74A93A1022052E077A6435548F0EE0C5FE2EAB1E5A56376BA360F924DA2E162CCA6C7CB30CB8114D51F9A17208CF113AF23B97ECD5FCD314FBAE52E' },
|
|
// status: 'error',
|
|
// type: 'response' }
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance requires property "TransactionType"');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
// Note: This transaction will fail at the `sign` step:
|
|
//
|
|
// Error: DepositPreXXXX is not a valid name or ordinal for TransactionType
|
|
//
|
|
// at Function.from (ripple-binary-codec/distrib/npm/enums/index.js:43:15)
|
|
it('prepares tx when TransactionType is invalid', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
Account: address,
|
|
TransactionType: 'DepositPreXXXX',
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreXXXX","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":23}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
it('rejects Promise when TransactionType is not a string', function (done) {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
Account: address,
|
|
TransactionType: 1234,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
try {
|
|
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance.TransactionType is not of a type(s) string');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
}
|
|
})
|
|
|
|
// Note: This transaction will fail at the `submit` step:
|
|
//
|
|
// [RippledError(Submit failed, { resultCode: 'temMALFORMED',
|
|
// resultMessage: 'Malformed transaction.',
|
|
// engine_result: 'temMALFORMED',
|
|
// engine_result_code: -299,
|
|
// engine_result_message: 'Malformed transaction.',
|
|
// tx_blob:
|
|
// '120013240000000468400000000000000C732102E1EA8199F570E7F997A7B34EDFDA0A7D8B38173A17450B121A2EB048FDD16CA97446304402201F0EF6A2DE7F96966F7082294D14F3EC1EF59C21E29443E5858A0120079357A302203CDB7FEBDEAAD93FF39CB589B55778CB80DC3979F96F27E828D5E659BEB26B7A8114D51F9A17208CF113AF23B97ECD5FCD314FBAE52E',
|
|
// tx_json:
|
|
// { Account: 'rLRt8bmZFBEeM5VMSxZy15k8KKJEs68W6C',
|
|
// Fee: '12',
|
|
// Sequence: 4,
|
|
// SigningPubKey:
|
|
// '02E1EA8199F570E7F997A7B34EDFDA0A7D8B38173A17450B121A2EB048FDD16CA9',
|
|
// TransactionType: 'DepositPreauth',
|
|
// TxnSignature:
|
|
// '304402201F0EF6A2DE7F96966F7082294D14F3EC1EF59C21E29443E5858A0120079357A302203CDB7FEBDEAAD93FF39CB589B55778CB80DC3979F96F27E828D5E659BEB26B7A',
|
|
// hash:
|
|
// 'C181D470684311658852713DA81F8201062535C8DE2FF853F7DD9981BB85312F' } })]
|
|
it('prepares tx when a required field is missing', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
Account: address,
|
|
TransactionType: 'DepositPreauth',
|
|
// Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo' // Normally required, intentionally removed
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":23}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
describe('preparePayment', function () {
|
|
|
|
it('normal', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.normal, localInstructions).then(
|
|
_.partial(checkResult, responses.preparePayment.normal, 'prepare'));
|
|
});
|
|
|
|
it('preparePayment - min amount xrp', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.minAmountXRP, localInstructions).then(
|
|
_.partial(checkResult,
|
|
responses.preparePayment.minAmountXRP, 'prepare'));
|
|
});
|
|
|
|
it('preparePayment - min amount xrp2xrp', function () {
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.minAmount, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult,
|
|
responses.preparePayment.minAmountXRPXRP, 'prepare'));
|
|
});
|
|
|
|
it('preparePayment - XRP to XRP', function () {
|
|
const payment = {
|
|
"source": {
|
|
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
|
|
"maxAmount": {
|
|
"value": "1",
|
|
"currency": "XRP"
|
|
}
|
|
},
|
|
"destination": {
|
|
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
|
|
"amount": {
|
|
"value": "1",
|
|
"currency": "XRP"
|
|
}
|
|
}
|
|
}
|
|
return this.api.preparePayment(address, payment, instructionsWithMaxLedgerVersionOffset).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":"1000000","Flags":2147483648,"LastLedgerSequence":8820051,"Sequence":23,"Fee":"12"}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
});
|
|
|
|
it('preparePayment - XRP drops to XRP drops', function () {
|
|
const payment = {
|
|
"source": {
|
|
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
|
|
"maxAmount": {
|
|
"value": "1000000",
|
|
"currency": "drops"
|
|
}
|
|
},
|
|
"destination": {
|
|
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
|
|
"amount": {
|
|
"value": "1000000",
|
|
"currency": "drops"
|
|
}
|
|
}
|
|
}
|
|
return this.api.preparePayment(address, payment, instructionsWithMaxLedgerVersionOffset).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":"1000000","Flags":2147483648,"LastLedgerSequence":8820051,"Sequence":23,"Fee":"12"}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
});
|
|
|
|
it('preparePayment - XRP drops to XRP', function () {
|
|
const payment = {
|
|
"source": {
|
|
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
|
|
"maxAmount": {
|
|
"value": "1000000",
|
|
"currency": "drops"
|
|
}
|
|
},
|
|
"destination": {
|
|
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
|
|
"amount": {
|
|
"value": "1",
|
|
"currency": "XRP"
|
|
}
|
|
}
|
|
}
|
|
return this.api.preparePayment(address, payment, instructionsWithMaxLedgerVersionOffset).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":"1000000","Flags":2147483648,"LastLedgerSequence":8820051,"Sequence":23,"Fee":"12"}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
});
|
|
|
|
it('preparePayment - XRP to XRP drops', function () {
|
|
const payment = {
|
|
"source": {
|
|
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
|
|
"maxAmount": {
|
|
"value": "1",
|
|
"currency": "XRP"
|
|
}
|
|
},
|
|
"destination": {
|
|
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
|
|
"amount": {
|
|
"value": "1000000",
|
|
"currency": "drops"
|
|
}
|
|
}
|
|
}
|
|
return this.api.preparePayment(address, payment, instructionsWithMaxLedgerVersionOffset).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":"1000000","Flags":2147483648,"LastLedgerSequence":8820051,"Sequence":23,"Fee":"12"}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
});
|
|
|
|
describe('errors', function () {
|
|
|
|
const senderAddress = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
|
|
const recipientAddress = 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo';
|
|
|
|
it('rejects promise and does not throw when payment object is invalid', function (done) {
|
|
const payment = {
|
|
source: {
|
|
address: senderAddress,
|
|
amount: { // instead of `maxAmount`
|
|
value: '1000',
|
|
currency: 'drops'
|
|
}
|
|
},
|
|
destination: {
|
|
address: recipientAddress,
|
|
amount: {
|
|
value: '1000',
|
|
currency: 'drops'
|
|
}
|
|
}
|
|
}
|
|
// Cannot use `assert.rejects` because then the test passes (with UnhandledPromiseRejectionWarning) even when it should not.
|
|
// See https://github.com/mochajs/mocha/issues/3097
|
|
try {
|
|
this.api.preparePayment(senderAddress, payment).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'payment must specify either (source.maxAmount and destination.amount) or (source.amount and destination.minAmount)');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('rejects promise and does not throw when field is missing', function (done) {
|
|
const payment = {
|
|
source: {
|
|
address: senderAddress
|
|
// `maxAmount` missing
|
|
},
|
|
destination: {
|
|
address: recipientAddress,
|
|
amount: {
|
|
value: '1000',
|
|
currency: 'drops'
|
|
}
|
|
}
|
|
}
|
|
// Cannot use `assert.rejects` because then the test passes (with UnhandledPromiseRejectionWarning) even when it should not.
|
|
// See https://github.com/mochajs/mocha/issues/3097
|
|
try {
|
|
this.api.preparePayment(senderAddress, payment).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance.payment.source is not exactly one from <sourceExactAdjustment>,<maxAdjustment>');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('rejects promise and does not throw when fee exceeds maxFeeXRP', function (done) {
|
|
const payment = {
|
|
source: {
|
|
address: senderAddress,
|
|
maxAmount: {
|
|
value: '1000',
|
|
currency: 'drops'
|
|
}
|
|
},
|
|
destination: {
|
|
address: recipientAddress,
|
|
amount: {
|
|
value: '1000',
|
|
currency: 'drops'
|
|
}
|
|
}
|
|
}
|
|
// Cannot use `assert.rejects` because then the test passes (with UnhandledPromiseRejectionWarning) even when it should not.
|
|
// See https://github.com/mochajs/mocha/issues/3097
|
|
try {
|
|
this.api.preparePayment(senderAddress, payment, {
|
|
fee: '3' // XRP
|
|
}).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'Fee of 3 XRP exceeds max of 2 XRP. To use this fee, increase `maxFeeXRP` in the RippleAPI constructor.');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('preparePayment - XRP to XRP no partial', function (done) {
|
|
try {
|
|
// Cannot return promise because we want/expect it to reject.
|
|
this.api.preparePayment(address, requests.preparePayment.wrongPartial).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'XRP to XRP payments cannot be partial payments');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('preparePayment - address must match payment.source.address', function (done) {
|
|
try {
|
|
// Cannot return promise because we want/expect it to reject.
|
|
this.api.preparePayment(address, requests.preparePayment.wrongAddress).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'address must match payment.source.address');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('preparePayment - wrong amount', function (done) {
|
|
try {
|
|
// Cannot return promise because we want/expect it to reject.
|
|
this.api.preparePayment(address, requests.preparePayment.wrongAmount).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'payment must specify either (source.maxAmount and destination.amount) or (source.amount and destination.minAmount)');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('preparePayment - throws when fee exceeds 2 XRP', function (done) {
|
|
const localInstructions = _.defaults({
|
|
fee: '2.1'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
try {
|
|
// Cannot return promise because we want/expect it to reject.
|
|
this.api.preparePayment(
|
|
address, requests.preparePayment.normal, localInstructions).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'Fee of 2.1 XRP exceeds max of 2 XRP. To use this fee, increase `maxFeeXRP` in the RippleAPI constructor.');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
});
|
|
|
|
it('preparePayment with all options specified', function () {
|
|
return this.api.getLedgerVersion().then(ver => {
|
|
const localInstructions = {
|
|
maxLedgerVersion: ver + 100,
|
|
fee: '0.000012'
|
|
};
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.allOptions, localInstructions).then(
|
|
_.partial(checkResult,
|
|
responses.preparePayment.allOptions, 'prepare'));
|
|
});
|
|
});
|
|
|
|
it('preparePayment without counterparty set', function () {
|
|
const localInstructions = _.defaults({ sequence: 23 }, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.noCounterparty, localInstructions)
|
|
.then(_.partial(checkResult, responses.preparePayment.noCounterparty,
|
|
'prepare'));
|
|
});
|
|
|
|
it('preparePayment - destination.minAmount', function () {
|
|
return this.api.preparePayment(address, responses.getPaths.sendAll[0],
|
|
instructionsWithMaxLedgerVersionOffset).then(_.partial(checkResult,
|
|
responses.preparePayment.minAmount, 'prepare'));
|
|
});
|
|
|
|
it('preparePayment - caps fee at 2 XRP by default', function () {
|
|
this.api._feeCushion = 1000000;
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"2000000\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "2",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.normal, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, expectedResponse, 'prepare'));
|
|
});
|
|
|
|
it('preparePayment - allows fee exceeding 2 XRP when maxFeeXRP is higher', function () {
|
|
this.api._maxFeeXRP = '2.2'
|
|
const localInstructions = _.defaults({
|
|
fee: '2.1'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"2100000\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "2.1",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.normal, localInstructions).then(
|
|
_.partial(checkResult, expectedResponse, 'prepare'));
|
|
});
|
|
});
|
|
|
|
it('prepareOrder - buy order', function () {
|
|
const request = requests.prepareOrder.buy;
|
|
return this.api.prepareOrder(address, request)
|
|
.then(_.partial(checkResult, responses.prepareOrder.buy, 'prepare'));
|
|
});
|
|
|
|
it('prepareOrder - buy order with expiration', function () {
|
|
const request = requests.prepareOrder.expiration;
|
|
const response = responses.prepareOrder.expiration;
|
|
return this.api.prepareOrder(address, request, instructionsWithMaxLedgerVersionOffset)
|
|
.then(_.partial(checkResult, response, 'prepare'));
|
|
});
|
|
|
|
it('prepareOrder - sell order', function () {
|
|
const request = requests.prepareOrder.sell;
|
|
return this.api.prepareOrder(address, request, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareOrder.sell, 'prepare'));
|
|
});
|
|
|
|
it('prepareOrder - invalid', function (done) {
|
|
const request = requests.prepareOrder.sell;
|
|
delete request.direction; // Make invalid
|
|
try {
|
|
this.api.prepareOrder(address, request, instructionsWithMaxLedgerVersionOffset).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance.order requires property "direction"');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('prepareOrderCancellation', function () {
|
|
const request = requests.prepareOrderCancellation.simple;
|
|
return this.api.prepareOrderCancellation(address, request, instructionsWithMaxLedgerVersionOffset)
|
|
.then(_.partial(checkResult, responses.prepareOrderCancellation.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareOrderCancellation - no instructions', function () {
|
|
const request = requests.prepareOrderCancellation.simple;
|
|
return this.api.prepareOrderCancellation(address, request)
|
|
.then(_.partial(checkResult,
|
|
responses.prepareOrderCancellation.noInstructions,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareOrderCancellation - with memos', function () {
|
|
const request = requests.prepareOrderCancellation.withMemos;
|
|
return this.api.prepareOrderCancellation(address, request)
|
|
.then(_.partial(checkResult,
|
|
responses.prepareOrderCancellation.withMemos,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareOrderCancellation - invalid', function (done) {
|
|
const request = requests.prepareOrderCancellation.withMemos;
|
|
delete request.orderSequence; // Make invalid
|
|
try {
|
|
this.api.prepareOrderCancellation(address, request).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance.orderCancellation requires property "orderSequence"');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('prepareTrustline - simple', function () {
|
|
return this.api.prepareTrustline(
|
|
address, requests.prepareTrustline.simple, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareTrustline.simple, 'prepare'));
|
|
});
|
|
|
|
it('prepareTrustline - frozen', function () {
|
|
return this.api.prepareTrustline(
|
|
address, requests.prepareTrustline.frozen).then(
|
|
_.partial(checkResult, responses.prepareTrustline.frozen, 'prepare'));
|
|
});
|
|
|
|
it('prepareTrustline - complex', function () {
|
|
return this.api.prepareTrustline(
|
|
address, requests.prepareTrustline.complex, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareTrustline.complex, 'prepare'));
|
|
});
|
|
|
|
it('prepareTrustline - invalid', function (done) {
|
|
const trustline = requests.prepareTrustline.complex;
|
|
delete trustline.limit; // Make invalid
|
|
try {
|
|
this.api.prepareTrustline(
|
|
address, trustline, instructionsWithMaxLedgerVersionOffset).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance.trustline requires property "limit"');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('prepareSettings', function () {
|
|
return this.api.prepareSettings(
|
|
address, requests.prepareSettings.domain, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareSettings.flags, 'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - no maxLedgerVersion', function () {
|
|
return this.api.prepareSettings(
|
|
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.domain).then(
|
|
_.partial(
|
|
checkResult,
|
|
responses.prepareSettings.noInstructions,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - regularKey', function () {
|
|
const regularKey = { regularKey: 'rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD' };
|
|
return this.api.prepareSettings(address, regularKey, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareSettings.regularKey, 'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - remove regularKey', function () {
|
|
const regularKey = { regularKey: null };
|
|
return this.api.prepareSettings(address, regularKey, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareSettings.removeRegularKey,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - flag set', function () {
|
|
const settings = { requireDestinationTag: true };
|
|
return this.api.prepareSettings(address, settings, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareSettings.flagSet, 'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - flag clear', function () {
|
|
const settings = { requireDestinationTag: false };
|
|
return this.api.prepareSettings(address, settings, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareSettings.flagClear, 'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - set depositAuth flag', function () {
|
|
const settings = { depositAuth: true };
|
|
return this.api.prepareSettings(address, settings, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareSettings.flagSetDepositAuth, 'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - clear depositAuth flag', function () {
|
|
const settings = { depositAuth: false };
|
|
return this.api.prepareSettings(address, settings, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareSettings.flagClearDepositAuth, 'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - integer field clear', function () {
|
|
const settings = { transferRate: null };
|
|
return this.api.prepareSettings(address, settings, instructionsWithMaxLedgerVersionOffset)
|
|
.then(data => {
|
|
assert(data);
|
|
assert.strictEqual(JSON.parse(data.txJSON).TransferRate, 0);
|
|
});
|
|
});
|
|
|
|
it('prepareSettings - set transferRate', function () {
|
|
const settings = { transferRate: 1 };
|
|
return this.api.prepareSettings(address, settings, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareSettings.setTransferRate,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - set signers', function () {
|
|
const settings = requests.prepareSettings.signers.normal;
|
|
return this.api.prepareSettings(address, settings, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, responses.prepareSettings.signers,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - signers no threshold', function (done) {
|
|
const settings = requests.prepareSettings.signers.noThreshold;
|
|
try {
|
|
this.api.prepareSettings(address, settings, instructionsWithMaxLedgerVersionOffset).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance.settings.signers requires property "threshold"');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('prepareSettings - signers no weights', function () {
|
|
const settings = requests.prepareSettings.signers.noWeights;
|
|
const localInstructions = _.defaults({
|
|
signersCount: 1
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.prepareSettings(
|
|
address, settings, localInstructions).then(
|
|
_.partial(checkResult, responses.prepareSettings.noWeights,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - fee for multisign', function () {
|
|
const localInstructions = _.defaults({
|
|
signersCount: 4
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.prepareSettings(
|
|
address, requests.prepareSettings.domain, localInstructions).then(
|
|
_.partial(checkResult, responses.prepareSettings.flagsMultisign,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - no signer list', function () {
|
|
const settings = requests.prepareSettings.noSignerEntries;
|
|
const localInstructions = _.defaults({
|
|
signersCount: 1
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.prepareSettings(
|
|
address, settings, localInstructions).then(
|
|
_.partial(checkResult, responses.prepareSettings.noSignerList,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareSettings - invalid', function (done) {
|
|
// domain must be a string
|
|
const settings = Object.assign({},
|
|
requests.prepareSettings.domain,
|
|
{domain: 123});
|
|
|
|
const localInstructions = _.defaults({
|
|
signersCount: 4
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
try {
|
|
this.api.prepareSettings(
|
|
address, settings, localInstructions).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance.settings.domain is not of a type(s) string');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('prepareEscrowCreation', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.prepareEscrowCreation(
|
|
address, requests.prepareEscrowCreation.normal,
|
|
localInstructions).then(
|
|
_.partial(checkResult, responses.prepareEscrowCreation.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareEscrowCreation full', function () {
|
|
return this.api.prepareEscrowCreation(
|
|
address, requests.prepareEscrowCreation.full).then(
|
|
_.partial(checkResult, responses.prepareEscrowCreation.full,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareEscrowCreation - invalid', function (done) {
|
|
const escrow = Object.assign({}, requests.prepareEscrowCreation.full);
|
|
delete escrow.amount; // Make invalid
|
|
try {
|
|
this.api.prepareEscrowCreation(
|
|
address, escrow).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, 'instance.escrowCreation requires property "amount"');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('prepareEscrowExecution', function () {
|
|
return this.api.prepareEscrowExecution(
|
|
address,
|
|
requests.prepareEscrowExecution.normal, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult,
|
|
responses.prepareEscrowExecution.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareEscrowExecution - simple', function () {
|
|
return this.api.prepareEscrowExecution(
|
|
address,
|
|
requests.prepareEscrowExecution.simple).then(
|
|
_.partial(checkResult,
|
|
responses.prepareEscrowExecution.simple,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareEscrowExecution - no condition', function (done) {
|
|
try {
|
|
this.api.prepareEscrowExecution(address,
|
|
requests.prepareEscrowExecution.noCondition, instructionsWithMaxLedgerVersionOffset).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, '"condition" and "fulfillment" fields on EscrowFinish must only be specified together.');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('prepareEscrowExecution - no fulfillment', function (done) {
|
|
try {
|
|
this.api.prepareEscrowExecution(address,
|
|
requests.prepareEscrowExecution.noFulfillment, instructionsWithMaxLedgerVersionOffset).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, '"condition" and "fulfillment" fields on EscrowFinish must only be specified together.');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('prepareEscrowCancellation', function () {
|
|
return this.api.prepareEscrowCancellation(
|
|
address,
|
|
requests.prepareEscrowCancellation.normal, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult,
|
|
responses.prepareEscrowCancellation.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareEscrowCancellation with memos', function () {
|
|
return this.api.prepareEscrowCancellation(
|
|
address,
|
|
requests.prepareEscrowCancellation.memos).then(
|
|
_.partial(checkResult,
|
|
responses.prepareEscrowCancellation.memos,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareCheckCreate', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.prepareCheckCreate(
|
|
address, requests.prepareCheckCreate.normal,
|
|
localInstructions).then(
|
|
_.partial(checkResult, responses.prepareCheckCreate.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareCheckCreate full', function () {
|
|
return this.api.prepareCheckCreate(
|
|
address, requests.prepareCheckCreate.full).then(
|
|
_.partial(checkResult, responses.prepareCheckCreate.full,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareCheckCash amount', function () {
|
|
return this.api.prepareCheckCash(
|
|
address, requests.prepareCheckCash.amount).then(
|
|
_.partial(checkResult, responses.prepareCheckCash.amount,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareCheckCash deliverMin', function () {
|
|
return this.api.prepareCheckCash(
|
|
address, requests.prepareCheckCash.deliverMin).then(
|
|
_.partial(checkResult, responses.prepareCheckCash.deliverMin,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareCheckCancel', function () {
|
|
return this.api.prepareCheckCancel(
|
|
address, requests.prepareCheckCancel.normal).then(
|
|
_.partial(checkResult, responses.prepareCheckCancel.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareTransaction - DepositPreauth - Authorize', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":23}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
it('prepareTransaction - DepositPreauth - Unauthorize', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset)
|
|
|
|
const txJSON = {
|
|
TransactionType: 'DepositPreauth',
|
|
Account: address,
|
|
Unauthorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
|
const expected = {
|
|
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Unauthorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":23}',
|
|
instructions: {
|
|
fee: '0.000012',
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051
|
|
}
|
|
}
|
|
return checkResult(expected, 'prepare', response)
|
|
})
|
|
})
|
|
|
|
describe('prepareTransaction - Payment', function () {
|
|
|
|
it('normal', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
const txJSON = {
|
|
TransactionType: 'Payment',
|
|
Account: address,
|
|
Destination: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Amount: {
|
|
currency: 'USD',
|
|
issuer: 'rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM',
|
|
value: '0.01'
|
|
},
|
|
SendMax: {
|
|
currency: 'USD',
|
|
issuer: 'rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM',
|
|
value: '0.01'
|
|
},
|
|
Flags: 0
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult, responses.preparePayment.normal, 'prepare'));
|
|
});
|
|
|
|
// prepareTransaction - Payment
|
|
it('min amount xrp', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
const txJSON = {
|
|
TransactionType: 'Payment',
|
|
Account: address,
|
|
Destination: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
|
|
// Max amount to send. Use 100 billion XRP to
|
|
// ensure that we send the full SendMax amount.
|
|
Amount: '100000000000000000',
|
|
|
|
SendMax: {
|
|
currency: 'USD',
|
|
issuer: 'rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM',
|
|
value: '0.01'
|
|
},
|
|
DeliverMin: '10000',
|
|
Flags: this.api.txFlags.Payment.PartialPayment
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult,
|
|
responses.preparePayment.minAmountXRP, 'prepare'));
|
|
});
|
|
|
|
// prepareTransaction - Payment
|
|
it('min amount xrp2xrp', function () {
|
|
const txJSON = {
|
|
TransactionType: 'Payment',
|
|
Account: address,
|
|
Destination: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Amount: '10000',
|
|
Flags: 0
|
|
}
|
|
return this.api.prepareTransaction(txJSON, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult,
|
|
responses.preparePayment.minAmountXRPXRP, 'prepare'));
|
|
});
|
|
|
|
// prepareTransaction - Payment
|
|
it('with all options specified', function () {
|
|
return this.api.getLedgerVersion().then(ver => {
|
|
const localInstructions = {
|
|
maxLedgerVersion: ver + 100,
|
|
fee: '0.000012'
|
|
};
|
|
const txJSON = {
|
|
TransactionType: 'Payment',
|
|
Account: address,
|
|
Destination: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
|
Amount: '10000',
|
|
InvoiceID: 'A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A',
|
|
SourceTag: 14,
|
|
DestinationTag: 58,
|
|
Memos: [
|
|
{
|
|
Memo: {
|
|
MemoType: this.api.convertStringToHex('test'),
|
|
MemoFormat: this.api.convertStringToHex('text/plain'),
|
|
MemoData: this.api.convertStringToHex('texted data')
|
|
}
|
|
}
|
|
],
|
|
Flags: 0 | this.api.txFlags.Payment.NoRippleDirect | this.api.txFlags.Payment.LimitQuality
|
|
}
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult,
|
|
responses.preparePayment.allOptions, 'prepare'));
|
|
});
|
|
});
|
|
|
|
// prepareTransaction - Payment
|
|
it('fee is capped at default maxFee of 2 XRP (using txJSON.LastLedgerSequence)', function () {
|
|
this.api._feeCushion = 1000000;
|
|
|
|
const txJSON = {
|
|
"Flags": 2147483648,
|
|
"TransactionType": "Payment",
|
|
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
|
|
"Destination": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
|
|
"Amount": {
|
|
"value": "0.01",
|
|
"currency": "USD",
|
|
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
|
|
},
|
|
"SendMax": {
|
|
"value": "0.01",
|
|
"currency": "USD",
|
|
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
|
|
},
|
|
"LastLedgerSequence": 8820051
|
|
}
|
|
|
|
const localInstructions = {}
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"2000000\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "2",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult,
|
|
expectedResponse, 'prepare'));
|
|
});
|
|
|
|
// prepareTransaction - Payment
|
|
it('fee is capped at default maxFee of 2 XRP (using instructions.maxLedgerVersion)', function () {
|
|
this.api._feeCushion = 1000000;
|
|
|
|
const txJSON = {
|
|
"Flags": 2147483648,
|
|
"TransactionType": "Payment",
|
|
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
|
|
"Destination": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
|
|
"Amount": {
|
|
"value": "0.01",
|
|
"currency": "USD",
|
|
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
|
|
},
|
|
"SendMax": {
|
|
"value": "0.01",
|
|
"currency": "USD",
|
|
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
|
|
}
|
|
}
|
|
|
|
const localInstructions = {
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"2000000\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "2",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult,
|
|
expectedResponse, 'prepare'));
|
|
});
|
|
|
|
// prepareTransaction - Payment
|
|
it('fee is capped to custom maxFeeXRP when maxFee exceeds maxFeeXRP', function () {
|
|
this.api._feeCushion = 1000000
|
|
this.api._maxFeeXRP = '3'
|
|
const localInstructions = {
|
|
maxFee: '4' // We are testing that this does not matter; fee is still capped to maxFeeXRP
|
|
};
|
|
|
|
const txJSON = {
|
|
"Flags": 2147483648,
|
|
"TransactionType": "Payment",
|
|
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
|
|
"Destination": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
|
|
"Amount": {
|
|
"value": "0.01",
|
|
"currency": "USD",
|
|
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
|
|
},
|
|
"SendMax": {
|
|
"value": "0.01",
|
|
"currency": "USD",
|
|
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
|
|
},
|
|
"LastLedgerSequence": 8820051
|
|
}
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"3000000\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "3",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult,
|
|
expectedResponse, 'prepare'));
|
|
});
|
|
|
|
// prepareTransaction - Payment
|
|
it('fee is capped to maxFee', function () {
|
|
this.api._feeCushion = 1000000
|
|
this.api._maxFeeXRP = '5'
|
|
const localInstructions = {
|
|
maxFee: '4' // maxFeeXRP does not matter if maxFee is lower than maxFeeXRP
|
|
};
|
|
|
|
const txJSON = {
|
|
"Flags": 2147483648,
|
|
"TransactionType": "Payment",
|
|
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
|
|
"Destination": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
|
|
"Amount": {
|
|
"value": "0.01",
|
|
"currency": "USD",
|
|
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
|
|
},
|
|
"SendMax": {
|
|
"value": "0.01",
|
|
"currency": "USD",
|
|
"issuer": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
|
|
},
|
|
"LastLedgerSequence": 8820051,
|
|
}
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"4000000\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "4",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult,
|
|
expectedResponse, 'prepare'));
|
|
});
|
|
|
|
it('fee - calculated fee does not use more than 6 decimal places', function () {
|
|
this.api.connection._send(JSON.stringify({
|
|
command: 'config',
|
|
data: { loadFactor: 5407.96875 }
|
|
}));
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"64896\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "0.064896",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.normal, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, expectedResponse, 'prepare'));
|
|
});
|
|
});
|
|
|
|
it('prepareTransaction - PaymentChannelCreate', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.prepareTransaction({
|
|
Account: address,
|
|
TransactionType: 'PaymentChannelCreate',
|
|
Amount: '1000000', // 1 XRP in drops. Use a string-encoded integer.
|
|
Destination: 'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW',
|
|
SettleDelay: 86400,
|
|
PublicKey: '32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A'
|
|
// If cancelAfter is used, you must use RippleTime.
|
|
// You can use `iso8601ToRippleTime()` to convert to RippleTime.
|
|
|
|
// Other fields are available (but not used in this test),
|
|
// including `sourceTag` and `destinationTag`.
|
|
}, localInstructions).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelCreate.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareTransaction - PaymentChannelCreate full', function () {
|
|
const txJSON = {
|
|
Account: address,
|
|
TransactionType: 'PaymentChannelCreate',
|
|
Amount: this.api.xrpToDrops('1'), // or '1000000'
|
|
Destination: 'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW',
|
|
SettleDelay: 86400,
|
|
|
|
// Ensure this is in upper case if it is not already
|
|
PublicKey: '32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A'.toUpperCase(),
|
|
|
|
CancelAfter: this.api.iso8601ToRippleTime('2017-02-17T15:04:57Z'),
|
|
SourceTag: 11747,
|
|
DestinationTag: 23480
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelCreate.full,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareTransaction - PaymentChannelFund', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
const txJSON = {
|
|
Account: address,
|
|
TransactionType: 'PaymentChannelFund',
|
|
Channel: 'C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198',
|
|
Amount: this.api.xrpToDrops('1') // or '1000000'
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelFund.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareTransaction - PaymentChannelFund full', function () {
|
|
const txJSON = {
|
|
Account: address,
|
|
TransactionType: 'PaymentChannelFund',
|
|
Channel: 'C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198',
|
|
Amount: this.api.xrpToDrops('1'), // or '1000000'
|
|
Expiration: this.api.iso8601ToRippleTime('2017-02-17T15:04:57Z')
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelFund.full,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareTransaction - PaymentChannelClaim', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
const txJSON = {
|
|
Account: address,
|
|
TransactionType: 'PaymentChannelClaim',
|
|
Channel: 'C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198',
|
|
Flags: 0
|
|
}
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelClaim.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareTransaction - PaymentChannelClaim with renew', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
const txJSON = {
|
|
Account: address,
|
|
TransactionType: 'PaymentChannelClaim',
|
|
Channel: 'C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198',
|
|
Balance: this.api.xrpToDrops('1'), // or '1000000'
|
|
Amount: this.api.xrpToDrops('1'), // or '1000000'
|
|
Signature: '30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B',
|
|
PublicKey: '32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A',
|
|
Flags: 0
|
|
}
|
|
txJSON.Flags |= this.api.txFlags.PaymentChannelClaim.Renew
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelClaim.renew,
|
|
'prepare'));
|
|
});
|
|
|
|
it('prepareTransaction - PaymentChannelClaim with close', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
const txJSON = {
|
|
Account: address,
|
|
TransactionType: 'PaymentChannelClaim',
|
|
Channel: 'C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198',
|
|
Balance: this.api.xrpToDrops('1'), // or 1000000
|
|
Amount: this.api.xrpToDrops('1'), // or 1000000
|
|
Signature: '30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B',
|
|
PublicKey: '32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A',
|
|
Flags: 0
|
|
}
|
|
txJSON.Flags |= this.api.txFlags.PaymentChannelClaim.Close
|
|
|
|
return this.api.prepareTransaction(txJSON, localInstructions).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelClaim.close,
|
|
'prepare'));
|
|
});
|
|
|
|
it('preparePaymentChannelCreate', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.preparePaymentChannelCreate(
|
|
address, requests.preparePaymentChannelCreate.normal,
|
|
localInstructions).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelCreate.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('preparePaymentChannelCreate full', function () {
|
|
return this.api.preparePaymentChannelCreate(
|
|
address, requests.preparePaymentChannelCreate.full).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelCreate.full,
|
|
'prepare'));
|
|
});
|
|
|
|
it('preparePaymentChannelFund', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.preparePaymentChannelFund(
|
|
address, requests.preparePaymentChannelFund.normal,
|
|
localInstructions).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelFund.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('preparePaymentChannelFund full', function () {
|
|
return this.api.preparePaymentChannelFund(
|
|
address, requests.preparePaymentChannelFund.full).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelFund.full,
|
|
'prepare'));
|
|
});
|
|
|
|
it('preparePaymentChannelClaim', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.preparePaymentChannelClaim(
|
|
address, requests.preparePaymentChannelClaim.normal,
|
|
localInstructions).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelClaim.normal,
|
|
'prepare'));
|
|
});
|
|
|
|
it('preparePaymentChannelClaim with renew', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.preparePaymentChannelClaim(
|
|
address, requests.preparePaymentChannelClaim.renew,
|
|
localInstructions).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelClaim.renew,
|
|
'prepare'));
|
|
});
|
|
|
|
it('preparePaymentChannelClaim with close', function () {
|
|
const localInstructions = _.defaults({
|
|
maxFee: '0.000012'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
return this.api.preparePaymentChannelClaim(
|
|
address, requests.preparePaymentChannelClaim.close,
|
|
localInstructions).then(
|
|
_.partial(checkResult, responses.preparePaymentChannelClaim.close,
|
|
'prepare'));
|
|
});
|
|
|
|
it('rejects Promise on preparePaymentChannelClaim with renew and close', function (done) {
|
|
try {
|
|
this.api.preparePaymentChannelClaim(
|
|
address, requests.preparePaymentChannelClaim.full).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, '"renew" and "close" flags on PaymentChannelClaim are mutually exclusive');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('rejects Promise on preparePaymentChannelClaim with no signature', function (done) {
|
|
try {
|
|
this.api.preparePaymentChannelClaim(
|
|
address, requests.preparePaymentChannelClaim.noSignature).then(prepared => {
|
|
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
|
}).catch(err => {
|
|
assert.strictEqual(err.name, 'ValidationError');
|
|
assert.strictEqual(err.message, '"signature" and "publicKey" fields on PaymentChannelClaim must only be specified together.');
|
|
done();
|
|
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
|
} catch (err) {
|
|
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
|
};
|
|
});
|
|
|
|
it('sign', function () {
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
const result = this.api.sign(requests.sign.normal.txJSON, secret);
|
|
assert.deepEqual(result, responses.sign.normal);
|
|
schemaValidator.schemaValidate('sign', result);
|
|
});
|
|
|
|
it('sign - already signed', function () {
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
const result = this.api.sign(requests.sign.normal.txJSON, secret);
|
|
assert.throws(() => {
|
|
const tx = JSON.stringify(binary.decode(result.signedTransaction));
|
|
this.api.sign(tx, secret);
|
|
}, /txJSON must not contain "TxnSignature" or "Signers" properties/);
|
|
});
|
|
|
|
it('sign - EscrowExecution', function () {
|
|
const secret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb';
|
|
const result = this.api.sign(requests.sign.escrow.txJSON, secret);
|
|
assert.deepEqual(result, responses.sign.escrow);
|
|
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('sign - withKeypair', function () {
|
|
const keypair = {
|
|
privateKey:
|
|
'00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A',
|
|
publicKey:
|
|
'02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8'
|
|
};
|
|
const result = this.api.sign(requests.sign.normal.txJSON, keypair);
|
|
assert.deepEqual(result, responses.sign.normal);
|
|
schemaValidator.schemaValidate('sign', result);
|
|
});
|
|
|
|
it('sign - withKeypair already signed', function () {
|
|
const keypair = {
|
|
privateKey:
|
|
'00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A',
|
|
publicKey:
|
|
'02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8'
|
|
};
|
|
const result = this.api.sign(requests.sign.normal.txJSON, keypair);
|
|
assert.throws(() => {
|
|
const tx = JSON.stringify(binary.decode(result.signedTransaction));
|
|
this.api.sign(tx, keypair);
|
|
}, /txJSON must not contain "TxnSignature" or "Signers" properties/);
|
|
});
|
|
|
|
it('sign - withKeypair EscrowExecution', function () {
|
|
const keypair = {
|
|
privateKey:
|
|
'001ACAAEDECE405B2A958212629E16F2EB46B153EEE94CDD350FDEFF52795525B7',
|
|
publicKey:
|
|
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020'
|
|
};
|
|
const result = this.api.sign(requests.sign.escrow.txJSON, keypair);
|
|
assert.deepEqual(result, responses.sign.escrow);
|
|
schemaValidator.schemaValidate('sign', result);
|
|
});
|
|
|
|
it('sign - withKeypair signAs', function () {
|
|
const txJSON = requests.sign.signAs;
|
|
const keypair = {
|
|
privateKey:
|
|
'001ACAAEDECE405B2A958212629E16F2EB46B153EEE94CDD350FDEFF52795525B7',
|
|
publicKey:
|
|
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020'
|
|
};
|
|
const signature = this.api.sign(JSON.stringify(txJSON), keypair, {
|
|
signAs: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
|
|
});
|
|
assert.deepEqual(signature, responses.sign.signAs);
|
|
});
|
|
|
|
it('sign - succeeds - prepared payment', async function () {
|
|
const payment = await this.api.preparePayment('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', {
|
|
source: {
|
|
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
|
maxAmount: {
|
|
value: '1',
|
|
currency: 'drops'
|
|
}
|
|
},
|
|
destination: {
|
|
address: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',
|
|
amount: {
|
|
value: '1',
|
|
currency: 'drops'
|
|
}
|
|
}
|
|
});
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
const result = this.api.sign(payment.txJSON, secret);
|
|
const expectedResult = {
|
|
signedTransaction:
|
|
'12000022800000002400000017201B008694F261400000000000000168400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100A9C91D4CFAE45686146EE0B56D4C53A2E7C2D672FB834D43E0BE2D2E9106519A022075DDA2F92DE552B0C45D83D4E6D35889B3FBF51BFBBD9B25EBF70DE3C96D0D6681145E7B112523F68D2F5E879DB4EAC51C6698A693048314FDB08D07AAA0EB711793A3027304D688E10C3648',
|
|
id:
|
|
'88D6B913C66279EA31ADC25C5806C48B2D4E5680261666790A736E1961217700'
|
|
};
|
|
assert.deepEqual(result, expectedResult);
|
|
schemaValidator.schemaValidate('sign', result);
|
|
});
|
|
|
|
it('sign - succeeds - no flags', async function () {
|
|
const txJSON = '{"TransactionType":"Payment","Account":"r45Rev1EXGxy2hAUmJPCne97KUE7qyrD3j","Destination":"rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r","Amount":"20000000","Sequence":1,"Fee":"12"}';
|
|
const secret = 'shotKgaEotpcYsshSE39vmSnBDRim';
|
|
const result = this.api.sign(txJSON, secret);
|
|
const expectedResult = {
|
|
signedTransaction:
|
|
'1200002400000001614000000001312D0068400000000000000C7321022B05847086686F9D0499B13136B94AD4323EE1B67D4C429ECC987AB35ACFA34574473045022100C104B7B97C31FACA4597E7D6FCF13BD85BD11375963A62A0AC45B0061236E39802207784F157F6A98DFC85B051CDDF61CC3084C4F5750B82674801C8E9950280D1998114EE3046A5DDF8422C40DDB93F1D522BB4FE6419158314FDB08D07AAA0EB711793A3027304D688E10C3648',
|
|
id:
|
|
'0596925967F541BF332FF6756645B2576A9858414B5B363DC3D34915BE8A70D6'
|
|
};
|
|
const decoded = binary.decode(result.signedTransaction);
|
|
assert(decoded.Flags === undefined, `Flags = ${decoded.Flags}, should be undefined`);
|
|
assert.deepEqual(result, expectedResult);
|
|
schemaValidator.schemaValidate('sign', result);
|
|
});
|
|
|
|
it('sign - throws when encoded tx does not match decoded tx - prepared payment', async function () {
|
|
const payment = await this.api.preparePayment('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', {
|
|
source: {
|
|
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
|
maxAmount: {
|
|
value: '1.1234567',
|
|
currency: 'drops'
|
|
}
|
|
},
|
|
destination: {
|
|
address: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',
|
|
amount: {
|
|
value: '1.1234567',
|
|
currency: 'drops'
|
|
}
|
|
}
|
|
});
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
assert.throws(
|
|
() => {
|
|
this.api.sign(payment.txJSON, secret);
|
|
},
|
|
/^Error: 1\.1234567 is an illegal amount/
|
|
);
|
|
});
|
|
|
|
it('sign - throws when encoded tx does not match decoded tx - prepared order', async function () {
|
|
const order = {
|
|
direction: 'sell',
|
|
quantity: {
|
|
currency: 'USD',
|
|
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
|
value: '3.140000'
|
|
},
|
|
totalPrice: {
|
|
currency: 'XRP',
|
|
value: '31415'
|
|
}
|
|
};
|
|
const prepared = await this.api.prepareOrder('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', order, {
|
|
sequence: 123
|
|
});
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
try {
|
|
this.api.sign(prepared.txJSON, secret);
|
|
return Promise.reject(new Error('api.sign should have thrown'));
|
|
} catch (error) {
|
|
assert.equal(error.name, 'ValidationError');
|
|
assert.equal(error.message, 'Serialized transaction does not match original txJSON. See `error.data`');
|
|
assert.deepEqual(error.data.diff, {
|
|
TakerGets: {
|
|
value: '3.14'
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
it('sign - throws when encoded tx does not match decoded tx - AccountSet', function () {
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
const request = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"1.2\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
|
|
"instructions": {
|
|
"fee": "0.0000012",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
assert.throws(
|
|
() => {
|
|
this.api.sign(request.txJSON, secret);
|
|
},
|
|
/Error: 1\.2 is an illegal amount/
|
|
);
|
|
});
|
|
|
|
it('sign - throws when encoded tx does not match decoded tx - higher fee', function () {
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
const request = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"1123456.7\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
|
|
"instructions": {
|
|
"fee": "1.1234567",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
assert.throws(
|
|
() => {
|
|
this.api.sign(request.txJSON, secret);
|
|
},
|
|
/Error: 1123456\.7 is an illegal amount/
|
|
);
|
|
});
|
|
|
|
it('sign - throws when Fee exceeds maxFeeXRP (in drops)', function () {
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
const request = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"2010000\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
|
|
"instructions": {
|
|
"fee": "2.01",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
assert.throws(() => {
|
|
this.api.sign(request.txJSON, secret)
|
|
}, /Fee" should not exceed "2000000"\. To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor\./)
|
|
});
|
|
|
|
it('sign - throws when Fee exceeds maxFeeXRP (in drops) - custom maxFeeXRP', function () {
|
|
this.api._maxFeeXRP = '1.9'
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
const request = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"2010000\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
|
|
"instructions": {
|
|
"fee": "2.01",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
assert.throws(() => {
|
|
this.api.sign(request.txJSON, secret)
|
|
}, /Fee" should not exceed "1900000"\. To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor\./)
|
|
});
|
|
|
|
it('sign - permits fee exceeding 2000000 drops when maxFeeXRP is higher than 2 XRP', function () {
|
|
this.api._maxFeeXRP = '2.1'
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
const request = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"2010000\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
|
|
"instructions": {
|
|
"fee": "2.01",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
const result = this.api.sign(request.txJSON, secret)
|
|
|
|
const expectedResponse = {
|
|
signedTransaction: "12000322800000002400000017201B008695536840000000001EAB90732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8744630440220384FBB48EEE7B0E58BD89294A609F9407C51FBE8FA08A4B305B22E9A7489D66602200152315EFE752DA381E74493419871550D206AC6503841DA5F8C30E35D9E3892770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304",
|
|
id: "A1586D6AF7B0821E7075E12A0132D9EB50BC1874A0749441201497F7561795FB"
|
|
}
|
|
|
|
assert.deepEqual(result, expectedResponse)
|
|
schemaValidator.schemaValidate('sign', result)
|
|
});
|
|
|
|
it('submit', function () {
|
|
return this.api.submit(responses.sign.normal.signedTransaction).then(response => {
|
|
checkResult(responses.submit, 'submit', response);
|
|
});
|
|
});
|
|
|
|
it('submit - failure', function () {
|
|
return this.api.submit('BAD').then(() => {
|
|
assert(false, 'Should throw RippledError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.RippledError);
|
|
assert.strictEqual(error.data.resultCode, 'temBAD_FEE');
|
|
});
|
|
});
|
|
|
|
it('signPaymentChannelClaim', function () {
|
|
const privateKey =
|
|
'ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A';
|
|
const result = this.api.signPaymentChannelClaim(
|
|
requests.signPaymentChannelClaim.channel,
|
|
requests.signPaymentChannelClaim.amount, privateKey);
|
|
checkResult(responses.signPaymentChannelClaim,
|
|
'signPaymentChannelClaim', result)
|
|
});
|
|
|
|
it('verifyPaymentChannelClaim', function () {
|
|
const publicKey =
|
|
'02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8';
|
|
const result = this.api.verifyPaymentChannelClaim(
|
|
requests.signPaymentChannelClaim.channel,
|
|
requests.signPaymentChannelClaim.amount,
|
|
responses.signPaymentChannelClaim, publicKey);
|
|
checkResult(true, 'verifyPaymentChannelClaim', result)
|
|
});
|
|
|
|
it('verifyPaymentChannelClaim - invalid', function () {
|
|
const publicKey =
|
|
'03A6523FE4281DA48A6FD77FAF3CB77F5C7001ABA0B32BCEDE0369AC009758D7D9';
|
|
const result = this.api.verifyPaymentChannelClaim(
|
|
requests.signPaymentChannelClaim.channel,
|
|
requests.signPaymentChannelClaim.amount,
|
|
responses.signPaymentChannelClaim, publicKey);
|
|
checkResult(false,
|
|
'verifyPaymentChannelClaim', result)
|
|
});
|
|
|
|
it('combine', function () {
|
|
const combined = this.api.combine(requests.combine.setDomain);
|
|
checkResult(responses.combine.single, 'sign', combined);
|
|
});
|
|
|
|
it('combine - different transactions', function () {
|
|
const request = [requests.combine.setDomain[0]];
|
|
const tx = binary.decode(requests.combine.setDomain[0]);
|
|
tx.Flags = 0;
|
|
request.push(binary.encode(tx));
|
|
assert.throws(() => {
|
|
this.api.combine(request);
|
|
}, /txJSON is not the same for all signedTransactions/);
|
|
});
|
|
|
|
describe('RippleAPI', function () {
|
|
|
|
it('getBalances', function () {
|
|
return this.api.getBalances(address).then(
|
|
_.partial(checkResult, responses.getBalances, 'getBalances'));
|
|
});
|
|
|
|
it('getBalances - limit', function () {
|
|
const options = {
|
|
limit: 3,
|
|
ledgerVersion: 123456
|
|
};
|
|
const expectedResponse = responses.getBalances.slice(0, 3);
|
|
return this.api.getBalances(address, options).then(
|
|
_.partial(checkResult, expectedResponse, 'getBalances'));
|
|
});
|
|
|
|
it('getBalances - limit & currency', function () {
|
|
const options = {
|
|
currency: 'USD',
|
|
limit: 3
|
|
};
|
|
const expectedResponse = _.filter(responses.getBalances,
|
|
item => item.currency === 'USD').slice(0, 3);
|
|
return this.api.getBalances(address, options).then(
|
|
_.partial(checkResult, expectedResponse, 'getBalances'));
|
|
});
|
|
|
|
it('getBalances - limit & currency & issuer', function () {
|
|
const options = {
|
|
currency: 'USD',
|
|
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
|
limit: 3
|
|
};
|
|
const expectedResponse = _.filter(responses.getBalances,
|
|
item => item.currency === 'USD' &&
|
|
item.counterparty === 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B').slice(0, 3);
|
|
return this.api.getBalances(address, options).then(
|
|
_.partial(checkResult, expectedResponse, 'getBalances'));
|
|
});
|
|
});
|
|
|
|
it('getBalanceSheet', function () {
|
|
return this.api.getBalanceSheet(address).then(
|
|
_.partial(checkResult, responses.getBalanceSheet, 'getBalanceSheet'));
|
|
});
|
|
|
|
it('getBalanceSheet - invalid options', function () {
|
|
return this.api.getBalanceSheet(address, { invalid: 'options' }).then(() => {
|
|
assert(false, 'Should throw ValidationError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.ValidationError);
|
|
});
|
|
});
|
|
|
|
it('getBalanceSheet - empty', function () {
|
|
const options = { ledgerVersion: 123456 };
|
|
return this.api.getBalanceSheet(address, options).then(
|
|
_.partial(checkResult, {}, 'getBalanceSheet'));
|
|
});
|
|
|
|
describe('getTransaction', () => {
|
|
it('getTransaction - payment', function () {
|
|
return this.api.getTransaction(hashes.VALID_TRANSACTION_HASH).then(
|
|
_.partial(checkResult, responses.getTransaction.payment,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - payment - include raw transaction', function () {
|
|
const options = {
|
|
includeRawTransaction: true
|
|
}
|
|
return this.api.getTransaction(
|
|
hashes.VALID_TRANSACTION_HASH, options
|
|
).then(
|
|
_.partial(checkResult, responses.getTransaction.paymentIncludeRawTransaction,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - settings', function () {
|
|
const hash =
|
|
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.settings,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - settings - include raw transaction', function () {
|
|
const hash =
|
|
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B';
|
|
const options = {
|
|
includeRawTransaction: true
|
|
}
|
|
const expected = responses.getTransaction.settings
|
|
expected.rawTransaction = "{\"Account\":\"rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe\",\"Fee\":\"10\",\"Flags\":2147483648,\"Sequence\":1,\"SetFlag\":2,\"SigningPubKey\":\"03EA3ADCA632F125EC2CC4F7F6A82DE0DCE2B65290CAC1F22242C5163F0DA9652D\",\"TransactionType\":\"AccountSet\",\"TxnSignature\":\"3045022100DE8B666B1A31EA65011B0F32130AB91A5747E32FA49B3054CEE8E8362DBAB98A022040CF0CF254677A8E5CD04C59CA2ED7F6F15F7E184641BAE169C561650967B226\",\"date\":460832270,\"hash\":\"4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B\",\"inLedger\":8206418,\"ledger_index\":8206418,\"meta\":{\"AffectedNodes\":[{\"ModifiedNode\":{\"FinalFields\":{\"Account\":\"rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe\",\"Balance\":\"29999990\",\"Flags\":786432,\"OwnerCount\":0,\"Sequence\":2},\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"3F5072C4875F32ED770DAF3610A716600ED7C7BB0348FADC7A98E011BB2CD36F\",\"PreviousFields\":{\"Balance\":\"30000000\",\"Flags\":4194304,\"Sequence\":1},\"PreviousTxnID\":\"3FB0350A3742BBCC0D8AA3C5247D1AEC01177D0A24D9C34762BAA2FEA8AD88B3\",\"PreviousTxnLgrSeq\":8206397}}],\"TransactionIndex\":5,\"TransactionResult\":\"tesSUCCESS\"},\"validated\":true}"
|
|
return this.api.getTransaction(hash, options).then(
|
|
_.partial(checkResult, expected,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - order', function () {
|
|
const hash =
|
|
'10A6FB4A66EE80BED46AAE4815D7DC43B97E944984CCD5B93BCF3F8538CABC51';
|
|
closeLedger(this.api.connection);
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.order,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - sell order', function () {
|
|
const hash =
|
|
'458101D51051230B1D56E9ACAFAA34451BF65FA000F95DF6F0FF5B3A62D83FC2';
|
|
closeLedger(this.api.connection);
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.orderSell,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - order cancellation', function () {
|
|
const hash =
|
|
'809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E';
|
|
closeLedger(this.api.connection);
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.orderCancellation,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - order with expiration cancellation', function () {
|
|
const hash =
|
|
'097B9491CC76B64831F1FEA82EAA93BCD728106D90B65A072C933888E946C40B';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.orderWithExpirationCancellation,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - trustline set', function () {
|
|
const hash =
|
|
'635A0769BD94710A1F6A76CDE65A3BC661B20B798807D1BBBDADCEA26420538D';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.trustline,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - trustline frozen off', function () {
|
|
const hash =
|
|
'FE72FAD0FA7CA904FB6C633A1666EDF0B9C73B2F5A4555D37EEF2739A78A531B';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.trustlineFrozenOff,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - trustline no quality', function () {
|
|
const hash =
|
|
'BAF1C678323C37CCB7735550C379287667D8288C30F83148AD3C1CB019FC9002';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.trustlineNoQuality,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - trustline add memo', function () {
|
|
const hash =
|
|
'9D6AC5FD6545B2584885B85E36759EB6440CDD41B6C55859F84AFDEE2B428220';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.trustlineAddMemo,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - not validated', function () {
|
|
const hash =
|
|
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA10';
|
|
return this.api.getTransaction(hash).then((response) => {
|
|
console.log(response);
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
assert.equal(error.message, 'Transaction not found');
|
|
});
|
|
});
|
|
|
|
it('getTransaction - tracking on', function () {
|
|
const hash =
|
|
'8925FC8844A1E930E2CC76AD0A15E7665AFCC5425376D548BB1413F484C31B8C';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.trackingOn,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - tracking off', function () {
|
|
const hash =
|
|
'C8C5E20DFB1BF533D0D81A2ED23F0A3CBD1EF2EE8A902A1D760500473CC9C582';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.trackingOff,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - set regular key', function () {
|
|
const hash =
|
|
'278E6687C1C60C6873996210A6523564B63F2844FB1019576C157353B1813E60';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult, responses.getTransaction.setRegularKey,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - not found in range', function () {
|
|
const hash =
|
|
'809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E';
|
|
const options = {
|
|
minLedgerVersion: 32570,
|
|
maxLedgerVersion: 32571
|
|
};
|
|
return this.api.getTransaction(hash, options).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
});
|
|
});
|
|
|
|
it('getTransaction - not found by hash', function () {
|
|
const hash = hashes.NOTFOUND_TRANSACTION_HASH;
|
|
return this.api.getTransaction(hash).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
});
|
|
});
|
|
|
|
it('getTransaction - missing ledger history', function () {
|
|
const hash = hashes.NOTFOUND_TRANSACTION_HASH;
|
|
// make gaps in history
|
|
closeLedger(this.api.connection);
|
|
return this.api.getTransaction(hash).then(() => {
|
|
assert(false, 'Should throw MissingLedgerHistoryError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.MissingLedgerHistoryError);
|
|
});
|
|
});
|
|
|
|
it('getTransaction - missing ledger history with ledger range', function () {
|
|
const hash = hashes.NOTFOUND_TRANSACTION_HASH;
|
|
const options = {
|
|
minLedgerVersion: 32569,
|
|
maxLedgerVersion: 32571
|
|
};
|
|
return this.api.getTransaction(hash, options).then(() => {
|
|
assert(false, 'Should throw MissingLedgerHistoryError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.MissingLedgerHistoryError);
|
|
});
|
|
});
|
|
|
|
it('getTransaction - not found - future maxLedgerVersion', function () {
|
|
const hash = hashes.NOTFOUND_TRANSACTION_HASH;
|
|
const options = {
|
|
maxLedgerVersion: 99999999999
|
|
};
|
|
return this.api.getTransaction(hash, options).then(() => {
|
|
assert(false, 'Should throw PendingLedgerVersionError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.PendingLedgerVersionError);
|
|
assert.strictEqual(error.message, 'maxLedgerVersion is greater than server\'s'
|
|
+ ' most recent validated ledger')
|
|
});
|
|
});
|
|
|
|
it('getTransaction - ledger_index not found', function () {
|
|
const hash =
|
|
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11';
|
|
return this.api.getTransaction(hash).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
assert(error.message.indexOf('ledger_index') !== -1);
|
|
});
|
|
});
|
|
|
|
it('getTransaction - transaction ledger not found', function () {
|
|
const hash =
|
|
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA12';
|
|
return this.api.getTransaction(hash).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
assert(error.message.indexOf('ledger not found') !== -1);
|
|
});
|
|
});
|
|
|
|
it('getTransaction - ledger missing close time', function () {
|
|
const hash =
|
|
'0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A04';
|
|
closeLedger(this.api.connection);
|
|
return this.api.getTransaction(hash).then(() => {
|
|
assert(false, 'Should throw UnexpectedError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.UnexpectedError);
|
|
});
|
|
});
|
|
|
|
// Checks
|
|
|
|
it('getTransaction - CheckCreate', function () {
|
|
const hash =
|
|
'605A2E2C8E48AECAF5C56085D1AEAA0348DC838CE122C9188F94EB19DA05C2FE';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.checkCreate,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - CheckCancel', function () {
|
|
const hash =
|
|
'B4105D1B2D83819647E4692B7C5843D674283F669524BD50C9614182E3A12CD4';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.checkCancel,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - CheckCash', function () {
|
|
const hash =
|
|
'8321208465F70BA52C28BCC4F646BAF3B012BA13B57576C0336F42D77E3E0749';
|
|
return this.api.getTransaction(hash/*, options*/).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.checkCash,
|
|
'getTransaction'));
|
|
});
|
|
|
|
// Escrows
|
|
|
|
it('getTransaction - EscrowCreation', function () {
|
|
const hash =
|
|
'144F272380BDB4F1BD92329A2178BABB70C20F59042C495E10BF72EBFB408EE1';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.escrowCreation,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - EscrowCancellation', function () {
|
|
const hash =
|
|
'F346E542FFB7A8398C30A87B952668DAB48B7D421094F8B71776DA19775A3B22';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.escrowCancellation,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - EscrowExecution', function () {
|
|
const options = {
|
|
minLedgerVersion: 10,
|
|
maxLedgerVersion: 15
|
|
};
|
|
const hash =
|
|
'CC5277137B3F25EE8B86259C83CB0EAADE818505E4E9BCBF19B1AC6FD136993B';
|
|
return this.api.getTransaction(hash, options).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.escrowExecution,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - EscrowExecution simple', function () {
|
|
const hash =
|
|
'CC5277137B3F25EE8B86259C83CB0EAADE818505E4E9BCBF19B1AC6FD1369931';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.escrowExecutionSimple,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - PaymentChannelCreate', function () {
|
|
const hash =
|
|
'0E9CA3AB1053FC0C1CBAA75F636FE1EC92F118C7056BBEF5D63E4C116458A16D';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.paymentChannelCreate,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - PaymentChannelFund', function () {
|
|
const hash =
|
|
'CD053D8867007A6A4ACB7A432605FE476D088DCB515AFFC886CF2B4EB6D2AE8B';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.paymentChannelFund,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - PaymentChannelClaim', function () {
|
|
const hash =
|
|
'81B9ECAE7195EB6E8034AEDF44D8415A7A803E14513FDBB34FA984AB37D59563';
|
|
return this.api.getTransaction(hash).then(
|
|
_.partial(checkResult,
|
|
responses.getTransaction.paymentChannelClaim,
|
|
'getTransaction'));
|
|
});
|
|
|
|
it('getTransaction - no Meta', function () {
|
|
const hash =
|
|
'AFB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B';
|
|
return this.api.getTransaction(hash).then(result => {
|
|
assert.deepEqual(result, responses.getTransaction.noMeta);
|
|
});
|
|
});
|
|
|
|
it('getTransaction - Unrecognized transaction type', function () {
|
|
const hash =
|
|
'AFB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11';
|
|
closeLedger(this.api.connection);
|
|
return this.api.getTransaction(hash).then(() => {
|
|
assert(false, 'Unrecognized transaction type');
|
|
}).catch(error => {
|
|
assert.strictEqual(error.message, 'Unrecognized transaction type');
|
|
});
|
|
});
|
|
|
|
it('getTransaction - amendment', function () {
|
|
const hash =
|
|
'A971B83ABED51D83749B73F3C1AAA627CD965AFF74BE8CD98299512D6FB0658F';
|
|
return this.api.getTransaction(hash).then(result => {
|
|
assert.deepEqual(result, responses.getTransaction.amendment);
|
|
});
|
|
});
|
|
|
|
it('getTransaction - feeUpdate', function () {
|
|
const hash =
|
|
'C6A40F56127436DCD830B1B35FF939FD05B5747D30D6542572B7A835239817AF';
|
|
return this.api.getTransaction(hash).then(result => {
|
|
assert.deepEqual(result, responses.getTransaction.feeUpdate);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('getTransactions', function () {
|
|
const options = { types: ['payment', 'order'], initiated: true, limit: 2 };
|
|
return this.api.getTransactions(address, options).then(
|
|
_.partial(checkResult, responses.getTransactions.normal,
|
|
'getTransactions'));
|
|
});
|
|
|
|
it('getTransactions - include raw transactions', function () {
|
|
const options = {
|
|
types: ['payment', 'order'],
|
|
initiated: true,
|
|
limit: 2,
|
|
includeRawTransactions: true
|
|
};
|
|
return this.api.getTransactions(address, options).then(
|
|
_.partial(checkResult, responses.getTransactions.includeRawTransactions,
|
|
'getTransactions'));
|
|
});
|
|
|
|
it('getTransactions - earliest first', function () {
|
|
const options = {
|
|
types: ['payment', 'order'], initiated: true, limit: 2,
|
|
earliestFirst: true
|
|
};
|
|
const expected = _.cloneDeep(responses.getTransactions.normal)
|
|
.sort(utils.compareTransactions);
|
|
return this.api.getTransactions(address, options).then(
|
|
_.partial(checkResult, expected, 'getTransactions'));
|
|
});
|
|
|
|
|
|
it('getTransactions - earliest first with start option', function () {
|
|
const options = {
|
|
types: ['payment', 'order'], initiated: true, limit: 2,
|
|
start: hashes.VALID_TRANSACTION_HASH,
|
|
earliestFirst: true
|
|
};
|
|
return this.api.getTransactions(address, options).then(data => {
|
|
assert.strictEqual(data.length, 0);
|
|
});
|
|
});
|
|
|
|
it('getTransactions - gap', function () {
|
|
const options = {
|
|
types: ['payment', 'order'], initiated: true, limit: 2,
|
|
maxLedgerVersion: 348858000
|
|
};
|
|
return this.api.getTransactions(address, options).then(() => {
|
|
assert(false, 'Should throw MissingLedgerHistoryError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.MissingLedgerHistoryError);
|
|
});
|
|
});
|
|
|
|
it('getTransactions - tx not found', function () {
|
|
const options = {
|
|
types: ['payment', 'order'], initiated: true, limit: 2,
|
|
start: hashes.NOTFOUND_TRANSACTION_HASH,
|
|
counterparty: address
|
|
};
|
|
return this.api.getTransactions(address, options).then((response) => {
|
|
console.log(response);
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
});
|
|
});
|
|
|
|
it('getTransactions - filters', function () {
|
|
const options = {
|
|
types: ['payment', 'order'], initiated: true, limit: 10,
|
|
excludeFailures: true,
|
|
counterparty: addresses.ISSUER
|
|
};
|
|
return this.api.getTransactions(address, options).then(data => {
|
|
assert.strictEqual(data.length, 10);
|
|
assert(_.every(data, t => t.type === 'payment' || t.type === 'order'));
|
|
assert(_.every(data, t => t.outcome.result === 'tesSUCCESS'));
|
|
});
|
|
});
|
|
|
|
it('getTransactions - filters for incoming', function () {
|
|
const options = {
|
|
types: ['payment', 'order'], initiated: false, limit: 10,
|
|
excludeFailures: true,
|
|
counterparty: addresses.ISSUER
|
|
};
|
|
return this.api.getTransactions(address, options).then(data => {
|
|
assert.strictEqual(data.length, 10);
|
|
assert(_.every(data, t => t.type === 'payment' || t.type === 'order'));
|
|
assert(_.every(data, t => t.outcome.result === 'tesSUCCESS'));
|
|
});
|
|
});
|
|
|
|
// this is the case where core.RippleError just falls
|
|
// through the api to the user
|
|
it('getTransactions - error', function () {
|
|
const options = { types: ['payment', 'order'], initiated: true, limit: 13 };
|
|
return this.api.getTransactions(address, options).then(() => {
|
|
assert(false, 'Should throw RippleError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.RippleError);
|
|
});
|
|
});
|
|
|
|
// TODO: this doesn't test much, just that it doesn't crash
|
|
it('getTransactions with start option', function () {
|
|
const options = {
|
|
start: hashes.VALID_TRANSACTION_HASH,
|
|
earliestFirst: false,
|
|
limit: 2
|
|
};
|
|
return this.api.getTransactions(address, options).then(
|
|
_.partial(checkResult, responses.getTransactions.normal,
|
|
'getTransactions'));
|
|
});
|
|
|
|
it('getTransactions - start transaction with zero ledger version', function (
|
|
) {
|
|
const options = {
|
|
start: '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA13',
|
|
limit: 1
|
|
};
|
|
return this.api.getTransactions(address, options).then(
|
|
_.partial(checkResult, [], 'getTransactions'));
|
|
});
|
|
|
|
it('getTransactions - no options', function () {
|
|
return this.api.getTransactions(addresses.OTHER_ACCOUNT).then(
|
|
_.partial(checkResult, responses.getTransactions.one, 'getTransactions'));
|
|
});
|
|
|
|
it('getTrustlines - filtered', function () {
|
|
const options = { currency: 'USD' };
|
|
return this.api.getTrustlines(address, options).then(
|
|
_.partial(checkResult,
|
|
responses.getTrustlines.filtered, 'getTrustlines'));
|
|
});
|
|
|
|
it('getTrustlines - more than 400 items', function () {
|
|
const options = { limit: 401 };
|
|
return this.api.getTrustlines(addresses.THIRD_ACCOUNT, options).then(
|
|
_.partial(checkResult, responses.getTrustlines.moreThan400Items, 'getTrustlines'));
|
|
});
|
|
|
|
it('getTrustlines - no options', function () {
|
|
return this.api.getTrustlines(address).then(
|
|
_.partial(checkResult, responses.getTrustlines.all, 'getTrustlines'));
|
|
});
|
|
|
|
it('generateAddress', function () {
|
|
function random() {
|
|
return _.fill(Array(16), 0);
|
|
}
|
|
assert.deepEqual(this.api.generateAddress({ entropy: random() }),
|
|
responses.generateAddress);
|
|
});
|
|
|
|
it('generateAddress invalid', function () {
|
|
assert.throws(() => {
|
|
function random() {
|
|
return _.fill(Array(1), 0);
|
|
}
|
|
this.api.generateAddress({ entropy: random() });
|
|
}, this.api.errors.UnexpectedError);
|
|
});
|
|
|
|
it('getSettings', function () {
|
|
return this.api.getSettings(address).then(
|
|
_.partial(checkResult, responses.getSettings, 'getSettings'));
|
|
});
|
|
|
|
it('getSettings - options undefined', function () {
|
|
return this.api.getSettings(address, undefined).then(
|
|
_.partial(checkResult, responses.getSettings, 'getSettings'));
|
|
});
|
|
|
|
it('getSettings - invalid options', function () {
|
|
return this.api.getSettings(address, { invalid: 'options' }).then(() => {
|
|
assert(false, 'Should throw ValidationError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.ValidationError);
|
|
});
|
|
});
|
|
|
|
it('getAccountInfo', function () {
|
|
return this.api.getAccountInfo(address).then(
|
|
_.partial(checkResult, responses.getAccountInfo, 'getAccountInfo'));
|
|
});
|
|
|
|
it('getAccountInfo - options undefined', function () {
|
|
return this.api.getAccountInfo(address, undefined).then(
|
|
_.partial(checkResult, responses.getAccountInfo, 'getAccountInfo'));
|
|
});
|
|
|
|
it('getAccountInfo - invalid options', function () {
|
|
return this.api.getAccountInfo(address, { invalid: 'options' }).then(() => {
|
|
assert(false, 'Should throw ValidationError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.ValidationError);
|
|
});
|
|
});
|
|
|
|
it('getAccountObjects', function () {
|
|
return this.api.getAccountObjects(address).then(response =>
|
|
checkResult(responses.getAccountObjects, 'AccountObjectsResponse', response));
|
|
});
|
|
|
|
it('getAccountObjects - invalid options', function () {
|
|
// Intentionally no local validation of these options
|
|
return this.api.getAccountObjects(address, {invalid: 'options'}).then(response =>
|
|
checkResult(responses.getAccountObjects, 'AccountObjectsResponse', response));
|
|
});
|
|
|
|
it('request account_objects', function () {
|
|
return this.api.request('account_objects', {
|
|
account: address
|
|
}).then(response =>
|
|
checkResult(responses.getAccountObjects, 'AccountObjectsResponse', response));
|
|
});
|
|
|
|
it('request account_objects - invalid options', function () {
|
|
// Intentionally no local validation of these options
|
|
return this.api.request('account_objects', {
|
|
account: address,
|
|
invalid: 'options'
|
|
}).then(response =>
|
|
checkResult(responses.getAccountObjects, 'AccountObjectsResponse', response));
|
|
});
|
|
|
|
it('getOrders', function () {
|
|
return this.api.getOrders(address).then(
|
|
_.partial(checkResult, responses.getOrders, 'getOrders'));
|
|
});
|
|
|
|
it('getOrders - limit', function () {
|
|
return this.api.getOrders(address, { limit: 20 }).then(
|
|
_.partial(checkResult, responses.getOrders, 'getOrders'));
|
|
});
|
|
|
|
it('getOrders - invalid options', function () {
|
|
return this.api.getOrders(address, { invalid: 'options' }).then(() => {
|
|
assert(false, 'Should throw ValidationError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.ValidationError);
|
|
});
|
|
});
|
|
|
|
describe('formatBidsAndAsks', function () {
|
|
|
|
it('normal', function () {
|
|
const orderbookInfo = {
|
|
"base": {
|
|
"currency": "USD",
|
|
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"counter": {
|
|
"currency": "BTC",
|
|
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
}
|
|
};
|
|
|
|
return Promise.all(
|
|
[
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
ledger_index: 'validated',
|
|
limit: 20,
|
|
taker: address
|
|
}),
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
ledger_index: 'validated',
|
|
limit: 20,
|
|
taker: address
|
|
})
|
|
]
|
|
).then((directOfferResults, reverseOfferResults) => {
|
|
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
|
assert.deepEqual(orderbook, responses.getOrderbook.normal);
|
|
});
|
|
});
|
|
|
|
it('with XRP', function () {
|
|
const orderbookInfo = {
|
|
"base": {
|
|
"currency": "USD",
|
|
"counterparty": "rp8rJYTpodf8qbSCHVTNacf8nSW8mRakFw"
|
|
},
|
|
"counter": {
|
|
"currency": "XRP"
|
|
}
|
|
};
|
|
|
|
return Promise.all(
|
|
[
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
ledger_index: 'validated',
|
|
limit: 20,
|
|
taker: address
|
|
}),
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
ledger_index: 'validated',
|
|
limit: 20,
|
|
taker: address
|
|
})
|
|
]
|
|
).then((directOfferResults, reverseOfferResults) => {
|
|
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
|
assert.deepEqual(orderbook, responses.getOrderbook.withXRP);
|
|
});
|
|
});
|
|
|
|
function checkSortingOfOrders(orders) {
|
|
let previousRate = '0';
|
|
for (var i = 0; i < orders.length; i++) {
|
|
const order = orders[i];
|
|
let rate;
|
|
|
|
// We calculate the quality of output/input here as a test.
|
|
// This won't hold in general because when output and input amounts get tiny,
|
|
// the quality can differ significantly. However, the offer stays in the
|
|
// order book where it was originally placed. It would be more consistent
|
|
// to check the quality from the offer book, but for the test data set,
|
|
// this calculation holds.
|
|
|
|
if (order.specification.direction === 'buy') {
|
|
rate = (new BigNumber(order.specification.quantity.value))
|
|
.dividedBy(order.specification.totalPrice.value)
|
|
.toString();
|
|
} else {
|
|
rate = (new BigNumber(order.specification.totalPrice.value))
|
|
.dividedBy(order.specification.quantity.value)
|
|
.toString();
|
|
}
|
|
assert((new BigNumber(rate)).greaterThanOrEqualTo(previousRate),
|
|
'Rates must be sorted from least to greatest: ' +
|
|
rate + ' should be >= ' + previousRate);
|
|
previousRate = rate;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
it('sample XRP/JPY book has orders sorted correctly', function () {
|
|
const orderbookInfo = {
|
|
"base": { // the first currency in pair
|
|
"currency": 'XRP'
|
|
},
|
|
"counter": {
|
|
"currency": 'JPY',
|
|
"counterparty": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS"
|
|
}
|
|
};
|
|
|
|
const myAddress = 'rE9qNjzJXpiUbVomdv7R4xhrXVeH2oVmGR';
|
|
|
|
return Promise.all(
|
|
[
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
ledger_index: 'validated',
|
|
limit: 400, // must match `test/fixtures/rippled/requests/1-taker_gets-XRP-taker_pays-JPY.json`
|
|
taker: myAddress
|
|
}),
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
ledger_index: 'validated',
|
|
limit: 400, // must match `test/fixtures/rippled/requests/2-taker_gets-JPY-taker_pays-XRP.json`
|
|
taker: myAddress
|
|
})
|
|
]
|
|
).then((directOfferResults, reverseOfferResults) => {
|
|
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
|
assert.deepStrictEqual([], orderbook.bids);
|
|
return checkSortingOfOrders(orderbook.asks);
|
|
});
|
|
});
|
|
|
|
it('sample USD/XRP book has orders sorted correctly', function () {
|
|
const orderbookInfo = { counter: { currency: 'XRP' },
|
|
base: { currency: 'USD',
|
|
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' } };
|
|
|
|
const myAddress = 'rE9qNjzJXpiUbVomdv7R4xhrXVeH2oVmGR';
|
|
|
|
return Promise.all(
|
|
[
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
ledger_index: 'validated',
|
|
limit: 400, // must match `test/fixtures/rippled/requests/1-taker_gets-XRP-taker_pays-JPY.json`
|
|
taker: myAddress
|
|
}),
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
ledger_index: 'validated',
|
|
limit: 400, // must match `test/fixtures/rippled/requests/2-taker_gets-JPY-taker_pays-XRP.json`
|
|
taker: myAddress
|
|
})
|
|
]
|
|
).then((directOfferResults, reverseOfferResults) => {
|
|
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
|
return checkSortingOfOrders(orderbook.bids) && checkSortingOfOrders(orderbook.asks);
|
|
});
|
|
});
|
|
|
|
it('sorted so that best deals come first', function () {
|
|
const orderbookInfo = {
|
|
"base": {
|
|
"currency": "USD",
|
|
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"counter": {
|
|
"currency": "BTC",
|
|
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
}
|
|
};
|
|
|
|
return Promise.all(
|
|
[
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
ledger_index: 'validated',
|
|
limit: 20,
|
|
taker: address
|
|
}),
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
ledger_index: 'validated',
|
|
limit: 20,
|
|
taker: address
|
|
})
|
|
]
|
|
).then((directOfferResults, reverseOfferResults) => {
|
|
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
|
|
|
const bidRates = orderbook.bids.map(bid => bid.properties.makerExchangeRate);
|
|
const askRates = orderbook.asks.map(ask => ask.properties.makerExchangeRate);
|
|
// makerExchangeRate = quality = takerPays.value/takerGets.value
|
|
// so the best deal for the taker is the lowest makerExchangeRate
|
|
// bids and asks should be sorted so that the best deals come first
|
|
assert.deepEqual(_.sortBy(bidRates, x => Number(x)), bidRates);
|
|
assert.deepEqual(_.sortBy(askRates, x => Number(x)), askRates);
|
|
});
|
|
});
|
|
|
|
it('currency & counterparty are correct', function () {
|
|
const orderbookInfo = {
|
|
"base": {
|
|
"currency": "USD",
|
|
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"counter": {
|
|
"currency": "BTC",
|
|
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
}
|
|
};
|
|
|
|
return Promise.all(
|
|
[
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
ledger_index: 'validated',
|
|
limit: 20,
|
|
taker: address
|
|
}),
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
ledger_index: 'validated',
|
|
limit: 20,
|
|
taker: address
|
|
})
|
|
]
|
|
).then((directOfferResults, reverseOfferResults) => {
|
|
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
|
|
|
const orders = _.flatten([orderbook.bids, orderbook.asks]);
|
|
_.forEach(orders, order => {
|
|
const quantity = order.specification.quantity;
|
|
const totalPrice = order.specification.totalPrice;
|
|
const { base, counter } = requests.getOrderbook.normal;
|
|
assert.strictEqual(quantity.currency, base.currency);
|
|
assert.strictEqual(quantity.counterparty, base.counterparty);
|
|
assert.strictEqual(totalPrice.currency, counter.currency);
|
|
assert.strictEqual(totalPrice.counterparty, counter.counterparty);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('direction is correct for bids and asks', function () {
|
|
const orderbookInfo = {
|
|
"base": {
|
|
"currency": "USD",
|
|
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"counter": {
|
|
"currency": "BTC",
|
|
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
}
|
|
};
|
|
|
|
return Promise.all(
|
|
[
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
ledger_index: 'validated',
|
|
limit: 20,
|
|
taker: address
|
|
}),
|
|
this.api.request('book_offers', {
|
|
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
|
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
|
ledger_index: 'validated',
|
|
limit: 20,
|
|
taker: address
|
|
})
|
|
]
|
|
).then((directOfferResults, reverseOfferResults) => {
|
|
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
|
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
|
|
|
assert(
|
|
_.every(orderbook.bids, bid => bid.specification.direction === 'buy'));
|
|
assert(
|
|
_.every(orderbook.asks, ask => ask.specification.direction === 'sell'));
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getOrderbook', function () {
|
|
|
|
it('normal', function () {
|
|
return this.api.getOrderbook(address,
|
|
requests.getOrderbook.normal, { limit: 20 }).then(
|
|
_.partial(checkResult,
|
|
responses.getOrderbook.normal, 'getOrderbook'));
|
|
});
|
|
|
|
it('invalid options', function () {
|
|
return this.api.getOrderbook(
|
|
address, requests.getOrderbook.normal, { invalid: 'options' }
|
|
).then(() => {
|
|
assert(false, 'Should throw ValidationError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.ValidationError);
|
|
});
|
|
});
|
|
|
|
it('with XRP', function () {
|
|
return this.api.getOrderbook(address, requests.getOrderbook.withXRP).then(
|
|
_.partial(checkResult, responses.getOrderbook.withXRP, 'getOrderbook'));
|
|
});
|
|
|
|
function checkSortingOfOrders(orders) {
|
|
let previousRate = '0';
|
|
for (var i = 0; i < orders.length; i++) {
|
|
const order = orders[i];
|
|
let rate;
|
|
|
|
// We calculate the quality of output/input here as a test.
|
|
// This won't hold in general because when output and input amounts get tiny,
|
|
// the quality can differ significantly. However, the offer stays in the
|
|
// order book where it was originally placed. It would be more consistent
|
|
// to check the quality from the offer book, but for the test data set,
|
|
// this calculation holds.
|
|
|
|
if (order.specification.direction === 'buy') {
|
|
rate = (new BigNumber(order.specification.quantity.value))
|
|
.dividedBy(order.specification.totalPrice.value)
|
|
.toString();
|
|
} else {
|
|
rate = (new BigNumber(order.specification.totalPrice.value))
|
|
.dividedBy(order.specification.quantity.value)
|
|
.toString();
|
|
}
|
|
assert((new BigNumber(rate)).greaterThanOrEqualTo(previousRate),
|
|
'Rates must be sorted from least to greatest: ' +
|
|
rate + ' should be >= ' + previousRate);
|
|
previousRate = rate;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
it('sample XRP/JPY book has orders sorted correctly', function () {
|
|
const orderbookInfo = {
|
|
"base": { // the first currency in pair
|
|
"currency": 'XRP'
|
|
},
|
|
"counter": {
|
|
"currency": 'JPY',
|
|
"counterparty": "rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS"
|
|
}
|
|
};
|
|
|
|
const myAddress = 'rE9qNjzJXpiUbVomdv7R4xhrXVeH2oVmGR';
|
|
|
|
return this.api.getOrderbook(myAddress, orderbookInfo).then(orderbook => {
|
|
assert.deepStrictEqual([], orderbook.bids);
|
|
return checkSortingOfOrders(orderbook.asks);
|
|
});
|
|
});
|
|
|
|
it('sample USD/XRP book has orders sorted correctly', function () {
|
|
const orderbookInfo = { counter: { currency: 'XRP' },
|
|
base: { currency: 'USD',
|
|
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' } };
|
|
|
|
const myAddress = 'rE9qNjzJXpiUbVomdv7R4xhrXVeH2oVmGR';
|
|
|
|
return this.api.getOrderbook(myAddress, orderbookInfo).then(orderbook => {
|
|
return checkSortingOfOrders(orderbook.bids) && checkSortingOfOrders(orderbook.asks);
|
|
});
|
|
});
|
|
|
|
// WARNING: This test fails to catch the sorting bug, issue #766
|
|
it('sorted so that best deals come first [bad test]', function () {
|
|
return this.api.getOrderbook(address, requests.getOrderbook.normal)
|
|
.then(data => {
|
|
const bidRates = data.bids.map(bid => bid.properties.makerExchangeRate);
|
|
const askRates = data.asks.map(ask => ask.properties.makerExchangeRate);
|
|
// makerExchangeRate = quality = takerPays.value/takerGets.value
|
|
// so the best deal for the taker is the lowest makerExchangeRate
|
|
// bids and asks should be sorted so that the best deals come first
|
|
assert.deepEqual(_.sortBy(bidRates, x => Number(x)), bidRates);
|
|
assert.deepEqual(_.sortBy(askRates, x => Number(x)), askRates);
|
|
});
|
|
});
|
|
|
|
it('currency & counterparty are correct', function () {
|
|
return this.api.getOrderbook(address, requests.getOrderbook.normal)
|
|
.then(data => {
|
|
const orders = _.flatten([data.bids, data.asks]);
|
|
_.forEach(orders, order => {
|
|
const quantity = order.specification.quantity;
|
|
const totalPrice = order.specification.totalPrice;
|
|
const { base, counter } = requests.getOrderbook.normal;
|
|
assert.strictEqual(quantity.currency, base.currency);
|
|
assert.strictEqual(quantity.counterparty, base.counterparty);
|
|
assert.strictEqual(totalPrice.currency, counter.currency);
|
|
assert.strictEqual(totalPrice.counterparty, counter.counterparty);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('direction is correct for bids and asks', function () {
|
|
return this.api.getOrderbook(address, requests.getOrderbook.normal)
|
|
.then(data => {
|
|
assert(
|
|
_.every(data.bids, bid => bid.specification.direction === 'buy'));
|
|
assert(
|
|
_.every(data.asks, ask => ask.specification.direction === 'sell'));
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
it('getPaymentChannel', function () {
|
|
const channelId =
|
|
'E30E709CF009A1F26E0E5C48F7AA1BFB79393764F15FB108BDC6E06D3CBD8415';
|
|
return this.api.getPaymentChannel(channelId).then(
|
|
_.partial(checkResult, responses.getPaymentChannel.normal,
|
|
'getPaymentChannel'));
|
|
});
|
|
|
|
it('getPaymentChannel - full', function () {
|
|
const channelId =
|
|
'D77CD4713AA08195E6B6D0E5BC023DA11B052EBFF0B5B22EDA8AE85345BCF661';
|
|
return this.api.getPaymentChannel(channelId).then(
|
|
_.partial(checkResult, responses.getPaymentChannel.full,
|
|
'getPaymentChannel'));
|
|
});
|
|
|
|
it('getPaymentChannel - not found', function () {
|
|
const channelId =
|
|
'DFA557EA3497585BFE83F0F97CC8E4530BBB99967736BB95225C7F0C13ACE708';
|
|
return this.api.getPaymentChannel(channelId).then(() => {
|
|
assert(false, 'Should throw entryNotFound');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.RippledError);
|
|
assert.equal(error.message, 'entryNotFound');
|
|
assert.equal(error.data.error, 'entryNotFound');
|
|
});
|
|
});
|
|
|
|
it('getPaymentChannel - wrong type', function () {
|
|
const channelId =
|
|
'8EF9CCB9D85458C8D020B3452848BBB42EAFDDDB69A93DD9D1223741A4CA562B';
|
|
return this.api.getPaymentChannel(channelId).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(_.includes(error.message,
|
|
'Payment channel ledger entry not found'));
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
});
|
|
});
|
|
|
|
it('getServerInfo', function () {
|
|
return this.api.getServerInfo().then(
|
|
_.partial(checkResult, responses.getServerInfo, 'getServerInfo'));
|
|
});
|
|
|
|
it('getServerInfo - error', function () {
|
|
this.api.connection._send(JSON.stringify({
|
|
command: 'config',
|
|
data: { returnErrorOnServerInfo: true }
|
|
}));
|
|
|
|
return this.api.getServerInfo().then(() => {
|
|
assert(false, 'Should throw NetworkError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.RippledError);
|
|
assert.equal(error.message, 'You are placing too much load on the server.');
|
|
assert.equal(error.data.error, 'slowDown');
|
|
});
|
|
});
|
|
|
|
it('getServerInfo - no validated ledger', function () {
|
|
this.api.connection._send(JSON.stringify({
|
|
command: 'config',
|
|
data: { serverInfoWithoutValidated: true }
|
|
}));
|
|
|
|
return this.api.getServerInfo().then(info => {
|
|
assert.strictEqual(info.networkLedger, 'waiting');
|
|
}).catch(error => {
|
|
assert(false, 'Should not throw Error, got ' + String(error));
|
|
});
|
|
});
|
|
|
|
it('getFee', function () {
|
|
return this.api.getFee().then(fee => {
|
|
assert.strictEqual(fee, '0.000012');
|
|
});
|
|
});
|
|
|
|
it('getFee default', function () {
|
|
this.api._feeCushion = undefined;
|
|
return this.api.getFee().then(fee => {
|
|
assert.strictEqual(fee, '0.000012');
|
|
});
|
|
});
|
|
|
|
it('getFee - high load_factor', function () {
|
|
this.api.connection._send(JSON.stringify({
|
|
command: 'config',
|
|
data: { highLoadFactor: true }
|
|
}));
|
|
|
|
return this.api.getFee().then(fee => {
|
|
assert.strictEqual(fee, '2');
|
|
});
|
|
});
|
|
|
|
it('getFee - high load_factor with custom maxFeeXRP', function () {
|
|
// Ensure that overriding with high maxFeeXRP of '51540' causes no errors.
|
|
// (fee will actually be 51539.607552)
|
|
this.api._maxFeeXRP = '51540'
|
|
this.api.connection._send(JSON.stringify({
|
|
command: 'config',
|
|
data: { highLoadFactor: true }
|
|
}));
|
|
|
|
return this.api.getFee().then(fee => {
|
|
assert.strictEqual(fee, '51539.607552');
|
|
});
|
|
});
|
|
|
|
it('fee - default maxFee of 2 XRP', function () {
|
|
this.api._feeCushion = 1000000;
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"2000000\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "2",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.normal, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, expectedResponse, 'prepare'));
|
|
});
|
|
|
|
it('fee - capped to maxFeeXRP when maxFee exceeds maxFeeXRP', function () {
|
|
this.api._feeCushion = 1000000
|
|
this.api._maxFeeXRP = '3'
|
|
const localInstructions = _.defaults({
|
|
maxFee: '4'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"3000000\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "3",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.normal, localInstructions).then(
|
|
_.partial(checkResult, expectedResponse, 'prepare'));
|
|
});
|
|
|
|
it('fee - capped to maxFee', function () {
|
|
this.api._feeCushion = 1000000
|
|
this.api._maxFeeXRP = '5'
|
|
const localInstructions = _.defaults({
|
|
maxFee: '4'
|
|
}, instructionsWithMaxLedgerVersionOffset);
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"4000000\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "4",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.normal, localInstructions).then(
|
|
_.partial(checkResult, expectedResponse, 'prepare'));
|
|
});
|
|
|
|
it('fee - calculated fee does not use more than 6 decimal places', function () {
|
|
this.api.connection._send(JSON.stringify({
|
|
command: 'config',
|
|
data: { loadFactor: 5407.96875 }
|
|
}));
|
|
|
|
const expectedResponse = {
|
|
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"64896\",\"Sequence\":23}",
|
|
"instructions": {
|
|
"fee": "0.064896",
|
|
"sequence": 23,
|
|
"maxLedgerVersion": 8820051
|
|
}
|
|
}
|
|
|
|
return this.api.preparePayment(
|
|
address, requests.preparePayment.normal, instructionsWithMaxLedgerVersionOffset).then(
|
|
_.partial(checkResult, expectedResponse, 'prepare'));
|
|
});
|
|
|
|
it('getFee custom cushion', function () {
|
|
this.api._feeCushion = 1.4;
|
|
return this.api.getFee().then(fee => {
|
|
assert.strictEqual(fee, '0.000014');
|
|
});
|
|
});
|
|
|
|
// This is not recommended since it may result in attempting to pay
|
|
// less than the base fee. However, this test verifies
|
|
// the existing behavior.
|
|
it('getFee cushion less than 1.0', function () {
|
|
this.api._feeCushion = 0.9;
|
|
return this.api.getFee().then(fee => {
|
|
assert.strictEqual(fee, '0.000009');
|
|
});
|
|
});
|
|
|
|
it('disconnect & isConnected', function () {
|
|
assert.strictEqual(this.api.isConnected(), true);
|
|
return this.api.disconnect().then(() => {
|
|
assert.strictEqual(this.api.isConnected(), false);
|
|
});
|
|
});
|
|
|
|
it('getPaths', function () {
|
|
return this.api.getPaths(requests.getPaths.normal).then(
|
|
_.partial(checkResult, responses.getPaths.XrpToUsd, 'getPaths'));
|
|
});
|
|
|
|
it('getPaths - result path has source_amount in drops', function () {
|
|
return this.api.getPaths({
|
|
source: {
|
|
address: 'rB2NTuTTS3eNCsWxZYzJ4wqRqxNLZqA9Vx',
|
|
amount: {
|
|
value: this.api.dropsToXrp(1000000),
|
|
currency: 'XRP'
|
|
}
|
|
},
|
|
destination: {
|
|
address: 'rhpJkBfZGQyT1xeDbwtKEuSrSXw3QZSAy5',
|
|
amount: {
|
|
counterparty: 'rGpGaj4sxEZGenW1prqER25EUi7x4fqK9u',
|
|
currency: 'EUR'
|
|
}
|
|
}
|
|
}).then(
|
|
_.partial(checkResult, [
|
|
{
|
|
"source": {
|
|
"address": "rB2NTuTTS3eNCsWxZYzJ4wqRqxNLZqA9Vx",
|
|
"amount": {
|
|
"currency": "XRP",
|
|
"value": "1"
|
|
}
|
|
},
|
|
"destination": {
|
|
"address": "rhpJkBfZGQyT1xeDbwtKEuSrSXw3QZSAy5",
|
|
"minAmount": {
|
|
"currency": "EUR",
|
|
"value": "1",
|
|
"counterparty": "rGpGaj4sxEZGenW1prqER25EUi7x4fqK9u"
|
|
}
|
|
},
|
|
"paths": "[[{\"currency\":\"USD\",\"issuer\":\"rGpGaj4sxEZGenW1prqER25EUi7x4fqK9u\"},{\"currency\":\"EUR\",\"issuer\":\"rGpGaj4sxEZGenW1prqER25EUi7x4fqK9u\"}]]"
|
|
}
|
|
], 'getPaths'));
|
|
});
|
|
|
|
it('getPaths - queuing', function () {
|
|
return Promise.all([
|
|
this.api.getPaths(requests.getPaths.normal),
|
|
this.api.getPaths(requests.getPaths.UsdToUsd),
|
|
this.api.getPaths(requests.getPaths.XrpToXrp)
|
|
]).then(results => {
|
|
checkResult(responses.getPaths.XrpToUsd, 'getPaths', results[0]);
|
|
checkResult(responses.getPaths.UsdToUsd, 'getPaths', results[1]);
|
|
checkResult(responses.getPaths.XrpToXrp, 'getPaths', results[2]);
|
|
});
|
|
});
|
|
|
|
// @TODO
|
|
// need decide what to do with currencies/XRP:
|
|
// if add 'XRP' in currencies, then there will be exception in
|
|
// xrpToDrops function (called from toRippledAmount)
|
|
it('getPaths USD 2 USD', function () {
|
|
return this.api.getPaths(requests.getPaths.UsdToUsd).then(
|
|
_.partial(checkResult, responses.getPaths.UsdToUsd, 'getPaths'));
|
|
});
|
|
|
|
it('getPaths XRP 2 XRP', function () {
|
|
return this.api.getPaths(requests.getPaths.XrpToXrp).then(
|
|
_.partial(checkResult, responses.getPaths.XrpToXrp, 'getPaths'));
|
|
});
|
|
|
|
it('getPaths - source with issuer', function () {
|
|
return this.api.getPaths(requests.getPaths.issuer).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
});
|
|
});
|
|
|
|
it('getPaths - XRP 2 XRP - not enough', function () {
|
|
return this.api.getPaths(requests.getPaths.XrpToXrpNotEnough).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
});
|
|
});
|
|
|
|
it('getPaths - invalid PathFind', function () {
|
|
assert.throws(() => {
|
|
this.api.getPaths(requests.getPaths.invalid);
|
|
}, /Cannot specify both source.amount/);
|
|
});
|
|
|
|
it('getPaths - does not accept currency', function () {
|
|
return this.api.getPaths(requests.getPaths.NotAcceptCurrency).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
});
|
|
});
|
|
|
|
it('getPaths - no paths', function () {
|
|
return this.api.getPaths(requests.getPaths.NoPaths).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
});
|
|
});
|
|
|
|
it('getPaths - no paths source amount', function () {
|
|
return this.api.getPaths(requests.getPaths.NoPathsSource).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
});
|
|
});
|
|
|
|
|
|
it('getPaths - no paths with source currencies', function () {
|
|
const pathfind = requests.getPaths.NoPathsWithCurrencies;
|
|
return this.api.getPaths(pathfind).then(() => {
|
|
assert(false, 'Should throw NotFoundError');
|
|
}).catch(error => {
|
|
assert(error instanceof this.api.errors.NotFoundError);
|
|
});
|
|
});
|
|
|
|
it('getPaths - error: srcActNotFound', function () {
|
|
const pathfind = _.assign({}, requests.getPaths.normal,
|
|
{ source: { address: addresses.NOTFOUND } });
|
|
return this.api.getPaths(pathfind).catch(error => {
|
|
assert(error instanceof this.api.errors.RippleError);
|
|
});
|
|
});
|
|
|
|
it('getPaths - send all', function () {
|
|
return this.api.getPaths(requests.getPaths.sendAll).then(
|
|
_.partial(checkResult, responses.getPaths.sendAll, 'getPaths'));
|
|
});
|
|
|
|
it('getLedgerVersion', function (done) {
|
|
this.api.getLedgerVersion().then(ver => {
|
|
assert.strictEqual(ver, 8819951);
|
|
done();
|
|
}, done);
|
|
});
|
|
|
|
it('getFeeBase', function (done) {
|
|
this.api.connection.getFeeBase().then(fee => {
|
|
assert.strictEqual(fee, 10);
|
|
done();
|
|
}, done);
|
|
});
|
|
|
|
it('getFeeRef', function (done) {
|
|
this.api.connection.getFeeRef().then(fee => {
|
|
assert.strictEqual(fee, 10);
|
|
done();
|
|
}, done);
|
|
});
|
|
|
|
it('getLedger', function () {
|
|
return this.api.getLedger().then(
|
|
_.partial(checkResult, responses.getLedger.header, 'getLedger'));
|
|
});
|
|
|
|
it('getLedger - by hash', function () {
|
|
return this.api.getLedger({ ledgerHash: '15F20E5FA6EA9770BBFFDBD62787400960B04BE32803B20C41F117F41C13830D' }).then(
|
|
_.partial(checkResult, responses.getLedger.headerByHash, 'getLedger'));
|
|
});
|
|
|
|
it('getLedger - future ledger version', function () {
|
|
return this.api.getLedger({ ledgerVersion: 14661789 }).then(response => {
|
|
assert(response)
|
|
})
|
|
});
|
|
|
|
it('getLedger - with state as hashes', function () {
|
|
const request = {
|
|
includeTransactions: true,
|
|
includeAllData: false,
|
|
includeState: true,
|
|
ledgerVersion: 6
|
|
};
|
|
return this.api.getLedger(request).then(
|
|
_.partial(checkResult, responses.getLedger.withStateAsHashes,
|
|
'getLedger'));
|
|
});
|
|
|
|
it('getLedger - with settings transaction', function () {
|
|
const request = {
|
|
includeTransactions: true,
|
|
includeAllData: true,
|
|
ledgerVersion: 4181996
|
|
};
|
|
return this.api.getLedger(request).then(
|
|
_.partial(checkResult, responses.getLedger.withSettingsTx, 'getLedger'));
|
|
});
|
|
|
|
it('getLedger - with partial payment', function () {
|
|
const request = {
|
|
includeTransactions: true,
|
|
includeAllData: true,
|
|
ledgerVersion: 22420574
|
|
};
|
|
return this.api.getLedger(request).then(
|
|
_.partial(checkResult, responses.getLedger.withPartial, 'getLedger'));
|
|
});
|
|
|
|
it('getLedger - pre 2014 with partial payment', function () {
|
|
const request = {
|
|
includeTransactions: true,
|
|
includeAllData: true,
|
|
ledgerVersion: 100001
|
|
};
|
|
return this.api.getLedger(request).then(
|
|
_.partial(checkResult,
|
|
responses.getLedger.pre2014withPartial,
|
|
'getLedger'));
|
|
});
|
|
|
|
it('getLedger - full, then computeLedgerHash', function () {
|
|
const request = {
|
|
includeTransactions: true,
|
|
includeState: true,
|
|
includeAllData: true,
|
|
ledgerVersion: 38129
|
|
};
|
|
return this.api.getLedger(request).then(
|
|
_.partial(checkResult, responses.getLedger.full, 'getLedger'))
|
|
.then(response => {
|
|
const ledger = _.assign({}, response,
|
|
{ parentCloseTime: response.closeTime });
|
|
const hash = this.api.computeLedgerHash(ledger, {computeTreeHashes: true});
|
|
assert.strictEqual(hash,
|
|
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E');
|
|
});
|
|
});
|
|
|
|
it('computeLedgerHash - given corrupt data - should fail', function () {
|
|
const request = {
|
|
includeTransactions: true,
|
|
includeState: true,
|
|
includeAllData: true,
|
|
ledgerVersion: 38129
|
|
};
|
|
return this.api.getLedger(request).then(ledger => {
|
|
assert.strictEqual(ledger.transactions[0].rawTransaction, "{\"Account\":\"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV\",\"Amount\":\"10000000000\",\"Destination\":\"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj\",\"Fee\":\"10\",\"Flags\":0,\"Sequence\":62,\"SigningPubKey\":\"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E\",\"TransactionType\":\"Payment\",\"TxnSignature\":\"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639\",\"hash\":\"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF\",\"meta\":{\"AffectedNodes\":[{\"CreatedNode\":{\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E\",\"NewFields\":{\"Account\":\"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj\",\"Balance\":\"10000000000\",\"Sequence\":1}}},{\"ModifiedNode\":{\"FinalFields\":{\"Account\":\"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV\",\"Balance\":\"981481999380\",\"Flags\":0,\"OwnerCount\":0,\"Sequence\":63},\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A\",\"PreviousFields\":{\"Balance\":\"991481999390\",\"Sequence\":62},\"PreviousTxnID\":\"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F\",\"PreviousTxnLgrSeq\":31317}}],\"TransactionIndex\":0,\"TransactionResult\":\"tesSUCCESS\"},\"ledger_index\":38129}");
|
|
|
|
// Change Amount to 12000000000
|
|
ledger.transactions[0].rawTransaction = "{\"Account\":\"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV\",\"Amount\":\"12000000000\",\"Destination\":\"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj\",\"Fee\":\"10\",\"Flags\":0,\"Sequence\":62,\"SigningPubKey\":\"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E\",\"TransactionType\":\"Payment\",\"TxnSignature\":\"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639\",\"hash\":\"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF\",\"meta\":{\"AffectedNodes\":[{\"CreatedNode\":{\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E\",\"NewFields\":{\"Account\":\"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj\",\"Balance\":\"10000000000\",\"Sequence\":1}}},{\"ModifiedNode\":{\"FinalFields\":{\"Account\":\"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV\",\"Balance\":\"981481999380\",\"Flags\":0,\"OwnerCount\":0,\"Sequence\":63},\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A\",\"PreviousFields\":{\"Balance\":\"991481999390\",\"Sequence\":62},\"PreviousTxnID\":\"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F\",\"PreviousTxnLgrSeq\":31317}}],\"TransactionIndex\":0,\"TransactionResult\":\"tesSUCCESS\"},\"ledger_index\":38129}";
|
|
|
|
ledger.parentCloseTime = ledger.closeTime;
|
|
|
|
let hash;
|
|
try {
|
|
hash = this.api.computeLedgerHash(ledger, {computeTreeHashes: true});
|
|
} catch (error) {
|
|
assert(error instanceof this.api.errors.ValidationError);
|
|
assert.strictEqual(error.message, 'transactionHash in header does not match computed hash of transactions');
|
|
assert.deepStrictEqual(error.data, {
|
|
transactionHashInHeader: 'DB83BF807416C5B3499A73130F843CF615AB8E797D79FE7D330ADF1BFA93951A',
|
|
computedHashOfTransactions: 'EAA1ADF4D627339450F0E95EA88B7069186DD64230BAEBDCF3EEC4D616A9FC68'
|
|
});
|
|
return;
|
|
}
|
|
assert(false, 'Should throw ValidationError instead of producing hash: ' + hash);
|
|
});
|
|
});
|
|
|
|
it('computeLedgerHash - given ledger without raw transactions - should throw', function () {
|
|
const request = {
|
|
includeTransactions: true,
|
|
includeState: true,
|
|
includeAllData: true,
|
|
ledgerVersion: 38129
|
|
};
|
|
return this.api.getLedger(request).then(ledger => {
|
|
assert.strictEqual(ledger.transactions[0].rawTransaction, "{\"Account\":\"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV\",\"Amount\":\"10000000000\",\"Destination\":\"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj\",\"Fee\":\"10\",\"Flags\":0,\"Sequence\":62,\"SigningPubKey\":\"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E\",\"TransactionType\":\"Payment\",\"TxnSignature\":\"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639\",\"hash\":\"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF\",\"meta\":{\"AffectedNodes\":[{\"CreatedNode\":{\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E\",\"NewFields\":{\"Account\":\"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj\",\"Balance\":\"10000000000\",\"Sequence\":1}}},{\"ModifiedNode\":{\"FinalFields\":{\"Account\":\"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV\",\"Balance\":\"981481999380\",\"Flags\":0,\"OwnerCount\":0,\"Sequence\":63},\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A\",\"PreviousFields\":{\"Balance\":\"991481999390\",\"Sequence\":62},\"PreviousTxnID\":\"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F\",\"PreviousTxnLgrSeq\":31317}}],\"TransactionIndex\":0,\"TransactionResult\":\"tesSUCCESS\"},\"ledger_index\":38129}");
|
|
|
|
// Delete rawTransaction
|
|
delete ledger.transactions[0].rawTransaction;
|
|
|
|
ledger.parentCloseTime = ledger.closeTime;
|
|
|
|
let hash;
|
|
try {
|
|
hash = this.api.computeLedgerHash(ledger, {computeTreeHashes: true});
|
|
} catch (error) {
|
|
assert(error instanceof this.api.errors.ValidationError);
|
|
assert.strictEqual(error.message, 'ledger'
|
|
+ ' is missing raw transactions');
|
|
return;
|
|
}
|
|
assert(false, 'Should throw ValidationError instead of producing hash: ' + hash);
|
|
});
|
|
});
|
|
|
|
|
|
it('computeLedgerHash - given ledger without state or transactions - only compute ledger hash', function () {
|
|
const request = {
|
|
includeTransactions: true,
|
|
includeState: true,
|
|
includeAllData: true,
|
|
ledgerVersion: 38129
|
|
};
|
|
return this.api.getLedger(request).then(ledger => {
|
|
assert.strictEqual(ledger.transactions[0].rawTransaction, "{\"Account\":\"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV\",\"Amount\":\"10000000000\",\"Destination\":\"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj\",\"Fee\":\"10\",\"Flags\":0,\"Sequence\":62,\"SigningPubKey\":\"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E\",\"TransactionType\":\"Payment\",\"TxnSignature\":\"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639\",\"hash\":\"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF\",\"meta\":{\"AffectedNodes\":[{\"CreatedNode\":{\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E\",\"NewFields\":{\"Account\":\"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj\",\"Balance\":\"10000000000\",\"Sequence\":1}}},{\"ModifiedNode\":{\"FinalFields\":{\"Account\":\"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV\",\"Balance\":\"981481999380\",\"Flags\":0,\"OwnerCount\":0,\"Sequence\":63},\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A\",\"PreviousFields\":{\"Balance\":\"991481999390\",\"Sequence\":62},\"PreviousTxnID\":\"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F\",\"PreviousTxnLgrSeq\":31317}}],\"TransactionIndex\":0,\"TransactionResult\":\"tesSUCCESS\"},\"ledger_index\":38129}");
|
|
|
|
ledger.parentCloseTime = ledger.closeTime;
|
|
|
|
const computeLedgerHash = this.api.computeLedgerHash;
|
|
const ValidationError = this.api.errors.ValidationError
|
|
function testCompute(ledger, expectedError) {
|
|
let hash = computeLedgerHash(ledger);
|
|
assert.strictEqual(hash,
|
|
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E');
|
|
|
|
// fail if required to compute tree hashes
|
|
try {
|
|
hash = computeLedgerHash(ledger, {computeTreeHashes: true});
|
|
} catch (error) {
|
|
assert(error instanceof ValidationError);
|
|
assert.strictEqual(error.message, expectedError);
|
|
return;
|
|
}
|
|
assert(false, 'Should throw ValidationError instead of producing hash: ' + hash);
|
|
}
|
|
|
|
const transactions = ledger.transactions;
|
|
delete ledger.transactions;
|
|
testCompute(ledger, 'transactions property is missing from the ledger');
|
|
delete ledger.rawState;
|
|
testCompute(ledger, 'transactions property is missing from the ledger');
|
|
ledger.transactions = transactions;
|
|
testCompute(ledger, 'rawState property is missing from the ledger');
|
|
});
|
|
});
|
|
|
|
it('computeLedgerHash - wrong hash', function () {
|
|
const request = {
|
|
includeTransactions: true,
|
|
includeState: true,
|
|
includeAllData: true,
|
|
ledgerVersion: 38129
|
|
};
|
|
return this.api.getLedger(request).then(
|
|
_.partial(checkResult, responses.getLedger.full, 'getLedger'))
|
|
.then(response => {
|
|
const ledger = _.assign({}, response, {
|
|
parentCloseTime: response.closeTime, stateHash:
|
|
'D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44'
|
|
});
|
|
assert.throws(() => {
|
|
const hash = this.api.computeLedgerHash(ledger);
|
|
unused(hash);
|
|
}, /does not match computed hash of state/);
|
|
});
|
|
});
|
|
|
|
it('RippleError with data', function () {
|
|
const error = new this.api.errors.RippleError('_message_', '_data_');
|
|
assert.strictEqual(error.toString(),
|
|
'[RippleError(_message_, \'_data_\')]');
|
|
});
|
|
|
|
it('NotFoundError default message', function () {
|
|
const error = new this.api.errors.NotFoundError();
|
|
assert.strictEqual(error.toString(),
|
|
'[NotFoundError(Not found)]');
|
|
});
|
|
|
|
it('common utils - toRippledAmount', function () {
|
|
const amount = { issuer: 'is', currency: 'c', value: 'v' };
|
|
|
|
assert.deepEqual(utils.common.toRippledAmount(amount), {
|
|
issuer: 'is', currency: 'c', value: 'v'
|
|
});
|
|
});
|
|
|
|
it('ledger utils - renameCounterpartyToIssuerInOrder', function () {
|
|
const order = {
|
|
taker_gets: { counterparty: '1' },
|
|
taker_pays: { counterparty: '1' }
|
|
};
|
|
const expected = {
|
|
taker_gets: { issuer: '1' },
|
|
taker_pays: { issuer: '1' }
|
|
};
|
|
assert.deepEqual(utils.renameCounterpartyToIssuerInOrder(order), expected);
|
|
});
|
|
|
|
it('ledger utils - compareTransactions', function () {
|
|
assert.strictEqual(utils.compareTransactions({}, {}), 0);
|
|
let first = { outcome: { ledgerVersion: 1, indexInLedger: 100 } };
|
|
let second = { outcome: { ledgerVersion: 1, indexInLedger: 200 } };
|
|
|
|
assert.strictEqual(utils.compareTransactions(first, second), -1);
|
|
|
|
first = { outcome: { ledgerVersion: 1, indexInLedger: 100 } };
|
|
second = { outcome: { ledgerVersion: 1, indexInLedger: 100 } };
|
|
|
|
assert.strictEqual(utils.compareTransactions(first, second), 0);
|
|
|
|
first = { outcome: { ledgerVersion: 1, indexInLedger: 200 } };
|
|
second = { outcome: { ledgerVersion: 1, indexInLedger: 100 } };
|
|
|
|
assert.strictEqual(utils.compareTransactions(first, second), 1);
|
|
});
|
|
|
|
it('ledger utils - getRecursive', function () {
|
|
function getter(marker, limit) {
|
|
return new Promise((resolve, reject) => {
|
|
if (marker === undefined) {
|
|
resolve({ marker: 'A', limit: limit, results: [1] });
|
|
} else {
|
|
reject(new Error());
|
|
}
|
|
});
|
|
}
|
|
return utils.getRecursive(getter, 10).then(() => {
|
|
assert(false, 'Should throw Error');
|
|
}).catch(error => {
|
|
assert(error instanceof Error);
|
|
});
|
|
});
|
|
|
|
describe('schema-validator', function () {
|
|
it('valid', function () {
|
|
assert.doesNotThrow(function () {
|
|
schemaValidator.schemaValidate('hash256',
|
|
'0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A0F');
|
|
});
|
|
});
|
|
|
|
it('invalid', function () {
|
|
assert.throws(function () {
|
|
schemaValidator.schemaValidate('hash256', 'invalid');
|
|
}, this.api.errors.ValidationError);
|
|
});
|
|
|
|
it('invalid - empty value', function () {
|
|
assert.throws(function () {
|
|
schemaValidator.schemaValidate('hash256', '');
|
|
}, this.api.errors.ValidationError);
|
|
});
|
|
|
|
it('schema not found error', function () {
|
|
assert.throws(function () {
|
|
schemaValidator.schemaValidate('unexisting', 'anything');
|
|
}, /no schema/);
|
|
});
|
|
|
|
});
|
|
|
|
describe('validator', function () {
|
|
|
|
it('validateLedgerRange', function () {
|
|
const options = {
|
|
minLedgerVersion: 20000,
|
|
maxLedgerVersion: 10000
|
|
};
|
|
const thunk = _.partial(validate.getTransactions,
|
|
{ address, options });
|
|
assert.throws(thunk, this.api.errors.ValidationError);
|
|
assert.throws(thunk,
|
|
/minLedgerVersion must not be greater than maxLedgerVersion/);
|
|
});
|
|
|
|
it('secret', function () {
|
|
function validateSecret(secret) {
|
|
validate.sign({ txJSON: '', secret });
|
|
}
|
|
assert.doesNotThrow(_.partial(validateSecret,
|
|
'shzjfakiK79YQdMjy4h8cGGfQSV6u'));
|
|
assert.throws(_.partial(validateSecret,
|
|
'shzjfakiK79YQdMjy4h8cGGfQSV6v'), this.api.errors.ValidationError);
|
|
assert.throws(_.partial(validateSecret, 1),
|
|
this.api.errors.ValidationError);
|
|
assert.throws(_.partial(validateSecret, ''),
|
|
this.api.errors.ValidationError);
|
|
assert.throws(_.partial(validateSecret, 's!!!'),
|
|
this.api.errors.ValidationError);
|
|
assert.throws(_.partial(validateSecret, 'passphrase'),
|
|
this.api.errors.ValidationError);
|
|
// 32 0s is a valid hex repr of seed bytes
|
|
const hex = new Array(33).join('0');
|
|
assert.throws(_.partial(validateSecret, hex),
|
|
this.api.errors.ValidationError);
|
|
});
|
|
|
|
});
|
|
|
|
it('ledger event', function (done) {
|
|
this.api.on('ledger', message => {
|
|
checkResult(responses.ledgerEvent, 'ledgerEvent', message);
|
|
done();
|
|
});
|
|
closeLedger(this.api.connection);
|
|
});
|
|
});
|
|
|
|
describe('RippleAPI - offline', function () {
|
|
it('prepareSettings and sign', function () {
|
|
const api = new RippleAPI();
|
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
|
const settings = requests.prepareSettings.domain;
|
|
const instructions = {
|
|
sequence: 23,
|
|
maxLedgerVersion: 8820051,
|
|
fee: '0.000012'
|
|
};
|
|
return api.prepareSettings(address, settings, instructions).then(data => {
|
|
checkResult(responses.prepareSettings.flags, 'prepare', data);
|
|
assert.deepEqual(api.sign(data.txJSON, secret),
|
|
responses.prepareSettings.signed);
|
|
});
|
|
});
|
|
|
|
it('getServerInfo - offline', function () {
|
|
const api = new RippleAPI();
|
|
return api.getServerInfo().then(() => {
|
|
assert(false, 'Should throw error');
|
|
}).catch(error => {
|
|
assert(error instanceof api.errors.NotConnectedError);
|
|
});
|
|
});
|
|
|
|
it('computeLedgerHash', function () {
|
|
const api = new RippleAPI();
|
|
const header = requests.computeLedgerHash.header;
|
|
const ledgerHash = api.computeLedgerHash(header);
|
|
assert.strictEqual(ledgerHash,
|
|
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349');
|
|
});
|
|
|
|
it('computeLedgerHash - with transactions', function () {
|
|
const api = new RippleAPI();
|
|
const header = _.omit(requests.computeLedgerHash.header,
|
|
'transactionHash');
|
|
header.rawTransactions = JSON.stringify(
|
|
requests.computeLedgerHash.transactions);
|
|
const ledgerHash = api.computeLedgerHash(header);
|
|
assert.strictEqual(ledgerHash,
|
|
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349');
|
|
});
|
|
|
|
it('computeLedgerHash - incorrent transaction_hash', function () {
|
|
const api = new RippleAPI();
|
|
const header = _.assign({}, requests.computeLedgerHash.header,
|
|
{
|
|
transactionHash:
|
|
'325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C9'
|
|
});
|
|
header.rawTransactions = JSON.stringify(
|
|
requests.computeLedgerHash.transactions);
|
|
assert.throws(() => api.computeLedgerHash(header));
|
|
});
|
|
|
|
/* eslint-disable no-unused-vars */
|
|
it('RippleAPI - implicit server port', function () {
|
|
const api = new RippleAPI({ server: 'wss://s1.ripple.com' });
|
|
});
|
|
/* eslint-enable no-unused-vars */
|
|
it('RippleAPI invalid options', function () {
|
|
assert.throws(() => new RippleAPI({ invalid: true }));
|
|
});
|
|
|
|
it('RippleAPI valid options', function () {
|
|
const api = new RippleAPI({ server: 'wss://s:1' });
|
|
assert.deepEqual(api.connection._url, 'wss://s:1');
|
|
});
|
|
|
|
it('RippleAPI invalid server uri', function () {
|
|
assert.throws(() => new RippleAPI({ server: 'wss//s:1' }));
|
|
});
|
|
|
|
xit('RippleAPI connect() times out after 2 seconds', function () {
|
|
// TODO: Use a timer mock like https://jestjs.io/docs/en/timer-mocks
|
|
// to test that connect() times out after 2 seconds.
|
|
});
|
|
});
|