UNPKG

@nextrope/xrpl

Version:

A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser

433 lines 18.2 kB
"use strict"; 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