Compare commits

...

98 Commits
0.9.0 ... 0.9.4

Author SHA1 Message Date
Geert Weening
12e428733a [TASK] bump version to 0.9.4 2014-12-05 15:48:24 -08:00
Geert Weening
9cc6ad09a9 [DOC] update release notes 2014-12-05 15:47:46 -08:00
Geert Weening
84abb5962e [TASK] bump ripple-lib to 0.9.4-rc2 2014-12-04 12:56:17 -08:00
Geert Weening
4bba55d2dc Revert "[FEATURE] improve memo support"
This reverts commit 89adcf4f4e.
2014-12-04 12:54:48 -08:00
Geert Weening
b4cabad44e [TASK] bump version to 0.9.4-rc1 2014-12-04 11:03:01 -08:00
Geert Weening
28cc0f9e3b [DOC] update release notes 2014-12-04 11:02:20 -08:00
wltsmrz
95a2cc18fe Merge pull request #213 from geertweening/feature/memo_format_type
[FEATURE] improve memo support
2014-12-02 00:00:10 -08:00
Geert Weening
8e315a9859 [DOC] update generate wallet example
to take advantage of randomness collected from a rippled
2014-12-01 17:54:34 -08:00
Geert Weening
89adcf4f4e [FEATURE] improve memo support
- add MemoFormat property for memo
- MemoFormat and MemoType must be valid ASCII
- Memo content is converted on the serialization level
- add parsed_* version of Memo content if the parser understand the format
- support `text` and `json` MemoFormat
2014-12-01 09:48:56 -08:00
Geert Weening
3a6c5e41c9 Merge pull request #217 from ripple/orderbook-cleanup
Cleanup, normalize offers from book_offers and transaction stream
2014-11-30 14:14:01 -08:00
wltsmrz
86ed24b94c Cleanup, normalize offers from book_offers and transaction stream 2014-11-29 15:24:15 -08:00
wltsmrz
c792c471c3 Merge pull request #215 from ripple/fix-precision-rounding
Fix to_human precision rounding
2014-11-26 18:31:29 -08:00
wltsmrz
e371cc2c3c Fix to_human precision rounding 2014-11-26 11:32:15 -08:00
Geert Weening
ccf218c8f0 Merge pull request #214 from ripple/fix-fractional-drops
Fix fractional drops in funded taker_pays setter
2014-11-26 09:16:55 -08:00
wltsmrz
0d7fc0a573 Fix fractional drops in funded taker_pays setter 2014-11-25 21:10:57 -08:00
Geert Weening
74cacd5209 [DOC] update offer example 2014-11-19 18:04:45 -08:00
Geert Weening
bb79cf2a87 [TASK] bump version to 0.9.3 2014-11-19 12:09:18 -08:00
Geert Weening
28451df1a8 Merge branch 'develop' into release 2014-11-19 11:32:19 -08:00
Geert Weening
38e288f62a [TASK] bump version to 0.9.2-rc3 2014-11-19 11:31:19 -08:00
Geert Weening
905f908450 [DOC] update release notes 2014-11-19 11:31:19 -08:00
wltsmrz
672171fd0c Merge pull request #211 from jks-liu/fix-link
Fix link in README
2014-11-19 05:24:58 -08:00
Jks Liu
520660ecbc Fix link in README 2014-11-19 16:42:25 +08:00
Geert Weening
06acb5faf2 [TASK] bump version to 0.9.2-rc3 2014-11-18 11:58:41 -08:00
Geert Weening
d43fa03f05 [DOC] update release notes 2014-11-18 11:57:49 -08:00
Geert Weening
baed1aaf92 Merge branch 'release' into develop 2014-11-18 11:54:17 -08:00
wltsmrz
cc229e803c Merge pull request #210 from geertweening/fix/max_fee
[TASK] change default `max_fee` for Remote to 1 XRP
2014-11-18 11:48:37 -08:00
Geert Weening
d6b1728c23 [TASK] change default max_fee for Remote to 1 XRP 2014-11-18 10:47:28 -08:00
wltsmrz
bc5dcc359c Merge pull request #209 from ximinez/ledger_accept
Request ledger_accept returns the Request, not Remote
2014-11-17 12:08:38 -08:00
Edward Hennis
ced07e1d6b Request ledger_accept returns the Request, not Remote 2014-11-17 15:02:45 -05:00
Geert Weening
cffffd9591 [TASK] bump version to 0.9.3-rc2 2014-11-14 10:24:44 -08:00
Geert Weening
b8766e263f [DOC] update release notes 2014-11-14 10:24:00 -08:00
Geert Weening
fc426d5764 Merge branch 'release' into develop 2014-11-14 10:16:34 -08:00
Geert Weening
056d2381cd Merge pull request #208 from ripple/relocate-presubmit
Relocate presubmit emission to immediately before transaction submit
2014-11-14 10:14:02 -08:00
Geert Weening
2932a0ec5f [DOC] add doc that accountRequest throws
if a marker is provided, but no ledger_index or ledger_hash
2014-11-14 10:11:26 -08:00
Geert Weening
d3d85a3fcf [DOC] add doc that accountRequest throws
if a marker is provided, but no ledger_index or ledger_hash
2014-11-14 10:11:13 -08:00
wltsmrz
7a1feaa897 Relocate presubmit emission to immediately before transaction submit 2014-11-13 21:44:20 -08:00
Geert Weening
5f3cf72cc6 Merge pull request #207 from shekenahglory/develop
[TASK] binformat: update fields to match rippled
2014-11-13 14:13:52 -08:00
Matthew Fettig
cae980788e binformat: update fields to match rippled 2014-11-13 11:14:46 -08:00
Geert Weening
df763b8765 Merge pull request #205 from ripple/core-build
Add core build
2014-11-12 10:51:45 -08:00
wltsmrz
365085809e Add note on restricted browser builds 2014-11-12 03:04:51 -08:00
Geert Weening
3ee7998261 [TASK] bump version to 0.9.3-rc1 2014-11-11 17:52:30 -08:00
Geert Weening
6fb9ed8312 [DOC] update release notes 2014-11-11 17:52:13 -08:00
Geert Weening
89f79c35f5 Merge pull request #206 from geertweening/fix/tec_wait_for_validated
[TASK] wait for validation before returning tec error
2014-11-11 17:49:38 -08:00
Geert Weening
6bdd4b2670 [TASK] wait for validation before returning tec error 2014-11-11 16:54:25 -08:00
Geert Weening
acd79d19e2 [TASK] bump version to 0.9.2 2014-11-11 12:14:04 -08:00
Geert Weening
674d4a957d [TASK] bump version to 0.9.2-rc6 2014-11-11 11:51:54 -08:00
Geert Weening
bdbf264771 [FIX] support string '0' being interpreted as XRP 2014-11-11 11:51:12 -08:00
wltsmrz
8f17873da2 Remove server._computeFee(Transaction), require fee units argument 2014-11-11 06:24:34 -08:00
wltsmrz
b0cac776ee Throw an error when trying to use unavailable class in WebPack build 2014-11-11 06:23:37 -08:00
wltsmrz
625dba4d85 Add build-core gulp task 2014-11-11 05:30:50 -08:00
Geert Weening
261b72d0fc [DOC] update API reference 2014-11-10 14:32:35 -08:00
Geert Weening
b5b167ef6d [DOC] update README and GUIDES
to match current API's
2014-11-10 10:14:11 -08:00
Geert Weening
66d21b24cd [TASK] bump ripple-lib to 0.9.2-rc5 2014-11-07 18:22:23 -08:00
Geert Weening
5a084ea3cc [TEST] fix broken tests
as a result of updating account_info request in account.js
2014-11-07 18:22:05 -08:00
Geert Weening
486944fa4c [FIX] request account info in account.js 2014-11-07 18:04:04 -08:00
Geert Weening
b63a76d298 [TASK] bump version to 0.9.2-rc4 2014-11-07 17:45:17 -08:00
Geert Weening
31045039c0 [DOC] update release notes 2014-11-07 17:44:30 -08:00
Geert Weening
6f5d1104aa [TASK] change accountRequest method signature
`(type, options, callback)` where the last argument will be considered for callback if it's a function
2014-11-07 17:08:01 -08:00
Geert Weening
3c9660203b Merge pull request #203 from geertweening/feature/marker_valid_ledger
[FEATURE] check for valid ledger when using marker
2014-11-07 12:57:57 -08:00
Geert Weening
29e1423f84 [FEATURE] check for valid ledger when using marker
when using a marker on an account request, a valid ledger_index or ledger_hash is required, otherwise the results can't be guaranteed to be reliable

consolidated test values for addresses, indexes and hashes
2014-11-07 11:18:24 -08:00
Geert Weening
e42e67e259 [TASK] consolidate accountRequest and pagingAccountRequest
update `requestAccountCurrencies` and  `requestAccountInfo`
2014-11-07 09:52:17 -08:00
Geert Weening
ed018282c4 [TASK] remove clone in favor of copy and update
see 'object' case in `parse_json`
2014-11-06 22:58:04 -08:00
Geert Weening
fbe015758c [TASK] bump version to 0.9.2-rc3 2014-11-06 21:48:01 -08:00
Geert Weening
7e24a81764 [DOC] update release notes 2014-11-06 21:47:44 -08:00
Geert Weening
9ab77e90fe Merge pull request #202 from geertweening/feature/paging_account_request
[FEATURE] add paging account request
2014-11-06 21:41:44 -08:00
Geert Weening
ae3ed699db Merge pull request #201 from geertweening/fix/from_human_full_name_native
[FIX] from_human 'XRP' with full name
2014-11-06 21:41:30 -08:00
Geert Weening
0c22a9753e Merge pull request #199 from geertweening/fix/amount_cap
[FIX] cap IOU Amounts to their max and min value
2014-11-06 21:41:08 -08:00
Geert Weening
a447f6b723 [TEST] add missing testcases for interest bearing currencies
- `get_interest_at` by providing a Date object
- `get_interest_at` for a Currency without interest
2014-11-06 21:38:00 -08:00
Geert Weening
a8ef614b81 [FIX] Currency constructor with Currency object
clone the given Currency instance in `parse_json`
2014-11-06 21:36:57 -08:00
Geert Weening
9025e8bfa8 [FIX] get interest for currency without interest
- `get_interest_at` for a currency without interest would not hit the intended check since the function was used as variable instead of calling the method
2014-11-06 21:35:41 -08:00
Geert Weening
722f4e175d [FEATURE] add paging account request
some requests return results that can be paged through, e.g. `account_lines`
use `limit` and `marker` options to specify results per response and position

change `requestAccountLines` and `requestAccountOffers` to use the `pagingAccountRequest`
2014-11-06 18:40:45 -08:00
Geert Weening
1ad6e5a15f [FIX] from_human 'XRP' with full name
from_human 'XRP - Ripples' should result in native XRP
2014-11-05 16:54:45 -08:00
Geert Weening
3554572db7 [TASK] bump version to 0.9.2-rc2 2014-11-05 16:32:37 -08:00
Geert Weening
f1abff962f Merge pull request #200 from boxbag/fix-max-fee
[FIX] fix test and do not set tx_json.Fee in maxFee method
2014-11-05 16:07:01 -08:00
Geert Weening
f05941fbc4 [FIX] cap IOU Amounts to their max and min value
respect rippled's limits
2014-11-05 16:04:02 -08:00
Bo Chen
237c46d5a0 [FIX] fix test and do not set tx_json.Fee in maxFee method 2014-11-05 15:59:57 -08:00
Geert Weening
76cfb69d9f [TASK] bump version to 0.9.2-rc1 2014-11-05 14:38:47 -08:00
Geert Weening
7610df0fbb [DOC] update release notes 2014-11-05 14:38:35 -08:00
Geert Weening
8bc935aa62 Merge pull request #198 from boxbag/transaction-max-fee
[FEATURE] set max fee the submitter of a transaction is willing to pay
2014-11-05 09:27:26 -08:00
Bo Chen
24587fab9c [FEATURE] set max fee the submitter of a transaction is willing to pay 2014-11-04 14:13:42 -08:00
Geert Weening
0248475473 [DOC] update description in package.json 2014-10-31 18:52:09 -07:00
Geert Weening
d2fa5c4b12 [TASK] bump version to 0.9.1 2014-10-30 10:01:16 -07:00
Geert Weening
c60c0cb6e0 [TASK] bump version to 0.9.1-rc3 2014-10-28 17:35:29 -07:00
Geert Weening
cdf1112666 [FIX] ledgerSelect setting both ledger_index and ledger_hash 2014-10-28 17:33:55 -07:00
Geert Weening
d861bb2e34 [TASK] bump version to 0.9.1-rc2 2014-10-27 17:32:37 -07:00
Geert Weening
006849a3d5 [DOC] update release notes 2014-10-27 17:32:18 -07:00
wltsmrz
a3c1d06eba Change initial account transaction sequence to 1 2014-10-27 17:29:51 -07:00
Geert Weening
4bd1e7a2bc [TASK] bump version to 0.9.1-rc1 2014-10-27 16:40:46 -07:00
Geert Weening
68643f3118 [DOC] update release notes 2014-10-27 16:37:14 -07:00
Geert Weening
560dfc8ae6 Merge branch 'release' into develop 2014-10-27 16:27:52 -07:00
Geert Weening
13685d03e1 Merge pull request #195 from ripple/use-ledgerselect
Switch account requests to use ledgerSelect rather than ledgerChoose
2014-10-27 16:07:40 -07:00
wltsmrz
278df9025a Switch account requests to use ledgerSelect rather than ledgerChoose 2014-10-27 15:35:07 -07:00
Geert Weening
cb608406f8 Merge pull request #191 from geertweening/fix/undefined_remote
[FIX] transaction without explicit remote
2014-10-27 12:09:59 -07:00
Geert Weening
f4a55d03d3 Merge pull request #192 from geertweening/fix/amount_precision
[FIX] confusion between precision and min_precision
2014-10-26 19:21:09 -04:00
Geert Weening
d3b6b8127c [FIX] transaction without explicit remote
remote was instantiated as an object and checks through the class for `this.remote` would pass and cause 
unintended behavior

e.g. `.complete()` would view an undefined remote as untrusted and not allow local signing
e.g. calling `_computeFee()` with an undefined remote would crash ripple-lib
2014-10-26 19:09:04 -04:00
wltsmrz
bc1f9f8a28 Fix account root request ledger argument #121 2014-10-26 15:39:17 -07:00
wltsmrz
9a5c9aea75 Merge pull request #193 from professorhantzen/patch-1
[FIX] correct usage example with surrounding apostrophes
2014-10-24 18:12:32 -07:00
professorhantzen
f1004c6db2 Correct usage example with surrounding apostrophes 2014-10-25 13:14:07 +13:00
27 changed files with 1340 additions and 560 deletions

View File

@@ -68,45 +68,6 @@ gulp.task('build', [ 'concat-sjcl' ], function(callback) {
}, callback); }, callback);
}); });
gulp.task('bower-build', [ 'build' ], function(callback) {
return gulp.src([ './build/ripple-', '.js' ].join(pkg.version))
.pipe(rename('ripple.js'))
.pipe(gulp.dest('./dist/'));
});
gulp.task('bower-build-min', [ 'build-min' ], function(callback) {
return gulp.src([ './build/ripple-', '-min.js' ].join(pkg.version))
.pipe(rename('ripple-min.js'))
.pipe(gulp.dest('./dist/'));
});
gulp.task('bower-build-debug', [ 'build-debug' ], function(callback) {
return gulp.src([ './build/ripple-', '-debug.js' ].join(pkg.version))
.pipe(rename('ripple-debug.js'))
.pipe(gulp.dest('./dist/'));
});
gulp.task('bower-version', function() {
gulp.src('./dist/bower.json')
.pipe(bump({version: pkg.version}))
.pipe(gulp.dest('./dist/'));
});
gulp.task('version-bump', function() {
if (!argv.type) {
throw new Error("No type found, pass it in using the --type argument");
}
gulp.src('./package.json')
.pipe(bump({type:argv.type}))
.pipe(gulp.dest('./'));
});
gulp.task('version-beta', function() {
gulp.src('./package.json')
.pipe(bump({version: pkg.version+'-beta'}))
.pipe(gulp.dest('./'));
});
gulp.task('build-min', [ 'build' ], function(callback) { gulp.task('build-min', [ 'build' ], function(callback) {
return gulp.src([ './build/ripple-', '.js' ].join(pkg.version)) return gulp.src([ './build/ripple-', '.js' ].join(pkg.version))
.pipe(uglify()) .pipe(uglify())
@@ -128,6 +89,66 @@ gulp.task('build-debug', [ 'concat-sjcl' ], function(callback) {
}, callback); }, callback);
}); });
/**
* Generate a WebPack external for a given unavailable module which replaces
* that module's constructor with an error-thrower
*/
function buildUseError(cons) {
return 'var {<CONS>:function(){throw new Error("Class is unavailable in this build: <CONS>")}}'
.replace(new RegExp('<CONS>', 'g'), cons);
};
gulp.task('build-core', [ 'concat-sjcl' ], function(callback) {
webpack({
entry: [
'./src/js/ripple/remote.js'
],
externals: [
{
'./transaction': buildUseError('Transaction'),
'./orderbook': buildUseError('OrderBook'),
'./account': buildUseError('Account'),
'./serializedobject': buildUseError('SerializedObject')
}
],
output: {
library: 'ripple',
path: './build/',
filename: [ 'ripple-', '-core.js' ].join(pkg.version)
},
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
}, callback);
});
gulp.task('bower-build', [ 'build' ], function(callback) {
return gulp.src([ './build/ripple-', '.js' ].join(pkg.version))
.pipe(rename('ripple.js'))
.pipe(gulp.dest('./dist/'));
});
gulp.task('bower-build-min', [ 'build-min' ], function(callback) {
return gulp.src([ './build/ripple-', '-min.js' ].join(pkg.version))
.pipe(rename('ripple-min.js'))
.pipe(gulp.dest('./dist/'));
});
gulp.task('bower-build-debug', [ 'build-debug' ], function(callback) {
return gulp.src([ './build/ripple-', '-debug.js' ].join(pkg.version))
.pipe(rename('ripple-debug.js'))
.pipe(gulp.dest('./dist/'));
});
gulp.task('bower-version', function() {
gulp.src('./dist/bower.json')
.pipe(bump({ version: pkg.version }))
.pipe(gulp.dest('./dist/'));
});
gulp.task('bower', ['bower-build', 'bower-build-min', 'bower-build-debug', 'bower-version']);
gulp.task('lint', function() { gulp.task('lint', function() {
gulp.src('src/js/ripple/*.js') gulp.src('src/js/ripple/*.js')
.pipe(jshint()) .pipe(jshint())
@@ -158,6 +179,20 @@ gulp.task('watch', function() {
gulp.watch('src/js/ripple/*', [ 'build-debug' ]); gulp.watch('src/js/ripple/*', [ 'build-debug' ]);
}); });
gulp.task('default', [ 'concat-sjcl', 'build', 'build-debug', 'build-min' ]); gulp.task('version-bump', function() {
if (!argv.type) {
throw new Error("No type found, pass it in using the --type argument");
}
gulp.task('bower', ['bower-build', 'bower-build-min', 'bower-build-debug', 'bower-version']); gulp.src('./package.json')
.pipe(bump({ type: argv.type }))
.pipe(gulp.dest('./'));
});
gulp.task('version-beta', function() {
gulp.src('./package.json')
.pipe(bump({ version: pkg.version + '-beta' }))
.pipe(gulp.dest('./'));
});
gulp.task('default', [ 'concat-sjcl', 'build', 'build-debug', 'build-min' ]);

View File

@@ -1,3 +1,72 @@
##0.9.4
+ [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)
+ [Add a "core" browser build of ripple-lib which has a subset of features and smaller file size](https://github.com/ripple/ripple-lib/pull/205)
+ [Update binformat with missing fields from rippled](https://github.com/ripple/ripple-lib/commit/cae980788efb00191bfd0988ed836d60cdf7a9a2)
+ [Wait for transaction validation before returning `tec` error](https://github.com/ripple/ripple-lib/commit/6bdd4b2670906588852fc4dda457607b4aac08e4)
+ [Change default `max_fee` on `Remote` to `1 XRP`](https://github.com/ripple/ripple-lib/commit/d6b1728c23ff85c3cc791bed6982a750641fd95f)
+ [Fix: Request ledger_accept should return the Remote](https://github.com/ripple/ripple-lib/pull/209)
##0.9.2
+ [**Breaking change**: Change accountRequest method signature](https://github.com/ripple/ripple-lib/commit/6f5d1104aa3eb440c518ec4f39e264fdce15fa15)
+ [Add paging behavior for account requests, `account_lines` and `account_offers`](https://github.com/ripple/ripple-lib/commit/722f4e175dbbf378e51b49142d0285f87acb22d7)
+ [Add max_fee setter to transactions to set max fee the submitter is willing to pay] (https://github.com/ripple/ripple-lib/commit/24587fab9c8ad3840d7aa345a7037b48839e09d7)
+ [Fix: cap IOU Amounts to their max and min value] (https://github.com/ripple/ripple-lib/commit/f05941fbc46fdb7c6fe7ad72927af02d527ffeed)
Example on how to use paging with `account_offers`:
```
// A valid `ledger_index` or `ledger_hash` is required to provide a reliable result.
// Results can change between ledger closes, so the provided ledger will be used as base.
var options = {
account: < rippleAccount >,
limit: < Number between 10 and 400 >,
ledger: < valid ledger_index or ledger_hash >
}
// The `marker` comes back in an account request if there are more results than are returned
// in the current response. The amount of results per response are determined by the `limit`.
if (marker) {
options.marker = < marker >;
}
var request = remote.requestAccountOffers(options);
```
[Full working example](https://github.com/geertweening/ripple-lib-scripts/blob/master/account_offers_paging.js)
##0.9.1
+ Switch account requests to use ledgerSelect rather than ledgerChoose ([278df90](https://github.com/ripple/ripple-lib/commit/278df9025a20228de22379a53c76ca12d40fa591))
+ **Deprecated** setting `ident` and `account_index` on account requests ([278df90](https://github.com/ripple/ripple-lib/commit/278df9025a20228de22379a53c76ca12d40fa591))
+ Change initial account transaction sequence to 1 ([a3c1d06](https://github.com/ripple/ripple-lib/commit/a3c1d06eba883dc84fe2bfe700e4309795c84cac))
+ Fix: instance transaction withoute remote ([d3b6b81](https://github.com/ripple/ripple-lib/commit/d3b6b8127c7b01e416b400c25abf1719bdd008ca))
+ Fix: account root request ledger argument ([bc1f9f8](https://github.com/ripple/ripple-lib/commit/bc1f9f8a286b187d36ebaf552694e31e73742293))
+ Fix: rsign.js local signing and example ([d3b6b81](https://github.com/ripple/ripple-lib/commit/d3b6b8127c7b01e416b400c25abf1719bdd008ca) and [f1004c6](https://github.com/ripple/ripple-lib/commit/f1004c6db2a0ce59bbabbb8f2b355a9fd9995fd8))
##0.9.0 ##0.9.0
+ Add routes to the vault client for KYC attestations ([ed2da574](https://github.com/ripple/ripple-lib/commit/ed2da57475acf5e9d2cf3373858f4274832bd83f)) + Add routes to the vault client for KYC attestations ([ed2da574](https://github.com/ripple/ripple-lib/commit/ed2da57475acf5e9d2cf3373858f4274832bd83f))

View File

@@ -1,6 +1,6 @@
#ripple-lib #ripple-lib
JavaScript client for [rippled](https://github.com/ripple/rippled) A JavaScript API for interacting with Ripple in Node.js and the browser
[![Build Status](https://travis-ci.org/ripple/ripple-lib.svg?branch=develop)](https://travis-ci.org/ripple/ripple-lib) [![Coverage Status](https://coveralls.io/repos/ripple/ripple-lib/badge.png?branch=develop)](https://coveralls.io/r/ripple/ripple-lib?branch=develop) [![Build Status](https://travis-ci.org/ripple/ripple-lib.svg?branch=develop)](https://travis-ci.org/ripple/ripple-lib) [![Coverage Status](https://coveralls.io/repos/ripple/ripple-lib/badge.png?branch=develop)](https://coveralls.io/r/ripple/ripple-lib?branch=develop)
@@ -15,9 +15,9 @@ JavaScript client for [rippled](https://github.com/ripple/rippled)
###In this file ###In this file
1. [Installation](README.md#installation) 1. [Installation](#installation)
2. [Quickstart](README.md#quickstart) 2. [Quick start](#quick-start)
3. [Running tests](https://github.com/ripple/ripple-lib#running-tests) 3. [Running tests](#running-tests)
###Additional documentation ###Additional documentation
@@ -47,7 +47,9 @@ JavaScript client for [rippled](https://github.com/ripple/rippled)
See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for additional bower instructions See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for additional bower instructions
**Building ripple-lib from github** **Building ripple-lib for browser environments**
ripple-lib uses Gulp to generate browser builds. These steps will generate minified and non-minified builds of ripple-lib in the `build/` directory.
``` ```
$ git clone https://github.com/ripple/ripple-lib $ git clone https://github.com/ripple/ripple-lib
@@ -55,9 +57,13 @@ See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for addition
$ npm run build $ npm run build
``` ```
Then use the minified `build/ripple-*-min.js` **Restricted browser builds**
##Quickstart You may generate browser builds that contain a subset of features. To do this, run `./node_modules/.bin/gulp build-<name>`
+ `build-core` Contains the functionality to make requests and listen for events such as `ledgerClose`. Only `ripple.Remote` is currently exposed. Advanced features like transaction submission and orderbook tracking are excluded from this build.
##Quick start
`Remote.js` ([remote.js](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/remote.js)) is the point of entry for interacting with rippled `Remote.js` ([remote.js](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/remote.js)) is the point of entry for interacting with rippled
@@ -75,8 +81,8 @@ var remote = new Remote({
remote.connect(function() { remote.connect(function() {
/* remote connected */ /* remote connected */
remote.request('server_info', function(err, info) { remote.requestServerInfo(function(err, info) {
// process err and info
}); });
}); });
``` ```
@@ -87,7 +93,7 @@ remote.connect(function() {
2. `cd` into the repository and install dependencies with `npm install` 2. `cd` into the repository and install dependencies with `npm install`
3. `npm test` or `node_modules/.bin/mocha test/*-test.js` 3. `npm test`
**Generating code coverage** **Generating code coverage**

View File

@@ -56,7 +56,7 @@ function ready() {
function print_usage() { function print_usage() {
console.log( console.log(
'Usage: rsign.js <secret> <json>\n\n', 'Usage: rsign.js <secret> <json>\n\n',
'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC', 'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC','\''+
JSON.stringify({ JSON.stringify({
TransactionType: 'Payment', TransactionType: 'Payment',
Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi', Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi',
@@ -64,7 +64,7 @@ function print_usage() {
Amount: '200000000', Amount: '200000000',
Fee: '10', Fee: '10',
Sequence: 1 Sequence: 1
}) })+'\''
); );
}; };

View File

@@ -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) 1. [The ripple-lib README](../README.md)
2. [The ripple-lib API Reference](REFERENCE.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 ##Connecting to the Ripple network
1. [Get ripple-lib](README.md#getting-ripple-lib) 1. [Get ripple-lib](README.md#getting-ripple-lib)
@@ -40,9 +29,19 @@ This file provides step-by-step walkthroughs for some of the most common usages
``` ```
3. Create a new `Remote` and connect to the network: 3. Create a new `Remote` and connect to the network:
```js ```js
var options = {
trace : false,
trusted: true,
local_signing: true,
servers: [
{ host: 's-west.ripple.com', port: 443, secure: true }
]
}
var remote = new Remote({options}); var remote = new Remote({options});
remote.connect(function() { remote.connect(function(err, res) {
/* remote connected, use some remote functions here */ /* remote connected, use some remote functions here */
}); });
``` ```
@@ -50,18 +49,45 @@ 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. 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 ##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. A `Request` is an `EventEmitter` so you can listen for success or failure events -- or, instead, you can provide a callback.
Here is an example, using [request_server_info](https://ripple.com/wiki/JSON_Messages#server_info). Here is an example, using [requestServerInfo](https://ripple.com/wiki/JSON_Messages#server_info).
+ Constructing a `Request` with event listeners + Constructing a `Request` with event listeners
```js ```js
var request = remote.request('server_info'); var request = remote.requestServerInfo();
request.on('success', function onSuccess(res) { request.on('success', function onSuccess(res) {
//handle success //handle success
@@ -102,23 +128,43 @@ See the [wiki](https://ripple.com/wiki/JSON_Messages#subscribe) for details on s
var remote = new Remote({options}); var remote = new Remote({options});
remote.connect(function() { remote.connect(function() {
var request = remote.request('subscribe'); var remote = new Remote({
// see the API Reference for available options
request.addStream('ledger'); //remote will emit `ledger_closed` servers: [ 'wss://s1.ripple.com:443' ]
request.addStream('transactions'); //remote will emit `transaction`
request.on('ledger_closed', function onLedgerClosed(ledgerData) {
//handle ledger
}); });
request.on('transaction', function onTransacstion(transaction) { remote.connect(function() {
//handle transaction console.log('Remote connected');
});
request.request(function(err) { var streams = [
if (err) { 'ledger',
} else { '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();
}); });
}); });
``` ```
@@ -130,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. 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 ```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 Remote = require('ripple-lib').Remote;
var Amount = require('ripple-lib').Amount; var Amount = require('ripple-lib').Amount;
@@ -149,8 +195,8 @@ remote.connect(function() {
remote.setSecret(MY_ADDRESS, MY_SECRET); remote.setSecret(MY_ADDRESS, MY_SECRET);
var transaction = remote.createTransaction('Payment', { var transaction = remote.createTransaction('Payment', {
account: MY_ADDRESS, account: MY_ADDRESS,
destination: RECIPIENT, destination: RECIPIENT,
amount: AMOUNT amount: AMOUNT
}); });
@@ -171,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. 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 ```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 Remote = require('ripple-lib').Remote;
var Amount = require('ripple-lib').Amount; var Amount = require('ripple-lib').Amount;
@@ -195,7 +241,7 @@ remote.connect(function() {
var transaction = remote.createTransaction('OfferCreate', { var transaction = remote.createTransaction('OfferCreate', {
account: MY_ADDRESS, account: MY_ADDRESS,
taker_pays: '1', taker_pays: '100',
taker_gets: '1/USD/' + GATEWAY taker_gets: '1/USD/' + GATEWAY
}); });

View File

@@ -18,7 +18,7 @@ __(More examples coming soon!)__
###Also see: ###Also see:
1. [The ripple-lib README](../README.md) 1. [The ripple-lib README](../README.md)
2. [The ripple-lib GUIDES](GUIDES.md) 2. [The ripple-lib GUIDES](GUIDES.md)a
#Remote options #Remote options
@@ -61,14 +61,34 @@ or
#Request constructor functions #Request constructor functions
Some requests have helper methods to construct the requests object and set properties on the message object. These will often be the more used requests and the helper methods is the preferred way of constructing these requests.
Other request can still be made, but the type will have to be passed in directly to request constructor. See examples below.
If the method is camelCased and starts with `request`, it's a helper method that wraps the request constructor.
##Server requests ##Server requests
**[server_info([callback])](https://ripple.com/wiki/JSON_Messages#server_info)** **[requestServerInfo([callback])](https://ripple.com/wiki/JSON_Messages#server_info)**
Returns information about the state of the server. If you are connected to multiple servers and want to select by a particular host, use `request.setServer`. Example: Returns information about the state of the server. If you are connected to multiple servers and want to select by a particular host, use `request.setServer`. Example:
```js ```js
var request = remote.request('server_info'); var request = remote.requestServerInfo();
request.setServer('wss://s1.ripple.com');
request.request(function(err, res) {
});
```
**[requestPeers([callback])](https://ripple.com/wiki/JSON_Messages#peers)**
**[requestConnect(ip, port, [callback])](https://ripple.com/wiki/JSON_Messages#connect)**
**[unl_list([callback])](https://ripple.com/wiki/JSON_Messages#unl_list)**
```js
var request = remote.request('un_list');
request.setServer('wss://s1.ripple.com'); request.setServer('wss://s1.ripple.com');
@@ -77,42 +97,48 @@ request.request(function(err, res) {
}); });
``` ```
**[unl_list([callback])](https://ripple.com/wiki/JSON_Messages#unl_list)**
**[unl_add(addr, comment, [callback])](https://ripple.com/wiki/JSON_Messages#unl_add)** **[unl_add(addr, comment, [callback])](https://ripple.com/wiki/JSON_Messages#unl_add)**
**[unl_delete(node, [callback])](https://ripple.com/wiki/JSON_Messages#unl_delete)** **[unl_delete(node, [callback])](https://ripple.com/wiki/JSON_Messages#unl_delete)**
**[requestPeers([callback])](https://ripple.com/wiki/JSON_Messages#peers)**
**[connect(ip, port, [callback])](https://ripple.com/wiki/JSON_Messages#connect)**
##Ledger requests ##Ledger requests
**[ledger(ledger, [opts], [callback])](https://ripple.com/wiki/JSON_Messages#ledger)** **[requestLedger([opts], [callback])](https://ripple.com/wiki/JSON_Messages#ledger)**
**ledger_header([callback])** **[requestLedgerHeader([callback])](https://wiki.ripple.com/JSON_Messages#ledger_data)**
**[ledger_current([callback])](https://ripple.com/wiki/JSON_Messages#ledger_current)** **[requestLedgerCurrent([callback])](https://ripple.com/wiki/JSON_Messages#ledger_current)**
**[ledger_entry(type, [callback])](https://ripple.com/wiki/JSON_Messages#ledger_entry)** **[requestLedgerEntry(type, [callback])](https://ripple.com/wiki/JSON_Messages#ledger_entry)**
**[subscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#subscribe)** **[requestSubscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#subscribe)**
Start receiving selected streams from the server. Start receiving selected streams from the server.
**[unsubscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#unsubscribe)** **[requestUnsubscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#unsubscribe)**
Stop receiving selected streams from the server. Stop receiving selected streams from the server.
##Account requests ##Account requests
**[account_info(account, [callback])](https://ripple.com/wiki/JSON_Messages#account_info)** **[requestAccountInfo(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_info)**
Return information about the specified account. Return information about the specified account.
``` ```
var options = {
account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
ledger: 'validated'
};
var request = remote.requestAccountInfo(options, function(err, info) {
/* process info */
});
// response
{ {
ledger_current_index: <number>, ledger_current_index: <number>,
account_data: { account_data: {
@@ -129,13 +155,35 @@ Return information about the specified account.
} }
``` ```
**[account_lines(accountID, [account_index], [ledger], [callback])](https://ripple.com/wiki/JSON_Messages#account_lines)** **[requestAccountLines(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_lines)**
**[account_offers(accountID, [account_index], [ledger], [callback])](https://ripple.com/wiki/JSON_Messages#account_offers)** **[requestAccountOffers(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_offers)**
Return the specified account's outstanding offers. Return the specified account's outstanding offers.
**[account_tx(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_tx)** Requests for both `account_lines` and `account_offers` support paging. The amount of results per response can be configured with the `limit`.
The responses can be paged through by using the `marker`.
```
// A valid `ledger_index` or `ledger_hash` is required to provide a reliable result.
// Results can change between ledger closes, so the provided ledger will be used as base.
var options = {
account: < rippleAccount >,
limit: < Number between 10 and 400 >,
ledger: < valid ledger_index or ledger_hash >
}
// The `marker` comes back in an account request if there are more results than are returned
// in the current response. The amount of results per response are determined by the `limit`.
if (marker) {
options.marker = < marker >;
}
var request = remote.requestAccountOffers(options);
```
**[requestAccountTransactions(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_tx)**
Fetch a list of transactions that applied to this account. Fetch a list of transactions that applied to this account.
@@ -153,42 +201,46 @@ Options:
+ `fwd_marker` + `fwd_marker`
+ `rev_marker` + `rev_marker`
**[wallet_accounts(seed, [callback])](https://ripple.com/wiki/JSON_Messages#wallet_accounts)** **[requestWalletAccounts(seed, [callback])](https://ripple.com/wiki/JSON_Messages#wallet_accounts)**
Return a list of accounts for a wallet. *Requires trusted remote* Return a list of accounts for a wallet. *Requires trusted remote*
**account_balance(account, [ledger], [callback])** **requestAccountBalance(account, [ledger], [callback])**
Get the balance for an account. Returns an [Amount](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/amount.js) object. Get the balance for an account. Returns an [Amount](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/amount.js) object.
**account_flags(account, [ledger], [callback])** **requestAccountFlags(account, [ledger], [callback])**
Return the flags for an account. Return the flags for an account.
**owner_count(account, [ledger], [callback])** **requestOwnerCount(account, [ledger], [callback])**
Return the owner count for an account. Return the owner count for an account.
**ripple_balance(account, issuer, currency, [ledger], [callback])** **requestRippleBalance(account, issuer, currency, [ledger], [callback])**
Return a request to get a ripple balance Return a request to get a ripple balance
##Orderbook requests ##Orderbook requests
**[book_offers(options, [callback])](https://ripple.com/wiki/JSON_Messages#book_offers)** **[requestBookOffers(options, [callback])](https://ripple.com/wiki/JSON_Messages#book_offers)**
Return the offers for an order book, also called a *snapshot* Return the offers for an order book, also called a *snapshot*
```js ```js
var request = remote.request('book_offers', { var options = {
taker_gets: { gets: {
'currency':'XRP' issuer: < issuer >,
currency: < currency >
}, },
taker_pays: { pays: {
'currency':'USD', issuer: < issuer >,
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' currency: < currency >
} },
}); limit: < limit >
};
var request = remote.requestBookOffers(options);
request.request(function(err, offers) { request.request(function(err, offers) {
//handle offers //handle offers
@@ -197,23 +249,23 @@ request.request(function(err, offers) {
##Transaction requests ##Transaction requests
**[transaction_entry(hash, [ledger_hash], [callback])](https://ripple.com/wiki/JSON_Messages#transaction_entry)** **[requestTransactionEntry(hash, [ledger_hash], [callback])](https://ripple.com/wiki/JSON_Messages#transaction_entry)**
Searches a particular ledger for a transaction hash. Default ledger is the open ledger. Searches a particular ledger for a transaction hash. Default ledger is the open ledger.
**[tx(hash, [callback])](https://ripple.com/wiki/JSON_Messages#tx)** **[requestTransaction(hash, [callback])](https://ripple.com/wiki/JSON_Messages#tx)**
Searches ledger history for validated transaction hashes. Searches ledger history for validated transaction hashes.
**[sign(secret, tx_json, [callback])](https://ripple.com/wiki/JSON_Messages#sign)** **[requestSign(secret, tx_json, [callback])](https://ripple.com/wiki/JSON_Messages#sign)**
Sign a transaction. *Requires trusted remote* Sign a transaction. *Requires trusted remote*
**[submit([callback])](https://ripple.com/wiki/JSON_Messages#submit)** **[requestSubmit([callback])](https://ripple.com/wiki/JSON_Messages#submit)**
Submit a transaction to the network. This command is used internally to submit transactions with a greater degree of reliability. See [Submitting a payment to the network](GUIDES.md#3-submitting-a-payment-to-the-network) for details. Submit a transaction to the network. This command is used internally to submit transactions with a greater degree of reliability. See [Submitting a payment to the network](GUIDES.md#3-submitting-a-payment-to-the-network) for details.
**[ripple_path_find(src_account, dst_account, dst_amount, src_currencies, [callback])](https://ripple.com/wiki/JSON_Messages#path_find)** **[pathFind(src_account, dst_account, dst_amount, src_currencies)](https://ripple.com/wiki/JSON_Messages#path_find)**
#Transaction constructors #Transaction constructors

2
npm-shrinkwrap.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "ripple-lib", "name": "ripple-lib",
"version": "0.9.0-rc5", "version": "0.9.4",
"dependencies": { "dependencies": {
"async": { "async": {
"version": "0.8.0", "version": "0.8.0",

View File

@@ -1,7 +1,7 @@
{ {
"name": "ripple-lib", "name": "ripple-lib",
"version": "0.9.0", "version": "0.9.4",
"description": "Ripple JavaScript client library", "description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [ "files": [
"src/js/*", "src/js/*",
"bin/*", "bin/*",

View File

@@ -132,7 +132,7 @@ Account.prototype.isValid = function() {
*/ */
Account.prototype.getInfo = function(callback) { Account.prototype.getInfo = function(callback) {
return this._remote.request_account_info(this._account_id, callback); return this._remote.requestAccountInfo({account: this._account_id}, callback);
}; };
/** /**
@@ -174,8 +174,8 @@ Account.prototype.getNextSequence = function(callback) {
function accountInfo(err, info) { function accountInfo(err, info) {
if (isNotFound(err)) { if (isNotFound(err)) {
// New accounts will start out as sequence zero // New accounts will start out as sequence one
callback(null, 0); callback(null, 1);
} else if (err) { } else if (err) {
callback(err); callback(err);
} else { } else {
@@ -211,7 +211,7 @@ Account.prototype.lines = function(callback) {
} }
} }
this._remote.requestAccountLines(this._account_id, accountLines); this._remote.requestAccountLines({account: this._account_id}, accountLines);
return this; return this;
}; };

View File

@@ -54,7 +54,9 @@ var consts = {
// Maximum possible amount for non-XRP currencies using the maximum mantissa // Maximum possible amount for non-XRP currencies using the maximum mantissa
// with maximum exponent. Corresponds to hex 0xEC6386F26FC0FFFF. // with maximum exponent. Corresponds to hex 0xEC6386F26FC0FFFF.
max_value: '9999999999999999e80' max_value: '9999999999999999e80',
// Minimum possible amount for non-XRP currencies.
min_value: '-1000000000000000e-96'
}; };
// Add constants to Amount class // Add constants to Amount class
@@ -424,6 +426,33 @@ Amount.prototype.invert = function() {
return this.copy()._invert(); return this.copy()._invert();
}; };
/**
* Canonicalize amount value
*
* Mirrors rippled's internal Amount representation
* From https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol/STAmount.h#L31-L40
*
* Internal form:
* 1: If amount is zero, then value is zero and offset is -100
* 2: Otherwise:
* legal offset range is -96 to +80 inclusive
* value range is 10^15 to (10^16 - 1) inclusive
* amount = value * [10 ^ offset]
*
* -------------------
*
* The amount can be epxresses as A x 10^B
* Where:
* - A must be an integer between 10^15 and (10^16)-1 inclusive
* - B must be between -96 and 80 inclusive
*
* This results
* - minumum: 10^15 x 10^-96 -> 10^-81 -> -1e-81
* - maximum: (10^16)-1 x 10^80 -> 9999999999999999e80
*
* @returns {Amount}
* @throws {Error} if offset exceeds legal ranges, meaning the amount value is bigger than supported
*/
Amount.prototype.canonicalize = function() { Amount.prototype.canonicalize = function() {
if (!(this._value instanceof BigInteger)) { if (!(this._value instanceof BigInteger)) {
// NaN. // NaN.
@@ -447,9 +476,8 @@ Amount.prototype.canonicalize = function() {
} }
} }
// XXX Make sure not bigger than supported. Throw if so.
} else if (this.is_zero()) { } else if (this.is_zero()) {
this._offset = -100; this._offset = Amount.cMinOffset;
this._is_negative = false; this._is_negative = false;
} else { } else {
// Normalize mantissa to valid range. // Normalize mantissa to valid range.
@@ -465,6 +493,16 @@ Amount.prototype.canonicalize = function() {
} }
} }
// Make sure not bigger than supported. Throw if so.
if (this.is_negative() && this._offset < Amount.cMinOffset) {
throw new Error('Exceeding min value of ' + Amount.min_value);
}
// Make sure not smaller than supported. Throw if so.
if (!this.is_negative() && this._offset > Amount.cMaxOffset) {
throw new Error('Exceeding max value of ' + Amount.max_value);
}
return this; return this;
}; };
@@ -539,9 +577,7 @@ Amount.prototype.equals = function(d, ignore_issuer) {
return this.equals(Amount.from_json(d)); return this.equals(Amount.from_json(d));
} }
var result = true; var result = !((!this.is_valid() || !d.is_valid())
result = !((!this.is_valid() || !d.is_valid())
|| (this._is_native !== d._is_native) || (this._is_native !== d._is_native)
|| (!this._value.equals(d._value) || this._offset !== d._offset) || (!this._value.equals(d._value) || this._offset !== d._offset)
|| (this._is_negative !== d._is_negative) || (this._is_negative !== d._is_negative)
@@ -1109,25 +1145,21 @@ Amount.prototype.to_human = function(opts) {
fraction_part = fraction_part.replace(/0*$/, ''); fraction_part = fraction_part.replace(/0*$/, '');
if (fraction_part.length || !opts.skip_empty_fraction) { if (fraction_part.length || !opts.skip_empty_fraction) {
// Enforce the maximum number of decimal digits (precision) // Enforce the maximum number of decimal digits (precision)
if (typeof opts.precision === 'number') { 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 (rounded < 1) {
if (fraction_part.charCodeAt(0) >= 53) { fraction_part = rounded.substring(2);
int_part = (Number(int_part) + 1).toString();
}
fraction_part = '';
} else { } else {
var precision = Math.min(opts.precision, fraction_part.length); int_part = (Number(int_part) + 1).toString();
fraction_part = Math.round(fraction_part / Math.pow(10, fraction_part.length - precision)).toString(); fraction_part = '';
}
// because the division above will cut off the leading 0's we have to add them back again while (fraction_part.length < precision) {
// XXX look for a more elegant alternative fraction_part = '0' + fraction_part;
while (fraction_part.length < precision) {
fraction_part = '0' + fraction_part;
}
} }
} }
@@ -1135,7 +1167,7 @@ Amount.prototype.to_human = function(opts) {
if (typeof opts.max_sig_digits === 'number') { if (typeof opts.max_sig_digits === 'number') {
// First, we count the significant digits we have. // First, we count the significant digits we have.
// A zero in the integer part does not count. // 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; var digits = int_is_zero ? 0 : int_part.length;
// Don't count leading zeros in the fractional part if the integer part is // Don't count leading zeros in the fractional part if the integer part is
@@ -1161,6 +1193,7 @@ Amount.prototype.to_human = function(opts) {
// Enforce the minimum number of decimal digits (min_precision) // Enforce the minimum number of decimal digits (min_precision)
if (typeof opts.min_precision === 'number') { if (typeof opts.min_precision === 'number') {
opts.min_precision = Math.max(0, opts.min_precision);
while (fraction_part.length < opts.min_precision) { while (fraction_part.length < opts.min_precision) {
fraction_part += '0'; fraction_part += '0';
} }

View File

@@ -106,7 +106,8 @@ var FIELDS_MAP = exports.fields = {
16: 'BookDirectory', 16: 'BookDirectory',
17: 'InvoiceID', 17: 'InvoiceID',
18: 'Nickname', 18: 'Nickname',
19: 'Feature' 19: 'Amendment',
20: 'TicketID'
}, },
6: { // Amount 6: { // Amount
1: 'Amount', 1: 'Amount',
@@ -135,7 +136,8 @@ var FIELDS_MAP = exports.fields = {
10: 'ExpireCode', 10: 'ExpireCode',
11: 'CreateCode', 11: 'CreateCode',
12: 'MemoType', 12: 'MemoType',
13: 'MemoData' 13: 'MemoData',
14: 'MemoFormat'
}, },
8: { // Account 8: { // Account
1: 'Account', 1: 'Account',
@@ -187,7 +189,7 @@ var FIELDS_MAP = exports.fields = {
19: { // Vector256 19: { // Vector256
1: 'Indexes', 1: 'Indexes',
2: 'Hashes', 2: 'Hashes',
3: 'Features' 3: 'Amendments'
} }
}; };

View File

@@ -53,11 +53,7 @@ Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000';
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/; Currency.prototype.human_RE = /^\s*([a-zA-Z0-9]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
Currency.from_json = function(j, shouldInterpretXrpAsIou) { Currency.from_json = function(j, shouldInterpretXrpAsIou) {
if (j instanceof this) { return (new Currency()).parse_json(j, shouldInterpretXrpAsIou);
return j.clone();
} else {
return (new this()).parse_json(j, shouldInterpretXrpAsIou);
}
}; };
Currency.from_human = function(j, opts) { Currency.from_human = function(j, opts) {
@@ -71,12 +67,9 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
switch (typeof j) { switch (typeof j) {
case 'string': case 'string':
if (!j || /^(0|XRP)$/.test(j)) { // if an empty string is given, fall back to XRP
if (shouldInterpretXrpAsIou) { if (!j || j === '0') {
this.parse_hex(Currency.HEX_CURRENCY_BAD); this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
} else {
this.parse_hex(Currency.HEX_ZERO);
}
break; break;
} }
@@ -86,6 +79,17 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
if (matches) { if (matches) {
var currencyCode = matches[1]; var currencyCode = matches[1];
// for the currency 'XRP' case
// we drop everything else that could have been provided
// e.g. 'XRP - Ripple'
if (!currencyCode || /^(0|XRP)$/.test(currencyCode)) {
this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
// early break, we can't have interest on XRP
break;
}
// the full currency is matched as it is part of the valid currency format, but not stored // the full currency is matched as it is part of the valid currency format, but not stored
// var full_currency = matches[2] || ''; // var full_currency = matches[2] || '';
var interest = matches[3] || ''; var interest = matches[3] || '';
@@ -270,7 +274,7 @@ Currency.prototype.has_interest = function() {
* @returns {number} - interest for provided interval, can be negative for demurred currencies * @returns {number} - interest for provided interval, can be negative for demurred currencies
*/ */
Currency.prototype.get_interest_at = function(referenceDate, decimals) { Currency.prototype.get_interest_at = function(referenceDate, decimals) {
if (!this.has_interest) { if (!this.has_interest()) {
return 0; return 0;
} }

View File

@@ -77,10 +77,15 @@ function OrderBook(remote, getsC, getsI, paysC, paysI, key) {
listenersModified('remove', event); listenersModified('remove', event);
}); });
function updateFundedAmounts(transaction) {
self.updateFundedAmounts(transaction);
};
this._remote.on('transaction', updateFundedAmounts);
this.on('unsubscribe', function() { this.on('unsubscribe', function() {
self.resetCache(); self.resetCache();
self._remote.removeListener('transaction', updateFundedAmounts); self._remote.removeListener('transaction', updateFundedAmounts);
self._remote.removeListener('transaction', updateTransferRate);
}); });
this._remote.once('prepare_subscribe', function() { 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; return this;
}; };
@@ -115,30 +108,15 @@ util.inherits(OrderBook, EventEmitter);
* Events emitted from OrderBook * 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; OrderBook.DEFAULT_TRANSFER_RATE = 1000000000;
/** OrderBook.IOU_SUFFIX = '/000/rrrrrrrrrrrrrrrrrrrrrhoLvTp';
* 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())
);
};
/** /**
* Initialize orderbook. Get orderbook offers and subscribe to transactions * 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? //XXX What now?
}); });
}; };
@@ -308,8 +286,7 @@ OrderBook.prototype.applyTransferRate = function(balance, transferRate) {
return balance; return balance;
} }
var iouSuffix = '/USD/rrrrrrrrrrrrrrrrrrrrBZbvji'; var adjustedBalance = Amount.from_json(balance + OrderBook.IOU_SUFFIX)
var adjustedBalance = Amount.from_json(balance + iouSuffix)
.divide(transferRate) .divide(transferRate)
.multiply(Amount.from_json(OrderBook.DEFAULT_TRANSFER_RATE)) .multiply(Amount.from_json(OrderBook.DEFAULT_TRANSFER_RATE))
.to_json() .to_json()
@@ -321,40 +298,41 @@ OrderBook.prototype.applyTransferRate = function(balance, transferRate) {
/** /**
* Request transfer rate for this orderbook's issuer * Request transfer rate for this orderbook's issuer
* *
* @param [Function] calback * @param {Function} callback
*/ */
OrderBook.prototype.requestTransferRate = function(callback) { OrderBook.prototype.requestTransferRate = function(callback) {
assert.strictEqual(typeof callback, 'function');
var self = this; var self = this;
var issuer = this._issuerGets; var issuer = this._issuerGets;
this.once('transfer_rate', function(rate) {
if (typeof callback === 'function') {
callback(null, rate);
}
});
if (this._currencyGets.is_native()) { if (this._currencyGets.is_native()) {
// Transfer rate is default // Transfer rate is default (native currency)
return this.emit('transfer_rate', OrderBook.DEFAULT_TRANSFER_RATE); callback(null, OrderBook.DEFAULT_TRANSFER_RATE);
return;
} }
if (this._issuerTransferRate) { if (this._issuerTransferRate) {
// Transfer rate has been cached // Transfer rate has already been cached
return this.emit('transfer_rate', this._issuerTransferRate); callback(null, this._issuerTransferRate);
return;
} }
this._remote.requestAccountInfo(issuer, function(err, info) { this._remote.requestAccountInfo({ account: issuer }, function(err, info) {
if (err) { if (err) {
// XXX What now? // XXX What now?
return callback(err); return callback(err);
} }
var transferRate = info.account_data.TransferRate var transferRate = info.account_data.TransferRate;
|| OrderBook.DEFAULT_TRANSFER_RATE;
if (!transferRate) {
transferRate = OrderBook.DEFAULT_TRANSFER_RATE;
}
self._issuerTransferRate = transferRate; self._issuerTransferRate = transferRate;
self.emit('transfer_rate', transferRate); callback(null, transferRate);
}); });
}; };
@@ -380,11 +358,10 @@ OrderBook.prototype.setFundedAmount = function(offer, fundedAmount) {
return offer; return offer;
} }
var iouSuffix = '/' + this._currencyGets.to_json()
+ '/' + this._issuerGets;
offer.is_fully_funded = Amount.from_json( 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; ).compareTo(Amount.from_json(offer.TakerGets)) >= 0;
if (offer.is_fully_funded) { if (offer.is_fully_funded) {
@@ -395,40 +372,40 @@ OrderBook.prototype.setFundedAmount = function(offer, fundedAmount) {
offer.taker_gets_funded = fundedAmount; offer.taker_gets_funded = fundedAmount;
var takerPaysValue = typeof offer.TakerPays === 'object' var takerPaysValue = (typeof offer.TakerPays === 'object')
? offer.TakerPays.value ? offer.TakerPays.value
: offer.TakerPays; : offer.TakerPays;
var takerGetsValue = typeof offer.TakerGets === 'object' var takerGetsValue = (typeof offer.TakerGets === 'object')
? offer.TakerGets.value ? offer.TakerGets.value
: offer.TakerGets; : offer.TakerGets;
var takerPays = Amount.from_json( var takerPays = Amount.from_json(takerPaysValue + OrderBook.IOU_SUFFIX);
takerPaysValue + '/000/rrrrrrrrrrrrrrrrrrrrBZbvji' var takerGets = Amount.from_json(takerGetsValue + OrderBook.IOU_SUFFIX);
); var fundedPays = Amount.from_json(fundedAmount + OrderBook.IOU_SUFFIX);
var takerGets = Amount.from_json(
takerGetsValue + '/000/rrrrrrrrrrrrrrrrrrrrBZbvji'
);
var fundedPays = Amount.from_json(
fundedAmount + '/000/rrrrrrrrrrrrrrrrrrrrBZbvji'
);
var rate = takerPays.divide(takerGets); var rate = takerPays.divide(takerGets);
fundedPays = fundedPays.multiply(rate); fundedPays = fundedPays.multiply(rate);
if (fundedPays.compareTo(takerPays) < 0) { 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 { } else {
offer.taker_pays_funded = takerPays.to_json().value; fundedPays = takerPays.to_json().value;
} }
offer.taker_pays_funded = fundedPays;
return offer; return offer;
}; };
/** /**
* DEPRECATED:
* Should only be called for old versions of rippled
*
* Determine what an account is funded to offer for orderbook's * Determine what an account is funded to offer for orderbook's
* currency/issuer * currency/issuer
* *
@@ -447,7 +424,7 @@ OrderBook.prototype.requestFundedAmount = function(account, callback) {
} }
function requestNativeBalance(callback) { function requestNativeBalance(callback) {
self._remote.requestAccountInfo(account, function(err, info) { self._remote.requestAccountInfo({ account: account }, function(err, info) {
if (err) { if (err) {
callback(err); callback(err);
} else { } else {
@@ -457,12 +434,11 @@ OrderBook.prototype.requestFundedAmount = function(account, callback) {
}; };
function requestLineBalance(callback) { function requestLineBalance(callback) {
var request = self._remote.requestAccountLines( var request = self._remote.requestAccountLines({
account, // account account: account,
void(0), // account index ledger: 'validated',
'VALIDATED', // ledger peer: self._issuerGets
self._issuerGets //peer });
);
request.request(function(err, res) { request.request(function(err, res) {
if (err) { if (err) {
@@ -601,10 +577,10 @@ OrderBook.prototype.isBalanceChange = function(node) {
* @param {Object} transaction * @param {Object} transaction
*/ */
OrderBook.prototype.updateFundedAmounts = function(message) { OrderBook.prototype.updateFundedAmounts = function(transaction) {
var self = this; var self = this;
var affectedAccounts = message.mmeta.getAffectedAccounts(); var affectedAccounts = transaction.mmeta.getAffectedAccounts();
var isOwnerAffected = affectedAccounts.some(function(account) { var isOwnerAffected = affectedAccounts.some(function(account) {
return self.hasCachedFunds(account); return self.hasCachedFunds(account);
@@ -616,25 +592,23 @@ OrderBook.prototype.updateFundedAmounts = function(message) {
if (!this._currencyGets.is_native() && !this._issuerTransferRate) { if (!this._currencyGets.is_native() && !this._issuerTransferRate) {
// Defer until transfer rate is requested // Defer until transfer rate is requested
if (self._remote.trace) { if (this._remote.trace) {
log.info('waiting for transfer rate'); log.info('waiting for transfer rate');
} }
this.once('transfer_rate', function() { this.requestTransferRate(function() {
self.updateFundedAmounts(message); self.updateFundedAmounts(transaction);
}); });
this.requestTransferRate();
return; return;
} }
var nodes = message.mmeta.getNodes({ var affectedNodes = transaction.mmeta.getNodes({
nodeType: 'ModifiedNode', nodeType: 'ModifiedNode',
entryType: this._currencyGets.is_native() ? 'AccountRoot' : 'RippleState' entryType: this._currencyGets.is_native() ? 'AccountRoot' : 'RippleState'
}); });
for (var i=0; i<nodes.length; i++) { for (var i=0, l=affectedNodes.length; i<l; i++) {
var node = nodes[i]; var node = affectedNodes[i];
if (!this.isBalanceChange(node)) { if (!this.isBalanceChange(node)) {
continue; continue;
@@ -642,39 +616,72 @@ OrderBook.prototype.updateFundedAmounts = function(message) {
var result = this.getBalanceChange(node); var result = this.getBalanceChange(node);
if (result.isValid) { if (result.isValid && this.hasCachedFunds(result.account)) {
if (this.hasCachedFunds(result.account)) { this.updateAccountFunds(result.account, result.balance);
this.updateOfferFunds(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) { OrderBook.offerRewrite = function(offer) {
var self = this; var result = { };
var keys = Object.keys(offer);
var affectedAccounts = message.mmeta.getAffectedAccounts(); for (var i=0, l=keys.length; i<l; i++) {
var key = keys[i];
var isIssuerAffected = affectedAccounts.some(function(account) { switch (key) {
return account === self._issuerGets; case 'PreviousTxnID':
}); case 'PreviousTxnLgrSeq':
case 'quality':
if (!isIssuerAffected) { break;
return; default:
result[key] = offer[key];
}
} }
// XXX Update transfer rate result.Flags = result.Flags || 0;
// result.OwnerNode = result.OwnerNode || new Array(16 + 1).join('0');
// var nodes = message.mmeta.getNodes({ result.BookNode = result.BookNode || new Array(16 + 1).join('0');
// nodeType: 'ModifiedNode',
// entryType: 'AccountRoot' 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;
}; };
/** /**
@@ -706,27 +713,8 @@ OrderBook.prototype.requestOffers = function(callback) {
log.info('requested offers', self._key, 'offers: ' + res.offers.length); log.info('requested offers', self._key, 'offers: ' + res.offers.length);
} }
// Reset offers self.setOffers(res.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._synchronized = true; self._synchronized = true;
self.emit('model', self._offers); self.emit('model', self._offers);
callback(null, self._offers); callback(null, self._offers);
@@ -838,6 +826,57 @@ OrderBook.prototype.getOffersSync = function() {
return this._offers; 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 * Insert an offer into the orderbook
* *
@@ -849,8 +888,8 @@ OrderBook.prototype.insertOffer = function(node, fundedAmount) {
log.info('inserting offer', this._key, node.fields); 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; nodeFields.index = node.ledgerIndex;
if (!isNaN(fundedAmount)) { if (!isNaN(fundedAmount)) {
@@ -901,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]; var offer = this._offers[i];
if (offer.index === node.ledgerIndex) { if (offer.index === node.ledgerIndex) {
if (isDeletedNode) { if (isDeletedNode) {
@@ -920,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 * Notify orderbook of a relevant transaction
* *
@@ -978,7 +966,7 @@ OrderBook.prototype.updateOfferFunds = function(account, balance) {
* @api private * @api private
*/ */
OrderBook.prototype.notify = function(message) { OrderBook.prototype.notify = function(transaction) {
var self = this; var self = this;
// Unsubscribed from OrderBook // Unsubscribed from OrderBook
@@ -986,7 +974,7 @@ OrderBook.prototype.notify = function(message) {
return; return;
} }
var affectedNodes = message.mmeta.getNodes({ var affectedNodes = transaction.mmeta.getNodes({
entryType: 'Offer', entryType: 'Offer',
bookKey: this._key bookKey: this._key
}); });
@@ -996,7 +984,7 @@ OrderBook.prototype.notify = function(message) {
} }
if (this._remote.trace) { 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( var tradeGets = Amount.from_json(
@@ -1013,7 +1001,7 @@ OrderBook.prototype.notify = function(message) {
function handleNode(node, callback) { function handleNode(node, callback) {
var isDeletedNode = node.nodeType === 'DeletedNode'; var isDeletedNode = node.nodeType === 'DeletedNode';
var isOfferCancel = message.transaction.TransactionType === 'OfferCancel'; var isOfferCancel = transaction.transaction.TransactionType === 'OfferCancel';
switch (node.nodeType) { switch (node.nodeType) {
case 'DeletedNode': case 'DeletedNode':
@@ -1040,7 +1028,7 @@ OrderBook.prototype.notify = function(message) {
case 'CreatedNode': case 'CreatedNode':
self.incrementOfferCount(node.fields.Account); self.incrementOfferCount(node.fields.Account);
var fundedAmount = message.transaction.owner_funds; var fundedAmount = transaction.transaction.owner_funds;
if (!isNaN(fundedAmount)) { if (!isNaN(fundedAmount)) {
self.insertOffer(node, fundedAmount); self.insertOffer(node, fundedAmount);
@@ -1062,7 +1050,7 @@ OrderBook.prototype.notify = function(message) {
}; };
async.eachSeries(affectedNodes, handleNode, function() { async.eachSeries(affectedNodes, handleNode, function() {
self.emit('transaction', message); self.emit('transaction', transaction);
self.emit('model', self._offers); self.emit('model', self._offers);
if (!tradeGets.is_zero()) { if (!tradeGets.is_zero()) {
self.emit('trade', tradePays, tradeGets); self.emit('trade', tradePays, tradeGets);
@@ -1098,6 +1086,27 @@ OrderBook.prototype.to_json = function() {
return json; 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; exports.OrderBook = OrderBook;
// vim:sw=2:sts=2:ts=8:et // vim:sw=2:sts=2:ts=8:et

View File

@@ -23,6 +23,7 @@ var Server = require('./server').Server;
var Amount = require('./amount').Amount; var Amount = require('./amount').Amount;
var Currency = require('./currency').Currency; var Currency = require('./currency').Currency;
var UInt160 = require('./uint160').UInt160; var UInt160 = require('./uint160').UInt160;
var UInt256 = require('./uint256').UInt256;
var Transaction = require('./transaction').Transaction; var Transaction = require('./transaction').Transaction;
var Account = require('./account').Account; var Account = require('./account').Account;
var Meta = require('./meta').Meta; var Meta = require('./meta').Meta;
@@ -92,7 +93,7 @@ function Remote(opts, trace) {
this.canonical_signing = (typeof opts.canonical_signing === 'boolean') ? opts.canonical_signing : true; this.canonical_signing = (typeof opts.canonical_signing === 'boolean') ? opts.canonical_signing : true;
this.fee_cushion = (typeof opts.fee_cushion === 'number') ? opts.fee_cushion : 1.2; this.fee_cushion = (typeof opts.fee_cushion === 'number') ? opts.fee_cushion : 1.2;
this.max_fee = (typeof opts.max_fee === 'number') ? opts.max_fee : Infinity; this.max_fee = (typeof opts.max_fee === 'number') ? opts.max_fee : 1000000; // default max fee is 1 XRP, 10^6 drops
this.max_attempts = (typeof opts.max_attempts === 'number') ? opts.max_attempts : 10; this.max_attempts = (typeof opts.max_attempts === 'number') ? opts.max_attempts : 10;
@@ -945,6 +946,8 @@ Remote.prototype.requestLedger = function(options, callback) {
break; break;
case 'object': case 'object':
if (!options) break;
Object.keys(options).forEach(function(o) { Object.keys(options).forEach(function(o) {
switch (o) { switch (o) {
case 'full': case 'full':
@@ -953,17 +956,15 @@ Remote.prototype.requestLedger = function(options, callback) {
case 'accounts': case 'accounts':
request.message[o] = true; request.message[o] = true;
break; break;
case 'ledger_index': case 'ledger_index':
case 'ledger_hash': case 'ledger_hash':
request.message[o] = options[o]; request.message[o] = options[o];
break; break;
case 'closed' : case 'closed' :
case 'current' : case 'current' :
case 'validated' : case 'validated' :
request.message.ledger_index = o; request.message.ledger_index = o;
break; break;
} }
}, options); }, options);
break; break;
@@ -1198,19 +1199,43 @@ Remote.prototype.requestTx = function(hash, callback) {
}; };
/** /**
* Account request abstraction * Account Request
* *
* @api private * Optional paging with limit and marker options
* supported in rippled for 'account_lines' and 'account_offers'
*
* The paged responses aren't guaranteed to be reliable between
* ledger closes. You have to supply a ledger_index or ledger_hash
* when paging to ensure a complete response
*
* @param {String} type - request name, e.g. 'account_lines'
* @param {String} account - ripple address
* @param {Object} options - all optional
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Number] limit - max results per response
* @param {String} marker - start position in response paging
* @param [Function] callback
* @return {Request}
* @throws {Error} if a marker is provided, but no ledger_index or ledger_hash
*/ */
Remote.accountRequest = function(type, options, callback) {
var account, ledger, peer, limit, marker;
Remote.accountRequest = function(type, account, accountIndex, ledger, peer, callback) { if (typeof options === 'object') {
if (typeof account === 'object') { account = options.account;
var options = account; ledger = options.ledger;
callback = accountIndex; peer = options.peer;
ledger = options.ledger; limit = options.limit;
accountIndex = options.account_index || options.accountIndex; marker = options.marker;
account = options.accountID || options.account; }
peer = options.peer;
// if a marker is given, we need a ledger
// check if a valid ledger_index or ledger_hash is provided
if (marker) {
if (!(Number(ledger) > 0) && !UInt256.is_valid(ledger)) {
throw new Error('A ledger_index or ledger_hash must be provided when using a marker');
}
} }
var lastArg = arguments[arguments.length - 1]; var lastArg = arguments[arguments.length - 1];
@@ -1220,37 +1245,55 @@ Remote.accountRequest = function(type, account, accountIndex, ledger, peer, call
} }
var request = new Request(this, type); var request = new Request(this, type);
var account = UInt160.json_rewrite(account);
request.message.ident = account; //DEPRECATED; if (account) {
request.message.account = account; account = UInt160.json_rewrite(account);
request.message.account = account;
if (typeof accountIndex === 'number') {
request.message.index = accountIndex;
} }
if (!/^(undefined|function)$/.test(typeof ledger)) { request.ledgerSelect(ledger);
request.ledgerChoose(ledger);
}
if (!/^(undefined|function)$/.test(typeof peer)) { if (UInt160.is_valid(peer)) {
request.message.peer = UInt160.json_rewrite(peer); request.message.peer = UInt160.json_rewrite(peer);
} }
request.callback(callback); if (!isNaN(Number(limit))) {
limit = Number(limit);
// max for 32-bit unsigned int is 4294967295
// we'll clamp to 1e9
if (limit > 1e9) {
limit = 1e9;
}
// min for 32-bit unsigned int is 0
// we'll clamp to 0
if (limit < 0) {
limit = 0;
}
request.message.limit = limit;
}
if (marker) {
request.message.marker = marker;
}
request.callback(callback);
return request; return request;
}; };
/** /**
* Request account_info * Request account_info
* *
* @param {String} ripple address * @param {Object} options
* @param {String} account - ripple address
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Function] callback * @param [Function] callback
* @return {Request} * @return {Request}
*/ */
Remote.prototype.requestAccountInfo = function(account, callback) { Remote.prototype.requestAccountInfo = function(options, callback) {
var args = Array.prototype.concat.apply(['account_info'], arguments); var args = Array.prototype.concat.apply(['account_info'], arguments);
return Remote.accountRequest.apply(this, args); return Remote.accountRequest.apply(this, args);
}; };
@@ -1258,12 +1301,15 @@ Remote.prototype.requestAccountInfo = function(account, callback) {
/** /**
* Request account_currencies * Request account_currencies
* *
* @param {String} ripple address * @param {Object} options
* @param {String} account - ripple address
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Function] callback * @param [Function] callback
* @return {Request} * @return {Request}
*/ */
Remote.prototype.requestAccountCurrencies = function(account, callback) { Remote.prototype.requestAccountCurrencies = function(options, callback) {
var args = Array.prototype.concat.apply(['account_currencies'], arguments); var args = Array.prototype.concat.apply(['account_currencies'], arguments);
return Remote.accountRequest.apply(this, args); return Remote.accountRequest.apply(this, args);
}; };
@@ -1271,15 +1317,24 @@ Remote.prototype.requestAccountCurrencies = function(account, callback) {
/** /**
* Request account_lines * Request account_lines
* *
* @param {String} ripple address * Requests for account_lines support paging, provide a limit and marker
* @param {Number] sub-account index * to page through responses.
* @param [String|Number] ledger *
* @param [String] peer * The paged responses aren't guaranteed to be reliable between
* ledger closes. You have to supply a ledger_index or ledger_hash
* when paging to ensure a complete response
*
* @param {Object} options
* @param {String} account - ripple address
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Number] limit - max results per response
* @param {String} marker - start position in response paging
* @param [Function] callback * @param [Function] callback
* @return {Request} * @return {Request}
*/ */
Remote.prototype.requestAccountLines = function(account, accountIndex, ledger, peer, callback) { Remote.prototype.requestAccountLines = function(options, callback) {
// XXX Does this require the server to be trusted? // XXX Does this require the server to be trusted?
//utils.assert(this.trusted); //utils.assert(this.trusted);
var args = Array.prototype.concat.apply(['account_lines'], arguments); var args = Array.prototype.concat.apply(['account_lines'], arguments);
@@ -1289,20 +1344,27 @@ Remote.prototype.requestAccountLines = function(account, accountIndex, ledger, p
/** /**
* Request account_offers * Request account_offers
* *
* @param {String} ripple address * Requests for account_offers support paging, provide a limit and marker
* @param {Number] sub-account index * to page through responses.
* @param [String|Number] ledger *
* @param [String] peer * The paged responses aren't guaranteed to be reliable between
* ledger closes. You have to supply a ledger_index or ledger_hash
* when paging to ensure a complete response
*
* @param {Object} options
* @param {String} account - ripple address
* @param [String|Number] ledger identifier
* @param [Number] limit - max results per response
* @param {String} marker - start position in response paging
* @param [Function] callback * @param [Function] callback
* @return {Request} * @return {Request}
*/ */
Remote.prototype.requestAccountOffers = function(account, accountIndex, ledger, callback) { Remote.prototype.requestAccountOffers = function(options, callback) {
var args = Array.prototype.concat.apply(['account_offers'], arguments); var args = Array.prototype.concat.apply(['account_offers'], arguments);
return Remote.accountRequest.apply(this, args); return Remote.accountRequest.apply(this, args);
}; };
/** /**
* Request account_tx * Request account_tx
* *
@@ -1318,9 +1380,6 @@ Remote.prototype.requestAccountOffers = function(account, accountIndex, ledger,
* @param [Number] offset, defaults to 0 * @param [Number] offset, defaults to 0
* @param [Number] limit * @param [Number] limit
* *
* @param [Function] filter
* @param [Function] map
* @param [Function] reduce
* @param [Function] callback * @param [Function] callback
* @return {Request} * @return {Request}
*/ */
@@ -1600,12 +1659,13 @@ Remote.prototype.requestLedgerAccept = function(callback) {
request.callback(callback, 'ledger_closed'); request.callback(callback, 'ledger_closed');
request.request(); request.request();
return this; return request;
}; };
/** /**
* Account root request abstraction * Account root request abstraction
* *
* @this Remote
* @api private * @api private
*/ */
@@ -1625,7 +1685,7 @@ Remote.accountRootRequest = function(type, responseFilter, account, ledger, call
var request = this.requestLedgerEntry('account_root'); var request = this.requestLedgerEntry('account_root');
request.accountRoot(account); request.accountRoot(account);
request.ledgerChoose(ledger); request.ledgerSelect(ledger);
request.once('success', function(message) { request.once('success', function(message) {
request.emit(type, responseFilter(message)); request.emit(type, responseFilter(message));

View File

@@ -208,6 +208,13 @@ Request.prototype.ledgerIndex = function(ledger_index) {
return this; return this;
}; };
/**
* Set either ledger_index or ledger_hash based on heuristic
*
* @param {Number|String} ledger identifier
*/
Request.prototype.selectLedger =
Request.prototype.ledgerSelect = function(ledger) { Request.prototype.ledgerSelect = function(ledger) {
switch (ledger) { switch (ledger) {
case 'current': case 'current':
@@ -217,10 +224,10 @@ Request.prototype.ledgerSelect = function(ledger) {
break; break;
default: default:
if (isNaN(ledger)) { if (Number(ledger)) {
this.message.ledger_hash = ledger; this.message.ledger_index = Number(ledger);
} else if ((ledger = Number(ledger))) { } else if (/^[A-F0-9]+$/.test(ledger)) {
this.message.ledger_index = ledger; this.message.ledger_hash = ledger;
} }
break; break;
} }

View File

@@ -2,7 +2,6 @@ var util = require('util');
var url = require('url'); var url = require('url');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var Amount = require('./amount').Amount; var Amount = require('./amount').Amount;
var Transaction = require('./transaction').Transaction;
var log = require('./log').internal.sub('server'); var log = require('./log').internal.sub('server');
/** /**
@@ -564,7 +563,7 @@ Server.prototype._handleServerStatus = function(message) {
this._remote.emit('load', message, this); this._remote.emit('load', message, this);
var loadChanged = message.load_base !== this._load_base var loadChanged = message.load_base !== this._load_base
|| message.load_factor !== this._load_factor || message.load_factor !== this._load_factor;
if (loadChanged) { if (loadChanged) {
this._load_base = message.load_base; this._load_base = message.load_base;
@@ -760,22 +759,16 @@ Server.prototype._isConnected = function() {
* Calculate transaction fee * Calculate transaction fee
* *
* @param {Transaction|Number} Fee units for a provided transaction * @param {Transaction|Number} Fee units for a provided transaction
* @return {Number} Final fee in XRP for specified number of fee units * @return {String} Final fee in XRP for specified number of fee units
* @api private * @api private
*/ */
Server.prototype._computeFee = function(transaction) { Server.prototype._computeFee = function(feeUnits) {
var units; if (isNaN(feeUnits)) {
if (transaction instanceof Transaction) {
units = transaction._getFeeUnits();
} else if (typeof transaction === 'number') {
units = transaction;
} else {
throw new Error('Invalid argument'); throw new Error('Invalid argument');
} }
return this._feeTx(units).to_json(); return this._feeTx(Number(feeUnits)).to_json();
}; };
/** /**

View File

@@ -60,7 +60,7 @@ function Transaction(remote) {
var self = this; var self = this;
var remote = remote || { }; var remote = remote || void(0);
this.remote = remote; this.remote = remote;
@@ -69,7 +69,7 @@ function Transaction(remote) {
this._secret = void(0); this._secret = void(0);
this._build_path = false; this._build_path = false;
this._maxFee = this.remote.max_fee; this._maxFee = (typeof remote === 'object') ? this.remote.max_fee : void(0);
this.state = 'unsubmitted'; this.state = 'unsubmitted';
this.finalized = false; this.finalized = false;
@@ -241,7 +241,7 @@ Transaction.prototype.finalize = function(message) {
}; };
Transaction.prototype._accountSecret = function(account) { Transaction.prototype._accountSecret = function(account) {
return this.remote.secrets[account]; return this.remote ? this.remote.secrets[account] : void(0);
}; };
/** /**
@@ -266,13 +266,17 @@ Transaction.prototype.feeUnits = function() {
*/ */
Transaction.prototype._computeFee = function() { Transaction.prototype._computeFee = function() {
if (!this.remote) {
return void(0);
}
var servers = this.remote._servers; var servers = this.remote._servers;
var fees = [ ]; var fees = [ ];
for (var i=0; i<servers.length; i++) { for (var i=0; i<servers.length; i++) {
var server = servers[i]; var server = servers[i];
if (server._connected) { if (server._connected) {
fees.push(Number(server._computeFee(this))); fees.push(Number(server._computeFee(this._getFeeUnits())));
} }
} }
@@ -487,6 +491,23 @@ Transaction.prototype.lastLedger = function(sequence) {
return this; return this;
}; };
/*
* Set the transaction's proposed fee. No op when fee parameter
* is not 0 or a positive number
*
* @param {Number} fee The proposed fee
*
* @returns {Transaction} calling instance for chaining
*/
Transaction.prototype.maxFee = function(fee) {
if (typeof fee === 'number' && fee >= 0) {
this._setMaxFee = true;
this._maxFee = fee;
}
return this;
};
Transaction._pathRewrite = function(path) { Transaction._pathRewrite = function(path) {
if (!Array.isArray(path)) { if (!Array.isArray(path)) {
return; return;
@@ -898,6 +919,10 @@ Transaction.prototype.submit = function(callback) {
var account = this.tx_json.Account; var account = this.tx_json.Account;
if (!this.remote) {
return this.emit('error', new Error('No remote found'));
}
if (!UInt160.is_valid(account)) { if (!UInt160.is_valid(account)) {
return this.emit('error', new RippleError('tejInvalidAccount', 'Account is missing or invalid')); return this.emit('error', new RippleError('tejInvalidAccount', 'Account is missing or invalid'));
} }

View File

@@ -46,8 +46,16 @@ function TransactionManager(account) {
} }
if (submission instanceof Transaction) { if (submission instanceof Transaction) {
// ND: A `success` handler will `finalize` this later // ND: A `success` handler will `finalize` this later
submission.emit('success', transaction); switch (transaction.engine_result) {
case 'tesSUCCESS':
submission.emit('success', transaction);
break;
default:
submission.emit('error', transaction);
}
} else { } else {
self._pending.addReceivedId(hash, transaction); self._pending.addReceivedId(hash, transaction);
} }
@@ -357,8 +365,6 @@ TransactionManager.prototype._request = function(tx) {
return tx.emit('error', new RippleError('tejLocalSigningRequired', message)); return tx.emit('error', new RippleError('tejLocalSigningRequired', message));
} }
tx.emit('presubmit');
if (tx.finalized) { if (tx.finalized) {
return; return;
} }
@@ -413,8 +419,6 @@ TransactionManager.prototype._request = function(tx) {
if (tx.finalized) { if (tx.finalized) {
return; return;
} }
tx.emit('error', message);
}; };
function transactionFailedLocal(message) { function transactionFailedLocal(message) {
@@ -551,9 +555,11 @@ TransactionManager.prototype._request = function(tx) {
return; return;
} }
submitRequest.timeout(self._submissionTimeout, requestTimeout); tx.emit('presubmit');
tx.submissions = submitRequest.broadcast();
submitRequest.timeout(self._submissionTimeout, requestTimeout);
tx.submissions = submitRequest.broadcast();
tx.attempts++; tx.attempts++;
tx.emit('postsubmit'); tx.emit('postsubmit');
}; };

View File

@@ -29,13 +29,15 @@ describe('Account', function(){
}); });
// XXX: clean up the stubbed out remote methods
describe('#publicKeyIsActive()', function(){ describe('#publicKeyIsActive()', function(){
it('should respond true if the public key corresponds to the account address and the master key IS NOT disabled', function(){ it('should respond true if the public key corresponds to the account address and the master key IS NOT disabled', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
callback(null, { account_data: { callback(null, { account_data: {
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
@@ -56,7 +58,7 @@ describe('Account', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
callback(null, { account_data: { callback(null, { account_data: {
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
@@ -77,7 +79,7 @@ describe('Account', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
callback(null, { account_data: { callback(null, { account_data: {
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
@@ -99,7 +101,7 @@ describe('Account', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
callback(null, { account_data: { callback(null, { account_data: {
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
@@ -121,7 +123,7 @@ describe('Account', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
callback(null, { account_data: { callback(null, { account_data: {
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
@@ -142,7 +144,7 @@ describe('Account', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ') { if (address === 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ') {
callback({ error: 'remoteError', callback({ error: 'remoteError',
error_message: 'Remote reported an error.', error_message: 'Remote reported an error.',

View File

@@ -87,7 +87,7 @@ describe('Amount', function() {
assert.strictEqual(Amount.from_human("0.8 XAU").to_human({precision:0}), '1'); assert.strictEqual(Amount.from_human("0.8 XAU").to_human({precision:0}), '1');
}); });
it('to human, precision 0, precision 16', function() { 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() { 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'); 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() { 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'); 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() { describe('from_human', function() {
it('1 XRP', function() { it('1 XRP', function() {
@@ -1162,4 +1177,80 @@ describe('Amount', function() {
assert.strictEqual(demAmount.to_human_full(), '10.75853086191915/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(demAmount.to_human_full(), '10.75853086191915/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
}); });
}); });
describe('amount limits', function() {
it ('max JSON wire limite', function() {
assert.strictEqual(Amount.bi_xns_max.toString(), '9000000000000000000');
});
it ('max JSON wire limite', function() {
assert.strictEqual(Amount.bi_xns_min.toString(), '-9000000000000000000');
});
it('max mantissa value', function() {
assert.strictEqual(Amount.bi_man_max_value.toString(), '9999999999999999');
});
it('min mantissa value', function() {
assert.strictEqual(Amount.bi_man_min_value.toString(), '1000000000000000');
});
it ('from_json minimum XRP', function() {
console.log('max', Amount.bi_xns_max.toString());
var amt = Amount.from_json('-9000000000000000000');
assert.strictEqual(amt.to_json(), '-9000000000000000000');
});
it ('from_json maximum XRP', function() {
var amt = Amount.from_json('-9000000000000000000');
assert.strictEqual(amt.to_json(), '-9000000000000000000');
});
it ('from_json less than minimum XRP', function() {
var amt = Amount.from_json('-9000000000000000001');
assert.strictEqual(amt.to_json(), '0');
});
it ('from_json more than maximum XRP', function() {
var amt = Amount.from_json('9000000000000000001');
assert.strictEqual(amt.to_json(), '0');
});
it ('from_json minimum IOU', function() {
var amt = Amount.from_json('-1e-81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(amt._value.toString(), Amount.bi_man_min_value.toString());
assert.strictEqual(amt.to_text(), '-1000000000000000e-96');
assert.strictEqual(amt.to_text(), Amount.min_value);
});
it('from_json exceed minimum IOU', function() {
assert.throws(function() {
Amount.from_json('-1e-82/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')
}, 'Exceeding min value of ' + Amount.min_value);
});
it ('from_json maximum IOU', function() {
var amt = Amount.from_json('9999999999999999e80/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(amt._value.toString(), Amount.bi_man_max_value.toString());
assert.strictEqual(amt.to_text(), '9999999999999999e80');
});
it ('from_json exceed maximum IOU', function() {
assert.throws(function() {
Amount.from_json('9999999999999999e81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')
}, 'Exceeding max value of ' + Amount.max_value);
});
it ('from_json normalize mantissa to valid max range, lost significant digits', function() {
var amt = Amount.from_json('99999999999999999999999999999999/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(amt._value.toString(), Amount.bi_man_max_value.toString());
assert.strictEqual(amt.to_text(), '9999999999999999e16');
});
it ('from_json normalize mantissa to min valid range, lost significant digits', function() {
var amt = Amount.from_json('-0.0000000000000000000000001/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(amt._value.toString(), Amount.bi_man_min_value.toString());
assert.strictEqual(amt.to_text(), '-1000000000000000e-40');
});
});
}); });

View File

@@ -1,6 +1,7 @@
var assert = require('assert'); var assert = require('assert');
var utils = require('./testutils'); var utils = require('./testutils');
var currency = utils.load_module('currency').Currency; var currency = utils.load_module('currency').Currency;
var timeUtil = utils.load_module('utils').time;
describe('Currency', function() { describe('Currency', function() {
describe('json_rewrite', function() { describe('json_rewrite', function() {
@@ -16,11 +17,22 @@ describe('Currency', function() {
}); });
}); });
describe('from_json', function() { describe('from_json', function() {
it('from_json().to_json() == "XRP"', function() {
var r = currency.from_json();
assert(!r.is_valid());
assert.strictEqual('XRP', r.to_json());
});
it('from_json(NaN).to_json() == "XRP"', function() { it('from_json(NaN).to_json() == "XRP"', function() {
var r = currency.from_json(NaN); var r = currency.from_json(NaN);
assert(!r.is_valid()); assert(!r.is_valid());
assert.strictEqual('XRP', r.to_json()); assert.strictEqual('XRP', r.to_json());
}); });
it('from_json().to_json("") == "XRP"', function() {
var r = currency.from_json('');
assert(r.is_valid());
assert(r.is_native());
assert.strictEqual('XRP', r.to_json());
});
it('from_json("XRP").to_json() == "XRP"', function() { it('from_json("XRP").to_json() == "XRP"', function() {
var r = currency.from_json('XRP'); var r = currency.from_json('XRP');
assert(r.is_valid()); assert(r.is_valid());
@@ -103,6 +115,16 @@ describe('Currency', function() {
var cur = currency.from_human('INR - 30 Indian Rupees'); var cur = currency.from_human('INR - 30 Indian Rupees');
assert.strictEqual(cur.to_json(), 'INR'); assert.strictEqual(cur.to_json(), 'INR');
}); });
it('From human "XRP"', function() {
var cur = currency.from_human('XRP');
assert.strictEqual(cur.to_json(), 'XRP');
assert(cur.is_native(), true);
});
it('From human "XRP - Ripples"', function() {
var cur = currency.from_human('XRP - Ripples');
assert.strictEqual(cur.to_json(), 'XRP');
assert(cur.is_native(), true);
});
}); });
@@ -162,10 +184,30 @@ describe('Currency', function() {
assert.strictEqual(cur.to_json(), cur.to_human()); assert.strictEqual(cur.to_json(), cur.to_human());
}); });
}); });
describe('parse_json(currency obj)', function() { describe('parse_json', function() {
assert.strictEqual('USD', new currency().parse_json(currency.from_json('USD')).to_json()); it('should parse a currency object', function() {
assert.strictEqual('USD', new currency().parse_json(currency.from_json('USD')).to_json());
assert.strictEqual('USD (0.5%pa)', new currency().parse_json(currency.from_json('USD (0.5%pa)')).to_json());
});
it('should clone for parse_json on itself', function() {
var cur = currency.from_json('USD');
var cur2 = currency.from_json(cur);
assert.strictEqual(cur.to_json(), cur2.to_json());
assert.strictEqual('USD (0.5%pa)', new currency().parse_json(currency.from_json('USD (0.5%pa)')).to_json()); cur = currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000');
cur2 = currency.from_json(cur);
assert.strictEqual(cur.to_json(), cur2.to_json());
});
it('should parse json 0', function() {
var cur = currency.from_json(0);
assert.strictEqual(cur.to_json(), 'XRP');
assert.strictEqual(cur.get_iso(), 'XRP');
});
it('should parse json 0', function() {
var cur = currency.from_json('0');
assert.strictEqual(cur.to_json(), 'XRP');
assert.strictEqual(cur.get_iso(), 'XRP');
});
}); });
describe('is_valid', function() { describe('is_valid', function() {
@@ -207,14 +249,16 @@ describe('Currency', function() {
return +(Math.round(num + "e+"+precision) + "e-"+precision); return +(Math.round(num + "e+"+precision) + "e-"+precision);
} }
describe('get_interest_at', function() { describe('get_interest_at', function() {
it('returns demurred value for demurrage currency', function() { it('should return demurred value for demurrage currency', function() {
var cur = currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000'); var cur = currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000');
// At start, no demurrage should occur // At start, no demurrage should occur
assert.equal(1, cur.get_interest_at(443845330)); assert.equal(1, cur.get_interest_at(443845330));
assert.equal(1, precision(cur.get_interest_at(new Date(timeUtil.fromRipple(443845330))), 14));
// After one year, 0.5% should have occurred // After one year, 0.5% should have occurred
assert.equal(0.995, precision(cur.get_interest_at(443845330 + 31536000), 14)); assert.equal(0.995, precision(cur.get_interest_at(443845330 + 31536000), 14));
assert.equal(0.995, precision(cur.get_interest_at(new Date(timeUtil.fromRipple(443845330 + 31536000))), 14));
// After one demurrage period, 1/e should have occurred // After one demurrage period, 1/e should have occurred
assert.equal(1/Math.E, cur.get_interest_at(443845330 + 6291418827.05)); assert.equal(1/Math.E, cur.get_interest_at(443845330 + 6291418827.05));
@@ -225,6 +269,11 @@ describe('Currency', function() {
// One demurrage period before start, rate should be e // One demurrage period before start, rate should be e
assert.equal(Math.E, cur.get_interest_at(443845330 - 6291418827.05)); assert.equal(Math.E, cur.get_interest_at(443845330 - 6291418827.05));
}); });
it('should return 0 for currency without interest', function() {
var cur = currency.from_json('USD - US Dollar');
assert.equal(0, cur.get_interest_at(443845330));
assert.equal(0, cur.get_interest_at(443845330 + 31536000));
});
}); });
describe('get_iso', function() { describe('get_iso', function() {
it('should get "XRP" iso_code', function() { it('should get "XRP" iso_code', function() {

View File

@@ -270,7 +270,8 @@ describe('Message', function(){
//Remote.prototype.addServer = function(){}; //Remote.prototype.addServer = function(){};
var test_remote = new Remote(); var test_remote = new Remote();
test_remote.state = 'online'; test_remote.state = 'online';
test_remote.request_account_info = function(account, callback) { test_remote.requestAccountInfo = function(options, callback) {
var account = options.account;
if (account === data.account) { if (account === data.account) {
callback(null, { callback(null, {
"account_data": { "account_data": {
@@ -306,7 +307,8 @@ describe('Message', function(){
//Remote.prototype.addServer = function(){}; //Remote.prototype.addServer = function(){};
var test_remote = new Remote(); var test_remote = new Remote();
test_remote.state = 'online'; test_remote.state = 'online';
test_remote.request_account_info = function(account, callback) { test_remote.requestAccountInfo = function(options, callback) {
var account = options.account;
if (account === data.account) { if (account === data.account) {
callback(null, { callback(null, {
"account_data": { "account_data": {

View File

@@ -276,7 +276,6 @@ describe('OrderBook', function() {
assert.deepEqual(request.message, { assert.deepEqual(request.message, {
command: 'account_info', command: 'account_info',
id: void(0), id: void(0),
ident: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
account: 'rrrrrrrrrrrrrrrrrrrrBZbvji' account: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
}); });
request.emit('success', { request.emit('success', {
@@ -330,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 remote = new Remote();
var book = remote.createOrderBook({ var book = remote.createOrderBook({
currency_gets: 'BTC',
currency_pays: 'XRP', currency_pays: 'XRP',
issuer_gets: 'rrrrrrrrrrrrrrrrrrrrBZbvji', issuer_gets: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
currency_gets: 'BTC'
}); });
var offer = { var offer = {
@@ -360,7 +359,37 @@ describe('OrderBook', function() {
assert.deepEqual(offer, expected); 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 remote = new Remote();
var book = remote.createOrderBook({ var book = remote.createOrderBook({
currency_gets: 'XRP', currency_gets: 'XRP',
@@ -371,7 +400,37 @@ describe('OrderBook', function() {
var offer = { var offer = {
TakerGets: '100', TakerGets: '100',
TakerPays: { 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', currency: 'BTC',
issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji' issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
} }
@@ -384,67 +443,7 @@ describe('OrderBook', function() {
TakerPays: offer.TakerPays, TakerPays: offer.TakerPays,
is_fully_funded: false, is_fully_funded: false,
taker_gets_funded: '99', taker_gets_funded: '99',
taker_pays_funded: '122221.44' taker_pays_funded: '122.22144'
};
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'
}; };
assert.deepEqual(offer, expected); assert.deepEqual(offer, expected);
@@ -1360,7 +1359,6 @@ describe('OrderBook', function() {
assert.deepEqual(request.message, { assert.deepEqual(request.message, {
command: 'account_info', command: 'account_info',
id: undefined, id: undefined,
ident: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
}); });
@@ -1497,8 +1495,6 @@ describe('OrderBook', function() {
Flags: 131072, Flags: 131072,
LedgerEntryType: 'Offer', LedgerEntryType: 'Offer',
OwnerNode: '0000000000000000', OwnerNode: '0000000000000000',
PreviousTxnID: '9BB337CC8B34DC8D1A3FFF468556C8BA70977C37F7436439D8DA19610F214AD1',
PreviousTxnLgrSeq: 8342933,
Sequence: 195, Sequence: 195,
TakerGets: { currency: 'BTC', TakerGets: { currency: 'BTC',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
@@ -1511,7 +1507,6 @@ describe('OrderBook', function() {
}, },
index: 'B6BC3B0F87976370EE11F5575593FE63AA5DC1D602830DC96F04B2D597F044BF', index: 'B6BC3B0F87976370EE11F5575593FE63AA5DC1D602830DC96F04B2D597F044BF',
owner_funds: '0.1129267125000245', owner_funds: '0.1129267125000245',
quality: '496.5',
taker_gets_funded: '0.1127013098802639', taker_gets_funded: '0.1127013098802639',
taker_pays_funded: '55.95620035555102', taker_pays_funded: '55.95620035555102',
is_fully_funded: false }, is_fully_funded: false },
@@ -1523,8 +1518,6 @@ describe('OrderBook', function() {
Flags: 131072, Flags: 131072,
LedgerEntryType: 'Offer', LedgerEntryType: 'Offer',
OwnerNode: '0000000000000144', OwnerNode: '0000000000000144',
PreviousTxnID: 'C8296B9CCA6DC594C7CD271C5D8FD11FEE380021A07768B25935642CDB37048A',
PreviousTxnLgrSeq: 8342469,
Sequence: 29354, Sequence: 29354,
TakerGets: { TakerGets: {
currency: 'BTC', currency: 'BTC',
@@ -1538,7 +1531,6 @@ describe('OrderBook', function() {
}, },
index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86', index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86',
owner_funds: '0.950363009783092', owner_funds: '0.950363009783092',
quality: '498.6116758238228',
is_fully_funded: true, is_fully_funded: true,
taker_gets_funded: '0.2', taker_gets_funded: '0.2',
taker_pays_funded: '99.72233516476456' taker_pays_funded: '99.72233516476456'

View File

@@ -5,7 +5,14 @@ var Remote = utils.load_module('remote').Remote;
var Server = utils.load_module('server').Server; var Server = utils.load_module('server').Server;
var Request = utils.load_module('request').Request; var Request = utils.load_module('request').Request;
var options, spy, mock, stub, remote, callback, database, tx; var options, remote, callback, database, tx;
var ADDRESS = 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS';
var PEER_ADDRESS = 'rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX';
var LEDGER_INDEX = 9592219;
var LEDGER_HASH = 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE';
var PAGING_MARKER = '29F992CC252056BF690107D1E8F2D9FBAFF29FF107B62B1D1F4E4E11ADF2CC73';
describe('Remote', function () { describe('Remote', function () {
beforeEach(function () { beforeEach(function () {
@@ -28,19 +35,19 @@ describe('Remote', function () {
// 'bitcoin': 'localhost:3000' // 'bitcoin': 'localhost:3000'
// 'bitcoin': 'https://www.bitstamp.net/ripple/bridge/out/bitcoin/' // 'bitcoin': 'https://www.bitstamp.net/ripple/bridge/out/bitcoin/'
} }
}, }
}; };
}) });
it('remote server initialization - url object', function() { it('remote server initialization - url object', function() {
var remote = new Remote({ var remote = new Remote({
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ], servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ]
}); });
assert(Array.isArray(remote._servers)); assert(Array.isArray(remote._servers));
assert(remote._servers[0] instanceof Server); assert(remote._servers[0] instanceof Server);
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
}) });
it('remote server initialization - url object - no secure property', function() { it('remote server initialization - url object - no secure property', function() {
var remote = new Remote({ var remote = new Remote({
@@ -49,7 +56,7 @@ describe('Remote', function () {
assert(Array.isArray(remote._servers)); assert(Array.isArray(remote._servers));
assert(remote._servers[0] instanceof Server); assert(remote._servers[0] instanceof Server);
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
}) });
it('remote server initialization - url object - secure: false', function() { it('remote server initialization - url object - secure: false', function() {
var remote = new Remote({ var remote = new Remote({
@@ -67,7 +74,7 @@ describe('Remote', function () {
assert(Array.isArray(remote._servers)); assert(Array.isArray(remote._servers));
assert(remote._servers[0] instanceof Server); assert(remote._servers[0] instanceof Server);
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
}) });
it('remote server initialization - url object - invalid host', function() { it('remote server initialization - url object - invalid host', function() {
assert.throws( assert.throws(
@@ -76,7 +83,7 @@ describe('Remote', function () {
servers: [ { host: '+', port: 443, secure: true } ] servers: [ { host: '+', port: 443, secure: true } ]
}); });
}, Error); }, Error);
}) });
it('remote server initialization - url object - invalid port', function() { it('remote server initialization - url object - invalid port', function() {
assert.throws( assert.throws(
@@ -144,7 +151,35 @@ describe('Remote', function () {
); );
}); });
it('request constructors', function () { it('remote server initialization - set max_fee - number', function() {
var remote = new Remote({
max_fee: 10
});
assert.strictEqual(remote.max_fee, 10);
remote = new Remote({
max_fee: 1234567890
});
assert.strictEqual(remote.max_fee, 1234567890);
});
it('remote server initialization - set max_fee - string fails, should be number', function() {
var remote = new Remote({
max_fee: '1234567890'
});
assert.strictEqual(remote.max_fee, 1e6);
});
it('remote server initialization - max_fee - default', function() {
var remote = new Remote({
max_fee: void(0)
});
assert.strictEqual(remote.max_fee, 1e6);
assert.strictEqual(remote.max_fee, 1000000);
assert.strictEqual((new Remote()).max_fee, 1e6);
});
describe('request constructors', function () {
beforeEach(function () { beforeEach(function () {
callback = function () {} callback = function () {}
remote = new Remote(options); remote = new Remote(options);
@@ -184,7 +219,222 @@ describe('Remote', function () {
var request = remote.request_unl_delete(null, {}, callback); var request = remote.request_unl_delete(null, {}, callback);
assert(request instanceof Request); assert(request instanceof Request);
}); });
})
it('request account currencies with ledger index', function() {
var request = remote.requestAccountCurrencies({account: ADDRESS});
assert.strictEqual(request.message.command, 'account_currencies');
assert.strictEqual(request.message.account, ADDRESS);
});
it('request account info with ledger index', function() {
var request = remote.requestAccountInfo({account: ADDRESS, ledger: 9592219});
assert.strictEqual(request.message.command, 'account_info');
assert.strictEqual(request.message.account, ADDRESS);
assert.strictEqual(request.message.ledger_index, 9592219);
});
it('request account info with ledger hash', function() {
var request = remote.requestAccountInfo({account: ADDRESS, ledger: LEDGER_HASH});
assert.strictEqual(request.message.command, 'account_info');
assert.strictEqual(request.message.account, ADDRESS);
assert.strictEqual(request.message.ledger_hash, LEDGER_HASH);
});
it('request account info with ledger identifier', function() {
var request = remote.requestAccountInfo({account: ADDRESS, ledger: 'validated'});
assert.strictEqual(request.message.command, 'account_info');
assert.strictEqual(request.message.account, ADDRESS);
assert.strictEqual(request.message.ledger_index, 'validated');
});
it('request account balance with ledger index', function() {
var request = remote.requestAccountBalance(ADDRESS, 9592219);
assert.strictEqual(request.message.command, 'ledger_entry');
assert.strictEqual(request.message.account_root, ADDRESS);
assert.strictEqual(request.message.ledger_index, 9592219);
});
it('request account balance with ledger hash', function() {
var request = remote.requestAccountBalance(ADDRESS, LEDGER_HASH);
assert.strictEqual(request.message.command, 'ledger_entry');
assert.strictEqual(request.message.account_root, ADDRESS);
assert.strictEqual(request.message.ledger_hash, LEDGER_HASH);
});
it('request account balance with ledger identifier', function() {
var request = remote.requestAccountBalance(ADDRESS, 'validated');
assert.strictEqual(request.message.command, 'ledger_entry');
assert.strictEqual(request.message.account_root, ADDRESS);
assert.strictEqual(request.message.ledger_index, 'validated');
});
});
it('pagingAccountRequest', function() {
var request = Remote.accountRequest('account_lines', {account: ADDRESS});
assert.deepEqual(request.message, {
command: 'account_lines',
id: undefined,
account: ADDRESS
});
});
it('pagingAccountRequest - limit', function() {
var request = Remote.accountRequest('account_lines', {account: ADDRESS, limit: 100});
assert.deepEqual(request.message, {
command: 'account_lines',
id: undefined,
account: ADDRESS,
limit: 100
});
});
it('pagingAccountRequest - limit, marker', function() {
var request = Remote.accountRequest('account_lines', {account: ADDRESS, limit: 100, marker: PAGING_MARKER, ledger: 9592219});
assert.deepEqual(request.message, {
command: 'account_lines',
id: undefined,
account: ADDRESS,
limit: 100,
marker: PAGING_MARKER,
ledger_index: 9592219
});
assert(!request.requested);
});
it('accountRequest - limit min', function() {
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: 0}).message.limit, 0);
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: -1}).message.limit, 0);
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: -1e9}).message.limit, 0);
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: -1e24}).message.limit, 0);
});
it('accountRequest - limit max', function() {
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: 1e9}).message.limit, 1e9);
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: 1e9+1}).message.limit, 1e9);
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: 1e10}).message.limit, 1e9);
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: 1e24}).message.limit, 1e9);
});
it('accountRequest - a valid ledger is required when using a marker', function() {
assert.throws(function() {
Remote.accountRequest('account_lines', {account: ADDRESS, marker: PAGING_MARKER})
},'A ledger_index or ledger_hash must be provided when using a marker');
assert.throws(function() {
Remote.accountRequest('account_lines', {account: ADDRESS, marker: PAGING_MARKER, ledger:'validated'})
},'A ledger_index or ledger_hash must be provided when using a marker');
assert.throws(function() {
Remote.accountRequest('account_lines', {account: ADDRESS, marker: PAGING_MARKER, ledger:NaN})
},'A ledger_index or ledger_hash must be provided when using a marker');
assert.throws(function() {
Remote.accountRequest('account_lines', {account: ADDRESS, marker: PAGING_MARKER, ledger:LEDGER_HASH.substr(0,63)})
},'A ledger_index or ledger_hash must be provided when using a marker');
assert.throws(function() {
Remote.accountRequest('account_lines', {account: ADDRESS, marker: PAGING_MARKER, ledger:LEDGER_HASH+'F'})
},'A ledger_index or ledger_hash must be provided when using a marker');
});
it('requestAccountLines, account and callback', function() {
var callback = function() {};
var remote = new Remote({
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ]
});
var request = remote.requestAccountLines(
{account: ADDRESS},
callback
);
assert.deepEqual(request.message, {
command: 'account_lines',
id: undefined,
account: ADDRESS
});
assert(request.requested);
});
it('requestAccountLines, ledger, peer', function() {
var callback = function() {};
var remote = new Remote({
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ]
});
var request = remote.requestAccountLines(
{
account: ADDRESS,
ledger: LEDGER_HASH,
peer: PEER_ADDRESS
},
callback
);
assert.deepEqual(request.message, {
command: 'account_lines',
id: undefined,
account: ADDRESS,
ledger_hash: LEDGER_HASH,
peer: PEER_ADDRESS
});
assert(request.requested);
});
it('requestAccountLines, ledger, peer, limit and marker', function() {
var callback = function() {};
var remote = new Remote({
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ]
});
var request = remote.requestAccountLines(
{
account: ADDRESS,
ledger: LEDGER_INDEX,
peer: PEER_ADDRESS,
limit: 200,
marker: PAGING_MARKER
},
callback
);
assert.deepEqual(request.message, {
command: 'account_lines',
id: undefined,
account: ADDRESS,
ledger_index: LEDGER_INDEX,
peer: PEER_ADDRESS,
limit: 200,
marker: PAGING_MARKER
});
assert(request.requested);
});
it('requestAccountOffers, ledger, peer, limit and marker', function() {
var callback = function() {};
var remote = new Remote({
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ]
});
var request = remote.requestAccountOffers(
{
account: ADDRESS,
ledger: LEDGER_HASH,
peer: PEER_ADDRESS,
limit: 32,
marker: PAGING_MARKER
},
callback
);
assert.deepEqual(request.message, {
command: 'account_offers',
id: undefined,
account: ADDRESS,
ledger_hash: LEDGER_HASH,
peer: PEER_ADDRESS,
limit: 32,
marker: PAGING_MARKER
});
assert(request.requested);
});
it('create remote and get pending transactions', function() { it('create remote and get pending transactions', function() {
before(function() { before(function() {
@@ -244,7 +494,7 @@ describe('Remote', function () {
callback(null, tx); callback(null, tx);
} }
} }
}) });
it('should set transaction members correct ', function(done) { it('should set transaction members correct ', function(done) {
remote = new Remote(options); remote = new Remote(options);
@@ -267,9 +517,9 @@ describe('Remote', function () {
}, },
parseJson: function(json) {} parseJson: function(json) {}
} }
} };
remote.getPendingTransactions(); remote.getPendingTransactions();
}) })
}) })
}) });

View File

@@ -326,6 +326,7 @@ describe('Request', function() {
var request = new Request(remote, 'server_info'); var request = new Request(remote, 'server_info');
request.ledgerChoose(); request.ledgerChoose();
assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
assert.strictEqual(request.message.ledger_index, void(0));
}); });
it('Select ledger - identifier', function() { it('Select ledger - identifier', function() {
@@ -335,6 +336,7 @@ describe('Request', function() {
var request = new Request(remote, 'server_info'); var request = new Request(remote, 'server_info');
request.ledgerSelect('validated'); request.ledgerSelect('validated');
assert.strictEqual(request.message.ledger_index, 'validated'); assert.strictEqual(request.message.ledger_index, 'validated');
assert.strictEqual(request.message.ledger_hash, void(0));
}); });
it('Select ledger - index', function() { it('Select ledger - index', function() {
@@ -344,6 +346,7 @@ describe('Request', function() {
var request = new Request(remote, 'server_info'); var request = new Request(remote, 'server_info');
request.ledgerSelect(7016915); request.ledgerSelect(7016915);
assert.strictEqual(request.message.ledger_index, 7016915); assert.strictEqual(request.message.ledger_index, 7016915);
assert.strictEqual(request.message.ledger_hash, void(0));
}); });
it('Select ledger - hash', function() { it('Select ledger - hash', function() {
@@ -353,15 +356,23 @@ describe('Request', function() {
var request = new Request(remote, 'server_info'); var request = new Request(remote, 'server_info');
request.ledgerSelect('B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); request.ledgerSelect('B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
assert.strictEqual(request.message.ledger_index, void(0));
}); });
it('Select ledger - hash', function() { it('Select ledger - undefined', function() {
var remote = new Remote(); var remote = new Remote();
remote._connected = true; remote._connected = true;
var request = new Request(remote, 'server_info'); var request = new Request(remote, 'server_info');
request.ledgerSelect('B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); request.ledgerSelect();
assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); assert.strictEqual(request.message.ledger_hash, void(0));
assert.strictEqual(request.message.ledger_index, void(0));
request.ledgerSelect(null);
assert.strictEqual(request.message.ledger_hash, void(0));
assert.strictEqual(request.message.ledger_index, void(0));
request.ledgerSelect(NaN);
assert.strictEqual(request.message.ledger_hash, void(0));
assert.strictEqual(request.message.ledger_index, void(0));
}); });
it('Set account_root', function() { it('Set account_root', function() {

View File

@@ -1006,12 +1006,6 @@ describe('Server', function() {
assert(server._isConnected()); assert(server._isConnected());
}); });
it('Compute fee - transaction', function() {
var server = new Server(new Remote(), 'ws://localhost:5748');
var transaction = new Transaction();
assert.strictEqual(server._computeFee(transaction), '12');
});
it('Compute fee - fee units', function() { it('Compute fee - fee units', function() {
var server = new Server(new Remote(), 'ws://localhost:5748'); var server = new Server(new Remote(), 'ws://localhost:5748');
var transaction = new Transaction(); var transaction = new Transaction();
@@ -1033,7 +1027,7 @@ describe('Server', function() {
server._load_factor = 256 * 4; server._load_factor = 256 * 4;
var transaction = new Transaction(); var transaction = new Transaction();
assert.strictEqual(server._computeFee(transaction), '48'); assert.strictEqual(server._computeFee(10), '48');
}); });
it('Compute reserve', function() { it('Compute reserve', function() {

View File

@@ -229,6 +229,11 @@ describe('Transaction', function() {
assert.strictEqual(transaction._computeFee(), '72'); assert.strictEqual(transaction._computeFee(), '72');
}); });
it('Compute fee, no remote', function() {
var transaction = new Transaction();
assert.strictEqual(transaction._computeFee(10), void(0));
});
it('Compute fee - no connected server', function() { it('Compute fee - no connected server', function() {
var remote = new Remote(); var remote = new Remote();
@@ -371,6 +376,16 @@ describe('Transaction', function() {
done(); done();
}); });
it('Complete transaction, local signing, no remote', function(done) {
var transaction = new Transaction();
transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij';
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
assert(transaction.complete());
done();
});
it('Complete transaction - untrusted', function(done) { it('Complete transaction - untrusted', function(done) {
var remote = new Remote(); var remote = new Remote();
var transaction = new Transaction(remote); var transaction = new Transaction(remote);
@@ -820,6 +835,20 @@ describe('Transaction', function() {
assert(transaction._setLastLedger); assert(transaction._setLastLedger);
}); });
it('Set Max Fee', function() {
var transaction = new Transaction();
transaction.maxFee('a');
assert(!transaction._setMaxFee);
transaction.maxFee(NaN);
assert(!transaction._setMaxFee);
transaction.maxFee(1000);
assert.strictEqual(transaction._maxFee, 1000);
assert.strictEqual(transaction._setMaxFee, true);
});
it('Rewrite transaction path', function() { it('Rewrite transaction path', function() {
var transaction = new Transaction(); var transaction = new Transaction();
@@ -1505,6 +1534,18 @@ describe('Transaction', function() {
transaction.submit(submitCallback); transaction.submit(submitCallback);
}); });
it('Submit transaction - submission error, no remote', function(done) {
var transaction = new Transaction();
transaction.once('error', function(error) {
assert(error);
assert.strictEqual(error.message, 'No remote found');
done();
});
transaction.submit();
});
it('Submit transaction - invalid account', function(done) { it('Submit transaction - invalid account', function(done) {
var remote = new Remote(); var remote = new Remote();
var transaction = new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); var transaction = new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
@@ -1519,33 +1560,34 @@ describe('Transaction', function() {
transaction.submit(); transaction.submit();
}); });
it.skip('Abort submission', function(done) { it('Abort submission on presubmit', function(done) {
var remote = new Remote(); var remote = new Remote();
var transaction = new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); remote.setSecret('rJaT8TafQfYJqDm8aC5n3Yx5yWEL2Ery79', 'snPwFATthTkKnGjEW73q3TL4yci1Q');
var account = remote.addAccount('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
var server = new Server(remote, 'wss://s1.ripple.com:443');
server._computeFee = function() { return '12'; };
server._connected = true;
remote._servers.push(server);
remote._connected = true;
remote._ledger_current_index = 1;
var transaction = new Transaction(remote).accountSet('rJaT8TafQfYJqDm8aC5n3Yx5yWEL2Ery79');
var account = remote.account('rJaT8TafQfYJqDm8aC5n3Yx5yWEL2Ery79');
account._transactionManager._nextSequence = 1; account._transactionManager._nextSequence = 1;
account._transactionManager._request = function(tx) { transaction.once('presubmit', function() {
setTimeout(function() { transaction.abort();
tx.emit('success', { }); });
}, 20);
};
transaction.complete = function() { transaction.submit(function(err, res) {
return this;
};
function submitCallback(err, res) {
setImmediate(function() { setImmediate(function() {
assert(err); assert(err);
assert.strictEqual(err.result, 'tejAbort'); assert.strictEqual(err.result, 'tejAbort');
done(); done();
}); });
}; });
transaction.submit(submitCallback);
transaction.abort();
}); });
}); });