UNPKG

stacks-pay

Version:

A Payment Request Standard for Stacks Blockchain Payments

182 lines (181 loc) 6.57 kB
"use strict"; // src/index.ts Object.defineProperty(exports, "__esModule", { value: true }); exports.encodeStacksPayURL = encodeStacksPayURL; exports.decodeStacksPayURL = decodeStacksPayURL; const utils_1 = require("./utils"); // Operation parameters configuration const operationParameters = { support: { required: ["operation", "recipient"], optional: ["token", "description", "expiresAt", "memo"], ignore: [ "amount", "contractName", "dueDate", "functionName", "invoiceNumber", ], }, invoice: { required: ["operation", "recipient", "token", "amount"], optional: ["description", "expiresAt", "invoiceNumber", "dueDate", "memo"], ignore: ["contractName", "functionName"], }, mint: { required: ["operation", "contractName", "functionName", "token", "amount"], optional: [ "recipient", "description", "expiresAt", "invoiceNumber", "memo", ], ignore: ["dueDate"], }, }; // Validate parameters based on operation function validateParameters(params) { const opType = params.operation; const opConfig = operationParameters[opType]; if (!opConfig) { if (!params.operation.startsWith("custom-")) { throw new Error("Unknown operation type"); } // For custom operations, no specific validation is enforced return; } // Validate required parameters for (const param of opConfig.required) { if (!(param in params) || params[param] === undefined) { throw new Error(`Parameter '${param}' is required for operation '${opType}'`); } } // Validate and sanitize parameters for (const key in params) { const value = params[key]; if (value === undefined || value === "") continue; if (opConfig.ignore.includes(key)) { // Ignore this parameter delete params[key]; } else if (![...opConfig.required, ...opConfig.optional].includes(key)) { throw new Error(`Parameter '${key}' is not allowed for operation '${opType}'`); } else { // Validate individual parameters switch (key) { case "recipient": if (!(0, utils_1.validateStacksAddress)(value)) { throw new Error("Invalid Stacks address for recipient"); } break; case "token": if (!(0, utils_1.isValidToken)(value)) { throw new Error("Invalid token format"); } break; case "amount": if (!(0, utils_1.isValidAmount)(value)) { throw new Error("Invalid amount format"); } break; case "expiresAt": case "dueDate": if (!(0, utils_1.isValidISODate)(value)) { throw new Error(`Invalid date format for '${key}'`); } break; case "contractName": if (opType === "mint" && value.length === 0) { throw new Error("Contract name is required for mint operation"); } break; case "functionName": if (opType === "mint" && value.length === 0) { throw new Error("Function name is required for mint operation"); } break; // Additional validations can be added here } } } } // Encode Stacks Pay URL function encodeStacksPayURL(params) { try { // Validate and sanitize parameters validateParameters(params); const operation = params.operation; const queryParams = new URLSearchParams(); let allowedParams = []; if (operationParameters[operation]) { allowedParams = [ ...operationParameters[operation].required, ...operationParameters[operation].optional, ]; } else if (operation.startsWith("custom-")) { // For custom operations, include all parameters except 'operation' allowedParams = Object.keys(params).filter((k) => k !== "operation"); } else { throw new Error("Unknown operation type"); } for (const key of allowedParams) { const value = params[key]; if (value !== undefined && value !== "") { queryParams.append(key, value); } } const url = `${operation}?${queryParams.toString()}`; // Bech32m encode the URL with HRP 'stx' const bech32mEncodedURL = (0, utils_1.bech32mEncode)(url); return `web+stx:${bech32mEncodedURL}`; } catch (error) { console.error("Error encoding Stacks Pay URL:", error.message); throw error; } } // Decode Stacks Pay URL function decodeStacksPayURL(fullURL) { try { if (!fullURL.startsWith("web+stx:")) { throw new Error("Invalid protocol in URL"); } const bech32mEncodedURL = fullURL.replace("web+stx:", ""); const url = (0, utils_1.bech32mDecode)(bech32mEncodedURL); const questionMarkIndex = url.indexOf("?"); let operation; let queryString; if (questionMarkIndex >= 0) { operation = url.substring(0, questionMarkIndex); queryString = url.substring(questionMarkIndex + 1); } else { operation = url; queryString = ""; } const queryParams = new URLSearchParams(queryString); const queryEntries = Object.fromEntries(queryParams.entries()); // Construct params with operation included const params = Object.assign({ operation }, queryEntries); // Remove ignored parameters const opConfig = operationParameters[operation]; if (opConfig) { for (const param of opConfig.ignore) { delete params[param]; } } // Validate parameters after decoding validateParameters(params); return params; } catch (error) { console.error("Error decoding Stacks Pay URL:", error.message); throw error; } }