mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-17 19:05:47 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4cabad44e | ||
|
|
28cc0f9e3b | ||
|
|
95a2cc18fe | ||
|
|
8e315a9859 | ||
|
|
89adcf4f4e | ||
|
|
3a6c5e41c9 | ||
|
|
86ed24b94c | ||
|
|
c792c471c3 | ||
|
|
e371cc2c3c | ||
|
|
ccf218c8f0 | ||
|
|
0d7fc0a573 | ||
|
|
74cacd5209 |
10
HISTORY.md
10
HISTORY.md
@@ -1,3 +1,13 @@
|
||||
##0.9.4
|
||||
|
||||
+ [Improve memo support](https://github.com/ripple/ripple-lib/commit/89adcf4f4eebe1a5cc92a1b24b53f637422b96da)
|
||||
|
||||
+ [Normalize offers from book_offers and transaction stream](https://github.com/ripple/ripple-lib/commit/86ed24b94cf7c8929c87db3a63e9bbea7f767e9c)
|
||||
|
||||
+ [Fix: Amount.to_human() precision rounding](https://github.com/ripple/ripple-lib/commit/e371cc2c3ceccb3c1cfdf18b98d80093147dd8b2)
|
||||
|
||||
+ [Fix: fractional drops in funded taker_pays setter](https://github.com/ripple/ripple-lib/commit/0d7fc0a573a144caac15dd13798b23eeb1f95fb4)
|
||||
|
||||
##0.9.3
|
||||
|
||||
+ [Change `presubmit` to emit immediately before transaction submit](https://github.com/ripple/ripple-lib/commit/7a1feaa89701bf861ab31ebd8ffdc8d8d1474e29)
|
||||
|
||||
@@ -16,17 +16,6 @@ This file provides step-by-step walkthroughs for some of the most common usages
|
||||
1. [The ripple-lib README](../README.md)
|
||||
2. [The ripple-lib API Reference](REFERENCE.md)
|
||||
|
||||
##Generating a new Ripple Wallet
|
||||
|
||||
```js
|
||||
var Wallet = require('ripple-lib').Wallet;
|
||||
|
||||
var wallet = Wallet.generate();
|
||||
console.log(wallet);
|
||||
// { address: 'rEf4sbVobiiDGExrNj2PkNHGMA8eS6jWh3',
|
||||
// secret: 'shFh4a38EZpEdZxrLifEnVPAoBRce' }
|
||||
```
|
||||
|
||||
##Connecting to the Ripple network
|
||||
|
||||
1. [Get ripple-lib](README.md#getting-ripple-lib)
|
||||
@@ -60,10 +49,37 @@ This file provides step-by-step walkthroughs for some of the most common usages
|
||||
|
||||
4. You're connected! Read on to see what to do now.
|
||||
|
||||
##Generating a new Ripple Wallet
|
||||
|
||||
```js
|
||||
var ripple = require('ripple-lib');
|
||||
|
||||
// subscribing to a server allows for more entropy
|
||||
var remote = new ripple.Remote({
|
||||
servers: [
|
||||
{ host: 's1.ripple.com', port: 443, secure: true }
|
||||
]
|
||||
});
|
||||
|
||||
remote.connect(function(err, res) {
|
||||
/* remote connected */
|
||||
});
|
||||
|
||||
// Wait for randomness to have been added.
|
||||
// The entropy of the random generator is increased
|
||||
// by random data received from a rippled
|
||||
remote.once('random', function(err, info) {
|
||||
var wallet = ripple.Wallet.generate();
|
||||
console.log(wallet);
|
||||
// { address: 'rEf4sbVobiiDGExrNj2PkNHGMA8eS6jWh3',
|
||||
// secret: 'shFh4a38EZpEdZxrLifEnVPAoBRce' }
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
##Sending rippled API requests
|
||||
|
||||
`Remote` contains functions for constructing a `Request` object.
|
||||
`Remote` contains functions for constructing a `Request` object.
|
||||
|
||||
A `Request` is an `EventEmitter` so you can listen for success or failure events -- or, instead, you can provide a callback.
|
||||
|
||||
@@ -124,29 +140,29 @@ See the [wiki](https://ripple.com/wiki/JSON_Messages#subscribe) for details on s
|
||||
'ledger',
|
||||
'transactions'
|
||||
];
|
||||
|
||||
|
||||
var request = remote.requestSubscribe(streams);
|
||||
|
||||
|
||||
request.on('error', function(error) {
|
||||
console.log('request error: ', error);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// the `ledger_closed` and `transaction` will come in on the remote
|
||||
// since the request for subscribe is finalized after the success return
|
||||
// the streaming events will still come in, but not on the initial request
|
||||
remote.on('ledger_closed', function(ledger) {
|
||||
console.log('ledger_closed: ', JSON.stringify(ledger, null, 2));
|
||||
});
|
||||
|
||||
|
||||
remote.on('transaction', function(transaction) {
|
||||
console.log('transaction: ', JSON.stringify(transaction, null, 2));
|
||||
});
|
||||
|
||||
|
||||
remote.on('error', function(error) {
|
||||
console.log('remote error: ', error);
|
||||
});
|
||||
|
||||
|
||||
// fire the request
|
||||
request.request();
|
||||
});
|
||||
@@ -160,7 +176,7 @@ See the [wiki](https://ripple.com/wiki/JSON_Messages#subscribe) for details on s
|
||||
Submitting a payment transaction to the Ripple network involves connecting to a `Remote`, creating a transaction, signing it with the user's secret, and submitting it to the `rippled` server. Note that the `Amount` module is used to convert human-readable amounts like '1XRP' or '10.50USD' to the type of Amount object used by the Ripple network.
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
var Amount = require('ripple-lib').Amount;
|
||||
|
||||
@@ -179,8 +195,8 @@ remote.connect(function() {
|
||||
remote.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: RECIPIENT,
|
||||
account: MY_ADDRESS,
|
||||
destination: RECIPIENT,
|
||||
amount: AMOUNT
|
||||
});
|
||||
|
||||
@@ -201,12 +217,12 @@ Since the fee required for a transaction may change between the time when the or
|
||||
The [`max_fee`](REFERENCE.md#1-remote-options) option can be used to avoid submitting a transaction to a server that is charging unreasonably high fees.
|
||||
|
||||
|
||||
##4. Submitting a trade offer to the network
|
||||
##Submitting a trade offer to the network
|
||||
|
||||
Submitting a trade offer to the network is similar to submitting a payment transaction. Here is an example for a trade that expires in 24 hours where you are offering to sell 1 USD in exchange for 100 XRP:
|
||||
Submitting a trade offer to the network is similar to submitting a payment transaction. Here is an example offering to sell 1 USD in exchange for 100 XRP:
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
var Amount = require('ripple-lib').Amount;
|
||||
|
||||
@@ -225,7 +241,7 @@ remote.connect(function() {
|
||||
|
||||
var transaction = remote.createTransaction('OfferCreate', {
|
||||
account: MY_ADDRESS,
|
||||
taker_pays: '1',
|
||||
taker_pays: '100',
|
||||
taker_gets: '1/USD/' + GATEWAY
|
||||
});
|
||||
|
||||
|
||||
2
npm-shrinkwrap.json
generated
2
npm-shrinkwrap.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.9.3",
|
||||
"version": "0.9.4-rc1",
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "0.8.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.9.3",
|
||||
"version": "0.9.4-rc1",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"files": [
|
||||
"src/js/*",
|
||||
|
||||
@@ -1145,25 +1145,21 @@ Amount.prototype.to_human = function(opts) {
|
||||
fraction_part = fraction_part.replace(/0*$/, '');
|
||||
|
||||
if (fraction_part.length || !opts.skip_empty_fraction) {
|
||||
|
||||
// Enforce the maximum number of decimal digits (precision)
|
||||
if (typeof opts.precision === 'number') {
|
||||
if (opts.precision <= 0) {
|
||||
var precision = Math.max(0, opts.precision);
|
||||
precision = Math.min(precision, fraction_part.length);
|
||||
var rounded = Number('0.' + fraction_part).toFixed(precision);
|
||||
|
||||
// increment the int_part if the first decimal is 5 or higher
|
||||
if (fraction_part.charCodeAt(0) >= 53) {
|
||||
int_part = (Number(int_part) + 1).toString();
|
||||
}
|
||||
fraction_part = '';
|
||||
if (rounded < 1) {
|
||||
fraction_part = rounded.substring(2);
|
||||
} else {
|
||||
var precision = Math.min(opts.precision, fraction_part.length);
|
||||
fraction_part = Math.round(fraction_part / Math.pow(10, fraction_part.length - precision)).toString();
|
||||
int_part = (Number(int_part) + 1).toString();
|
||||
fraction_part = '';
|
||||
}
|
||||
|
||||
// because the division above will cut off the leading 0's we have to add them back again
|
||||
// XXX look for a more elegant alternative
|
||||
while (fraction_part.length < precision) {
|
||||
fraction_part = '0' + fraction_part;
|
||||
}
|
||||
while (fraction_part.length < precision) {
|
||||
fraction_part = '0' + fraction_part;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1171,7 +1167,7 @@ Amount.prototype.to_human = function(opts) {
|
||||
if (typeof opts.max_sig_digits === 'number') {
|
||||
// First, we count the significant digits we have.
|
||||
// A zero in the integer part does not count.
|
||||
var int_is_zero = +int_part === 0;
|
||||
var int_is_zero = Number(int_part) === 0;
|
||||
var digits = int_is_zero ? 0 : int_part.length;
|
||||
|
||||
// Don't count leading zeros in the fractional part if the integer part is
|
||||
@@ -1197,6 +1193,7 @@ Amount.prototype.to_human = function(opts) {
|
||||
|
||||
// Enforce the minimum number of decimal digits (min_precision)
|
||||
if (typeof opts.min_precision === 'number') {
|
||||
opts.min_precision = Math.max(0, opts.min_precision);
|
||||
while (fraction_part.length < opts.min_precision) {
|
||||
fraction_part += '0';
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
* Data type map.
|
||||
*
|
||||
* Mapping of type ids to data types. The type id is specified by the high
|
||||
*
|
||||
* For reference, see rippled's definition:
|
||||
* https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol/SField.cpp
|
||||
*/
|
||||
var TYPES_MAP = exports.types = [
|
||||
void(0),
|
||||
@@ -375,7 +378,7 @@ exports.ledger = {
|
||||
['Balance', REQUIRED],
|
||||
['LowLimit', REQUIRED],
|
||||
['HighLimit', REQUIRED]])
|
||||
}
|
||||
};
|
||||
|
||||
exports.metadata = [
|
||||
[ 'TransactionIndex' , REQUIRED ],
|
||||
|
||||
@@ -77,10 +77,15 @@ function OrderBook(remote, getsC, getsI, paysC, paysI, key) {
|
||||
listenersModified('remove', event);
|
||||
});
|
||||
|
||||
function updateFundedAmounts(transaction) {
|
||||
self.updateFundedAmounts(transaction);
|
||||
};
|
||||
|
||||
this._remote.on('transaction', updateFundedAmounts);
|
||||
|
||||
this.on('unsubscribe', function() {
|
||||
self.resetCache();
|
||||
self._remote.removeListener('transaction', updateFundedAmounts);
|
||||
self._remote.removeListener('transaction', updateTransferRate);
|
||||
});
|
||||
|
||||
this._remote.once('prepare_subscribe', function() {
|
||||
@@ -94,18 +99,6 @@ function OrderBook(remote, getsC, getsI, paysC, paysI, key) {
|
||||
});
|
||||
});
|
||||
|
||||
function updateFundedAmounts(message) {
|
||||
self.updateFundedAmounts(message);
|
||||
};
|
||||
|
||||
this._remote.on('transaction', updateFundedAmounts);
|
||||
|
||||
function updateTransferRate(message) {
|
||||
self.updateTransferRate(message);
|
||||
};
|
||||
|
||||
this._remote.on('transaction', updateTransferRate);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -115,30 +108,15 @@ util.inherits(OrderBook, EventEmitter);
|
||||
* Events emitted from OrderBook
|
||||
*/
|
||||
|
||||
OrderBook.EVENTS = [ 'transaction', 'model', 'trade', 'offer' ];
|
||||
OrderBook.EVENTS = [
|
||||
'transaction', 'model', 'trade',
|
||||
'offer_added', 'offer_removed',
|
||||
'offer_changed', 'offer_funds_changed'
|
||||
];
|
||||
|
||||
OrderBook.DEFAULT_TRANSFER_RATE = 1000000000;
|
||||
|
||||
/**
|
||||
* Whether the OrderBook is valid.
|
||||
*
|
||||
* Note: This only checks whether the parameters (currencies and issuer) are
|
||||
* syntactically valid. It does not check anything against the ledger.
|
||||
*
|
||||
* @return {Boolean} is valid
|
||||
*/
|
||||
|
||||
OrderBook.prototype.isValid =
|
||||
OrderBook.prototype.is_valid = function() {
|
||||
// XXX Should check for same currency (non-native) && same issuer
|
||||
return (
|
||||
this._currencyPays && this._currencyPays.is_valid() &&
|
||||
(this._currencyPays.is_native() || UInt160.is_valid(this._issuerPays)) &&
|
||||
this._currencyGets && this._currencyGets.is_valid() &&
|
||||
(this._currencyGets.is_native() || UInt160.is_valid(this._issuerGets)) &&
|
||||
!(this._currencyPays.is_native() && this._currencyGets.is_native())
|
||||
);
|
||||
};
|
||||
OrderBook.IOU_SUFFIX = '/000/rrrrrrrrrrrrrrrrrrrrrhoLvTp';
|
||||
|
||||
/**
|
||||
* Initialize orderbook. Get orderbook offers and subscribe to transactions
|
||||
@@ -167,7 +145,7 @@ OrderBook.prototype.subscribe = function() {
|
||||
}
|
||||
];
|
||||
|
||||
async.series(steps, function(err) {
|
||||
async.series(steps, function(err, res) {
|
||||
//XXX What now?
|
||||
});
|
||||
};
|
||||
@@ -308,8 +286,7 @@ OrderBook.prototype.applyTransferRate = function(balance, transferRate) {
|
||||
return balance;
|
||||
}
|
||||
|
||||
var iouSuffix = '/USD/rrrrrrrrrrrrrrrrrrrrBZbvji';
|
||||
var adjustedBalance = Amount.from_json(balance + iouSuffix)
|
||||
var adjustedBalance = Amount.from_json(balance + OrderBook.IOU_SUFFIX)
|
||||
.divide(transferRate)
|
||||
.multiply(Amount.from_json(OrderBook.DEFAULT_TRANSFER_RATE))
|
||||
.to_json()
|
||||
@@ -321,40 +298,41 @@ OrderBook.prototype.applyTransferRate = function(balance, transferRate) {
|
||||
/**
|
||||
* Request transfer rate for this orderbook's issuer
|
||||
*
|
||||
* @param [Function] calback
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
OrderBook.prototype.requestTransferRate = function(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var self = this;
|
||||
var issuer = this._issuerGets;
|
||||
|
||||
this.once('transfer_rate', function(rate) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(null, rate);
|
||||
}
|
||||
});
|
||||
|
||||
if (this._currencyGets.is_native()) {
|
||||
// Transfer rate is default
|
||||
return this.emit('transfer_rate', OrderBook.DEFAULT_TRANSFER_RATE);
|
||||
// Transfer rate is default (native currency)
|
||||
callback(null, OrderBook.DEFAULT_TRANSFER_RATE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._issuerTransferRate) {
|
||||
// Transfer rate has been cached
|
||||
return this.emit('transfer_rate', this._issuerTransferRate);
|
||||
// Transfer rate has already been cached
|
||||
callback(null, this._issuerTransferRate);
|
||||
return;
|
||||
}
|
||||
|
||||
this._remote.requestAccountInfo({account: issuer}, function(err, info) {
|
||||
this._remote.requestAccountInfo({ account: issuer }, function(err, info) {
|
||||
if (err) {
|
||||
// XXX What now?
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var transferRate = info.account_data.TransferRate
|
||||
|| OrderBook.DEFAULT_TRANSFER_RATE;
|
||||
var transferRate = info.account_data.TransferRate;
|
||||
|
||||
if (!transferRate) {
|
||||
transferRate = OrderBook.DEFAULT_TRANSFER_RATE;
|
||||
}
|
||||
|
||||
self._issuerTransferRate = transferRate;
|
||||
self.emit('transfer_rate', transferRate);
|
||||
callback(null, transferRate);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -380,11 +358,10 @@ OrderBook.prototype.setFundedAmount = function(offer, fundedAmount) {
|
||||
return offer;
|
||||
}
|
||||
|
||||
var iouSuffix = '/' + this._currencyGets.to_json()
|
||||
+ '/' + this._issuerGets;
|
||||
|
||||
offer.is_fully_funded = Amount.from_json(
|
||||
this._currencyGets.is_native() ? fundedAmount : fundedAmount + iouSuffix
|
||||
this._currencyGets.is_native()
|
||||
? fundedAmount
|
||||
: fundedAmount + OrderBook.IOU_SUFFIX
|
||||
).compareTo(Amount.from_json(offer.TakerGets)) >= 0;
|
||||
|
||||
if (offer.is_fully_funded) {
|
||||
@@ -395,40 +372,40 @@ OrderBook.prototype.setFundedAmount = function(offer, fundedAmount) {
|
||||
|
||||
offer.taker_gets_funded = fundedAmount;
|
||||
|
||||
var takerPaysValue = typeof offer.TakerPays === 'object'
|
||||
var takerPaysValue = (typeof offer.TakerPays === 'object')
|
||||
? offer.TakerPays.value
|
||||
: offer.TakerPays;
|
||||
|
||||
var takerGetsValue = typeof offer.TakerGets === 'object'
|
||||
var takerGetsValue = (typeof offer.TakerGets === 'object')
|
||||
? offer.TakerGets.value
|
||||
: offer.TakerGets;
|
||||
|
||||
var takerPays = Amount.from_json(
|
||||
takerPaysValue + '/000/rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
);
|
||||
|
||||
var takerGets = Amount.from_json(
|
||||
takerGetsValue + '/000/rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
);
|
||||
|
||||
var fundedPays = Amount.from_json(
|
||||
fundedAmount + '/000/rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
);
|
||||
|
||||
var takerPays = Amount.from_json(takerPaysValue + OrderBook.IOU_SUFFIX);
|
||||
var takerGets = Amount.from_json(takerGetsValue + OrderBook.IOU_SUFFIX);
|
||||
var fundedPays = Amount.from_json(fundedAmount + OrderBook.IOU_SUFFIX);
|
||||
var rate = takerPays.divide(takerGets);
|
||||
|
||||
fundedPays = fundedPays.multiply(rate);
|
||||
|
||||
if (fundedPays.compareTo(takerPays) < 0) {
|
||||
offer.taker_pays_funded = fundedPays.to_json().value;
|
||||
if (this._currencyPays.is_native()) {
|
||||
fundedPays = String(parseInt(fundedPays.to_json().value, 10));
|
||||
} else {
|
||||
fundedPays = fundedPays.to_json().value;
|
||||
}
|
||||
} else {
|
||||
offer.taker_pays_funded = takerPays.to_json().value;
|
||||
fundedPays = takerPays.to_json().value;
|
||||
}
|
||||
|
||||
offer.taker_pays_funded = fundedPays;
|
||||
|
||||
return offer;
|
||||
};
|
||||
|
||||
/**
|
||||
* DEPRECATED:
|
||||
* Should only be called for old versions of rippled
|
||||
*
|
||||
* Determine what an account is funded to offer for orderbook's
|
||||
* currency/issuer
|
||||
*
|
||||
@@ -447,7 +424,7 @@ OrderBook.prototype.requestFundedAmount = function(account, callback) {
|
||||
}
|
||||
|
||||
function requestNativeBalance(callback) {
|
||||
self._remote.requestAccountInfo({account: account}, function(err, info) {
|
||||
self._remote.requestAccountInfo({ account: account }, function(err, info) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
@@ -457,13 +434,11 @@ OrderBook.prototype.requestFundedAmount = function(account, callback) {
|
||||
};
|
||||
|
||||
function requestLineBalance(callback) {
|
||||
var request = self._remote.requestAccountLines(
|
||||
{
|
||||
account: account,
|
||||
ledger: 'validated',
|
||||
peer: self._issuerGets
|
||||
}
|
||||
);
|
||||
var request = self._remote.requestAccountLines({
|
||||
account: account,
|
||||
ledger: 'validated',
|
||||
peer: self._issuerGets
|
||||
});
|
||||
|
||||
request.request(function(err, res) {
|
||||
if (err) {
|
||||
@@ -602,10 +577,10 @@ OrderBook.prototype.isBalanceChange = function(node) {
|
||||
* @param {Object} transaction
|
||||
*/
|
||||
|
||||
OrderBook.prototype.updateFundedAmounts = function(message) {
|
||||
OrderBook.prototype.updateFundedAmounts = function(transaction) {
|
||||
var self = this;
|
||||
|
||||
var affectedAccounts = message.mmeta.getAffectedAccounts();
|
||||
var affectedAccounts = transaction.mmeta.getAffectedAccounts();
|
||||
|
||||
var isOwnerAffected = affectedAccounts.some(function(account) {
|
||||
return self.hasCachedFunds(account);
|
||||
@@ -617,25 +592,23 @@ OrderBook.prototype.updateFundedAmounts = function(message) {
|
||||
|
||||
if (!this._currencyGets.is_native() && !this._issuerTransferRate) {
|
||||
// Defer until transfer rate is requested
|
||||
if (self._remote.trace) {
|
||||
if (this._remote.trace) {
|
||||
log.info('waiting for transfer rate');
|
||||
}
|
||||
|
||||
this.once('transfer_rate', function() {
|
||||
self.updateFundedAmounts(message);
|
||||
this.requestTransferRate(function() {
|
||||
self.updateFundedAmounts(transaction);
|
||||
});
|
||||
|
||||
this.requestTransferRate();
|
||||
return;
|
||||
}
|
||||
|
||||
var nodes = message.mmeta.getNodes({
|
||||
var affectedNodes = transaction.mmeta.getNodes({
|
||||
nodeType: 'ModifiedNode',
|
||||
entryType: this._currencyGets.is_native() ? 'AccountRoot' : 'RippleState'
|
||||
});
|
||||
|
||||
for (var i=0; i<nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
for (var i=0, l=affectedNodes.length; i<l; i++) {
|
||||
var node = affectedNodes[i];
|
||||
|
||||
if (!this.isBalanceChange(node)) {
|
||||
continue;
|
||||
@@ -643,39 +616,72 @@ OrderBook.prototype.updateFundedAmounts = function(message) {
|
||||
|
||||
var result = this.getBalanceChange(node);
|
||||
|
||||
if (result.isValid) {
|
||||
if (this.hasCachedFunds(result.account)) {
|
||||
this.updateOfferFunds(result.account, result.balance);
|
||||
}
|
||||
if (result.isValid && this.hasCachedFunds(result.account)) {
|
||||
this.updateAccountFunds(result.account, result.balance);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update issuer's TransferRate as it changes
|
||||
* Normalize offers from book_offers and transaction stream
|
||||
*
|
||||
* @param {Object} transaction
|
||||
* @param {Object} offer
|
||||
* @return {Object} normalized
|
||||
*/
|
||||
|
||||
OrderBook.prototype.updateTransferRate = function(message) {
|
||||
var self = this;
|
||||
OrderBook.offerRewrite = function(offer) {
|
||||
var result = { };
|
||||
var keys = Object.keys(offer);
|
||||
|
||||
var affectedAccounts = message.mmeta.getAffectedAccounts();
|
||||
|
||||
var isIssuerAffected = affectedAccounts.some(function(account) {
|
||||
return account === self._issuerGets;
|
||||
});
|
||||
|
||||
if (!isIssuerAffected) {
|
||||
return;
|
||||
for (var i=0, l=keys.length; i<l; i++) {
|
||||
var key = keys[i];
|
||||
switch (key) {
|
||||
case 'PreviousTxnID':
|
||||
case 'PreviousTxnLgrSeq':
|
||||
case 'quality':
|
||||
break;
|
||||
default:
|
||||
result[key] = offer[key];
|
||||
}
|
||||
}
|
||||
|
||||
// XXX Update transfer rate
|
||||
//
|
||||
// var nodes = message.mmeta.getNodes({
|
||||
// nodeType: 'ModifiedNode',
|
||||
// entryType: 'AccountRoot'
|
||||
// });
|
||||
result.Flags = result.Flags || 0;
|
||||
result.OwnerNode = result.OwnerNode || new Array(16 + 1).join('0');
|
||||
result.BookNode = result.BookNode || new Array(16 + 1).join('0');
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset internal offers cache from book_offers request
|
||||
*
|
||||
* @param {Array} offers
|
||||
* @api private
|
||||
*/
|
||||
|
||||
OrderBook.prototype.setOffers = function(offers) {
|
||||
assert(Array.isArray(offers));
|
||||
|
||||
var newOffers = [ ];
|
||||
|
||||
for (var i=0, l=offers.length; i<l; i++) {
|
||||
var offer = OrderBook.offerRewrite(offers[i]);
|
||||
var fundedAmount;
|
||||
|
||||
if (this.hasCachedFunds(offer.Account)) {
|
||||
fundedAmount = this.getCachedFunds(offer.Account);
|
||||
} else if (offer.hasOwnProperty('owner_funds')) {
|
||||
fundedAmount = this.applyTransferRate(offer.owner_funds);
|
||||
this.addCachedFunds(offer.Account, fundedAmount);
|
||||
}
|
||||
|
||||
this.setFundedAmount(offer, fundedAmount);
|
||||
this.incrementOfferCount(offer.Account);
|
||||
|
||||
newOffers.push(offer);
|
||||
}
|
||||
|
||||
this._offers = newOffers;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -707,27 +713,8 @@ OrderBook.prototype.requestOffers = function(callback) {
|
||||
log.info('requested offers', self._key, 'offers: ' + res.offers.length);
|
||||
}
|
||||
|
||||
// Reset offers
|
||||
self._offers = [ ];
|
||||
|
||||
for (var i=0, l=res.offers.length; i<l; i++) {
|
||||
var offer = res.offers[i];
|
||||
var fundedAmount;
|
||||
|
||||
if (self.hasCachedFunds(offer.Account)) {
|
||||
fundedAmount = self.getCachedFunds(offer.Account);
|
||||
} else if (offer.hasOwnProperty('owner_funds')) {
|
||||
fundedAmount = self.applyTransferRate(offer.owner_funds);
|
||||
self.addCachedFunds(offer.Account, fundedAmount);
|
||||
}
|
||||
|
||||
self.setFundedAmount(offer, fundedAmount);
|
||||
self.incrementOfferCount(offer.Account);
|
||||
self._offers.push(offer);
|
||||
}
|
||||
|
||||
self.setOffers(res.offers);
|
||||
self._synchronized = true;
|
||||
|
||||
self.emit('model', self._offers);
|
||||
|
||||
callback(null, self._offers);
|
||||
@@ -839,6 +826,57 @@ OrderBook.prototype.getOffersSync = function() {
|
||||
return this._offers;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update offers whose account's funds have changed
|
||||
*
|
||||
* @param {String} account address
|
||||
* @param {String|Object} offer funds
|
||||
*/
|
||||
|
||||
OrderBook.prototype.updateAccountFunds = function(account, balance) {
|
||||
assert(UInt160.is_valid(account), 'Account is invalid');
|
||||
assert(!isNaN(balance), 'Funded amount is invalid');
|
||||
|
||||
if (this._remote.trace) {
|
||||
log.info('updating offer funds', this._key, account, fundedAmount);
|
||||
}
|
||||
|
||||
var fundedAmount = this.applyTransferRate(balance);
|
||||
|
||||
// Update cached account funds
|
||||
this.addCachedFunds(account, fundedAmount);
|
||||
|
||||
for (var i=0, l=this._offers.length; i<l; i++) {
|
||||
var offer = this._offers[i];
|
||||
|
||||
if (offer.Account !== account) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var previousOffer = extend({ }, offer);
|
||||
var previousFundedGets = Amount.from_json(
|
||||
offer.taker_gets_funded + OrderBook.IOU_SUFFIX
|
||||
);
|
||||
|
||||
offer.owner_funds = balance;
|
||||
this.setFundedAmount(offer, fundedAmount);
|
||||
|
||||
var hasChangedFunds = !previousFundedGets.equals(
|
||||
Amount.from_json(offer.taker_gets_funded + OrderBook.IOU_SUFFIX)
|
||||
);
|
||||
|
||||
if (!hasChangedFunds) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.emit('offer_changed', previousOffer, offer);
|
||||
this.emit('offer_funds_changed', offer,
|
||||
previousOffer.taker_gets_funded,
|
||||
offer.taker_gets_funded
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Insert an offer into the orderbook
|
||||
*
|
||||
@@ -850,8 +888,8 @@ OrderBook.prototype.insertOffer = function(node, fundedAmount) {
|
||||
log.info('inserting offer', this._key, node.fields);
|
||||
}
|
||||
|
||||
var nodeFields = node.fields;
|
||||
|
||||
var nodeFields = OrderBook.offerRewrite(node.fields);
|
||||
nodeFields.LedgerEntryType = node.entryType;
|
||||
nodeFields.index = node.ledgerIndex;
|
||||
|
||||
if (!isNaN(fundedAmount)) {
|
||||
@@ -902,7 +940,7 @@ OrderBook.prototype.modifyOffer = function(node, isDeletedNode) {
|
||||
}
|
||||
}
|
||||
|
||||
for (var i=0; i<this._offers.length; i++) {
|
||||
for (var i=0, l=this._offers.length; i<l; i++) {
|
||||
var offer = this._offers[i];
|
||||
if (offer.index === node.ledgerIndex) {
|
||||
if (isDeletedNode) {
|
||||
@@ -921,57 +959,6 @@ OrderBook.prototype.modifyOffer = function(node, isDeletedNode) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update funded status on offers whose account's balance has changed
|
||||
*
|
||||
* Update cached account funds
|
||||
*
|
||||
* @param {String} account address
|
||||
* @param {String|Object} offer funds
|
||||
*/
|
||||
|
||||
OrderBook.prototype.updateOfferFunds = function(account, balance) {
|
||||
assert(UInt160.is_valid(account), 'Account is invalid');
|
||||
assert(!isNaN(balance), 'Funded amount is invalid');
|
||||
|
||||
if (this._remote.trace) {
|
||||
log.info('updating offer funds', this._key, account, fundedAmount);
|
||||
}
|
||||
|
||||
var fundedAmount = this.applyTransferRate(balance);
|
||||
|
||||
// Update cached account funds
|
||||
this.addCachedFunds(account, fundedAmount);
|
||||
|
||||
for (var i=0; i<this._offers.length; i++) {
|
||||
var offer = this._offers[i];
|
||||
|
||||
if (offer.Account !== account) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var suffix = '/USD/rrrrrrrrrrrrrrrrrrrrBZbvji';
|
||||
var previousOffer = extend({}, offer);
|
||||
var previousFundedGets = Amount.from_json(offer.taker_gets_funded + suffix);
|
||||
|
||||
offer.owner_funds = balance;
|
||||
this.setFundedAmount(offer, fundedAmount);
|
||||
|
||||
var hasChangedFunds = !previousFundedGets.equals(
|
||||
Amount.from_json(offer.taker_gets_funded + suffix)
|
||||
);
|
||||
|
||||
if (hasChangedFunds) {
|
||||
this.emit('offer_changed', previousOffer, offer);
|
||||
this.emit(
|
||||
'offer_funds_changed', offer,
|
||||
previousOffer.taker_gets_funded,
|
||||
offer.taker_gets_funded
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify orderbook of a relevant transaction
|
||||
*
|
||||
@@ -979,7 +966,7 @@ OrderBook.prototype.updateOfferFunds = function(account, balance) {
|
||||
* @api private
|
||||
*/
|
||||
|
||||
OrderBook.prototype.notify = function(message) {
|
||||
OrderBook.prototype.notify = function(transaction) {
|
||||
var self = this;
|
||||
|
||||
// Unsubscribed from OrderBook
|
||||
@@ -987,7 +974,7 @@ OrderBook.prototype.notify = function(message) {
|
||||
return;
|
||||
}
|
||||
|
||||
var affectedNodes = message.mmeta.getNodes({
|
||||
var affectedNodes = transaction.mmeta.getNodes({
|
||||
entryType: 'Offer',
|
||||
bookKey: this._key
|
||||
});
|
||||
@@ -997,7 +984,7 @@ OrderBook.prototype.notify = function(message) {
|
||||
}
|
||||
|
||||
if (this._remote.trace) {
|
||||
log.info('notifying', this._key, message.transaction.hash);
|
||||
log.info('notifying', this._key, transaction.transaction.hash);
|
||||
}
|
||||
|
||||
var tradeGets = Amount.from_json(
|
||||
@@ -1014,7 +1001,7 @@ OrderBook.prototype.notify = function(message) {
|
||||
|
||||
function handleNode(node, callback) {
|
||||
var isDeletedNode = node.nodeType === 'DeletedNode';
|
||||
var isOfferCancel = message.transaction.TransactionType === 'OfferCancel';
|
||||
var isOfferCancel = transaction.transaction.TransactionType === 'OfferCancel';
|
||||
|
||||
switch (node.nodeType) {
|
||||
case 'DeletedNode':
|
||||
@@ -1041,7 +1028,7 @@ OrderBook.prototype.notify = function(message) {
|
||||
case 'CreatedNode':
|
||||
self.incrementOfferCount(node.fields.Account);
|
||||
|
||||
var fundedAmount = message.transaction.owner_funds;
|
||||
var fundedAmount = transaction.transaction.owner_funds;
|
||||
|
||||
if (!isNaN(fundedAmount)) {
|
||||
self.insertOffer(node, fundedAmount);
|
||||
@@ -1063,7 +1050,7 @@ OrderBook.prototype.notify = function(message) {
|
||||
};
|
||||
|
||||
async.eachSeries(affectedNodes, handleNode, function() {
|
||||
self.emit('transaction', message);
|
||||
self.emit('transaction', transaction);
|
||||
self.emit('model', self._offers);
|
||||
if (!tradeGets.is_zero()) {
|
||||
self.emit('trade', tradePays, tradeGets);
|
||||
@@ -1099,6 +1086,27 @@ OrderBook.prototype.to_json = function() {
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the OrderBook is valid.
|
||||
*
|
||||
* Note: This only checks whether the parameters (currencies and issuer) are
|
||||
* syntactically valid. It does not check anything against the ledger.
|
||||
*
|
||||
* @return {Boolean} is valid
|
||||
*/
|
||||
|
||||
OrderBook.prototype.isValid =
|
||||
OrderBook.prototype.is_valid = function() {
|
||||
// XXX Should check for same currency (non-native) && same issuer
|
||||
return (
|
||||
this._currencyPays && this._currencyPays.is_valid() &&
|
||||
(this._currencyPays.is_native() || UInt160.is_valid(this._issuerPays)) &&
|
||||
this._currencyGets && this._currencyGets.is_valid() &&
|
||||
(this._currencyGets.is_native() || UInt160.is_valid(this._issuerGets)) &&
|
||||
!(this._currencyPays.is_native() && this._currencyGets.is_native())
|
||||
);
|
||||
};
|
||||
|
||||
exports.OrderBook = OrderBook;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -24,6 +24,7 @@ var Currency = amount.Currency;
|
||||
// Shortcuts
|
||||
var hex = sjcl.codec.hex;
|
||||
var bytes = sjcl.codec.bytes;
|
||||
var utf8 = sjcl.codec.utf8String;
|
||||
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
@@ -52,7 +53,7 @@ function isBigInteger(val) {
|
||||
return val instanceof BigInteger;
|
||||
};
|
||||
|
||||
function serialize_hex(so, hexData, noLength) {
|
||||
function serializeHex(so, hexData, noLength) {
|
||||
var byteData = bytes.fromBits(hex.toBits(hexData));
|
||||
if (!noLength) {
|
||||
SerializedType.serialize_varint(so, byteData.length);
|
||||
@@ -63,10 +64,18 @@ function serialize_hex(so, hexData, noLength) {
|
||||
/**
|
||||
* parses bytes as hex
|
||||
*/
|
||||
function convert_bytes_to_hex (byte_array) {
|
||||
function convertByteArrayToHex (byte_array) {
|
||||
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)).toUpperCase();
|
||||
};
|
||||
|
||||
function convertStringToHex(string) {
|
||||
return hex.fromBits(utf8.toBits(string)).toUpperCase();
|
||||
}
|
||||
|
||||
function convertHexToString(hexString) {
|
||||
return utf8.fromBits(hex.toBits(hexString));
|
||||
}
|
||||
|
||||
SerializedType.serialize_varint = function (so, val) {
|
||||
if (val < 0) {
|
||||
throw new Error('Variable integers are unsigned.');
|
||||
@@ -115,7 +124,7 @@ SerializedType.prototype.parse_varint = function (so) {
|
||||
*
|
||||
* The result is appended to the serialized object ('so').
|
||||
*/
|
||||
function append_byte_array(so, val, bytes) {
|
||||
function convertIntegerToByteArray(val, bytes) {
|
||||
if (!isNumber(val)) {
|
||||
throw new Error('Value is not a number', bytes);
|
||||
}
|
||||
@@ -130,7 +139,7 @@ function append_byte_array(so, val, bytes) {
|
||||
newBytes.unshift(val >>> (i * 8) & 0xff);
|
||||
}
|
||||
|
||||
so.append(newBytes);
|
||||
return newBytes;
|
||||
};
|
||||
|
||||
// Convert a certain number of bytes from the serialized object ('so') into an integer.
|
||||
@@ -152,7 +161,7 @@ function readAndSum(so, bytes) {
|
||||
|
||||
var STInt8 = exports.Int8 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
append_byte_array(so, val, 1);
|
||||
so.append(convertIntegerToByteArray(val, 1));
|
||||
},
|
||||
parse: function (so) {
|
||||
return readAndSum(so, 1);
|
||||
@@ -163,7 +172,7 @@ STInt8.id = 16;
|
||||
|
||||
var STInt16 = exports.Int16 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
append_byte_array(so, val, 2);
|
||||
so.append(convertIntegerToByteArray(val, 2));
|
||||
},
|
||||
parse: function (so) {
|
||||
return readAndSum(so, 2);
|
||||
@@ -174,7 +183,7 @@ STInt16.id = 1;
|
||||
|
||||
var STInt32 = exports.Int32 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
append_byte_array(so, val, 4);
|
||||
so.append(convertIntegerToByteArray(val, 4));
|
||||
},
|
||||
parse: function (so) {
|
||||
return readAndSum(so, 4);
|
||||
@@ -217,7 +226,7 @@ var STInt64 = exports.Int64 = new SerializedType({
|
||||
hex = '0' + hex;
|
||||
}
|
||||
|
||||
serialize_hex(so, hex, true); //noLength = true
|
||||
serializeHex(so, hex, true); //noLength = true
|
||||
},
|
||||
parse: function (so) {
|
||||
var bytes = so.read(8);
|
||||
@@ -237,7 +246,7 @@ var STHash128 = exports.Hash128 = new SerializedType({
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error('Invalid Hash128');
|
||||
}
|
||||
serialize_hex(so, hash.to_hex(), true); //noLength = true
|
||||
serializeHex(so, hash.to_hex(), true); //noLength = true
|
||||
},
|
||||
parse: function (so) {
|
||||
return UInt128.from_bytes(so.read(16));
|
||||
@@ -252,7 +261,7 @@ var STHash256 = exports.Hash256 = new SerializedType({
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error('Invalid Hash256');
|
||||
}
|
||||
serialize_hex(so, hash.to_hex(), true); //noLength = true
|
||||
serializeHex(so, hash.to_hex(), true); //noLength = true
|
||||
},
|
||||
parse: function (so) {
|
||||
return UInt256.from_bytes(so.read(32));
|
||||
@@ -267,7 +276,7 @@ var STHash160 = exports.Hash160 = new SerializedType({
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error('Invalid Hash160');
|
||||
}
|
||||
serialize_hex(so, hash.to_hex(), true); //noLength = true
|
||||
serializeHex(so, hash.to_hex(), true); //noLength = true
|
||||
},
|
||||
parse: function (so) {
|
||||
return UInt160.from_bytes(so.read(20));
|
||||
@@ -294,7 +303,7 @@ var STCurrency = new SerializedType({
|
||||
// UInt160 value and consider it valid. But it doesn't, so for the
|
||||
// deserialization to be usable, we need to allow invalid results for now.
|
||||
//if (!currency.is_valid()) {
|
||||
// throw new Error('Invalid currency: '+convert_bytes_to_hex(bytes));
|
||||
// throw new Error('Invalid currency: '+convertByteArrayToHex(bytes));
|
||||
//}
|
||||
return currency;
|
||||
}
|
||||
@@ -409,15 +418,16 @@ STAmount.id = 6;
|
||||
|
||||
var STVL = exports.VariableLength = exports.VL = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
|
||||
if (typeof val === 'string') {
|
||||
serialize_hex(so, val);
|
||||
serializeHex(so, val);
|
||||
} else {
|
||||
throw new Error('Unknown datatype.');
|
||||
}
|
||||
},
|
||||
parse: function (so) {
|
||||
var len = this.parse_varint(so);
|
||||
return convert_bytes_to_hex(so.read(len));
|
||||
return convertByteArrayToHex(so.read(len));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -429,7 +439,7 @@ var STAccount = exports.Account = new SerializedType({
|
||||
if (!account.is_valid()) {
|
||||
throw new Error('Invalid account!');
|
||||
}
|
||||
serialize_hex(so, account.to_hex());
|
||||
serializeHex(so, account.to_hex());
|
||||
},
|
||||
parse: function (so) {
|
||||
var len = this.parse_varint(so);
|
||||
@@ -441,7 +451,6 @@ var STAccount = exports.Account = new SerializedType({
|
||||
var result = UInt160.from_bytes(so.read(len));
|
||||
result.set_version(Base.VER_ACCOUNT_ID);
|
||||
|
||||
//console.log('PARSED 160:', result.to_json());
|
||||
if (false && !result.is_valid()) {
|
||||
throw new Error('Invalid Account');
|
||||
}
|
||||
@@ -593,6 +602,104 @@ var STVector256 = exports.Vector256 = new SerializedType({
|
||||
|
||||
STVector256.id = 19;
|
||||
|
||||
// Internal
|
||||
var STMemo = exports.STMemo = new SerializedType({
|
||||
serialize: function(so, val, no_marker) {
|
||||
|
||||
var keys = [];
|
||||
|
||||
Object.keys(val).forEach(function (key) {
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
// convention.
|
||||
if (key[0] === key[0].toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
|
||||
throw new Error('JSON contains unknown field: "' + key + '"');
|
||||
}
|
||||
|
||||
keys.push(key);
|
||||
});
|
||||
|
||||
// Sort fields
|
||||
keys = sort_fields(keys);
|
||||
|
||||
// store that we're dealing with json
|
||||
var isJson = val.MemoFormat === 'json';
|
||||
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
var key = keys[i];
|
||||
switch (key) {
|
||||
|
||||
// MemoType and MemoFormat are always ASCII strings
|
||||
case 'MemoType':
|
||||
case 'MemoFormat':
|
||||
val[key] = convertStringToHex(val[key]);
|
||||
break;
|
||||
|
||||
// MemoData can be a JSON object, otherwise it's a string
|
||||
case 'MemoData':
|
||||
if (typeof val[key] !== 'string') {
|
||||
if (isJson) {
|
||||
try {
|
||||
val[key] = convertStringToHex(JSON.stringify(val[key]));
|
||||
} catch (e) {
|
||||
throw new Error('MemoFormat json with invalid JSON in MemoData field');
|
||||
}
|
||||
} else {
|
||||
throw new Error('MemoData can only be a JSON object with a valid json MemoFormat');
|
||||
}
|
||||
} else if (isString(val[key])) {
|
||||
val[key] = convertStringToHex(val[key]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
serialize(so, key, val[key]);
|
||||
}
|
||||
|
||||
if (!no_marker) {
|
||||
//Object ending marker
|
||||
STInt8.serialize(so, 0xe1);
|
||||
}
|
||||
|
||||
},
|
||||
parse: function(so) {
|
||||
var output = {};
|
||||
while (so.peek(1)[0] !== 0xe1) {
|
||||
var keyval = parse(so);
|
||||
output[keyval[0]] = keyval[1];
|
||||
}
|
||||
|
||||
if (output['MemoType'] !== void(0)) {
|
||||
output['parsed_memo_type'] = convertHexToString(output['MemoType']);
|
||||
}
|
||||
|
||||
if (output['MemoFormat'] !== void(0)) {
|
||||
output['parsed_memo_format'] = convertHexToString(output['MemoFormat']);
|
||||
}
|
||||
|
||||
if (output['MemoData'] !== void(0)) {
|
||||
|
||||
// see if we can parse JSON
|
||||
if (output['parsed_memo_format'] === 'json') {
|
||||
try {
|
||||
output['parsed_memo_data'] = JSON.parse(convertHexToString(output['MemoData']));
|
||||
} catch(e) {
|
||||
// fail, which is fine, we just won't add the memo_data field
|
||||
}
|
||||
} else if(output['parsed_memo_format'] === 'text') {
|
||||
output['parsed_memo_data'] = convertHexToString(output['MemoData']);
|
||||
}
|
||||
}
|
||||
|
||||
so.read(1);
|
||||
return output;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
exports.serialize = exports.serialize_whatever = serialize;
|
||||
|
||||
function serialize(so, field_name, value) {
|
||||
@@ -622,9 +729,15 @@ function serialize(so, field_name, value) {
|
||||
STInt8.serialize(so, field_bits);
|
||||
}
|
||||
|
||||
// Get the serializer class (ST...) for a field based on the type bits.
|
||||
var serialized_object_type = exports[binformat.types[type_bits]];
|
||||
//do something with val[keys] and val[keys[i]];
|
||||
// Get the serializer class (ST...)
|
||||
var serialized_object_type;
|
||||
if (field_name === 'Memo' && typeof value === 'object') {
|
||||
// for Memo we override the default behavior with our STMemo serializer
|
||||
serialized_object_type = exports.STMemo;
|
||||
} else {
|
||||
// for a field based on the type bits.
|
||||
serialized_object_type = exports[binformat.types[type_bits]];
|
||||
}
|
||||
|
||||
try {
|
||||
serialized_object_type.serialize(so, value);
|
||||
@@ -645,18 +758,21 @@ function parse(so) {
|
||||
type_bits = so.read(1)[0];
|
||||
}
|
||||
|
||||
// Get the parser class (ST...) for a field based on the type bits.
|
||||
var type = exports[binformat.types[type_bits]];
|
||||
|
||||
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
|
||||
|
||||
var field_bits = tag_byte & 0x0f;
|
||||
var field_name = (field_bits === 0)
|
||||
? field_name = binformat.fields[type_bits][so.read(1)[0]]
|
||||
: field_name = binformat.fields[type_bits][field_bits];
|
||||
? field_name = binformat.fields[type_bits][so.read(1)[0]]
|
||||
: field_name = binformat.fields[type_bits][field_bits];
|
||||
|
||||
assert(field_name, 'Unknown field - header byte is 0x' + tag_byte.toString(16));
|
||||
|
||||
// Get the parser class (ST...) for a field based on the type bits.
|
||||
var type = (field_name === 'Memo')
|
||||
? exports.STMemo
|
||||
: exports[binformat.types[type_bits]];
|
||||
|
||||
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
|
||||
|
||||
return [ field_name, type.parse(so) ]; //key, value
|
||||
};
|
||||
|
||||
@@ -678,18 +794,20 @@ function sort_fields(keys) {
|
||||
|
||||
var STObject = exports.Object = new SerializedType({
|
||||
serialize: function (so, val, no_marker) {
|
||||
var keys = Object.keys(val);
|
||||
var keys = [];
|
||||
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
// convention.
|
||||
keys = keys.filter(function (key) {
|
||||
return key[0] !== key[0].toLowerCase();
|
||||
});
|
||||
Object.keys(val).forEach(function (key) {
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
// convention.
|
||||
if (key[0] === key[0].toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
keys.forEach(function (key) {
|
||||
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
|
||||
throw new Error('JSON contains unknown field: "' + key + '"');
|
||||
}
|
||||
|
||||
keys.push(key);
|
||||
});
|
||||
|
||||
// Sort fields
|
||||
|
||||
@@ -181,6 +181,8 @@ Transaction.prototype.consts = {
|
||||
tecCLAIMED: 100
|
||||
};
|
||||
|
||||
Transaction.prototype.ascii_regex = /^[\x00-\x7F]*$/;
|
||||
|
||||
Transaction.from_json = function(j) {
|
||||
return (new Transaction()).parseJson(j);
|
||||
};
|
||||
@@ -620,40 +622,52 @@ Transaction.prototype.setFlags = function(flags) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a Memo to transaction. Memos can be used as key-value,
|
||||
* using the MemoType as a key
|
||||
* Add a Memo to transaction.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} data
|
||||
* @param {String} memoType - describes what the data represents, needs to be valid ASCII
|
||||
* @param {String} memoFormat - describes what format the data is in, MIME type, needs to be valid ASCII
|
||||
* @param {String} memoData - data for the memo, can be any JS object. Any object other than string will be stringified (JSON) for transport
|
||||
*/
|
||||
|
||||
Transaction.prototype.addMemo = function(type, data) {
|
||||
if (!/(undefined|string)/.test(typeof type)) {
|
||||
Transaction.prototype.addMemo = function(memoType, memoFormat, memoData) {
|
||||
|
||||
if (typeof memoType === 'object') {
|
||||
var opts = memoType;
|
||||
memoType = opts.memoType;
|
||||
memoFormat = opts.memoFormat;
|
||||
memoData = opts.memoData;
|
||||
}
|
||||
|
||||
if (!/(undefined|string)/.test(typeof memoType)) {
|
||||
throw new Error('MemoType must be a string');
|
||||
} else if (!this.ascii_regex.test(memoType)) {
|
||||
throw new Error('MemoType must be valid ASCII');
|
||||
}
|
||||
|
||||
if (!/(undefined|string)/.test(typeof data)) {
|
||||
throw new Error('MemoData must be a string');
|
||||
if (!/(undefined|string)/.test(typeof memoFormat)) {
|
||||
throw new Error('MemoFormat must be a string');
|
||||
} else if (!this.ascii_regex.test(memoFormat)) {
|
||||
throw new Error('MemoFormat must be valid ASCII');
|
||||
}
|
||||
|
||||
function toHex(str) {
|
||||
return sjcl.codec.hex.fromBits(sjcl.codec.utf8String.toBits(str));
|
||||
};
|
||||
var memo = {};
|
||||
|
||||
var memo = { };
|
||||
|
||||
if (type) {
|
||||
if (Transaction.MEMO_TYPES[type]) {
|
||||
if (memoType) {
|
||||
if (Transaction.MEMO_TYPES[memoType]) {
|
||||
//XXX Maybe in the future we want a schema validator for
|
||||
//memo types
|
||||
memo.MemoType = Transaction.MEMO_TYPES[type];
|
||||
memo.MemoType = Transaction.MEMO_TYPES[memoType];
|
||||
} else {
|
||||
memo.MemoType = toHex(type);
|
||||
memo.MemoType = memoType;
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
memo.MemoData = toHex(data);
|
||||
if (memoFormat) {
|
||||
memo.MemoFormat = memoFormat;
|
||||
}
|
||||
|
||||
if (memoData) {
|
||||
memo.MemoData = memoData;
|
||||
}
|
||||
|
||||
this.tx_json.Memos = (this.tx_json.Memos || []).concat({ Memo: memo });
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('Amount', function() {
|
||||
assert.strictEqual(Amount.from_human("0.8 XAU").to_human({precision:0}), '1');
|
||||
});
|
||||
it('to human, precision 0, precision 16', function() {
|
||||
assert.strictEqual(Amount.from_human("0.0 XAU").to_human({precision:16}), '0.0');
|
||||
assert.strictEqual(Amount.from_human("0.0 XAU").to_human({precision:16}), '0');
|
||||
});
|
||||
it('to human, precision 0, precision 8, min_precision 16', function() {
|
||||
assert.strictEqual(Amount.from_human("0.0 XAU").to_human({precision:8, min_precision:16}), '0.0000000000000000');
|
||||
@@ -101,6 +101,21 @@ describe('Amount', function() {
|
||||
it('to human, precision 16, min_precision 6, max_sig_digits 20', function() {
|
||||
assert.strictEqual(Amount.from_human("0.0 XAU").to_human({precision: 16, min_precision: 6, max_sig_digits: 20}), '0.000000');
|
||||
});
|
||||
it('to human rounding edge case, precision 2, 1', function() {
|
||||
assert.strictEqual(Amount.from_human("0.99 XAU").to_human({precision:1}), '1.0');
|
||||
});
|
||||
it('to human rounding edge case, precision 2, 2', function() {
|
||||
assert.strictEqual(Amount.from_human("0.99 XAU").to_human({precision:2}), '0.99');
|
||||
});
|
||||
it('to human rounding edge case, precision 2, 3', function() {
|
||||
assert.strictEqual(Amount.from_human("0.99 XAU").to_human({precision:3}), '0.99');
|
||||
});
|
||||
it('to human rounding edge case, precision 2, 3 min precision 3', function() {
|
||||
assert.strictEqual(Amount.from_human("0.99 XAU").to_human({precision:3, min_precision:3}), '0.990');
|
||||
});
|
||||
it('to human rounding edge case, precision 3, 2', function() {
|
||||
assert.strictEqual(Amount.from_human("0.999 XAU").to_human({precision:2}), '1.00');
|
||||
});
|
||||
});
|
||||
describe('from_human', function() {
|
||||
it('1 XRP', function() {
|
||||
|
||||
@@ -329,12 +329,12 @@ describe('OrderBook', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Set funded amount - funded', function() {
|
||||
it('Set funded amount - iou/xrp - funded', function() {
|
||||
var remote = new Remote();
|
||||
var book = remote.createOrderBook({
|
||||
currency_gets: 'BTC',
|
||||
currency_pays: 'XRP',
|
||||
issuer_gets: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
|
||||
currency_gets: 'BTC'
|
||||
issuer_gets: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
});
|
||||
|
||||
var offer = {
|
||||
@@ -359,7 +359,37 @@ describe('OrderBook', function() {
|
||||
assert.deepEqual(offer, expected);
|
||||
});
|
||||
|
||||
it('Set funded amount - unfunded', function() {
|
||||
it('Set funded amount - iou/xrp - unfunded', function() {
|
||||
var remote = new Remote();
|
||||
var book = remote.createOrderBook({
|
||||
currency_gets: 'BTC',
|
||||
currency_pays: 'XRP',
|
||||
issuer_gets: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
});
|
||||
|
||||
var offer = {
|
||||
TakerGets: {
|
||||
value: '100',
|
||||
currency: 'BTC',
|
||||
issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
},
|
||||
TakerPays: '123456'
|
||||
};
|
||||
|
||||
book.setFundedAmount(offer, '99');
|
||||
|
||||
var expected = {
|
||||
TakerGets: offer.TakerGets,
|
||||
TakerPays: offer.TakerPays,
|
||||
is_fully_funded: false,
|
||||
taker_gets_funded: '99',
|
||||
taker_pays_funded: '122221'
|
||||
};
|
||||
|
||||
assert.deepEqual(offer, expected);
|
||||
});
|
||||
|
||||
it('Set funded amount - xrp/iou - funded', function() {
|
||||
var remote = new Remote();
|
||||
var book = remote.createOrderBook({
|
||||
currency_gets: 'XRP',
|
||||
@@ -370,7 +400,37 @@ describe('OrderBook', function() {
|
||||
var offer = {
|
||||
TakerGets: '100',
|
||||
TakerPays: {
|
||||
value: '123456',
|
||||
value: '123.456',
|
||||
currency: 'BTC',
|
||||
issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
}
|
||||
};
|
||||
|
||||
book.setFundedAmount(offer, '100.1');
|
||||
|
||||
var expected = {
|
||||
TakerGets: offer.TakerGets,
|
||||
TakerPays: offer.TakerPays,
|
||||
is_fully_funded: true,
|
||||
taker_gets_funded: '100',
|
||||
taker_pays_funded: '123.456'
|
||||
};
|
||||
|
||||
assert.deepEqual(offer, expected);
|
||||
});
|
||||
|
||||
it('Set funded amount - xrp/iou - unfunded', function() {
|
||||
var remote = new Remote();
|
||||
var book = remote.createOrderBook({
|
||||
currency_gets: 'XRP',
|
||||
issuer_pays: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
|
||||
currency_pays: 'BTC'
|
||||
});
|
||||
|
||||
var offer = {
|
||||
TakerGets: '100',
|
||||
TakerPays: {
|
||||
value: '123.456',
|
||||
currency: 'BTC',
|
||||
issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
}
|
||||
@@ -383,67 +443,7 @@ describe('OrderBook', function() {
|
||||
TakerPays: offer.TakerPays,
|
||||
is_fully_funded: false,
|
||||
taker_gets_funded: '99',
|
||||
taker_pays_funded: '122221.44'
|
||||
};
|
||||
|
||||
assert.deepEqual(offer, expected);
|
||||
});
|
||||
|
||||
it('Set funded amount - native currency - funded', function() {
|
||||
var remote = new Remote();
|
||||
var book = remote.createOrderBook({
|
||||
currency_gets: 'XRP',
|
||||
issuer_pays: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
|
||||
currency_pays: 'BTC'
|
||||
});
|
||||
|
||||
var offer = {
|
||||
TakerGets: '100',
|
||||
TakerPays: {
|
||||
value: '100.1234',
|
||||
currency: 'USD',
|
||||
issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
}
|
||||
};
|
||||
|
||||
book.setFundedAmount(offer, '100');
|
||||
|
||||
var expected = {
|
||||
TakerGets: offer.TakerGets,
|
||||
TakerPays: offer.TakerPays,
|
||||
is_fully_funded: true,
|
||||
taker_gets_funded: '100',
|
||||
taker_pays_funded: '100.1234'
|
||||
};
|
||||
|
||||
assert.deepEqual(offer, expected);
|
||||
});
|
||||
|
||||
it('Set funded amount - native currency - unfunded', function() {
|
||||
var remote = new Remote();
|
||||
var book = remote.createOrderBook({
|
||||
currency_gets: 'XRP',
|
||||
issuer_pays: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
|
||||
currency_pays: 'USD'
|
||||
});
|
||||
|
||||
var offer = {
|
||||
TakerGets: {
|
||||
value: '100.1234',
|
||||
currency: 'USD',
|
||||
issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
},
|
||||
TakerPays: '123'
|
||||
};
|
||||
|
||||
book.setFundedAmount(offer, '100');
|
||||
|
||||
var expected = {
|
||||
TakerGets: offer.TakerGets,
|
||||
TakerPays: offer.TakerPays,
|
||||
is_fully_funded: false,
|
||||
taker_gets_funded: '100',
|
||||
taker_pays_funded: '122.8484050681459'
|
||||
taker_pays_funded: '122.22144'
|
||||
};
|
||||
|
||||
assert.deepEqual(offer, expected);
|
||||
@@ -1495,8 +1495,6 @@ describe('OrderBook', function() {
|
||||
Flags: 131072,
|
||||
LedgerEntryType: 'Offer',
|
||||
OwnerNode: '0000000000000000',
|
||||
PreviousTxnID: '9BB337CC8B34DC8D1A3FFF468556C8BA70977C37F7436439D8DA19610F214AD1',
|
||||
PreviousTxnLgrSeq: 8342933,
|
||||
Sequence: 195,
|
||||
TakerGets: { currency: 'BTC',
|
||||
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
@@ -1509,7 +1507,6 @@ describe('OrderBook', function() {
|
||||
},
|
||||
index: 'B6BC3B0F87976370EE11F5575593FE63AA5DC1D602830DC96F04B2D597F044BF',
|
||||
owner_funds: '0.1129267125000245',
|
||||
quality: '496.5',
|
||||
taker_gets_funded: '0.1127013098802639',
|
||||
taker_pays_funded: '55.95620035555102',
|
||||
is_fully_funded: false },
|
||||
@@ -1521,8 +1518,6 @@ describe('OrderBook', function() {
|
||||
Flags: 131072,
|
||||
LedgerEntryType: 'Offer',
|
||||
OwnerNode: '0000000000000144',
|
||||
PreviousTxnID: 'C8296B9CCA6DC594C7CD271C5D8FD11FEE380021A07768B25935642CDB37048A',
|
||||
PreviousTxnLgrSeq: 8342469,
|
||||
Sequence: 29354,
|
||||
TakerGets: {
|
||||
currency: 'BTC',
|
||||
@@ -1536,7 +1531,6 @@ describe('OrderBook', function() {
|
||||
},
|
||||
index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86',
|
||||
owner_funds: '0.950363009783092',
|
||||
quality: '498.6116758238228',
|
||||
is_fully_funded: true,
|
||||
taker_gets_funded: '0.2',
|
||||
taker_pays_funded: '99.72233516476456'
|
||||
|
||||
@@ -35,6 +35,7 @@ describe('Serialized object', function() {
|
||||
assert.deepEqual(input_json, output_json);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#from_json', function() {
|
||||
it('understands TransactionType as a Number', function() {
|
||||
var input_json = {
|
||||
@@ -52,6 +53,7 @@ describe('Serialized object', function() {
|
||||
assert.equal(0, input_json.TransactionType);
|
||||
assert.equal("Payment", output_json.TransactionType);
|
||||
});
|
||||
|
||||
it('understands LedgerEntryType as a Number', function() {
|
||||
var input_json = {
|
||||
// no, non required fields
|
||||
@@ -65,6 +67,7 @@ describe('Serialized object', function() {
|
||||
assert.equal(100, input_json.LedgerEntryType);
|
||||
assert.equal("DirectoryNode", output_json.LedgerEntryType);
|
||||
});
|
||||
|
||||
describe('Format validation', function() {
|
||||
// Peercover actually had a problem submitting transactions without a `Fee`
|
||||
// and rippled was only informing of "transaction is invalid"
|
||||
@@ -80,14 +83,198 @@ describe('Serialized object', function() {
|
||||
};
|
||||
assert.throws (
|
||||
function() {
|
||||
var output_json = SerializedObject.from_json(input_json);
|
||||
SerializedObject.from_json(input_json);
|
||||
},
|
||||
/Payment is missing fields: \["Fee"\]/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
describe('Memos', function() {
|
||||
|
||||
var input_json;
|
||||
|
||||
beforeEach(function() {
|
||||
input_json = {
|
||||
"Flags": 2147483648,
|
||||
"TransactionType": "Payment",
|
||||
"Account": "rhXzSyt1q9J8uiFXpK3qSugAAPJKXLtnrF",
|
||||
"Amount": "1",
|
||||
"Destination": "radqi6ppXFxVhJdjzaATRBxdrPcVTf1Ung",
|
||||
"Sequence": 281,
|
||||
"SigningPubKey": "03D642E6457B8AB4D140E2C66EB4C484FAFB1BF267CB578EC4815FE6CD06379C51",
|
||||
"Fee": "12000",
|
||||
"LastLedgerSequence": 10074214,
|
||||
"TxnSignature": "304402201180636F2CE215CE97A29CD302618FAE60D63EBFC8903DE17A356E857A449C430220290F4A54F9DE4AC79034C8BEA5F1F8757F7505F1A6FF04D2E19B6D62E867256B"
|
||||
};
|
||||
});
|
||||
|
||||
it('should serialize and parse - full memo, all strings text/plain ', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
"Memo": {
|
||||
"MemoType": "test",
|
||||
"MemoFormat": "text",
|
||||
"MemoData": "some data"
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
var so = SerializedObject.from_json(input_json).to_json();
|
||||
input_json.Memos[0].Memo.parsed_memo_type = 'test';
|
||||
input_json.Memos[0].Memo.parsed_memo_format = 'text';
|
||||
input_json.Memos[0].Memo.parsed_memo_data = 'some data';
|
||||
|
||||
assert.deepEqual(so, input_json);
|
||||
});
|
||||
|
||||
it('should serialize and parse - full memo, all strings, invalid MemoFormat', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
"Memo": {
|
||||
"MemoType": "test",
|
||||
"MemoFormat": "application/json",
|
||||
"MemoData": "some data"
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
var so = SerializedObject.from_json(input_json).to_json();
|
||||
input_json.Memos[0].Memo.parsed_memo_type = 'test';
|
||||
input_json.Memos[0].Memo.parsed_memo_format = 'application/json';
|
||||
assert.deepEqual(so, input_json);
|
||||
assert.strictEqual(input_json.Memos[0].Memo.parsed_memo_data, void(0));
|
||||
});
|
||||
|
||||
it('should throw an error - full memo, json data, invalid MemoFormat', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
"Memo": {
|
||||
"MemoType": "test",
|
||||
"MemoFormat": "text",
|
||||
"MemoData": {
|
||||
"string" : "some_string",
|
||||
"boolean" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
assert.throws(function() {
|
||||
SerializedObject.from_json(input_json);
|
||||
}, /^Error: MemoData can only be a JSON object with a valid json MemoFormat \(Memo\) \(Memos\)/);
|
||||
});
|
||||
|
||||
it('should serialize and parse - full memo, json data, valid MemoFormat', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
"Memo": {
|
||||
"MemoType": "test",
|
||||
"MemoFormat": "json",
|
||||
"ignored" : "ignored",
|
||||
"MemoData": {
|
||||
"string" : "some_string",
|
||||
"boolean" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
var so = SerializedObject.from_json(input_json).to_json();
|
||||
delete input_json.Memos[0].Memo.ignored;
|
||||
input_json.Memos[0].Memo.parsed_memo_type = 'test';
|
||||
input_json.Memos[0].Memo.parsed_memo_format = 'json';
|
||||
input_json.Memos[0].Memo.parsed_memo_data = {
|
||||
"string" : "some_string",
|
||||
"boolean" : true
|
||||
};
|
||||
|
||||
assert.deepEqual(so, input_json);
|
||||
});
|
||||
|
||||
it('should serialize and parse - full memo, json data, valid MemoFormat', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
"Memo": {
|
||||
"MemoType": "test",
|
||||
"MemoFormat": "json",
|
||||
"MemoData": {
|
||||
"string" : "some_string",
|
||||
"boolean" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
var so = SerializedObject.from_json(input_json).to_json();
|
||||
input_json.Memos[0].Memo.parsed_memo_type = 'test';
|
||||
input_json.Memos[0].Memo.parsed_memo_format = 'json';
|
||||
input_json.Memos[0].Memo.parsed_memo_data = {
|
||||
"string" : "some_string",
|
||||
"boolean" : true
|
||||
};
|
||||
|
||||
assert.deepEqual(so, input_json);
|
||||
});
|
||||
|
||||
it('should serialize and parse - full memo, json data, valid MemoFormat', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
"Memo": {
|
||||
"MemoType": "test",
|
||||
"MemoFormat": "json",
|
||||
"MemoData": 3
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
var so = SerializedObject.from_json(input_json).to_json();
|
||||
input_json.Memos[0].Memo.parsed_memo_type = 'test';
|
||||
input_json.Memos[0].Memo.parsed_memo_format = 'json';
|
||||
input_json.Memos[0].Memo.parsed_memo_data = 3;
|
||||
assert.deepEqual(so, input_json);
|
||||
});
|
||||
|
||||
it('should serialize and parse - full memo, json data, valid MemoFormat', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
"Memo": {
|
||||
"MemoType": "test",
|
||||
"MemoFormat": "json",
|
||||
"MemoData": 3
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
var so = SerializedObject.from_json(input_json).to_json();
|
||||
input_json.Memos[0].Memo.parsed_memo_type = 'test';
|
||||
input_json.Memos[0].Memo.parsed_memo_format = 'json';
|
||||
input_json.Memos[0].Memo.parsed_memo_data = 3;
|
||||
assert.deepEqual(so, input_json);
|
||||
});
|
||||
|
||||
it('should throw an error - invalid Memo field', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
"Memo": {
|
||||
"MemoType": "test",
|
||||
"MemoParty": "json",
|
||||
"MemoData": 3
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
assert.throws(function() {
|
||||
SerializedObject.from_json(input_json);
|
||||
}, /^Error: JSON contains unknown field: "MemoParty" \(Memo\) \(Memos\)/);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1052,26 +1052,84 @@ describe('Transaction', function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.tx_json.TransactionType = 'Payment';
|
||||
|
||||
transaction.addMemo('testkey', 'testvalue');
|
||||
transaction.addMemo('testkey2', 'testvalue2');
|
||||
transaction.addMemo('testkey3');
|
||||
transaction.addMemo(void(0), 'testvalue4');
|
||||
var memoType = 'message';
|
||||
var memoFormat = 'application/json';
|
||||
var memoData = {
|
||||
string: 'value',
|
||||
bool: true,
|
||||
integer: 1
|
||||
};
|
||||
|
||||
transaction.addMemo(memoType, memoFormat, memoData);
|
||||
|
||||
var expected = [
|
||||
{
|
||||
Memo:
|
||||
{
|
||||
MemoType: memoType,
|
||||
MemoFormat: memoFormat,
|
||||
MemoData: memoData
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
assert.deepEqual(transaction.tx_json.Memos, expected);
|
||||
});
|
||||
|
||||
it('Add Memo - by object', function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.tx_json.TransactionType = 'Payment';
|
||||
|
||||
var memo = {
|
||||
memoType: 'type',
|
||||
memoData: 'data'
|
||||
};
|
||||
|
||||
transaction.addMemo(memo);
|
||||
|
||||
var expected = [
|
||||
{
|
||||
Memo: {
|
||||
MemoType: memo.memoType,
|
||||
MemoData: memo.memoData
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
assert.deepEqual(transaction.tx_json.Memos, expected);
|
||||
});
|
||||
|
||||
it('Add Memos', function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.tx_json.TransactionType = 'Payment';
|
||||
|
||||
transaction.addMemo('testkey', void(0), 'testvalue');
|
||||
transaction.addMemo('testkey2', void(0), 'testvalue2');
|
||||
transaction.addMemo('testkey3', 'text/html');
|
||||
transaction.addMemo(void(0), void(0), 'testvalue4');
|
||||
transaction.addMemo('testkey4', 'text/html', '<html>');
|
||||
|
||||
var expected = [
|
||||
{ Memo: {
|
||||
MemoType: new Buffer('testkey').toString('hex'),
|
||||
MemoData: new Buffer('testvalue').toString('hex')
|
||||
MemoType: 'testkey',
|
||||
MemoData: 'testvalue'
|
||||
}},
|
||||
{ Memo: {
|
||||
MemoType: new Buffer('testkey2').toString('hex'),
|
||||
MemoData: new Buffer('testvalue2').toString('hex')
|
||||
MemoType: 'testkey2',
|
||||
MemoData: 'testvalue2'
|
||||
}},
|
||||
{ Memo: {
|
||||
MemoType: new Buffer('testkey3').toString('hex')
|
||||
MemoType: 'testkey3',
|
||||
MemoFormat: 'text/html'
|
||||
}},
|
||||
{ Memo: {
|
||||
MemoData: new Buffer('testvalue4').toString('hex')
|
||||
} }
|
||||
MemoData: 'testvalue4'
|
||||
}},
|
||||
{ Memo: {
|
||||
MemoType: 'testkey4',
|
||||
MemoFormat: 'text/html',
|
||||
MemoData: '<html>'
|
||||
}}
|
||||
];
|
||||
|
||||
assert.deepEqual(transaction.tx_json.Memos, expected);
|
||||
@@ -1086,13 +1144,76 @@ describe('Transaction', function() {
|
||||
}, /^Error: MemoType must be a string$/);
|
||||
});
|
||||
|
||||
it('Add Memo - invalid MemoData', function() {
|
||||
it('Add Memo - invalid ASCII MemoType', function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.tx_json.TransactionType = 'Payment';
|
||||
|
||||
assert.throws(function() {
|
||||
transaction.addMemo('key', 1);
|
||||
}, /^Error: MemoData must be a string$/);
|
||||
transaction.addMemo('한국어');
|
||||
}, /^Error: MemoType must be valid ASCII$/);
|
||||
});
|
||||
|
||||
it('Add Memo - invalid MemoFormat', function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.tx_json.TransactionType = 'Payment';
|
||||
|
||||
assert.throws(function() {
|
||||
transaction.addMemo(void(0), 1);
|
||||
}, /^Error: MemoFormat must be a string$/);
|
||||
});
|
||||
|
||||
it('Add Memo - invalid ASCII MemoFormat', function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.tx_json.TransactionType = 'Payment';
|
||||
|
||||
assert.throws(function() {
|
||||
transaction.addMemo(void(0), 'России');
|
||||
}, /^Error: MemoFormat must be valid ASCII$/);
|
||||
});
|
||||
|
||||
it('Add Memo - MemoData string', function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.tx_json.TransactionType = 'Payment';
|
||||
|
||||
transaction.addMemo({memoData:'some_string'});
|
||||
|
||||
assert.deepEqual(transaction.tx_json.Memos, [
|
||||
{
|
||||
Memo: {
|
||||
MemoData: 'some_string'
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('Add Memo - MemoData complex object', function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.tx_json.TransactionType = 'Payment';
|
||||
|
||||
var memo = {
|
||||
memoData: {
|
||||
string: 'string',
|
||||
int: 1,
|
||||
array: [
|
||||
{
|
||||
string: 'string'
|
||||
}
|
||||
],
|
||||
object: {
|
||||
string: 'string'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
transaction.addMemo(memo);
|
||||
|
||||
assert.deepEqual(transaction.tx_json.Memos, [
|
||||
{
|
||||
Memo: {
|
||||
MemoData: memo.memoData
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('Construct AccountSet transaction', function() {
|
||||
@@ -1269,7 +1390,7 @@ describe('Transaction', function() {
|
||||
var bid = '1/USD/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm';
|
||||
var ask = '1/EUR/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm';
|
||||
assert.throws(function() {
|
||||
var transaction = new Transaction().offerCreate('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', bid, ask);
|
||||
new Transaction().offerCreate('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', bid, ask);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1302,13 +1423,13 @@ describe('Transaction', function() {
|
||||
|
||||
it('Construct SetRegularKey transaction - invalid account', function() {
|
||||
assert.throws(function() {
|
||||
var transaction = new Transaction().setRegularKey('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
|
||||
new Transaction().setRegularKey('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
|
||||
});
|
||||
});
|
||||
|
||||
it('Construct SetRegularKey transaction - invalid regularKey', function() {
|
||||
assert.throws(function() {
|
||||
var transaction = new Transaction().setRegularKey('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'xr36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
|
||||
new Transaction().setRegularKey('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'xr36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user