xahau-binary-codec
Version:
XAH Ledger binary codec
212 lines • 7.81 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Amount = void 0;
const binary_parser_1 = require("../serdes/binary-parser");
const account_id_1 = require("./account-id");
const currency_1 = require("./currency");
const serialized_type_1 = require("./serialized-type");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const utils_1 = require("@xrplf/isomorphic/utils");
const utils_2 = require("../utils");
/**
* Constants for validating amounts
*/
const MIN_IOU_EXPONENT = -96;
const MAX_IOU_EXPONENT = 80;
const MAX_IOU_PRECISION = 16;
const MAX_DROPS = new bignumber_js_1.default('1e17');
const MIN_XAH = new bignumber_js_1.default('1e-6');
const mask = BigInt(0x00000000ffffffff);
/**
* BigNumber configuration for Amount IOUs
*/
bignumber_js_1.default.config({
EXPONENTIAL_AT: [
MIN_IOU_EXPONENT - MAX_IOU_PRECISION,
MAX_IOU_EXPONENT + MAX_IOU_PRECISION,
],
});
/**
* Type guard for AmountObject
*/
function isAmountObject(arg) {
const keys = Object.keys(arg).sort();
return (keys.length === 3 &&
keys[0] === 'currency' &&
keys[1] === 'issuer' &&
keys[2] === 'value');
}
/**
* Class for serializing/Deserializing Amounts
*/
class Amount extends serialized_type_1.SerializedType {
constructor(bytes) {
super(bytes !== null && bytes !== void 0 ? bytes : Amount.defaultAmount.bytes);
}
/**
* Construct an amount from an IOU or string amount
*
* @param value An Amount, object representing an IOU, or a string
* representing an integer amount
* @returns An Amount object
*/
static from(value) {
if (value instanceof Amount) {
return value;
}
let amount = new Uint8Array(8);
if (typeof value === 'string') {
Amount.assertXrpIsValid(value);
const number = BigInt(value);
const intBuf = [new Uint8Array(4), new Uint8Array(4)];
(0, utils_2.writeUInt32BE)(intBuf[0], Number(number >> BigInt(32)), 0);
(0, utils_2.writeUInt32BE)(intBuf[1], Number(number & BigInt(mask)), 0);
amount = (0, utils_1.concat)(intBuf);
amount[0] |= 0x40;
return new Amount(amount);
}
if (isAmountObject(value)) {
const number = new bignumber_js_1.default(value.value);
Amount.assertIouIsValid(number);
if (number.isZero()) {
amount[0] |= 0x80;
}
else {
const integerNumberString = number
.times(`1e${-((number.e || 0) - 15)}`)
.abs()
.toString();
const num = BigInt(integerNumberString);
const intBuf = [new Uint8Array(4), new Uint8Array(4)];
(0, utils_2.writeUInt32BE)(intBuf[0], Number(num >> BigInt(32)), 0);
(0, utils_2.writeUInt32BE)(intBuf[1], Number(num & BigInt(mask)), 0);
amount = (0, utils_1.concat)(intBuf);
amount[0] |= 0x80;
if (number.gt(new bignumber_js_1.default(0))) {
amount[0] |= 0x40;
}
const exponent = (number.e || 0) - 15;
const exponentByte = 97 + exponent;
amount[0] |= exponentByte >>> 2;
amount[1] |= (exponentByte & 0x03) << 6;
}
const currency = currency_1.Currency.from(value.currency).toBytes();
const issuer = account_id_1.AccountID.from(value.issuer).toBytes();
return new Amount((0, utils_1.concat)([amount, currency, issuer]));
}
throw new Error('Invalid type to construct an Amount');
}
/**
* Read an amount from a BinaryParser
*
* @param parser BinaryParser to read the Amount from
* @returns An Amount object
*/
static fromParser(parser) {
const isXAH = parser.peek() & 0x80;
const numBytes = isXAH ? 48 : 8;
return new Amount(parser.read(numBytes));
}
/**
* Get the JSON representation of this Amount
*
* @returns the JSON interpretation of this.bytes
*/
toJSON() {
if (this.isNative()) {
const bytes = this.bytes;
const isPositive = bytes[0] & 0x40;
const sign = isPositive ? '' : '-';
bytes[0] &= 0x3f;
const msb = BigInt((0, utils_2.readUInt32BE)(bytes.slice(0, 4), 0));
const lsb = BigInt((0, utils_2.readUInt32BE)(bytes.slice(4), 0));
const num = (msb << BigInt(32)) | lsb;
return `${sign}${num.toString()}`;
}
else {
const parser = new binary_parser_1.BinaryParser(this.toString());
const mantissa = parser.read(8);
const currency = currency_1.Currency.fromParser(parser);
const issuer = account_id_1.AccountID.fromParser(parser);
const b1 = mantissa[0];
const b2 = mantissa[1];
const isPositive = b1 & 0x40;
const sign = isPositive ? '' : '-';
const exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97;
mantissa[0] = 0;
mantissa[1] &= 0x3f;
const value = new bignumber_js_1.default(`${sign}0x${(0, utils_1.bytesToHex)(mantissa)}`).times(`1e${exponent}`);
Amount.assertIouIsValid(value);
return {
value: value.toString(),
currency: currency.toJSON(),
issuer: issuer.toJSON(),
};
}
}
/**
* Validate XAH amount
*
* @param amount String representing XAH amount
* @returns void, but will throw if invalid amount
*/
static assertXrpIsValid(amount) {
if (amount.indexOf('.') !== -1) {
throw new Error(`${amount.toString()} is an illegal amount`);
}
const decimal = new bignumber_js_1.default(amount);
if (!decimal.isZero()) {
if (decimal.lt(MIN_XAH) || decimal.gt(MAX_DROPS)) {
throw new Error(`${amount.toString()} is an illegal amount`);
}
}
}
/**
* Validate IOU.value amount
*
* @param decimal BigNumber object representing IOU.value
* @returns void, but will throw if invalid amount
*/
static assertIouIsValid(decimal) {
if (!decimal.isZero()) {
const p = decimal.precision();
const e = (decimal.e || 0) - 15;
if (p > MAX_IOU_PRECISION ||
e > MAX_IOU_EXPONENT ||
e < MIN_IOU_EXPONENT) {
throw new Error('Decimal precision out of range');
}
this.verifyNoDecimal(decimal);
}
}
/**
* Ensure that the value after being multiplied by the exponent does not
* contain a decimal.
*
* @param decimal a Decimal object
* @returns a string of the object without a decimal
*/
static verifyNoDecimal(decimal) {
const integerNumberString = decimal
.times(`1e${-((decimal.e || 0) - 15)}`)
.abs()
.toString();
if (integerNumberString.indexOf('.') !== -1) {
throw new Error('Decimal place found in integerNumberString');
}
}
/**
* Test if this amount is in units of Native Currency(XAH)
*
* @returns true if Native (XAH)
*/
isNative() {
return (this.bytes[0] & 0x80) === 0;
}
}
exports.Amount = Amount;
Amount.defaultAmount = new Amount((0, utils_1.hexToBytes)('4000000000000000'));
//# sourceMappingURL=amount.js.map
;