UNPKG

cardano-uri-parser

Version:

A modular, type-safe Cardano URI parser supporting CIP-13, claim, stake, browse, and future authorities.

346 lines (334 loc) 10.7 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { CardanoUriError: () => CardanoUriError, classifyCardanoAddress: () => classifyCardanoAddress, isValidBlockHeight: () => isValidBlockHeight, isValidHex64: () => isValidHex64, isValidMetadataLabel: () => isValidMetadataLabel, parse: () => parse, safeParse: () => safeParse }); module.exports = __toCommonJS(index_exports); // src/errors.ts var CardanoUriError = class extends Error { constructor(type, message, details) { super(message); this.name = "Errors"; this.type = type; this.details = details; } }; // src/utils/validators.ts function isValidHex64(value) { return /^[0-9a-fA-F]{64}$/.test(value); } function isValidBlockHeight(value) { return /^[0-9]+$/.test(value) && Number(value) >= 0; } function isValidMetadataLabel(value) { return /^[0-9]+$/.test(value); } function classifyCardanoAddress(value) { const is_testnet = value.includes("_test"); if (value.startsWith("stake1") || value.startsWith("stake_test1")) { return { valid: value.length === 59 || value.length === 64, type: "stake", is_testnet }; } if (value.startsWith("addr1")) { return { valid: value.length === 103 || value.length === 58, type: "shelley", is_testnet }; } if (value.startsWith("addr_test1")) { return { valid: value.length === 108 || value.length === 63, type: "shelley", is_testnet }; } if (value.startsWith("Ae2")) { return { valid: value.length >= 59 && value.length <= 64, type: "byron_icarus", is_testnet }; } if (value.startsWith("DdzFF")) { return { valid: value.length >= 104 && value.length <= 128, type: "byron_daedalus", is_testnet }; } const cip105Prefixes = [ "drep_vk", "drep_script", "drep", "cc_cold_vk", "cc_cold_script", "cc_cold", "cc_hot_vk", "cc_hot_script", "cc_hot" ]; for (const prefix of cip105Prefixes) { if (value.startsWith(prefix)) { return { valid: true, type: prefix, is_testnet }; } } return { valid: false }; } // src/handlers/addr.ts function handleAddrUri(rest) { const address = rest[0]; if (!address) { throw new CardanoUriError("MissingRequiredField", "Missing address"); } const { valid, type, is_testnet } = classifyCardanoAddress(address); if (!valid) { throw new CardanoUriError("InvalidAddress", "Invalid Cardano address format"); } return { type: "address", address, address_type: type, is_testnet }; } // src/handlers/block.ts function handleBlockUri(rest, queryParams) { const hash = queryParams.get("hash"); const height = queryParams.get("height"); if (!hash && !height) { throw new CardanoUriError("MissingRequiredField", "Block URI must have either hash or height"); } if (hash && height) { throw new CardanoUriError("InvalidCombination", "Cannot provide both block hash and height"); } if (hash && !isValidHex64(hash)) { throw new CardanoUriError("InvalidFormat", "Invalid block hash format"); } if (height && !isValidBlockHeight(height)) { throw new CardanoUriError("InvalidFormat", "Invalid block height format"); } return { type: "block", block_hash: hash || void 0, block_height: height ? Number(height) : void 0 }; } // src/handlers/browse.ts function handleBrowseUri(rest, queryParams) { if (rest.length < 2) { throw new CardanoUriError("MissingRequiredField", "Expected at least scheme and namespaced domain"); } const scheme = rest[0]; const namespacedDomain = rest[1]; if (!/^[a-zA-Z][a-zA-Z0-9+.-]*$/.test(scheme)) { throw new CardanoUriError("InvalidScheme", "Invalid scheme format: " + scheme); } if (!namespacedDomain.includes(".")) { throw new CardanoUriError("InvalidNamespace", "Invalid namespaced domain format: " + namespacedDomain); } const appPath = rest.slice(2).join("/"); const qp = {}; queryParams.forEach((value, key) => { qp[key] = value; }); const reversedDomain = namespacedDomain.split(".").reverse().join("."); const queryString = queryParams.toString() ? `?${queryParams.toString()}` : ""; const fullUrl = `${scheme}://${reversedDomain}/${appPath}${queryString}`; return { type: "browse", scheme, namespaced_domain: namespacedDomain, app_path: appPath, queryParams: qp, url: fullUrl }; } // src/handlers/claim.ts function handleClaimUri(rest, queryParams) { const versionStr = rest[0].replace("v", ""); const version = Number(versionStr); if (!versionStr || isNaN(version)) { throw new CardanoUriError("MissingRequiredField", "Missing or invalid version in claim URI"); } const faucetUrl = queryParams.get("faucet_url"); if (!faucetUrl) { throw new CardanoUriError("MissingRequiredField", "faucet_url is required in claim URI"); } return { type: "claim", version, faucet_url: faucetUrl, code: queryParams.get("code") }; } // src/handlers/pay.ts function handlePayUri(rest, queryParams) { const address = rest[0]; if (!address) { throw new CardanoUriError("MissingRequiredField", "Missing address in pay URI"); } const { valid } = classifyCardanoAddress(address); if (!valid) { throw new CardanoUriError("InvalidAddress", "Invalid Cardano address format"); } const result = { type: "pay", address }; if (queryParams.has("l")) { const lovelace = Number(queryParams.get("l")); if (!Number.isInteger(lovelace) || lovelace < 0) { throw new CardanoUriError("InvalidLovelace", "Invalid lovelace amount"); } result.lovelace = lovelace; } if (queryParams.has("i")) { result.paymentId = queryParams.get("i"); } if (queryParams.has("n")) { result.note = decodeURIComponent(queryParams.get("n")); } if (queryParams.has("t")) { const tokensStr = queryParams.get("t"); result.tokens = tokensStr.split(",").map((pair) => { const [assetId, qtyStr] = pair.split("|"); if (!assetId.startsWith("asset1") || assetId.length !== 45) { throw new CardanoUriError("InvalidToken", `Invalid asset ID: ${assetId}`); } const quantity = Number(qtyStr); if (!Number.isInteger(quantity) || quantity < 0) { throw new CardanoUriError("InvalidTokenQuantity", `Invalid quantity for asset: ${assetId}`); } return { assetId, quantity }; }); } return result; } // src/handlers/stake.ts function handleStakeUri(_rest, queryParams) { const pools = {}; queryParams.forEach((value, key) => { if (pools[key] !== void 0) { throw new CardanoUriError("HandlerError", `Duplicate pool key: ${key}`); } pools[key] = Number(value) || 1; }); return { type: "stake", pools }; } // src/handlers/transaction.ts function handleTransactionUri(pathParts, fragment) { const [tx_hash, ...subpaths] = pathParts; if (!tx_hash) { throw new CardanoUriError("MissingRequiredField", "Missing transaction hash"); } if (tx_hash !== "self" && !isValidHex64(tx_hash)) { throw new CardanoUriError("InvalidFormat", "Invalid transaction hash format"); } let metadata = void 0; if (subpaths[0] === "metadata") { if (subpaths[1] && !isValidMetadataLabel(subpaths[1])) { throw new CardanoUriError("InvalidFormat", "Invalid metadata label format"); } metadata = subpaths[1] ? { label: subpaths[1] } : {}; } const result = { type: "transaction", tx_hash, metadata }; if (fragment !== null) { const index = Number(fragment); if (!Number.isInteger(index) || index < 0) { throw new CardanoUriError("InvalidFormat", "Invalid output index fragment"); } result.output_index = index; } return result; } // src/handlers/default.ts function handleDefaultUri(address, queryParams) { const { valid, type, is_testnet } = classifyCardanoAddress(address); if (!valid || !type) { throw new CardanoUriError("InvalidAddress", "Provided address is not recognized as valid Cardano address"); } const amountStr = queryParams.get("amount"); const amount = amountStr ? Number(amountStr) : void 0; const era = type.startsWith("byron") ? "byron" : "shelley"; const network = is_testnet ? "testnet" : "mainnet"; return { type: "payment", address, amount, era, network }; } // src/cardano-uri-parser.ts function parse(uri) { let url; try { url = new URL(uri); } catch { throw new CardanoUriError("InvalidUri", "Invalid URI format"); } if (url.protocol !== "web+cardano:") { throw new CardanoUriError("InvalidScheme", "Unsupported scheme: " + url.protocol); } const authority = url.hostname; const pathParts = url.pathname.split("/").filter(Boolean); const queryParams = url.searchParams; try { switch (authority) { case "addr": return handleAddrUri(pathParts); case "block": return handleBlockUri(pathParts, queryParams); case "browse": return handleBrowseUri(pathParts, queryParams); case "claim": return handleClaimUri(pathParts, queryParams); case "pay": return handlePayUri(pathParts, queryParams); case "stake": return handleStakeUri(pathParts, queryParams); case "transaction": return handleTransactionUri(pathParts, url.hash ? url.hash.slice(1) : null); default: return handleDefaultUri(authority, queryParams); } } catch (err) { if (err instanceof CardanoUriError) { throw err; } throw new CardanoUriError("HandlerError", "Error in handler", { originalError: err }); } } function safeParse(uri) { try { return parse(uri); } catch { return null; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { CardanoUriError, classifyCardanoAddress, isValidBlockHeight, isValidHex64, isValidMetadataLabel, parse, safeParse }); //# sourceMappingURL=index.js.map