mcpay
Version:
SDK and CLI for MCPay functionality - MCP servers with payment capabilities
342 lines • 13.2 kB
JavaScript
;
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