UNPKG

ripple-rest-dinex

Version:

A RESTful API for submitting payments and monitoring accounts on the Ripple network.

340 lines (297 loc) 11.2 kB
/* eslint-disable valid-jsdoc */ 'use strict'; var _ = require('lodash'); var assert = require('assert'); var ripple = require('ripple-lib'); var transactions = require('./transactions.js'); var SubmitTransactionHooks = require('./lib/submit_transaction_hooks.js'); var errors = require('./lib/errors.js'); var TxToRestConverter = require('./lib/tx-to-rest-converter.js'); var RestToTxConverter = require('./lib/rest-to-tx-converter.js'); var InvalidRequestError = errors.InvalidRequestError; var AccountRootFlags = { PasswordSpent: { name: 'password_spent', value: ripple.Remote.flags.account_root.PasswordSpent }, RequireDestTag: { name: 'require_destination_tag', value: ripple.Remote.flags.account_root.RequireDestTag }, RequireAuth: { name: 'require_authorization', value: ripple.Remote.flags.account_root.RequireAuth }, DisallowXRP: { name: 'disallow_xrp', value: ripple.Remote.flags.account_root.DisallowXRP }, DisableMaster: { name: 'disable_master', value: ripple.Remote.flags.account_root.DisableMaster }, NoFreeze: { name: 'no_freeze', value: 0x00200000}, GlobalFreeze: { name: 'global_freeze', value: 0x00400000}, DefaultRipple: { name: 'default_ripple', value: ripple.Remote.flags.account_root.DefaultRipple } }; var AccountRootFields = { Sequence: {name: 'transaction_sequence'}, EmailHash: {name: 'email_hash', encoding: 'hex', length: 32, defaults: '0'}, WalletLocator: {name: 'wallet_locator', encoding: 'hex', length: 64, defaults: '0'}, WalletSize: {name: 'wallet_size', defaults: 0}, MessageKey: {name: 'message_key'}, Domain: {name: 'domain', encoding: 'hex'}, TransferRate: {name: 'transfer_rate', defaults: 0}, Signers: {name: 'signers'} }; var AccountSetIntFlags = { NoFreeze: {name: 'no_freeze', value: ripple.Transaction.set_clear_flags.AccountSet.asfNoFreeze}, GlobalFreeze: {name: 'global_freeze', value: ripple.Transaction.set_clear_flags.AccountSet.asfGlobalFreeze}, DefaultRipple: { name: 'default_ripple', value: ripple.Transaction.set_clear_flags.AccountSet.asfDefaultRipple } }; var AccountSetFlags = { RequireDestTag: {name: 'require_destination_tag', set: 'RequireDestTag', unset: 'OptionalDestTag'}, RequireAuth: {name: 'require_authorization', set: 'RequireAuth', unset: 'OptionalAuth'}, DisallowXRP: {name: 'disallow_xrp', set: 'DisallowXRP', unset: 'AllowXRP'} }; // Emptry string passed to setting will clear it var CLEAR_SETTING = ''; /** * Pad the value of a fixed-length field * * @param {String} value * @param {Number} length * @return {String} */ function padValue(value, length) { assert.strictEqual(typeof value, 'string'); assert.strictEqual(typeof length, 'number'); var result = value; while (result.length < length) { result = '0' + result; } return result; } function parseFieldsFromResponse(responseBody, fields) { var parsedBody = {}; for (var fieldName in fields) { var field = fields[fieldName]; var value = responseBody[fieldName] || ''; if (field.encoding === 'hex' && !field.length) { value = new Buffer(value, 'hex').toString('ascii'); } parsedBody[field.name] = value; } return parsedBody; } /** * Set integer flags on a transaction based on input and a flag map * * @param {Transaction} transaction * @param {Object} input - Object whose properties determine whether * to update the transaction's SetFlag or ClearFlag property * @param {Object} flags - Object that maps property names to transaction * integer flag values * * @returns undefined */ function setTransactionIntFlags(transaction, input, flags) { for (var flagName in flags) { var flag = flags[flagName]; if (!input.hasOwnProperty(flag.name)) { continue; } var value = input[flag.name]; if (value) { transaction.tx_json.SetFlag = flag.value; } else { transaction.tx_json.ClearFlag = flag.value; } } } /** * Set fields on a transaction based on input and fields schema object * * @param {Transaction} transaction * @param {Object} input - Object whose properties are used to set fields on * the transaction * @param {Object} fieldSchema - Object that holds the schema of each field * * @returns undefined */ function setTransactionFields(transaction, input, fieldSchema) { for (var fieldName in fieldSchema) { var field = fieldSchema[fieldName]; var value = input[field.name]; if (typeof value === 'undefined') { continue; } // The value required to clear an account root field varies if (value === CLEAR_SETTING && field.hasOwnProperty('defaults')) { value = field.defaults; } if (field.encoding === 'hex') { // If the field is supposed to be hex, why don't we do a // toString('hex') on it? if (field.length) { // Field is fixed length, why are we checking here though? // We could move this to validateInputs if (value.length > field.length) { throw new InvalidRequestError( 'Parameter length exceeded: ' + fieldName); } else if (value.length < field.length) { value = padValue(value, field.length); } } else { // Field is variable length. Expecting an ascii string as input. // This is currently only used for Domain field value = new Buffer(value, 'ascii').toString('hex'); } value = value.toUpperCase(); } transaction.tx_json[fieldName] = value; } } /** * Retrieves account settings for a given account * * @url * @param {String} request.params.account * */ function getSettings(account, callback) { if (!ripple.UInt160.is_valid(account)) { return callback(new errors.InvalidRequestError( 'Parameter is not a valid Ripple address: account')); } this.remote.requestAccountInfo({account: account}, function(error, info) { if (error) { return callback(error); } var data = info.account_data; var settings = { account: data.Account, transfer_rate: '0' }; // Attach account flags _.extend(settings, TxToRestConverter.parseFlagsFromResponse(data.Flags, AccountRootFlags)); // Attach account fields _.extend(settings, parseFieldsFromResponse(data, AccountRootFields)); settings.transaction_sequence = String(settings.transaction_sequence); callback(null, {settings: settings}); }); } /** * Change account settings * * @body * @param {Settings} request.body.settings * @param {String} request.body.secret * * @query * @param {String "true"|"false"} request.query.validated Used to force request * to wait until rippled has finished validating the submitted transaction * */ function changeSettings(account, settings, secret, options, callback) { var params = { secret: secret, validated: options.validated }; function validateParams(_callback) { if (typeof settings !== 'object') { return _callback(new InvalidRequestError('Parameter missing: settings')); } if (!ripple.UInt160.is_valid(account)) { return _callback(new InvalidRequestError( 'Parameter is not a valid Ripple address: account')); } if (!/(undefined|string)/.test(typeof settings.domain)) { return _callback(new InvalidRequestError( 'Parameter must be a string: domain')); } if (!/(undefined|string)/.test(typeof settings.wallet_locator)) { return _callback(new InvalidRequestError( 'Parameter must be a string: wallet_locator')); } if (!/(undefined|string)/.test(typeof settings.email_hash)) { return _callback(new InvalidRequestError( 'Parameter must be a string: email_hash')); } if (!/(undefined|string)/.test(typeof settings.message_key)) { return _callback(new InvalidRequestError( 'Parameter must be a string: message_key')); } if (!/(undefined|number)/.test(typeof settings.transfer_rate)) { if (settings.transfer_rate !== '') { return _callback(new InvalidRequestError( 'Parameter must be a number: transfer_rate')); } } if (!/(undefined|number)/.test(typeof settings.wallet_size)) { if (settings.wallet_size !== '') { return _callback(new InvalidRequestError( 'Parameter must be a number: wallet_size')); } } if (!/(undefined|boolean)/.test(typeof settings.no_freeze)) { return _callback(new InvalidRequestError( 'Parameter must be a boolean: no_freeze')); } if (!/(undefined|boolean)/.test(typeof settings.global_freeze)) { return _callback(new InvalidRequestError( 'Parameter must be a boolean: global_freeze')); } if (!/(undefined|boolean)/.test(typeof settings.password_spent)) { return _callback(new InvalidRequestError( 'Parameter must be a boolean: password_spent')); } if (!/(undefined|boolean)/.test(typeof settings.disable_master)) { return _callback(new InvalidRequestError( 'Parameter must be a boolean: disable_master')); } if (!/(undefined|boolean)/.test(typeof settings.require_destination_tag)) { return _callback(new InvalidRequestError( 'Parameter must be a boolean: require_destination_tag')); } if (!/(undefined|boolean)/.test(typeof settings.require_authorization)) { return _callback(new InvalidRequestError( 'Parameter must be a boolean: require_authorization')); } if (!/(undefined|boolean)/.test(typeof settings.disallow_xrp)) { return _callback(new InvalidRequestError( 'Parameter must be a boolean: disallow_xrp')); } var setCollision = (typeof settings.no_freeze === 'boolean') && (typeof settings.global_freeze === 'boolean') && settings.no_freeze === settings.global_freeze; if (setCollision) { return _callback(new InvalidRequestError( 'Unable to set/clear no_freeze and global_freeze')); } _callback(); } function setTransactionParameters(transaction) { transaction.accountSet(account); transactions.setTransactionBitFlags(transaction, { input: settings, flags: AccountSetFlags, clear_setting: CLEAR_SETTING }); setTransactionIntFlags(transaction, settings, AccountSetIntFlags); setTransactionFields(transaction, settings, AccountRootFields); transaction.tx_json.TransferRate = RestToTxConverter.convertTransferRate( transaction.tx_json.TransferRate); } var hooks = { validateParams: validateParams, formatTransactionResponse: TxToRestConverter.parseSettingResponseFromTx .bind(undefined, settings), setTransactionParameters: setTransactionParameters }; transactions.submit(this, params, new SubmitTransactionHooks(hooks), function(err, settingsResult) { if (err) { return callback(err); } callback(null, settingsResult); }); } module.exports = { get: getSettings, change: changeSettings, AccountSetIntFlags: AccountSetIntFlags, AccountRootFields: AccountRootFields };