UNPKG

mcpay

Version:

SDK and CLI for MCPay functionality - MCP servers with payment capabilities

342 lines 13.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExtendedPaymentRequirementsSchema = exports.SupportedNetworkSchema = exports.config = exports.PaymentPayloadSchema = exports.ExactEvmPayloadSchema = exports.ExactEvmPayloadAuthorizationSchema = exports.schemes = exports.ChainIdToNetwork = exports.EvmNetworkToChainId = exports.SupportedEVMNetworks = exports.NetworkSchema = exports.authorizationPrimaryType = exports.authorizationTypes = void 0; exports.isSignerWallet = isSignerWallet; exports.isAccount = isAccount; exports.createNonce = createNonce; exports.preparePaymentHeader = preparePaymentHeader; exports.createPayment = createPayment; exports.signAuthorization = signAuthorization; exports.safeBase64Encode = safeBase64Encode; exports.safeBase64Decode = safeBase64Decode; exports.encodePayment = encodePayment; exports.createPaymentHeaderExactEVM = createPaymentHeaderExactEVM; exports.signPaymentHeader = signPaymentHeader; exports.createPaymentHeader = createPaymentHeader; exports.getDefaultAsset = getDefaultAsset; exports.getUsdcAddressForChain = getUsdcAddressForChain; exports.getNetworkId = getNetworkId; exports.selectPaymentRequirements = selectPaymentRequirements; const viem_1 = require("viem"); const types_1 = require("x402/types"); const zod_1 = require("zod"); exports.authorizationTypes = { TransferWithAuthorization: [ { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" }, { name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" }, ], }; exports.authorizationPrimaryType = "TransferWithAuthorization"; exports.NetworkSchema = zod_1.z.enum([ "base-sepolia", "base", "avalanche-fuji", "avalanche", "iotex", "sei-testnet", ]); exports.SupportedEVMNetworks = [ "base-sepolia", "base", "avalanche-fuji", "avalanche", "iotex", "sei-testnet", ]; exports.EvmNetworkToChainId = new Map([ ["base-sepolia", 84532], ["base", 8453], ["avalanche-fuji", 43113], ["avalanche", 43114], ["iotex", 4689], ["sei-testnet", 1328], ]); exports.ChainIdToNetwork = Object.fromEntries(exports.SupportedEVMNetworks.map(network => [exports.EvmNetworkToChainId.get(network), network])); exports.schemes = ["exact"]; const isInteger = (value) => Number.isInteger(Number(value)) && Number(value) >= 0; const hasMaxLength = (maxLength) => (value) => value.length <= maxLength; const EvmECDSASignatureRegex = /^0x[0-9a-fA-F]{130}$/; const Evm6492SignatureRegex = /^0x[0-9a-fA-F]+6492649264926492649264926492649264926492649264926492649264926492$/; const EvmAddressRegex = /^0x[0-9a-fA-F]{40}$/; const EvmMaxAtomicUnits = 18; const HexEncoded64ByteRegex = /^0x[0-9a-fA-F]{64}$/; exports.ExactEvmPayloadAuthorizationSchema = zod_1.z.object({ from: zod_1.z.string().regex(EvmAddressRegex), to: zod_1.z.string().regex(EvmAddressRegex), value: zod_1.z.string().refine(isInteger).refine(hasMaxLength(EvmMaxAtomicUnits)), validAfter: zod_1.z.string().refine(isInteger), validBefore: zod_1.z.string().refine(isInteger), nonce: zod_1.z.string().regex(HexEncoded64ByteRegex), }); exports.ExactEvmPayloadSchema = zod_1.z.object({ signature: zod_1.z.string().regex(EvmECDSASignatureRegex).or(zod_1.z.string().regex(Evm6492SignatureRegex)), authorization: exports.ExactEvmPayloadAuthorizationSchema, }); exports.PaymentPayloadSchema = zod_1.z.object({ x402Version: zod_1.z.number().refine(val => types_1.x402Versions.includes(val)), scheme: zod_1.z.enum(exports.schemes), network: exports.NetworkSchema, payload: exports.ExactEvmPayloadSchema, }); function isSignerWallet(wallet) { return (typeof wallet === "object" && wallet !== null && "chain" in wallet && "transport" in wallet); } function isAccount(wallet) { const w = wallet; return (typeof wallet === "object" && wallet !== null && typeof w.address === "string" && typeof w.type === "string" && // Check for essential signing capabilities typeof w.sign === "function" && typeof w.signMessage === "function" && typeof w.signTypedData === "function" && // Check for transaction signing (required by LocalAccount) typeof w.signTransaction === "function"); } function createNonce() { const cryptoObj = typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function" ? globalThis.crypto : // Dynamic require is needed to support node.js // eslint-disable-next-line @typescript-eslint/no-require-imports require("crypto").webcrypto; return (0, viem_1.toHex)(cryptoObj.getRandomValues(new Uint8Array(32))); } function preparePaymentHeader(from, x402Version, paymentRequirements) { const nonce = createNonce(); const validAfter = BigInt(Math.floor(Date.now() / 1000) - 600).toString(); const validBefore = BigInt(Math.floor(Date.now() / 1000 + paymentRequirements.maxTimeoutSeconds)).toString(); return { x402Version, scheme: paymentRequirements.scheme, network: paymentRequirements.network, payload: { signature: undefined, authorization: { from, to: paymentRequirements.payTo, value: paymentRequirements.maxAmountRequired, validAfter: validAfter.toString(), validBefore: validBefore.toString(), nonce, }, }, }; } async function createPayment(client, x402Version, paymentRequirements) { const from = isSignerWallet(client) ? client.account.address : client.address; const unsignedPaymentHeader = preparePaymentHeader(from, x402Version, paymentRequirements); return signPaymentHeader(client, paymentRequirements, unsignedPaymentHeader); } async function signAuthorization(walletClient, { from, to, value, validAfter, validBefore, nonce }, { asset, network, extra }) { const chainId = getNetworkId(network); const name = extra?.name; const version = extra?.version; const data = { types: exports.authorizationTypes, domain: { name, version, chainId, verifyingContract: (0, viem_1.getAddress)(asset), }, primaryType: "TransferWithAuthorization", message: { from: (0, viem_1.getAddress)(from), to: (0, viem_1.getAddress)(to), value, validAfter, validBefore, nonce: nonce, }, }; if (isSignerWallet(walletClient)) { const signature = await walletClient.signTypedData(data); return { signature, }; } else if (isAccount(walletClient) && walletClient.signTypedData) { const signature = await walletClient.signTypedData(data); return { signature, }; } else { throw new Error("Invalid wallet client provided does not support signTypedData"); } } /** * Encodes a string to base64 format * * @param data - The string to be encoded to base64 * @returns The base64 encoded string */ function safeBase64Encode(data) { if (typeof globalThis !== "undefined" && typeof globalThis.btoa === "function") { return globalThis.btoa(data); } return Buffer.from(data).toString("base64"); } /** * Decodes a base64 string back to its original format * * @param data - The base64 encoded string to be decoded * @returns The decoded string in UTF-8 format */ function safeBase64Decode(data) { if (typeof globalThis !== "undefined" && typeof globalThis.atob === "function") { return globalThis.atob(data); } return Buffer.from(data, "base64").toString("utf-8"); } function encodePayment(payment) { const safe = { ...payment, payload: { ...payment.payload, authorization: Object.fromEntries(Object.entries(payment.payload.authorization).map(([key, value]) => [ key, typeof value === "bigint" ? value.toString() : value, ])), }, }; return safeBase64Encode(JSON.stringify(safe)); } async function createPaymentHeaderExactEVM(client, x402Version, paymentRequirements) { const payment = await createPayment(client, x402Version, paymentRequirements); return encodePayment(payment); } async function signPaymentHeader(client, paymentRequirements, unsignedPaymentHeader) { const { signature } = await signAuthorization(client, unsignedPaymentHeader.payload.authorization, paymentRequirements); return { ...unsignedPaymentHeader, payload: { ...unsignedPaymentHeader.payload, signature, }, }; } async function createPaymentHeader(client, x402Version, paymentRequirements) { if (paymentRequirements.scheme === "exact" && exports.SupportedEVMNetworks.includes(paymentRequirements.network)) { return await createPaymentHeaderExactEVM(client, x402Version, paymentRequirements); } throw new Error("Unsupported scheme"); } /** * Gets the default asset (USDC) for the given network * * @param network - The network to get the default asset for * @returns The default asset */ function getDefaultAsset(network) { return { address: getUsdcAddressForChain(getNetworkId(network)), decimals: 6, eip712: { name: network === "base" ? "USD Coin" : network === "iotex" ? "Bridged USDC" : "USDC", version: "2", }, }; } function getUsdcAddressForChain(chainId) { return exports.config[chainId.toString()]?.usdcAddress; } function getNetworkId(network) { if (exports.EvmNetworkToChainId.has(network)) { return exports.EvmNetworkToChainId.get(network); } // TODO: Solana throw new Error(`Unsupported network: ${network}`); } exports.config = { "84532": { usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", usdcName: "USDC", }, "8453": { usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", usdcName: "USDC", }, "43113": { usdcAddress: "0x5425890298aed601595a70AB815c96711a31Bc65", usdcName: "USD Coin", }, "43114": { usdcAddress: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", usdcName: "USDC", }, "4689": { usdcAddress: "0xcdf79194c6c285077a58da47641d4dbe51f63542", usdcName: "Bridged USDC", }, "1328": { usdcAddress: "0x4fCF1784B31630811181f670Aea7A7bEF803eaED", usdcName: "USDC", }, }; // Extended network schema that includes additional networks exports.SupportedNetworkSchema = zod_1.z.enum([ "base-sepolia", "base", "avalanche-fuji", "avalanche", "iotex", "sei-testnet" ]); // Extended PaymentRequirements schema that supports SupportedNetwork exports.ExtendedPaymentRequirementsSchema = zod_1.z.object({ scheme: zod_1.z.enum(["exact"]), network: exports.SupportedNetworkSchema, maxAmountRequired: zod_1.z.string(), resource: zod_1.z.string().url(), description: zod_1.z.string(), mimeType: zod_1.z.string(), outputSchema: zod_1.z.record(zod_1.z.any()).optional(), payTo: zod_1.z.string(), maxTimeoutSeconds: zod_1.z.number().int(), asset: zod_1.z.string(), extra: zod_1.z.record(zod_1.z.any()).optional(), }); function selectPaymentRequirements(paymentRequirements, network, scheme) { // Sort `base` payment requirements to the front of the list. This is to ensure that base is preferred if available. paymentRequirements.sort((a, b) => { if (a.network === "base" && b.network !== "base") { return -1; } if (a.network !== "base" && b.network === "base") { return 1; } return 0; }); // Filter down to the scheme/network if provided const broadlyAcceptedPaymentRequirements = paymentRequirements.filter(requirement => { // If the scheme is not provided, we accept any scheme. const isExpectedScheme = !scheme || requirement.scheme === scheme; // If the chain is not provided, we accept any chain. const isExpectedChain = !network || network == requirement.network; return isExpectedScheme && isExpectedChain; }); // Filter down to USDC requirements const usdcRequirements = broadlyAcceptedPaymentRequirements.filter(requirement => { // If the address is a USDC address, we return it. return requirement.asset === getUsdcAddressForChain(getNetworkId(requirement.network)); }); // Prioritize USDC requirements if available if (usdcRequirements.length > 0) { return usdcRequirements[0]; } // If no USDC requirements are found, return the first broadly accepted requirement. if (broadlyAcceptedPaymentRequirements.length > 0) { return broadlyAcceptedPaymentRequirements[0]; } // If no matching requirements are found, return the first requirement. return paymentRequirements[0]; } //# sourceMappingURL=index.js.map