@nextrope/xrpl
Version:
A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser
286 lines • 13.2 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.autofillBatchTxn = exports.handleDeliverMax = exports.checkAccountDeleteBlockers = exports.setLatestValidatedLedgerSequence = exports.getTransactionFee = exports.setNextValidSequenceNumber = exports.setValidAddresses = exports.txNeedsNetworkID = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const ripple_address_codec_1 = require("ripple-address-codec");
const errors_1 = require("../errors");
const utils_1 = require("../utils");
const getFeeXrp_1 = __importDefault(require("./getFeeXrp"));
const LEDGER_OFFSET = 20;
const RESTRICTED_NETWORKS = 1024;
const REQUIRED_NETWORKID_VERSION = '1.11.0';
function isNotLaterRippledVersion(source, target) {
if (source === target) {
return true;
}
const sourceDecomp = source.split('.');
const targetDecomp = target.split('.');
const sourceMajor = parseInt(sourceDecomp[0], 10);
const sourceMinor = parseInt(sourceDecomp[1], 10);
const targetMajor = parseInt(targetDecomp[0], 10);
const targetMinor = parseInt(targetDecomp[1], 10);
if (sourceMajor !== targetMajor) {
return sourceMajor < targetMajor;
}
if (sourceMinor !== targetMinor) {
return sourceMinor < targetMinor;
}
const sourcePatch = sourceDecomp[2].split('-');
const targetPatch = targetDecomp[2].split('-');
const sourcePatchVersion = parseInt(sourcePatch[0], 10);
const targetPatchVersion = parseInt(targetPatch[0], 10);
if (sourcePatchVersion !== targetPatchVersion) {
return sourcePatchVersion < targetPatchVersion;
}
if (sourcePatch.length !== targetPatch.length) {
return sourcePatch.length > targetPatch.length;
}
if (sourcePatch.length === 2) {
if (!sourcePatch[1][0].startsWith(targetPatch[1][0])) {
return sourcePatch[1] < targetPatch[1];
}
if (sourcePatch[1].startsWith('b')) {
return (parseInt(sourcePatch[1].slice(1), 10) <
parseInt(targetPatch[1].slice(1), 10));
}
return (parseInt(sourcePatch[1].slice(2), 10) <
parseInt(targetPatch[1].slice(2), 10));
}
return false;
}
function txNeedsNetworkID(client) {
if (client.networkID !== undefined &&
client.networkID > RESTRICTED_NETWORKS) {
if (client.buildVersion &&
isNotLaterRippledVersion(REQUIRED_NETWORKID_VERSION, client.buildVersion)) {
return true;
}
}
return false;
}
exports.txNeedsNetworkID = txNeedsNetworkID;
function setValidAddresses(tx) {
validateAccountAddress(tx, 'Account', 'SourceTag');
if (tx['Destination'] != null) {
validateAccountAddress(tx, 'Destination', 'DestinationTag');
}
convertToClassicAddress(tx, 'Authorize');
convertToClassicAddress(tx, 'Unauthorize');
convertToClassicAddress(tx, 'Owner');
convertToClassicAddress(tx, 'RegularKey');
}
exports.setValidAddresses = setValidAddresses;
function validateAccountAddress(tx, accountField, tagField) {
const { classicAccount, tag } = getClassicAccountAndTag(tx[accountField]);
tx[accountField] = classicAccount;
if (tag != null && tag !== false) {
if (tx[tagField] && tx[tagField] !== tag) {
throw new errors_1.ValidationError(`The ${tagField}, if present, must match the tag of the ${accountField} X-address`);
}
tx[tagField] = tag;
}
}
function getClassicAccountAndTag(account, expectedTag) {
if ((0, ripple_address_codec_1.isValidXAddress)(account)) {
const classic = (0, ripple_address_codec_1.xAddressToClassicAddress)(account);
if (expectedTag != null && classic.tag !== expectedTag) {
throw new errors_1.ValidationError('address includes a tag that does not match the tag specified in the transaction');
}
return {
classicAccount: classic.classicAddress,
tag: classic.tag,
};
}
return {
classicAccount: account,
tag: expectedTag,
};
}
function convertToClassicAddress(tx, fieldName) {
const account = tx[fieldName];
if (typeof account === 'string') {
const { classicAccount } = getClassicAccountAndTag(account);
tx[fieldName] = classicAccount;
}
}
function getNextValidSequenceNumber(client, account) {
return __awaiter(this, void 0, void 0, function* () {
const request = {
command: 'account_info',
account,
ledger_index: 'current',
};
const data = yield client.request(request);
return data.result.account_data.Sequence;
});
}
function setNextValidSequenceNumber(client, tx) {
return __awaiter(this, void 0, void 0, function* () {
tx.Sequence = yield getNextValidSequenceNumber(client, tx.Account);
});
}
exports.setNextValidSequenceNumber = setNextValidSequenceNumber;
function fetchOwnerReserveFee(client) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const response = yield client.request({ command: 'server_state' });
const fee = (_a = response.result.state.validated_ledger) === null || _a === void 0 ? void 0 : _a.reserve_inc;
if (fee == null) {
return Promise.reject(new Error('Could not fetch Owner Reserve.'));
}
return new bignumber_js_1.default(fee);
});
}
function calculateFeePerTransactionType(client, tx, signersCount = 0) {
return __awaiter(this, void 0, void 0, function* () {
const netFeeXRP = yield (0, getFeeXrp_1.default)(client);
const netFeeDrops = (0, utils_1.xrpToDrops)(netFeeXRP);
let baseFee = new bignumber_js_1.default(netFeeDrops);
const isSpecialTxCost = ['AccountDelete', 'AMMCreate'].includes(tx.TransactionType);
if (tx.TransactionType === 'EscrowFinish' && tx.Fulfillment != null) {
const fulfillmentBytesSize = Math.ceil(tx.Fulfillment.length / 2);
baseFee = new bignumber_js_1.default(scaleValue(netFeeDrops, 33 + fulfillmentBytesSize / 16));
}
else if (isSpecialTxCost) {
baseFee = yield fetchOwnerReserveFee(client);
}
else if (tx.TransactionType === 'Batch') {
const rawTxFees = yield tx.RawTransactions.reduce((acc, rawTxn) => __awaiter(this, void 0, void 0, function* () {
const resolvedAcc = yield acc;
const fee = yield calculateFeePerTransactionType(client, rawTxn.RawTransaction);
return bignumber_js_1.default.sum(resolvedAcc, fee);
}), Promise.resolve(new bignumber_js_1.default(0)));
baseFee = bignumber_js_1.default.sum(baseFee.times(2), rawTxFees);
}
if (signersCount > 0) {
baseFee = bignumber_js_1.default.sum(baseFee, scaleValue(netFeeDrops, signersCount));
}
const maxFeeDrops = (0, utils_1.xrpToDrops)(client.maxFeeXRP);
const totalFee = isSpecialTxCost
? baseFee
: bignumber_js_1.default.min(baseFee, maxFeeDrops);
return totalFee.dp(0, bignumber_js_1.default.ROUND_CEIL);
});
}
function getTransactionFee(client, tx, signersCount = 0) {
return __awaiter(this, void 0, void 0, function* () {
const fee = yield calculateFeePerTransactionType(client, tx, signersCount);
tx.Fee = fee.toString(10);
});
}
exports.getTransactionFee = getTransactionFee;
function scaleValue(value, multiplier) {
return new bignumber_js_1.default(value).times(multiplier).toString();
}
function setLatestValidatedLedgerSequence(client, tx) {
return __awaiter(this, void 0, void 0, function* () {
const ledgerSequence = yield client.getLedgerIndex();
tx.LastLedgerSequence = ledgerSequence + LEDGER_OFFSET;
});
}
exports.setLatestValidatedLedgerSequence = setLatestValidatedLedgerSequence;
function checkAccountDeleteBlockers(client, tx) {
return __awaiter(this, void 0, void 0, function* () {
const request = {
command: 'account_objects',
account: tx.Account,
ledger_index: 'validated',
deletion_blockers_only: true,
};
const response = yield client.request(request);
return new Promise((resolve, reject) => {
if (response.result.account_objects.length > 0) {
reject(new errors_1.XrplError(`Account ${tx.Account} cannot be deleted; there are Escrows, PayChannels, RippleStates, or Checks associated with the account.`, response.result.account_objects));
}
resolve();
});
});
}
exports.checkAccountDeleteBlockers = checkAccountDeleteBlockers;
function handleDeliverMax(tx) {
if (tx.DeliverMax != null) {
if (tx.Amount == null) {
tx.Amount = tx.DeliverMax;
}
if (tx.Amount != null && tx.Amount !== tx.DeliverMax) {
throw new errors_1.ValidationError('PaymentTransaction: Amount and DeliverMax fields must be identical when both are provided');
}
delete tx.DeliverMax;
}
}
exports.handleDeliverMax = handleDeliverMax;
function autofillBatchTxn(client, tx) {
var _a, e_1, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const accountSequences = {};
try {
for (var _d = true, _e = __asyncValues(tx.RawTransactions), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
_c = _f.value;
_d = false;
const rawTxn = _c;
const txn = rawTxn.RawTransaction;
if (txn.Sequence == null && txn.TicketSequence == null) {
if (txn.Account in accountSequences) {
txn.Sequence = accountSequences[txn.Account];
accountSequences[txn.Account] += 1;
}
else {
const nextSequence = yield getNextValidSequenceNumber(client, txn.Account);
const sequence = txn.Account === tx.Account ? nextSequence + 1 : nextSequence;
accountSequences[txn.Account] = sequence + 1;
txn.Sequence = sequence;
}
}
if (txn.Fee == null) {
txn.Fee = '0';
}
else if (txn.Fee !== '0') {
throw new errors_1.XrplError('Must have `Fee of "0" in inner Batch transaction.');
}
if (txn.SigningPubKey == null) {
txn.SigningPubKey = '';
}
else if (txn.SigningPubKey !== '') {
throw new errors_1.XrplError('Must have `SigningPubKey` of "" in inner Batch transaction.');
}
if (txn.TxnSignature != null) {
throw new errors_1.XrplError('Must not have `TxnSignature` in inner Batch transaction.');
}
if (txn.Signers != null) {
throw new errors_1.XrplError('Must not have `Signers` in inner Batch transaction.');
}
if (txn.NetworkID == null && txNeedsNetworkID(client)) {
txn.NetworkID = client.networkID;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
});
}
exports.autofillBatchTxn = autofillBatchTxn;
//# sourceMappingURL=autofill.js.map