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
JavaScript
;
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