divvy-rest
Version:
A RESTful API for submitting payments and monitoring accounts on the Divvy network.
287 lines (247 loc) • 8.06 kB
JavaScript
/* eslint-disable valid-jsdoc */
;
var _ = require('lodash');
var assert = require('assert');
var divvy = require('divvy-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 validate = require('./lib/validate');
var InvalidRequestError = errors.InvalidRequestError;
var AccountRootFlags = {
PasswordSpent: {
name: 'password_spent',
value: divvy.Remote.flags.account_root.PasswordSpent
},
RequireDestTag: {
name: 'require_destination_tag',
value: divvy.Remote.flags.account_root.RequireDestTag
},
RequireAuth: {
name: 'require_authorization',
value: divvy.Remote.flags.account_root.RequireAuth
},
DisallowXDV: {
name: 'disallow_xdv',
value: divvy.Remote.flags.account_root.DisallowXDV
},
DisableMaster: {
name: 'disable_master',
value: divvy.Remote.flags.account_root.DisableMaster
},
NoFreeze: {
name: 'no_freeze',
value: 0x00200000
},
GlobalFreeze: {
name: 'global_freeze',
value: 0x00400000
},
DefaultDivvy: {
name: 'default_divvy',
value: divvy.Remote.flags.account_root.DefaultDivvy
}
};
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: divvy.Transaction.set_clear_flags.AccountSet.asfNoFreeze},
GlobalFreeze: {name: 'global_freeze',
value: divvy.Transaction.set_clear_flags.AccountSet.asfGlobalFreeze},
DefaultDivvy: {name: 'default_divvy',
value: divvy.Transaction.set_clear_flags.AccountSet.asfDefaultDivvy}
};
var AccountSetFlags = {
RequireDestTag: {name: 'require_destination_tag', set: 'RequireDestTag',
unset: 'OptionalDestTag'},
RequireAuth: {name: 'require_authorization', set: 'RequireAuth',
unset: 'OptionalAuth'},
DisallowXDV: {name: 'disallow_xdv', set: 'DisallowXDV', unset: 'AllowXDV'}
};
// 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) {
validate.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 divvyd has finished validating the submitted transaction
*
*/
function changeSettings(account, settings, secret, options, callback) {
var params = {
secret: secret,
validated: options.validated
};
validate.address(account);
validate.settings(settings);
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 = {
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
};