mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-12-06 17:27:59 +00:00
Accommodate new transaction manager
This commit is contained in:
@@ -11,43 +11,45 @@
|
|||||||
|
|
||||||
// var network = require("./network.js");
|
// var network = require("./network.js");
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
var extend = require('extend');
|
||||||
|
|
||||||
var Amount = require('./amount').Amount;
|
var Amount = require('./amount').Amount;
|
||||||
var UInt160 = require('./uint160').UInt160;
|
var UInt160 = require('./uint160').UInt160;
|
||||||
|
var TransactionManager = require('./transactionmanager').TransactionManager;
|
||||||
|
|
||||||
var extend = require('extend');
|
|
||||||
|
|
||||||
var Account = function (remote, account) {
|
function Account(remote, account) {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._remote = remote;
|
this._tx_manager = null;
|
||||||
this._account = UInt160.from_json(account);
|
this._remote = remote;
|
||||||
|
this._account = UInt160.from_json(account);
|
||||||
this._account_id = this._account.to_json();
|
this._account_id = this._account.to_json();
|
||||||
this._subs = 0;
|
this._subs = 0;
|
||||||
|
|
||||||
// Ledger entry object
|
// Ledger entry object
|
||||||
// Important: This must never be overwritten, only extend()-ed
|
// Important: This must never be overwritten, only extend()-ed
|
||||||
this._entry = {};
|
this._entry = { };
|
||||||
|
|
||||||
this.on('newListener', function (type, listener) {
|
this.on('newListener', function (type, listener) {
|
||||||
if (Account.subscribe_events.indexOf(type) !== -1) {
|
if (~Account.subscribe_events.indexOf(type)) {
|
||||||
if (!self._subs && 'open' === self._remote._online_state) {
|
if (!self._subs && self._remote._connected) {
|
||||||
self._remote.request_subscribe()
|
self._remote.request_subscribe()
|
||||||
.accounts(self._account_id)
|
.accounts(self._account_id)
|
||||||
.request();
|
.request();
|
||||||
}
|
}
|
||||||
self._subs += 1;
|
self._subs += 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('removeListener', function (type, listener) {
|
this.on('removeListener', function (type, listener) {
|
||||||
if (Account.subscribe_events.indexOf(type) !== -1) {
|
if (~Account.subscribe_events.indexOf(type)) {
|
||||||
self._subs -= 1;
|
self._subs -= 1;
|
||||||
|
if (!self._subs && self._remote._connected) {
|
||||||
if (!self._subs && 'open' === self._remote._online_state) {
|
|
||||||
self._remote.request_unsubscribe()
|
self._remote.request_unsubscribe()
|
||||||
.accounts(self._account_id)
|
.accounts(self._account_id)
|
||||||
.request();
|
.request();
|
||||||
@@ -83,8 +85,7 @@ util.inherits(Account, EventEmitter);
|
|||||||
*/
|
*/
|
||||||
Account.subscribe_events = ['transaction', 'entry'];
|
Account.subscribe_events = ['transaction', 'entry'];
|
||||||
|
|
||||||
Account.prototype.to_json = function ()
|
Account.prototype.to_json = function () {
|
||||||
{
|
|
||||||
return this._account.to_json();
|
return this._account.to_json();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,8 +94,7 @@ Account.prototype.to_json = function ()
|
|||||||
*
|
*
|
||||||
* Note: This does not tell you whether the account exists in the ledger.
|
* Note: This does not tell you whether the account exists in the ledger.
|
||||||
*/
|
*/
|
||||||
Account.prototype.is_valid = function ()
|
Account.prototype.is_valid = function () {
|
||||||
{
|
|
||||||
return this._account.is_valid();
|
return this._account.is_valid();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,18 +106,17 @@ Account.prototype.is_valid = function ()
|
|||||||
*
|
*
|
||||||
* @param {function (err, entry)} callback Called with the result
|
* @param {function (err, entry)} callback Called with the result
|
||||||
*/
|
*/
|
||||||
Account.prototype.entry = function (callback)
|
Account.prototype.entry = function (callback) {
|
||||||
{
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var callback = typeof callback === 'function'
|
||||||
|
? callback
|
||||||
|
: function(){};
|
||||||
|
|
||||||
self._remote.request_account_info(this._account_id)
|
self._remote.request_account_info(this._account_id)
|
||||||
.on('success', function (e) {
|
.on('success', function (e) {
|
||||||
extend(self._entry, e.account_data);
|
extend(self._entry, e.account_data);
|
||||||
self.emit('entry', self._entry);
|
self.emit('entry', self._entry);
|
||||||
|
callback(null, e);
|
||||||
if ("function" === typeof callback) {
|
|
||||||
callback(null, e);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.on('error', function (e) {
|
.on('error', function (e) {
|
||||||
callback(e);
|
callback(e);
|
||||||
@@ -127,14 +126,23 @@ Account.prototype.entry = function (callback)
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Account.prototype.get_next_sequence = function(callback) {
|
||||||
|
this._remote.request_account_info(this._account_id, function(err, entry) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
callback(null, entry.account_data.Sequence);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify object of a relevant transaction.
|
* Notify object of a relevant transaction.
|
||||||
*
|
*
|
||||||
* This is only meant to be called by the Remote class. You should never have to
|
* This is only meant to be called by the Remote class. You should never have to
|
||||||
* call this yourself.
|
* call this yourself.
|
||||||
*/
|
*/
|
||||||
Account.prototype.notifyTx = function (message)
|
Account.prototype.notifyTx = function (message) {
|
||||||
{
|
|
||||||
// Only trigger the event if the account object is actually
|
// Only trigger the event if the account object is actually
|
||||||
// subscribed - this prevents some weird phantom events from
|
// subscribed - this prevents some weird phantom events from
|
||||||
// occurring.
|
// occurring.
|
||||||
@@ -143,6 +151,13 @@ Account.prototype.notifyTx = function (message)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Account.prototype.submit = function(tx) {
|
||||||
|
if (!this._tx_manager) {
|
||||||
|
this._tx_manager = new TransactionManager(this);
|
||||||
|
}
|
||||||
|
this._tx_manager.submit(tx);
|
||||||
|
};
|
||||||
|
|
||||||
exports.Account = Account;
|
exports.Account = Account;
|
||||||
|
|
||||||
// vim:sw=2:sts=2:ts=8:et
|
// vim:sw=2:sts=2:ts=8:et
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
//
|
|
||||||
// Transactions
|
// Transactions
|
||||||
//
|
//
|
||||||
// Construction:
|
// Construction:
|
||||||
@@ -56,83 +55,63 @@ var SerializedObject = require('./serializedobject').SerializedObject;
|
|||||||
|
|
||||||
var config = require('./config');
|
var config = require('./config');
|
||||||
|
|
||||||
var SUBMIT_MISSING = 4; // Report missing.
|
|
||||||
var SUBMIT_LOST = 8; // Give up tracking.
|
|
||||||
|
|
||||||
// A class to implement transactions.
|
// A class to implement transactions.
|
||||||
// - Collects parameters
|
// - Collects parameters
|
||||||
// - Allow event listeners to be attached to determine the outcome.
|
// - Allow event listeners to be attached to determine the outcome.
|
||||||
var Transaction = function (remote) {
|
function Transaction(remote) {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
// YYY Make private as many variables as possible.
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.callback = undefined;
|
this.remote = remote;
|
||||||
this.remote = remote;
|
this._secret = void(0);
|
||||||
this._secret = undefined;
|
this._build_path = false;
|
||||||
this._build_path = false;
|
|
||||||
|
|
||||||
// Transaction data.
|
// Transaction data.
|
||||||
this.tx_json = {
|
this.tx_json = { Flags: 0 };
|
||||||
'Flags' : 0, // XXX Would be nice if server did not require this.
|
|
||||||
};
|
|
||||||
|
|
||||||
this.hash = undefined;
|
this.hash = void(0);
|
||||||
this.submit_index = undefined; // ledger_current_index was this when transaction was submited.
|
|
||||||
this.state = undefined; // Under construction.
|
|
||||||
this.finalized = false;
|
|
||||||
|
|
||||||
this.on('success', function (message) {
|
// ledger_current_index was this when transaction was submited.
|
||||||
if (message.engine_result) {
|
this.submit_index = void(0);
|
||||||
self.hash = message.tx_json.hash;
|
|
||||||
|
|
||||||
self.set_state('client_proposed');
|
// Under construction.
|
||||||
|
this.state = void(0);
|
||||||
|
|
||||||
self.emit('proposed', {
|
this.finalized = false;
|
||||||
'tx_json' : message.tx_json,
|
this._previous_signing_hash = void(0);
|
||||||
'result' : message.engine_result,
|
|
||||||
'result_code' : message.engine_result_code,
|
|
||||||
'result_message' : message.engine_result_message,
|
|
||||||
'rejected' : self.isRejected(message.engine_result_code), // If server is honest, don't expect a final if rejected.
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('error', function (message) {
|
|
||||||
// Might want to give more detailed information.
|
|
||||||
self.set_state('remoteError');
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
util.inherits(Transaction, EventEmitter);
|
util.inherits(Transaction, EventEmitter);
|
||||||
|
|
||||||
// XXX This needs to be determined from the network.
|
// XXX This needs to be determined from the network.
|
||||||
Transaction.fees = {
|
Transaction.fees = {
|
||||||
'default' : 10,
|
default : Amount.from_json('10'),
|
||||||
|
nickname_create : Amount.from_json('1000'),
|
||||||
|
offer : Amount.from_json('10'),
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.flags = {
|
Transaction.flags = {
|
||||||
'AccountSet' : {
|
AccountSet : {
|
||||||
'RequireDestTag' : 0x00010000,
|
RequireDestTag : 0x00010000,
|
||||||
'OptionalDestTag' : 0x00020000,
|
OptionalDestTag : 0x00020000,
|
||||||
'RequireAuth' : 0x00040000,
|
RequireAuth : 0x00040000,
|
||||||
'OptionalAuth' : 0x00080000,
|
OptionalAuth : 0x00080000,
|
||||||
'DisallowXRP' : 0x00100000,
|
DisallowXRP : 0x00100000,
|
||||||
'AllowXRP' : 0x00200000,
|
AllowXRP : 0x00200000,
|
||||||
},
|
},
|
||||||
|
|
||||||
'OfferCreate' : {
|
OfferCreate : {
|
||||||
'Passive' : 0x00010000,
|
Passive : 0x00010000,
|
||||||
'ImmediateOrCancel' : 0x00020000,
|
ImmediateOrCancel : 0x00020000,
|
||||||
'FillOrKill' : 0x00040000,
|
FillOrKill : 0x00040000,
|
||||||
'Sell' : 0x00080000,
|
Sell : 0x00080000,
|
||||||
},
|
},
|
||||||
|
|
||||||
'Payment' : {
|
Payment : {
|
||||||
'NoRippleDirect' : 0x00010000,
|
NoRippleDirect : 0x00010000,
|
||||||
'PartialPayment' : 0x00020000,
|
PartialPayment : 0x00020000,
|
||||||
'LimitQuality' : 0x00040000,
|
LimitQuality : 0x00040000,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -185,6 +164,15 @@ Transaction.prototype.set_state = function (state) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* Actually do this right
|
||||||
|
*/
|
||||||
|
|
||||||
|
Transaction.prototype.get_fee = function() {
|
||||||
|
return Transaction.fees['default'].to_json();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to complete the transaction for submission.
|
* Attempts to complete the transaction for submission.
|
||||||
*
|
*
|
||||||
@@ -195,11 +183,11 @@ Transaction.prototype.set_state = function (state) {
|
|||||||
Transaction.prototype.complete = function () {
|
Transaction.prototype.complete = function () {
|
||||||
var tx_json = this.tx_json;
|
var tx_json = this.tx_json;
|
||||||
|
|
||||||
if ("undefined" === typeof tx_json.Fee && this.remote.local_fee) {
|
if (tx_json.Fee === void(0) && this.remote.local_fee) {
|
||||||
this.tx_json.Fee = "" + Math.ceil(this.remote.fee_tx() * this.fee_units());
|
tx_json.Fee = Transaction.fees['default'].to_json();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("undefined" === typeof tx_json.SigningPubKey && (!this.remote || this.remote.local_signing)) {
|
if (tx_json.SigningPubKey === void(0) && (!this.remote || this.remote.local_signing)) {
|
||||||
var seed = Seed.from_json(this._secret);
|
var seed = Seed.from_json(this._secret);
|
||||||
var key = seed.get_key(this.tx_json.Account);
|
var key = seed.get_key(this.tx_json.Account);
|
||||||
tx_json.SigningPubKey = key.to_hex_pub();
|
tx_json.SigningPubKey = key.to_hex_pub();
|
||||||
@@ -223,6 +211,11 @@ Transaction.prototype.signing_hash = function () {
|
|||||||
Transaction.prototype.sign = function () {
|
Transaction.prototype.sign = function () {
|
||||||
var seed = Seed.from_json(this._secret);
|
var seed = Seed.from_json(this._secret);
|
||||||
var hash = this.signing_hash();
|
var hash = this.signing_hash();
|
||||||
|
|
||||||
|
if (this.tx_json.TxnSignature && hash === this._previous_signing_hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var key = seed.get_key(this.tx_json.Account);
|
var key = seed.get_key(this.tx_json.Account);
|
||||||
var sig = key.sign(hash, 0);
|
var sig = key.sign(hash, 0);
|
||||||
var hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
|
var hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
|
||||||
@@ -236,206 +229,6 @@ Transaction.prototype._hasTransactionListeners = function() {
|
|||||||
|| this.listeners('pending').length
|
|| this.listeners('pending').length
|
||||||
};
|
};
|
||||||
|
|
||||||
// Submit a transaction to the network.
|
|
||||||
// XXX Don't allow a submit without knowing ledger_index.
|
|
||||||
// XXX Have a network canSubmit(), post events for following.
|
|
||||||
// XXX Also give broader status for tracking through network disconnects.
|
|
||||||
// callback = function (status, info) {
|
|
||||||
// // status is final status. Only works under a ledger_accepting conditions.
|
|
||||||
// switch status:
|
|
||||||
// case 'tesSUCCESS': all is well.
|
|
||||||
// case 'tejSecretUnknown': unable to sign transaction - secret unknown
|
|
||||||
// case 'tejServerUntrusted': sending secret to untrusted server.
|
|
||||||
// case 'tejInvalidAccount': locally detected error.
|
|
||||||
// case 'tejLost': locally gave up looking
|
|
||||||
// default: some other TER
|
|
||||||
// }
|
|
||||||
|
|
||||||
Transaction.prototype.submit = function (callback) {
|
|
||||||
var self = this;
|
|
||||||
var tx_json = this.tx_json;
|
|
||||||
|
|
||||||
this.callback = typeof callback === 'function'
|
|
||||||
? callback
|
|
||||||
: function(){};
|
|
||||||
|
|
||||||
function finish(err) {
|
|
||||||
self.emit('error', err);
|
|
||||||
self.callback('error', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof tx_json.Account !== 'string') {
|
|
||||||
finish({
|
|
||||||
'error' : 'tejInvalidAccount',
|
|
||||||
'error_message' : 'Bad account.'
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// YYY Might check paths for invalid accounts.
|
|
||||||
|
|
||||||
this.complete();
|
|
||||||
|
|
||||||
//console.log('Callback or has listeners');
|
|
||||||
|
|
||||||
// There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them.
|
|
||||||
|
|
||||||
this.submit_index = this.remote._ledger_current_index;
|
|
||||||
|
|
||||||
// When a ledger closes, look for the result.
|
|
||||||
function on_ledger_closed(message) {
|
|
||||||
if (self.finalized) return;
|
|
||||||
|
|
||||||
var ledger_hash = message.ledger_hash;
|
|
||||||
var ledger_index = message.ledger_index;
|
|
||||||
var stop = false;
|
|
||||||
|
|
||||||
// XXX make sure self.hash is available.
|
|
||||||
var transaction_entry = self.remote.request_transaction_entry(self.hash)
|
|
||||||
|
|
||||||
transaction_entry.ledger_hash(ledger_hash)
|
|
||||||
|
|
||||||
transaction_entry.on('success', function (message) {
|
|
||||||
if (self.finalized) return;
|
|
||||||
self.set_state(message.metadata.TransactionResult);
|
|
||||||
self.remote.removeListener('ledger_closed', on_ledger_closed);
|
|
||||||
self.emit('final', message);
|
|
||||||
self.finalized = true;
|
|
||||||
self.callback(message.metadata.TransactionResult, message);
|
|
||||||
});
|
|
||||||
|
|
||||||
transaction_entry.on('error', function (message) {
|
|
||||||
if (self.finalized) return;
|
|
||||||
|
|
||||||
if (message.error === 'remoteError' && message.remote.error === 'transactionNotFound') {
|
|
||||||
if (self.submit_index + SUBMIT_LOST < ledger_index) {
|
|
||||||
self.set_state('client_lost'); // Gave up.
|
|
||||||
self.emit('lost');
|
|
||||||
self.callback('tejLost', message);
|
|
||||||
self.remote.removeListener('ledger_closed', on_ledger_closed);
|
|
||||||
self.emit('final', message);
|
|
||||||
self.finalized = true;
|
|
||||||
} else if (self.submit_index + SUBMIT_MISSING < ledger_index) {
|
|
||||||
self.set_state('client_missing'); // We don't know what happened to transaction, still might find.
|
|
||||||
self.emit('pending');
|
|
||||||
} else {
|
|
||||||
self.emit('pending');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// XXX Could log other unexpectedness.
|
|
||||||
});
|
|
||||||
|
|
||||||
transaction_entry.request();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.remote.on('ledger_closed', on_ledger_closed);
|
|
||||||
|
|
||||||
this.once('error', function (message) {
|
|
||||||
self.callback(message.error, message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set_state('client_submitted');
|
|
||||||
|
|
||||||
if (self.remote.local_sequence && !self.tx_json.Sequence) {
|
|
||||||
|
|
||||||
self.tx_json.Sequence = this.remote.account_seq(self.tx_json.Account, 'ADVANCE');
|
|
||||||
// console.log("Sequence: %s", self.tx_json.Sequence);
|
|
||||||
|
|
||||||
if (!self.tx_json.Sequence) {
|
|
||||||
//console.log('NO SEQUENCE');
|
|
||||||
|
|
||||||
// Look in the last closed ledger.
|
|
||||||
var account_seq = this.remote.account_seq_cache(self.tx_json.Account, false)
|
|
||||||
|
|
||||||
account_seq.on('success_account_seq_cache', function () {
|
|
||||||
// Try again.
|
|
||||||
self.submit();
|
|
||||||
})
|
|
||||||
|
|
||||||
account_seq.on('error_account_seq_cache', function (message) {
|
|
||||||
// XXX Maybe be smarter about this. Don't want to trust an untrusted server for this seq number.
|
|
||||||
// Look in the current ledger.
|
|
||||||
self.remote.account_seq_cache(self.tx_json.Account, 'CURRENT')
|
|
||||||
.on('success_account_seq_cache', function () {
|
|
||||||
// Try again.
|
|
||||||
self.submit();
|
|
||||||
})
|
|
||||||
.on('error_account_seq_cache', function (message) {
|
|
||||||
// Forward errors.
|
|
||||||
self.emit('error', message);
|
|
||||||
})
|
|
||||||
.request();
|
|
||||||
})
|
|
||||||
|
|
||||||
account_seq.request();
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the transaction fails we want to either undo incrementing the sequence
|
|
||||||
// or submit a noop transaction to consume the sequence remotely.
|
|
||||||
this.once('success', function (res) {
|
|
||||||
if (typeof res.engine_result === 'string') {
|
|
||||||
switch (res.engine_result.slice(0, 3)) {
|
|
||||||
// Synchronous local error
|
|
||||||
case 'tej':
|
|
||||||
self.remote.account_seq(self.tx_json.Account, 'REWIND');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ter':
|
|
||||||
// XXX: What do we do in case of ter?
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tel':
|
|
||||||
case 'tem':
|
|
||||||
case 'tef':
|
|
||||||
// XXX Once we have a transaction submission manager class, we can
|
|
||||||
// check if there are any other transactions pending. If there are,
|
|
||||||
// we should submit a dummy transaction to ensure those
|
|
||||||
// transactions are still valid.
|
|
||||||
//var noop = self.remote.transaction().account_set(self.tx_json.Account);
|
|
||||||
//noop.submit();
|
|
||||||
|
|
||||||
// XXX Hotfix. This only works if no other transactions are pending.
|
|
||||||
self.remote.account_seq(self.tx_json.Account, 'REWIND');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare request
|
|
||||||
var request = this.remote.request_submit();
|
|
||||||
|
|
||||||
// Forward events
|
|
||||||
request.emit = this.emit.bind(this);
|
|
||||||
|
|
||||||
if (!this._secret && !this.tx_json.Signature) {
|
|
||||||
finish({
|
|
||||||
'result' : 'tejSecretUnknown',
|
|
||||||
'result_message' : "Could not sign transactions because we."
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
} else if (this.remote.local_signing) {
|
|
||||||
this.sign();
|
|
||||||
request.tx_blob(this.serialize().to_hex());
|
|
||||||
} else {
|
|
||||||
if (!this.remote.trusted) {
|
|
||||||
finish({
|
|
||||||
'result' : 'tejServerUntrusted',
|
|
||||||
'result_message' : "Attempt to give a secret to an untrusted server."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
request.secret(this._secret);
|
|
||||||
request.build_path(this._build_path);
|
|
||||||
request.tx_json(this.tx_json);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.request();
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Set options for Transactions
|
// Set options for Transactions
|
||||||
@@ -453,7 +246,7 @@ Transaction.prototype.build_path = function (build) {
|
|||||||
// tag should be undefined or a 32 bit integer.
|
// tag should be undefined or a 32 bit integer.
|
||||||
// YYY Add range checking for tag.
|
// YYY Add range checking for tag.
|
||||||
Transaction.prototype.destination_tag = function (tag) {
|
Transaction.prototype.destination_tag = function (tag) {
|
||||||
if (tag !== undefined) {
|
if (tag !== void(0)) {
|
||||||
this.tx_json.DestinationTag = tag;
|
this.tx_json.DestinationTag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,9 +256,9 @@ Transaction.prototype.destination_tag = function (tag) {
|
|||||||
Transaction._path_rewrite = function (path) {
|
Transaction._path_rewrite = function (path) {
|
||||||
var path_new = [];
|
var path_new = [];
|
||||||
|
|
||||||
for (var i = 0, l = path.length; i < l; i++) {
|
for (var i=0, l=path.length; i<l; i++) {
|
||||||
var node = path[i];
|
var node = path[i];
|
||||||
var node_new = {};
|
var node_new = {};
|
||||||
|
|
||||||
if ('account' in node)
|
if ('account' in node)
|
||||||
node_new.account = UInt160.json_rewrite(node.account);
|
node_new.account = UInt160.json_rewrite(node.account);
|
||||||
@@ -492,7 +285,7 @@ Transaction.prototype.path_add = function (path) {
|
|||||||
// --> paths: undefined or array of path
|
// --> paths: undefined or array of path
|
||||||
// A path is an array of objects containing some combination of: account, currency, issuer
|
// A path is an array of objects containing some combination of: account, currency, issuer
|
||||||
Transaction.prototype.paths = function (paths) {
|
Transaction.prototype.paths = function (paths) {
|
||||||
for (var i = 0, l = paths.length; i < l; i++) {
|
for (var i=0, l=paths.length; i<l; i++) {
|
||||||
this.path_add(paths[i]);
|
this.path_add(paths[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,7 +333,7 @@ Transaction.prototype.set_flags = function (flags) {
|
|||||||
var transaction_flags = Transaction.flags[this.tx_json.TransactionType];
|
var transaction_flags = Transaction.flags[this.tx_json.TransactionType];
|
||||||
|
|
||||||
// We plan to not define this field on new Transaction.
|
// We plan to not define this field on new Transaction.
|
||||||
if (this.tx_json.Flags === undefined) {
|
if (this.tx_json.Flags === void(0)) {
|
||||||
this.tx_json.Flags = 0;
|
this.tx_json.Flags = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,20 +380,20 @@ Transaction.prototype.account_set = function (src) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype.claim = function (src, generator, public_key, signature) {
|
Transaction.prototype.claim = function (src, generator, public_key, signature) {
|
||||||
this._secret = this._account_secret(src);
|
this._secret = this._account_secret(src);
|
||||||
this.tx_json.TransactionType = 'Claim';
|
this.tx_json.TransactionType = 'Claim';
|
||||||
this.tx_json.Generator = generator;
|
this.tx_json.Generator = generator;
|
||||||
this.tx_json.PublicKey = public_key;
|
this.tx_json.PublicKey = public_key;
|
||||||
this.tx_json.Signature = signature;
|
this.tx_json.Signature = signature;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype.offer_cancel = function (src, sequence) {
|
Transaction.prototype.offer_cancel = function (src, sequence) {
|
||||||
this._secret = this._account_secret(src);
|
this._secret = this._account_secret(src);
|
||||||
this.tx_json.TransactionType = 'OfferCancel';
|
this.tx_json.TransactionType = 'OfferCancel';
|
||||||
this.tx_json.Account = UInt160.json_rewrite(src);
|
this.tx_json.Account = UInt160.json_rewrite(src);
|
||||||
this.tx_json.OfferSequence = Number(sequence);
|
this.tx_json.OfferSequence = Number(sequence);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@@ -610,11 +403,15 @@ Transaction.prototype.offer_cancel = function (src, sequence) {
|
|||||||
// --> expiration : if not undefined, Date or Number
|
// --> expiration : if not undefined, Date or Number
|
||||||
// --> cancel_sequence : if not undefined, Sequence
|
// --> cancel_sequence : if not undefined, Sequence
|
||||||
Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expiration, cancel_sequence) {
|
Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expiration, cancel_sequence) {
|
||||||
this._secret = this._account_secret(src);
|
this._secret = this._account_secret(src);
|
||||||
this.tx_json.TransactionType = 'OfferCreate';
|
this.tx_json.TransactionType = 'OfferCreate';
|
||||||
this.tx_json.Account = UInt160.json_rewrite(src);
|
this.tx_json.Account = UInt160.json_rewrite(src);
|
||||||
this.tx_json.TakerPays = Amount.json_rewrite(taker_pays);
|
this.tx_json.TakerPays = Amount.json_rewrite(taker_pays);
|
||||||
this.tx_json.TakerGets = Amount.json_rewrite(taker_gets);
|
this.tx_json.TakerGets = Amount.json_rewrite(taker_gets);
|
||||||
|
|
||||||
|
if (this.remote.local_fee) {
|
||||||
|
this.tx_json.Fee = Transaction.fees.offer.to_json();
|
||||||
|
}
|
||||||
|
|
||||||
if (expiration) {
|
if (expiration) {
|
||||||
this.tx_json.Expiration = expiration instanceof Date
|
this.tx_json.Expiration = expiration instanceof Date
|
||||||
@@ -630,20 +427,20 @@ Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expi
|
|||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype.password_fund = function (src, dst) {
|
Transaction.prototype.password_fund = function (src, dst) {
|
||||||
this._secret = this._account_secret(src);
|
this._secret = this._account_secret(src);
|
||||||
this.tx_json.TransactionType = 'PasswordFund';
|
this.tx_json.TransactionType = 'PasswordFund';
|
||||||
this.tx_json.Destination = UInt160.json_rewrite(dst);
|
this.tx_json.Destination = UInt160.json_rewrite(dst);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction.prototype.password_set = function (src, authorized_key, generator, public_key, signature) {
|
Transaction.prototype.password_set = function (src, authorized_key, generator, public_key, signature) {
|
||||||
this._secret = this._account_secret(src);
|
this._secret = this._account_secret(src);
|
||||||
this.tx_json.TransactionType = 'PasswordSet';
|
this.tx_json.TransactionType = 'PasswordSet';
|
||||||
this.tx_json.RegularKey = authorized_key;
|
this.tx_json.RegularKey = authorized_key;
|
||||||
this.tx_json.Generator = generator;
|
this.tx_json.Generator = generator;
|
||||||
this.tx_json.PublicKey = public_key;
|
this.tx_json.PublicKey = public_key;
|
||||||
this.tx_json.Signature = signature;
|
this.tx_json.Signature = signature;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -666,19 +463,19 @@ Transaction.prototype.password_set = function (src, authorized_key, generator, p
|
|||||||
// .set_flags()
|
// .set_flags()
|
||||||
// .source_tag()
|
// .source_tag()
|
||||||
Transaction.prototype.payment = function (src, dst, deliver_amount) {
|
Transaction.prototype.payment = function (src, dst, deliver_amount) {
|
||||||
this._secret = this._account_secret(src);
|
this._secret = this._account_secret(src);
|
||||||
this.tx_json.TransactionType = 'Payment';
|
this.tx_json.TransactionType = 'Payment';
|
||||||
this.tx_json.Account = UInt160.json_rewrite(src);
|
this.tx_json.Account = UInt160.json_rewrite(src);
|
||||||
this.tx_json.Amount = Amount.json_rewrite(deliver_amount);
|
this.tx_json.Amount = Amount.json_rewrite(deliver_amount);
|
||||||
this.tx_json.Destination = UInt160.json_rewrite(dst);
|
this.tx_json.Destination = UInt160.json_rewrite(dst);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction.prototype.ripple_line_set = function (src, limit, quality_in, quality_out) {
|
Transaction.prototype.ripple_line_set = function (src, limit, quality_in, quality_out) {
|
||||||
this._secret = this._account_secret(src);
|
this._secret = this._account_secret(src);
|
||||||
this.tx_json.TransactionType = 'TrustSet';
|
this.tx_json.TransactionType = 'TrustSet';
|
||||||
this.tx_json.Account = UInt160.json_rewrite(src);
|
this.tx_json.Account = UInt160.json_rewrite(src);
|
||||||
|
|
||||||
// Allow limit of 0 through.
|
// Allow limit of 0 through.
|
||||||
if (limit !== undefined)
|
if (limit !== undefined)
|
||||||
@@ -706,20 +503,49 @@ Transaction.prototype.wallet_add = function (src, amount, authorized_key, public
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// Submit a transaction to the network.
|
||||||
* Returns the number of fee units this transaction will cost.
|
// XXX Don't allow a submit without knowing ledger_index.
|
||||||
*
|
// XXX Have a network canSubmit(), post events for following.
|
||||||
* Each Ripple transaction based on its type and makeup costs a certain number
|
// XXX Also give broader status for tracking through network disconnects.
|
||||||
* of fee units. The fee units are calculated on a per-server basis based on the
|
// callback = function (status, info) {
|
||||||
* current load on both the network and the server.
|
// // status is final status. Only works under a ledger_accepting conditions.
|
||||||
*
|
// switch status:
|
||||||
* @see https://ripple.com/wiki/Transaction_Fee
|
// case 'tesSUCCESS': all is well.
|
||||||
*/
|
// case 'tejSecretUnknown': unable to sign transaction - secret unknown
|
||||||
Transaction.prototype.fee_units = function ()
|
// case 'tejServerUntrusted': sending secret to untrusted server.
|
||||||
{
|
// case 'tejInvalidAccount': locally detected error.
|
||||||
return Transaction.fees["default"];
|
// case 'tejLost': locally gave up looking
|
||||||
};
|
// default: some other TER
|
||||||
|
// }
|
||||||
|
|
||||||
exports.Transaction = Transaction;
|
Transaction.prototype.submit = function (callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.callback = (typeof callback === 'function') ? callback : function(){};
|
||||||
|
|
||||||
|
this.once('error', function transaction_error(error, message) {
|
||||||
|
self.callback(error, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.once('success', function transaction_success(message) {
|
||||||
|
self.callback(null, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
var account = this.tx_json.Account;
|
||||||
|
|
||||||
|
if (typeof account !== 'string') {
|
||||||
|
this.emit('error', {
|
||||||
|
error: 'tejInvalidAccount',
|
||||||
|
error_message: 'Account is unspecified'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// YYY Might check paths for invalid accounts.
|
||||||
|
this.remote.get_account(account).submit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.Transaction = Transaction;
|
||||||
|
|
||||||
// vim:sw=2:sts=2:ts=8:et
|
// vim:sw=2:sts=2:ts=8:et
|
||||||
|
|||||||
Reference in New Issue
Block a user