@nextrope/xrpl
Version:
A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser
433 lines • 18.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateMPTokenMetadata = exports.isDomainID = exports.containsDuplicates = exports.validateCredentialsList = exports.validateCredentialType = exports.parseAmountValue = exports.validateBaseTransaction = exports.GlobalFlags = exports.validateOptionalField = exports.validateRequiredField = exports.validateHexMetadata = exports.isLedgerEntryId = exports.isArray = exports.isXChainBridge = exports.isAmount = exports.isAccount = exports.isClawbackAmount = exports.isMPTAmount = exports.isAuthorizeCredential = exports.isIssuedCurrencyAmount = exports.isIssuedCurrency = exports.isCurrency = exports.isXRPLNumber = exports.isValue = exports.isNull = exports.isNumber = exports.isString = exports.isRecord = exports.MPT_META_WARNING_HEADER = exports.VAULT_DATA_MAX_BYTE_LENGTH = exports.MAX_MPT_META_BYTE_LENGTH = exports.MAX_AUTHORIZED_CREDENTIALS = void 0;
const utils_1 = require("@xrplf/isomorphic/utils");
const ripple_address_codec_1 = require("ripple-address-codec");
const ripple_binary_codec_1 = require("ripple-binary-codec");
const errors_1 = require("../../errors");
const utils_2 = require("../utils");
const MEMO_SIZE = 3;
exports.MAX_AUTHORIZED_CREDENTIALS = 8;
const MAX_CREDENTIAL_BYTE_LENGTH = 64;
const MAX_CREDENTIAL_TYPE_LENGTH = MAX_CREDENTIAL_BYTE_LENGTH * 2;
exports.MAX_MPT_META_BYTE_LENGTH = 1024;
const SHA_512_HALF_LENGTH = 64;
exports.VAULT_DATA_MAX_BYTE_LENGTH = 256;
const TICKER_REGEX = /^[A-Z0-9]{1,6}$/u;
const MAX_MPT_META_TOP_LEVEL_FIELD_COUNT = 9;
const MPT_META_URL_FIELD_COUNT = 3;
const MPT_META_REQUIRED_FIELDS = [
'ticker',
'name',
'icon',
'asset_class',
'issuer_name',
];
const MPT_META_ASSET_CLASSES = [
'rwa',
'memes',
'wrapped',
'gaming',
'defi',
'other',
];
const MPT_META_ASSET_SUB_CLASSES = [
'stablecoin',
'commodity',
'real_estate',
'private_credit',
'equity',
'treasury',
'other',
];
exports.MPT_META_WARNING_HEADER = 'MPTokenMetadata is not properly formatted as JSON as per the XLS-89d standard. ' +
"While adherence to this standard is not mandatory, such non-compliant MPToken's might not be discoverable " +
'by Explorers and Indexers in the XRPL ecosystem.';
function isMemo(obj) {
if (!isRecord(obj)) {
return false;
}
const memo = obj.Memo;
if (!isRecord(memo)) {
return false;
}
const size = Object.keys(memo).length;
const validData = memo.MemoData == null || (isString(memo.MemoData) && (0, utils_2.isHex)(memo.MemoData));
const validFormat = memo.MemoFormat == null ||
(isString(memo.MemoFormat) && (0, utils_2.isHex)(memo.MemoFormat));
const validType = memo.MemoType == null || (isString(memo.MemoType) && (0, utils_2.isHex)(memo.MemoType));
return (size >= 1 &&
size <= MEMO_SIZE &&
validData &&
validFormat &&
validType &&
(0, utils_2.onlyHasFields)(memo, ['MemoFormat', 'MemoData', 'MemoType']));
}
const SIGNER_SIZE = 3;
function isSigner(obj) {
if (!isRecord(obj)) {
return false;
}
const signer = obj.Signer;
if (!isRecord(signer)) {
return false;
}
return (Object.keys(signer).length === SIGNER_SIZE &&
isString(signer.Account) &&
isString(signer.TxnSignature) &&
isString(signer.SigningPubKey));
}
const XRP_CURRENCY_SIZE = 1;
const ISSUE_SIZE = 2;
const ISSUED_CURRENCY_SIZE = 3;
const XCHAIN_BRIDGE_SIZE = 4;
const MPTOKEN_SIZE = 2;
const AUTHORIZE_CREDENTIAL_SIZE = 1;
function isRecord(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
exports.isRecord = isRecord;
function isString(str) {
return typeof str === 'string';
}
exports.isString = isString;
function isNumber(num) {
return typeof num === 'number';
}
exports.isNumber = isNumber;
function isNull(inp) {
return inp == null;
}
exports.isNull = isNull;
function isValue(value) {
const isValueInternal = (inp) => inp === value;
return isValueInternal;
}
exports.isValue = isValue;
function isXRPLNumber(value) {
return (typeof value === 'string' &&
/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$/u.test(value.trim()));
}
exports.isXRPLNumber = isXRPLNumber;
function isCurrency(input) {
return isString(input) || isIssuedCurrency(input);
}
exports.isCurrency = isCurrency;
function isIssuedCurrency(input) {
return (isRecord(input) &&
((Object.keys(input).length === ISSUE_SIZE &&
isString(input.issuer) &&
isString(input.currency)) ||
(Object.keys(input).length === XRP_CURRENCY_SIZE &&
input.currency === 'XRP')));
}
exports.isIssuedCurrency = isIssuedCurrency;
function isIssuedCurrencyAmount(input) {
return (isRecord(input) &&
Object.keys(input).length === ISSUED_CURRENCY_SIZE &&
isString(input.value) &&
isString(input.issuer) &&
isString(input.currency));
}
exports.isIssuedCurrencyAmount = isIssuedCurrencyAmount;
function isAuthorizeCredential(input) {
return (isRecord(input) &&
isRecord(input.Credential) &&
Object.keys(input).length === AUTHORIZE_CREDENTIAL_SIZE &&
typeof input.Credential.CredentialType === 'string' &&
typeof input.Credential.Issuer === 'string');
}
exports.isAuthorizeCredential = isAuthorizeCredential;
function isMPTAmount(input) {
return (isRecord(input) &&
Object.keys(input).length === MPTOKEN_SIZE &&
typeof input.value === 'string' &&
typeof input.mpt_issuance_id === 'string');
}
exports.isMPTAmount = isMPTAmount;
function isClawbackAmount(input) {
return isIssuedCurrencyAmount(input) || isMPTAmount(input);
}
exports.isClawbackAmount = isClawbackAmount;
function isAccount(account) {
return (typeof account === 'string' &&
((0, ripple_address_codec_1.isValidClassicAddress)(account) || (0, ripple_address_codec_1.isValidXAddress)(account)));
}
exports.isAccount = isAccount;
function isAmount(amount) {
return (typeof amount === 'string' ||
isIssuedCurrencyAmount(amount) ||
isMPTAmount(amount));
}
exports.isAmount = isAmount;
function isXChainBridge(input) {
return (isRecord(input) &&
Object.keys(input).length === XCHAIN_BRIDGE_SIZE &&
typeof input.LockingChainDoor === 'string' &&
isIssuedCurrency(input.LockingChainIssue) &&
typeof input.IssuingChainDoor === 'string' &&
isIssuedCurrency(input.IssuingChainIssue));
}
exports.isXChainBridge = isXChainBridge;
function isArray(input) {
return input != null && Array.isArray(input);
}
exports.isArray = isArray;
function isLedgerEntryId(input) {
return isString(input) && (0, utils_2.isHex)(input) && input.length === SHA_512_HALF_LENGTH;
}
exports.isLedgerEntryId = isLedgerEntryId;
function validateHexMetadata(input, lengthUpto) {
return (isString(input) &&
(0, utils_2.isHex)(input) &&
input.length > 0 &&
input.length <= lengthUpto);
}
exports.validateHexMetadata = validateHexMetadata;
function validateRequiredField(tx, param, checkValidity, errorOpts = {}) {
var _a, _b;
const paramNameStr = (_a = errorOpts.paramName) !== null && _a !== void 0 ? _a : param;
const txType = (_b = errorOpts.txType) !== null && _b !== void 0 ? _b : tx.TransactionType;
if (tx[param] == null) {
throw new errors_1.ValidationError(`${txType}: missing field ${String(paramNameStr)}`);
}
if (!checkValidity(tx[param])) {
throw new errors_1.ValidationError(`${txType}: invalid field ${String(paramNameStr)}`);
}
}
exports.validateRequiredField = validateRequiredField;
function validateOptionalField(tx, param, checkValidity, errorOpts = {}) {
var _a, _b;
const paramNameStr = (_a = errorOpts.paramName) !== null && _a !== void 0 ? _a : param;
const txType = (_b = errorOpts.txType) !== null && _b !== void 0 ? _b : tx.TransactionType;
if (tx[param] !== undefined && !checkValidity(tx[param])) {
throw new errors_1.ValidationError(`${txType}: invalid field ${String(paramNameStr)}`);
}
}
exports.validateOptionalField = validateOptionalField;
var GlobalFlags;
(function (GlobalFlags) {
GlobalFlags[GlobalFlags["tfInnerBatchTxn"] = 1073741824] = "tfInnerBatchTxn";
})(GlobalFlags || (exports.GlobalFlags = GlobalFlags = {}));
function validateBaseTransaction(common) {
if (!isRecord(common)) {
throw new errors_1.ValidationError('BaseTransaction: invalid, expected a valid object');
}
if (common.TransactionType === undefined) {
throw new errors_1.ValidationError('BaseTransaction: missing field TransactionType');
}
if (typeof common.TransactionType !== 'string') {
throw new errors_1.ValidationError('BaseTransaction: TransactionType not string');
}
if (!ripple_binary_codec_1.TRANSACTION_TYPES.includes(common.TransactionType)) {
throw new errors_1.ValidationError(`BaseTransaction: Unknown TransactionType ${common.TransactionType}`);
}
validateRequiredField(common, 'Account', isString);
validateOptionalField(common, 'Fee', isString);
validateOptionalField(common, 'Sequence', isNumber);
validateOptionalField(common, 'AccountTxnID', isString);
validateOptionalField(common, 'LastLedgerSequence', isNumber);
const memos = common.Memos;
if (memos != null && (!isArray(memos) || !memos.every(isMemo))) {
throw new errors_1.ValidationError('BaseTransaction: invalid Memos');
}
const signers = common.Signers;
if (signers != null &&
(!isArray(signers) || signers.length === 0 || !signers.every(isSigner))) {
throw new errors_1.ValidationError('BaseTransaction: invalid Signers');
}
validateOptionalField(common, 'SourceTag', isNumber);
validateOptionalField(common, 'SigningPubKey', isString);
validateOptionalField(common, 'TicketSequence', isNumber);
validateOptionalField(common, 'TxnSignature', isString);
validateOptionalField(common, 'NetworkID', isNumber);
validateOptionalField(common, 'Delegate', isAccount);
const delegate = common.Delegate;
if (delegate != null && delegate === common.Account) {
throw new errors_1.ValidationError('BaseTransaction: Account and Delegate addresses cannot be the same');
}
}
exports.validateBaseTransaction = validateBaseTransaction;
function parseAmountValue(amount) {
if (!isAmount(amount)) {
return NaN;
}
if (typeof amount === 'string') {
return parseFloat(amount);
}
return parseFloat(amount.value);
}
exports.parseAmountValue = parseAmountValue;
function validateCredentialType(tx) {
if (typeof tx.TransactionType !== 'string') {
throw new errors_1.ValidationError('Invalid TransactionType');
}
if (tx.CredentialType === undefined) {
throw new errors_1.ValidationError(`${tx.TransactionType}: missing field CredentialType`);
}
if (!isString(tx.CredentialType)) {
throw new errors_1.ValidationError(`${tx.TransactionType}: CredentialType must be a string`);
}
if (tx.CredentialType.length === 0) {
throw new errors_1.ValidationError(`${tx.TransactionType}: CredentialType cannot be an empty string`);
}
else if (tx.CredentialType.length > MAX_CREDENTIAL_TYPE_LENGTH) {
throw new errors_1.ValidationError(`${tx.TransactionType}: CredentialType length cannot be > ${MAX_CREDENTIAL_TYPE_LENGTH}`);
}
if (!utils_1.HEX_REGEX.test(tx.CredentialType)) {
throw new errors_1.ValidationError(`${tx.TransactionType}: CredentialType must be encoded in hex`);
}
}
exports.validateCredentialType = validateCredentialType;
function validateCredentialsList(credentials, transactionType, isStringID, maxCredentials) {
if (credentials == null) {
return;
}
if (!isArray(credentials)) {
throw new errors_1.ValidationError(`${transactionType}: Credentials must be an array`);
}
if (credentials.length > maxCredentials) {
throw new errors_1.ValidationError(`${transactionType}: Credentials length cannot exceed ${maxCredentials} elements`);
}
else if (credentials.length === 0) {
throw new errors_1.ValidationError(`${transactionType}: Credentials cannot be an empty array`);
}
credentials.forEach((credential) => {
if (isStringID) {
if (!isString(credential)) {
throw new errors_1.ValidationError(`${transactionType}: Invalid Credentials ID list format`);
}
}
else if (!isAuthorizeCredential(credential)) {
throw new errors_1.ValidationError(`${transactionType}: Invalid Credentials format`);
}
});
if (containsDuplicates(credentials)) {
throw new errors_1.ValidationError(`${transactionType}: Credentials cannot contain duplicate elements`);
}
}
exports.validateCredentialsList = validateCredentialsList;
function isAuthorizeCredentialArray(list) {
return typeof list[0] !== 'string';
}
function containsDuplicates(objectList) {
if (typeof objectList[0] === 'string') {
const objSet = new Set(objectList.map((obj) => JSON.stringify(obj)));
return objSet.size !== objectList.length;
}
const seen = new Set();
if (isAuthorizeCredentialArray(objectList)) {
for (const item of objectList) {
const key = `${item.Credential.Issuer}-${item.Credential.CredentialType}`;
if (seen.has(key)) {
return true;
}
seen.add(key);
}
}
return false;
}
exports.containsDuplicates = containsDuplicates;
const _DOMAIN_ID_LENGTH = 64;
function isDomainID(domainID) {
return (isString(domainID) &&
domainID.length === _DOMAIN_ID_LENGTH &&
(0, utils_2.isHex)(domainID));
}
exports.isDomainID = isDomainID;
function validateMPTokenMetadata(input) {
const validationMessages = [];
if (!(0, utils_2.isHex)(input)) {
validationMessages.push(`MPTokenMetadata must be in hex format.`);
return validationMessages;
}
if (input.length / 2 > exports.MAX_MPT_META_BYTE_LENGTH) {
validationMessages.push(`MPTokenMetadata must be max ${exports.MAX_MPT_META_BYTE_LENGTH} bytes.`);
return validationMessages;
}
let jsonMetaData;
try {
jsonMetaData = JSON.parse((0, utils_1.hexToString)(input));
}
catch (err) {
validationMessages.push(`MPTokenMetadata is not properly formatted as JSON - ${String(err)}`);
return validationMessages;
}
if (jsonMetaData == null ||
typeof jsonMetaData !== 'object' ||
Array.isArray(jsonMetaData)) {
validationMessages.push('MPTokenMetadata is not properly formatted as per XLS-89d.');
return validationMessages;
}
const obj = jsonMetaData;
const fieldCount = Object.keys(obj).length;
if (fieldCount > MAX_MPT_META_TOP_LEVEL_FIELD_COUNT) {
validationMessages.push(`MPTokenMetadata must not contain more than ${MAX_MPT_META_TOP_LEVEL_FIELD_COUNT} top-level fields (found ${fieldCount}).`);
return validationMessages;
}
const incorrectRequiredFields = MPT_META_REQUIRED_FIELDS.filter((field) => !isString(obj[field]));
if (incorrectRequiredFields.length > 0) {
incorrectRequiredFields.forEach((field) => validationMessages.push(`${field} is required and must be string.`));
return validationMessages;
}
if (obj.desc != null && !isString(obj.desc)) {
validationMessages.push(`desc must be a string.`);
return validationMessages;
}
if (obj.asset_subclass != null && !isString(obj.asset_subclass)) {
validationMessages.push(`asset_subclass must be a string.`);
return validationMessages;
}
if (obj.additional_info != null &&
!isString(obj.additional_info) &&
!isRecord(obj.additional_info)) {
validationMessages.push(`additional_info must be a string or JSON object.`);
return validationMessages;
}
if (obj.urls != null) {
if (!Array.isArray(obj.urls)) {
validationMessages.push('urls must be an array as per XLS-89d.');
return validationMessages;
}
if (!obj.urls.every(isValidMPTokenMetadataUrlStructure)) {
validationMessages.push('One or more urls are not structured per XLS-89d.');
return validationMessages;
}
}
const mptMPTokenMetadata = obj;
if (!TICKER_REGEX.test(mptMPTokenMetadata.ticker)) {
validationMessages.push(`ticker should have uppercase letters (A-Z) and digits (0-9) only. Max 6 characters recommended.`);
}
if (!mptMPTokenMetadata.icon.startsWith('https://')) {
validationMessages.push(`icon should be a valid https url.`);
}
if (!MPT_META_ASSET_CLASSES.includes(mptMPTokenMetadata.asset_class.toLowerCase())) {
validationMessages.push(`asset_class should be one of ${MPT_META_ASSET_CLASSES.join(', ')}.`);
}
if (mptMPTokenMetadata.asset_subclass != null &&
!MPT_META_ASSET_SUB_CLASSES.includes(mptMPTokenMetadata.asset_subclass.toLowerCase())) {
validationMessages.push(`asset_subclass should be one of ${MPT_META_ASSET_SUB_CLASSES.join(', ')}.`);
}
if (mptMPTokenMetadata.asset_class.toLowerCase() === 'rwa' &&
mptMPTokenMetadata.asset_subclass == null) {
validationMessages.push(`asset_subclass is required when asset_class is rwa.`);
}
if (mptMPTokenMetadata.urls != null &&
!mptMPTokenMetadata.urls.every((ele) => ele.url.startsWith('https://'))) {
validationMessages.push(`url should be a valid https url.`);
}
return validationMessages;
}
exports.validateMPTokenMetadata = validateMPTokenMetadata;
function isValidMPTokenMetadataUrlStructure(input) {
if (input == null) {
return false;
}
const obj = input;
return (typeof obj === 'object' &&
isString(obj.url) &&
isString(obj.type) &&
isString(obj.title) &&
Object.keys(obj).length === MPT_META_URL_FIELD_COUNT);
}
//# sourceMappingURL=common.js.map