@x402r/refund 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -0
- package/dist/cjs/index.d.ts +253 -0
- package/dist/cjs/index.js +713 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.d.mts +253 -0
- package/dist/esm/index.mjs +678 -0
- package/dist/esm/index.mjs.map +1 -0
- package/package.json +68 -0
- package/src/facilitator.ts +778 -0
- package/src/index.ts +119 -0
- package/src/server/computeRelayAddress.ts +73 -0
- package/src/server.ts +334 -0
- package/src/types.ts +74 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/types.ts","../../src/server/computeRelayAddress.ts","../../src/server.ts","../../src/facilitator.ts"],"sourcesContent":["/**\n * Refund Helper Extension for x402\n *\n * Enables merchants to route payments to DepositRelay contracts via escrow,\n * providing refund and dispute resolution capabilities.\n *\n * ## Overview\n *\n * The refund helper allows merchants to mark payment options as refundable,\n * which routes payments through DepositRelay contracts into escrow accounts.\n * This enables dispute resolution and refunds even if merchants are uncooperative.\n *\n * ## For Merchants (Server-Side)\n *\n * ### Step 1: Mark Payment Options as Refundable\n *\n * Use `refundable()` to mark payment options that should support refunds:\n *\n * ```typescript\n * import { refundable } from '@x402r/extensions/refund';\n *\n * const option = refundable({\n * scheme: 'exact',\n * payTo: '0xmerchant123...', // Your merchant payout address\n * price: '$0.01',\n * network: 'eip155:84532',\n * });\n * ```\n *\n * ### Step 2: Process Routes with DepositRelay\n *\n * Use `withRefund()` to process route configurations and route refundable\n * payments to DepositRelay:\n *\n * ```typescript\n * import { refundable, withRefund } from '@x402r/extensions/refund';\n *\n * const FACTORY_ADDRESS = '0xFactory123...'; // Any CREATE3-compatible factory\n * const CREATEX_ADDRESS = '0xCreateX123...'; // CreateX contract address\n *\n * const routes = {\n * '/api': {\n * accepts: refundable({\n * scheme: 'exact',\n * payTo: '0xmerchant123...',\n * price: '$0.01',\n * network: 'eip155:84532',\n * }),\n * },\n * };\n *\n * // Process routes to route refundable payments to DepositRelay\n * // Uses CREATE3 - no bytecode needed! Works with any CREATE3-compatible factory.\n * // Version is optional - defaults to config file value\n * // CreateX address is optional - uses standard address for network if not provided\n * const processedRoutes = withRefund(routes, FACTORY_ADDRESS);\n *\n * // Use processedRoutes with paymentMiddleware\n * app.use(paymentMiddleware(processedRoutes, server));\n * ```\n *\n * ## For Facilitators\n *\n * Facilitators use `settleWithRefundHelper()` in hooks to handle refund settlements:\n *\n * ```typescript\n * import { settleWithRefundHelper } from '@x402r/extensions/refund';\n * import { x402Facilitator } from '@x402/core/facilitator';\n *\n * const ESCROW_FACTORY = '0xEscrowFactory123...';\n *\n * facilitator.onBeforeSettle(async (context) => {\n * const result = await settleWithRefundHelper(\n * context.paymentPayload,\n * context.paymentRequirements,\n * signer,\n * ESCROW_FACTORY,\n * );\n *\n * if (result) {\n * // Refund was handled via DepositRelay\n * return { abort: true, reason: 'handled_by_refund_helper' };\n * }\n *\n * return null; // Proceed with normal settlement\n * });\n * ```\n *\n * ## How It Works\n *\n * 1. **Merchant Setup**: Merchant deploys escrow via EscrowFactory and marks options with `refundable()`\n * 2. **Route Processing**: `withRefund()` sets `payTo` to DepositRelay address, stores original merchantPayout in `extra`\n * 3. **Client Payment**: Client makes payment to DepositRelay address (transparent to client)\n * 4. **Facilitator Settlement**: Facilitator detects refund payment, queries EscrowFactory for escrow, calls DepositRelay.executeDeposit()\n * 5. **Escrow Hold**: Funds are held in escrow, enabling dispute resolution and refunds\n *\n * ## Key Features\n *\n * - **No Core Changes**: Works entirely through helpers and hooks\n * - **Client Transparent**: Clients don't need to change anything\n * - **Flexible**: Mix refundable and non-refundable options in same route\n * - **Deep Cloning**: All helpers return new objects, don't mutate originals\n */\n\n// Export types\nexport {\n REFUND_EXTENSION_KEY,\n REFUND_MARKER_KEY,\n isRefundableOption,\n type RefundExtension,\n type RefundExtensionInfo,\n} from \"./types\";\n\n// Export server-side helpers\nexport { declareRefundExtension, refundable, withRefund } from \"./server\";\nexport { computeRelayAddress } from \"./server/computeRelayAddress\";\n\n// Export facilitator-side helpers\nexport { extractRefundInfo, settleWithRefundHelper } from \"./facilitator\";\n","/**\n * Type definitions for the Refund Helper Extension\n */\n\nimport type { PaymentOption } from \"@x402/core/http\";\n\n/**\n * Extension identifier constant for the refund extension\n */\nexport const REFUND_EXTENSION_KEY = \"refund\";\n\n/**\n * Constant for the refund marker key (internal marker)\n * Used to identify refundable payment options\n * The merchantPayout is read directly from the option's payTo field when processing\n */\nexport const REFUND_MARKER_KEY = \"_x402_refund\";\n\n/**\n * Refund extension info structure\n *\n * merchantPayouts: Map of proxy address -> merchantPayout\n * This allows multiple refundable options with different merchantPayouts\n */\nexport interface RefundExtensionInfo {\n factoryAddress: string;\n merchantPayouts: Record<string, string>; // proxyAddress -> merchantPayout\n}\n\n/**\n * Refund extension structure (matches extension pattern with info and schema)\n */\nexport interface RefundExtension {\n info: RefundExtensionInfo;\n schema: {\n $schema: \"https://json-schema.org/draft/2020-12/schema\";\n type: \"object\";\n properties: {\n factoryAddress: {\n type: \"string\";\n pattern: \"^0x[a-fA-F0-9]{40}$\";\n description: \"The X402DepositRelayFactory contract address\";\n };\n merchantPayouts: {\n type: \"object\";\n additionalProperties: {\n type: \"string\";\n pattern: \"^0x[a-fA-F0-9]{40}$\";\n };\n description: \"Map of proxy address to merchant payout address\";\n };\n };\n required: [\"factoryAddress\", \"merchantPayouts\"];\n additionalProperties: false;\n };\n}\n\n/**\n * Type guard to check if a payment option is refundable\n * A refundable option has a marker stored in extra\n *\n * @param option - The payment option to check\n * @returns True if the option is refundable\n */\nexport function isRefundableOption(option: PaymentOption): boolean {\n // Check for marker in extra\n return (\n option.extra !== undefined &&\n typeof option.extra === \"object\" &&\n option.extra !== null &&\n REFUND_MARKER_KEY in option.extra &&\n option.extra[REFUND_MARKER_KEY] === true\n );\n}\n","/**\n * Helper to compute CREATE3 address for RelayProxy\n *\n * Uses the CREATE3 formula via CreateX (matching Solidity implementation):\n *\n * Where:\n * - salt = keccak256(abi.encodePacked(factoryAddress, merchantPayout))\n * - guardedSalt = keccak256(abi.encode(salt)) // CreateX guards the salt\n * - createxDeployer = the CreateX contract address\n *\n * CREATE3 is much simpler than CREATE2 - no bytecode needed!\n * The address depends only on the deployer (CreateX) and salt.\n *\n * IMPORTANT: The CreateX address must match the one used by the factory contract.\n * The factory stores its CreateX address and can be queried via factory.getCreateX().\n * This function computes addresses locally without any on-chain calls.\n */\n\nimport { keccak256, encodePacked, encodeAbiParameters, getAddress } from \"viem\";\nimport { predictCreate3Address } from \"@whoislewys/predict-deterministic-address\";\n\n/**\n * Computes the CREATE3 address for a merchant's relay proxy\n *\n * This matches the Solidity implementation in DepositRelayFactory.getRelayAddress():\n * 1. salt = keccak256(abi.encodePacked(merchantPayout))\n * 2. guardedSalt = keccak256(abi.encode(salt)) // CreateX guards the salt\n * 3. return CREATEX.computeCreate3Address(guardedSalt)\n *\n * Uses the @whoislewys/predict-deterministic-address library which correctly\n * implements the CREATE3 formula used by CreateX (based on Solady's CREATE3).\n * This ensures the computed address matches the factory's on-chain computation\n * without requiring any on-chain calls.\n *\n * @param createxAddress - The CreateX contract address\n * @param factoryAddress - The DepositRelayFactory contract address\n * @param merchantPayout - The merchant's payout address\n * @returns The deterministic proxy address\n *\n * @example\n * ```typescript\n * // No bytecode needed! Computes locally without on-chain calls.\n * const relayAddress = computeRelayAddress(\n * \"0xCreateX123...\",\n * \"0xMerchant123...\",\n * 0n // version\n * );\n * ```\n */\nexport function computeRelayAddress(\n createxAddress: string,\n factoryAddress: string,\n merchantPayout: string,\n): string {\n // Normalize addresses to checksummed format\n const createx = getAddress(createxAddress);\n const factory = getAddress(factoryAddress);\n const merchant = getAddress(merchantPayout);\n\n // Step 1: salt = keccak256(abi.encodePacked(factoryAddress, merchantPayout))\n const salt = keccak256(encodePacked([\"address\", \"address\"], [factory, merchant]));\n\n // Step 2: guardedSalt = keccak256(abi.encode(salt))\n // Note: abi.encode (not encodePacked) - this adds length prefixes\n const guardedSalt = keccak256(\n encodeAbiParameters([{ type: \"bytes32\" }], [salt as `0x${string}`]),\n );\n\n // Step 3: Use predictCreate3Address to compute the CREATE3 address\n // This matches CreateX's computeCreate3Address implementation exactly\n // No on-chain calls needed - computes locally!\n return predictCreate3Address(createx, guardedSalt as `0x${string}`);\n}\n","/**\n * Server-side helpers for the Refund Helper Extension\n *\n * These helpers allow merchants to mark payment options as refundable\n * and process route configurations to route payments to X402DepositRelayProxy contracts.\n */\n\nimport type { PaymentOption, RouteConfig, RoutesConfig } from \"@x402/core/http\";\nimport {\n REFUND_MARKER_KEY,\n isRefundableOption,\n REFUND_EXTENSION_KEY,\n type RefundExtension,\n} from \"./types\";\nimport { computeRelayAddress } from \"./server/computeRelayAddress\";\n\n/**\n * Declares a refund extension with factory address and merchantPayouts map\n *\n * @param factoryAddress - The X402DepositRelayFactory contract address\n * @param merchantPayouts - Map of proxy address to merchant payout address\n * @returns Refund extension object with info and schema\n *\n * @example\n * ```typescript\n * const extension = declareRefundExtension(\"0xFactory123...\", {\n * \"0xProxy1...\": \"0xMerchant1...\",\n * \"0xProxy2...\": \"0xMerchant2...\",\n * });\n * ```\n */\nexport function declareRefundExtension(\n factoryAddress: string,\n merchantPayouts: Record<string, string>,\n): Record<string, RefundExtension> {\n return {\n [REFUND_EXTENSION_KEY]: {\n info: {\n factoryAddress,\n merchantPayouts,\n },\n schema: {\n $schema: \"https://json-schema.org/draft/2020-12/schema\",\n type: \"object\",\n properties: {\n factoryAddress: {\n type: \"string\",\n pattern: \"^0x[a-fA-F0-9]{40}$\",\n description: \"The X402DepositRelayFactory contract address\",\n },\n merchantPayouts: {\n type: \"object\",\n additionalProperties: {\n type: \"string\",\n pattern: \"^0x[a-fA-F0-9]{40}$\",\n },\n description: \"Map of proxy address to merchant payout address\",\n },\n },\n required: [\"factoryAddress\", \"merchantPayouts\"],\n additionalProperties: false,\n },\n },\n };\n}\n\n/**\n * Marks a payment option as refundable.\n *\n * This function marks the option as refundable so it can be processed by `withRefund()`.\n * The merchantPayout is read directly from the option's `payTo` field when processing.\n *\n * @param option - The payment option to mark as refundable\n * @returns A new PaymentOption marked as refundable (does not mutate original)\n *\n * @example\n * ```typescript\n * const refundableOption = refundable({\n * scheme: \"exact\",\n * payTo: \"0xmerchant123...\",\n * price: \"$0.01\",\n * network: \"eip155:84532\",\n * });\n * // refundableOption.extra._x402_refund = true\n * ```\n */\nexport function refundable(option: PaymentOption): PaymentOption {\n // Deep clone the option to avoid mutation\n const clonedOption: PaymentOption = {\n ...option,\n extra: {\n ...option.extra,\n },\n };\n\n // Set marker to indicate this option is refundable\n if (!clonedOption.extra) {\n clonedOption.extra = {};\n }\n\n clonedOption.extra[REFUND_MARKER_KEY] = true;\n\n return clonedOption;\n}\n\n/**\n * Standard CreateX contract addresses per network.\n * These are the official CreateX deployments from https://github.com/pcaversaccio/createx#createx-deployments\n *\n * Note: If a network is not listed here, CreateX may need to be deployed separately.\n * The factory stores the CreateX address and can be queried via factory.getCreateX().\n */\nconst STANDARD_CREATEX_ADDRESSES: Record<string, string> = {\n // Ethereum Mainnet\n \"eip155:1\": \"0xba5Ed099633D3B313e4D5F7bdc1305d3c32ba066\",\n // Base Mainnet\n \"eip155:8453\": \"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed\",\n // Base Sepolia\n \"eip155:84532\": \"0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed\",\n // Add more networks as CreateX deployments become available\n};\n\n/**\n * Processes route configuration to handle refundable payment options.\n *\n * This function finds all payment options marked with `refundable()` and:\n * 1. Computes the proxy address using CREATE3 (no bytecode needed!)\n * 2. Sets `payTo` to the proxy address\n * 3. Adds the refund extension with factory address\n *\n * @param routes - Route configuration (single RouteConfig or Record<string, RouteConfig>)\n * @param factoryAddress - The X402DepositRelayFactory contract address (required)\n * @param createxAddress - The CreateX contract address (optional, will use standard address for network if not provided)\n * @returns A new RoutesConfig with refundable options routed to proxy (deep cloned, does not mutate original)\n *\n * @example\n * ```typescript\n * const routes = {\n * \"/api\": {\n * accepts: refundable({\n * scheme: \"exact\",\n * payTo: \"0xmerchant123...\",\n * price: \"$0.01\",\n * network: \"eip155:84532\",\n * }),\n * },\n * };\n *\n * // Version is optional - defaults to config file value\n * // CreateX address is optional - uses standard address for the network\n * const processedRoutes = withRefund(routes, \"0xFactory123...\");\n * // processedRoutes[\"/api\"].accepts.payTo = computed proxy address\n * // processedRoutes[\"/api\"].extensions.refund = { info: { factoryAddress: \"0xFactory123...\" }, schema: {...} }\n * ```\n */\nexport function withRefund(\n routes: RoutesConfig,\n factoryAddress: string,\n createxAddress?: string,\n): RoutesConfig {\n // Deep clone to avoid mutation\n if (typeof routes === \"object\" && routes !== null && !(\"accepts\" in routes)) {\n // Nested RoutesConfig: Record<string, RouteConfig>\n const nestedRoutes = routes as Record<string, RouteConfig>;\n const processedRoutes: Record<string, RouteConfig> = {};\n\n for (const [pattern, config] of Object.entries(nestedRoutes)) {\n processedRoutes[pattern] = processRouteConfig(config, factoryAddress, createxAddress);\n }\n\n return processedRoutes;\n } else {\n // Single RouteConfig\n return processRouteConfig(routes as RouteConfig, factoryAddress, createxAddress);\n }\n}\n\n/**\n * Gets the CreateX address for a given network.\n * First checks if provided explicitly, then falls back to standard addresses.\n *\n * @param network - The network identifier (e.g., \"eip155:84532\")\n * @param providedAddress - Optional CreateX address provided by user\n * @returns The CreateX address to use\n * @throws Error if no CreateX address can be determined\n */\nfunction getCreateXAddress(network: string, providedAddress?: string): string {\n if (providedAddress) {\n return providedAddress;\n }\n\n const standardAddress = STANDARD_CREATEX_ADDRESSES[network];\n if (standardAddress) {\n return standardAddress;\n }\n\n throw new Error(\n `CreateX address not provided and no standard address found for network ${network}. ` +\n `Please provide createxAddress parameter or check if CreateX is deployed on this network. ` +\n `See https://github.com/pcaversaccio/createx#createx-deployments for standard deployments.`,\n );\n}\n\n/**\n * Processes a single RouteConfig to transform refundable payment options.\n *\n * @param config - The route configuration to process\n * @param factoryAddress - The X402DepositRelayFactory contract address\n * @param createxAddress - The CreateX contract address (optional)\n * @returns A new RouteConfig with refundable options transformed\n */\nfunction processRouteConfig(\n config: RouteConfig,\n factoryAddress: string,\n createxAddress?: string,\n): RouteConfig {\n // Get the network from the first payment option to determine CreateX address\n const firstOption = Array.isArray(config.accepts) ? config.accepts[0] : config.accepts;\n const network = firstOption?.network;\n\n if (!network) {\n throw new Error(\"Payment option must have a network field to determine CreateX address\");\n }\n\n // Get CreateX address (from parameter or standard mapping)\n const resolvedCreatexAddress = getCreateXAddress(network, createxAddress);\n\n // Check if any option is refundable\n const hasRefundable = Array.isArray(config.accepts)\n ? config.accepts.some(isRefundableOption)\n : isRefundableOption(config.accepts);\n\n // Build map of proxyAddress -> merchantPayout BEFORE processing options\n // This allows us to store all merchantPayouts even if there are multiple refundable options\n const merchantPayoutsMap: Record<string, string> = {};\n\n if (hasRefundable) {\n const refundableOptions = Array.isArray(config.accepts)\n ? config.accepts.filter(isRefundableOption)\n : isRefundableOption(config.accepts)\n ? [config.accepts]\n : [];\n\n // Collect merchantPayouts from original options before processing\n for (const option of refundableOptions) {\n if (typeof option.payTo === \"string\") {\n const merchantPayout = option.payTo; // Original merchantPayout (before we overwrite it)\n const proxyAddress = computeRelayAddress(\n resolvedCreatexAddress,\n factoryAddress,\n merchantPayout,\n );\n merchantPayoutsMap[proxyAddress.toLowerCase()] = merchantPayout;\n }\n }\n }\n\n // Deep clone the config and process options\n const processedConfig: RouteConfig = {\n ...config,\n accepts: Array.isArray(config.accepts)\n ? config.accepts.map(option =>\n processPaymentOption(option, factoryAddress, resolvedCreatexAddress),\n )\n : processPaymentOption(config.accepts, factoryAddress, resolvedCreatexAddress),\n extensions: {\n ...config.extensions,\n },\n };\n\n // Add refund extension if any option is refundable\n if (hasRefundable && Object.keys(merchantPayoutsMap).length > 0) {\n processedConfig.extensions = {\n ...processedConfig.extensions,\n ...declareRefundExtension(factoryAddress, merchantPayoutsMap),\n };\n } else if (hasRefundable) {\n throw new Error(\n \"Refundable option must have a string payTo address. DynamicPayTo is not supported for refundable options.\",\n );\n }\n\n return processedConfig;\n}\n\n/**\n * Processes a single PaymentOption to transform it if refundable.\n *\n * @param option - The payment option to process\n * @param factoryAddress - The X402DepositRelayFactory contract address\n * @param createxAddress - The CreateX contract address (required)\n * @returns A new PaymentOption (transformed if refundable, unchanged otherwise)\n */\nfunction processPaymentOption(\n option: PaymentOption,\n factoryAddress: string,\n createxAddress: string,\n): PaymentOption {\n // Check if option is refundable\n if (isRefundableOption(option)) {\n // Read merchantPayout directly from payTo field (before we overwrite it)\n const merchantPayout = option.payTo;\n\n // If it's a function (DynamicPayTo), we can't compute the address (would need to call it)\n // For now, require it to be a string\n if (typeof merchantPayout !== \"string\") {\n throw new Error(\n \"DynamicPayTo is not supported for refundable options. Use a static address.\",\n );\n }\n\n // Compute proxy address using CREATE3 (no bytecode needed!)\n const proxyAddress = computeRelayAddress(createxAddress, factoryAddress, merchantPayout);\n\n // Deep clone the option\n const processedOption: PaymentOption = {\n ...option,\n payTo: proxyAddress, // Set payTo to proxy address\n extra: {\n ...option.extra,\n },\n };\n\n // Remove the marker since we've processed it\n if (processedOption.extra) {\n delete processedOption.extra[REFUND_MARKER_KEY];\n }\n\n return processedOption;\n }\n\n // Not refundable, return as-is (still clone to avoid mutation)\n return { ...option, extra: { ...option.extra } };\n}\n","/**\n * Facilitator-side helpers for the Refund Helper Extension\n *\n * These helpers allow facilitator operators to validate refund info\n * and handle refund settlements via X402DepositRelayProxy contracts.\n */\n\nimport type { PaymentPayload, PaymentRequirements, SettleResponse } from \"@x402/core/types\";\nimport type { FacilitatorEvmSigner } from \"@x402/evm\";\nimport { getAddress, isAddress, parseErc6492Signature, parseSignature, zeroAddress } from \"viem\";\nimport { REFUND_EXTENSION_KEY, type RefundExtension } from \"./types\";\n\n/**\n * Checks if an error is a rate limit error (429)\n */\nfunction isRateLimitError(error: unknown): boolean {\n if (error && typeof error === \"object\") {\n // Check for viem error structure\n const err = error as { status?: number; message?: string; details?: string; cause?: unknown };\n if (err.status === 429) {\n return true;\n }\n const message = err.message || err.details || \"\";\n if (typeof message === \"string\" && message.toLowerCase().includes(\"rate limit\")) {\n return true;\n }\n // Check nested cause\n if (err.cause && isRateLimitError(err.cause)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Wraps readContract calls with retry logic for rate limit errors\n * Uses exponential backoff: 1s, 2s, 4s, 8s, 16s\n */\nasync function readContractWithRetry<T>(\n signer: FacilitatorEvmSigner,\n args: {\n address: `0x${string}`;\n abi: readonly unknown[];\n functionName: string;\n args?: readonly unknown[];\n },\n maxRetries = 5,\n): Promise<T> {\n let lastError: unknown;\n \n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return (await signer.readContract(args)) as T;\n } catch (error) {\n lastError = error;\n \n // Only retry on rate limit errors\n if (!isRateLimitError(error)) {\n throw error;\n }\n \n // Don't retry on last attempt\n if (attempt >= maxRetries) {\n break;\n }\n \n // Exponential backoff: 1s, 2s, 4s, 8s, 16s\n const delayMs = Math.min(1000 * Math.pow(2, attempt), 16000);\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n }\n \n throw lastError;\n}\n\n/**\n * Factory ABI - any CREATE3-compatible factory that implements these methods can be used\n * No interface required - duck typing at runtime!\n *\n * Note: Proxies store all data directly (merchantPayout, token, escrow), so factory\n * only needs methods for deployment and address computation.\n */\nconst FACTORY_ABI = [\n {\n name: \"getMerchantFromRelay\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [{ name: \"relayAddress\", type: \"address\" }],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n {\n name: \"getRelayAddress\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [{ name: \"merchantPayout\", type: \"address\" }],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n {\n name: \"deployRelay\",\n type: \"function\",\n stateMutability: \"nonpayable\",\n inputs: [{ name: \"merchantPayout\", type: \"address\" }],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n {\n name: \"getCreateX\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n] as const;\n\n/**\n * Escrow ABI for shared escrow\n */\nconst ESCROW_ABI = [\n {\n name: \"registerMerchant\",\n type: \"function\",\n stateMutability: \"nonpayable\",\n inputs: [\n { name: \"merchantPayout\", type: \"address\" },\n { name: \"arbiter\", type: \"address\" },\n ],\n outputs: [],\n },\n {\n name: \"registeredMerchants\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [{ name: \"merchantPayout\", type: \"address\" }],\n outputs: [{ name: \"\", type: \"bool\" }],\n },\n {\n name: \"merchantArbiters\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [{ name: \"merchantPayout\", type: \"address\" }],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n {\n name: \"getArbiter\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [{ name: \"merchantPayout\", type: \"address\" }],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n {\n name: \"noteDeposit\",\n type: \"function\",\n stateMutability: \"nonpayable\",\n inputs: [\n { name: \"user\", type: \"address\" },\n { name: \"merchantPayout\", type: \"address\" },\n { name: \"amount\", type: \"uint256\" },\n ],\n outputs: [{ name: \"\", type: \"uint256\" }],\n },\n {\n name: \"release\",\n type: \"function\",\n stateMutability: \"nonpayable\",\n inputs: [\n { name: \"user\", type: \"address\" },\n { name: \"depositNonce\", type: \"uint256\" },\n ],\n outputs: [],\n },\n {\n name: \"refund\",\n type: \"function\",\n stateMutability: \"nonpayable\",\n inputs: [\n { name: \"user\", type: \"address\" },\n { name: \"depositNonce\", type: \"uint256\" },\n ],\n outputs: [],\n },\n {\n name: \"deposits\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [\n { name: \"user\", type: \"address\" },\n { name: \"depositNonce\", type: \"uint256\" },\n ],\n outputs: [\n { name: \"principal\", type: \"uint256\" },\n { name: \"timestamp\", type: \"uint256\" },\n { name: \"nonce\", type: \"uint256\" },\n { name: \"merchantPayout\", type: \"address\" },\n ],\n },\n] as const;\n\n/**\n * RelayProxy ABI - proxy stores all data directly\n */\nconst RELAY_PROXY_ABI = [\n {\n name: \"executeDeposit\",\n type: \"function\",\n stateMutability: \"nonpayable\",\n inputs: [\n { name: \"fromUser\", type: \"address\" },\n { name: \"amount\", type: \"uint256\" },\n { name: \"validAfter\", type: \"uint256\" },\n { name: \"validBefore\", type: \"uint256\" },\n { name: \"nonce\", type: \"bytes32\" },\n { name: \"v\", type: \"uint8\" },\n { name: \"r\", type: \"bytes32\" },\n { name: \"s\", type: \"bytes32\" },\n ],\n outputs: [],\n },\n {\n name: \"MERCHANT_PAYOUT\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n {\n name: \"TOKEN\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n {\n name: \"ESCROW\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [],\n outputs: [{ name: \"\", type: \"address\" }],\n },\n] as const;\n\n/**\n * Extracts refund extension info from payment payload or requirements\n *\n * @param paymentPayload - The payment payload (may contain extensions)\n * @param _ - The payment requirements (currently unused, kept for API compatibility)\n * @returns Refund extension info if valid, null otherwise\n */\nexport function extractRefundInfo(\n paymentPayload: PaymentPayload,\n _: PaymentRequirements,\n): { factoryAddress: string; merchantPayouts: Record<string, string> } | null {\n // Get extension from payload (extensions flow from PaymentRequired through PaymentPayload)\n const extension = paymentPayload.extensions?.[REFUND_EXTENSION_KEY] as\n | RefundExtension\n | undefined;\n\n if (!extension || !extension.info || !extension.info.factoryAddress) {\n return null;\n }\n\n const factoryAddress = extension.info.factoryAddress;\n const merchantPayouts = extension.info.merchantPayouts || {};\n\n // Validate factory address format\n if (!isAddress(factoryAddress)) {\n return null;\n }\n\n return { factoryAddress, merchantPayouts };\n}\n\n/**\n * Helper for facilitator operators to handle refund settlements via X402DepositRelayProxy.\n *\n * This function:\n * 1. Extracts refund extension info (factory address)\n * 2. Validates factory exists\n * 3. Reads merchantPayout and escrow directly from proxy storage\n * 4. Checks if merchant is registered\n * 5. Deploys relay on-demand if needed (via factory)\n * 6. Calls proxy.executeDeposit() to deposit funds into escrow\n *\n * Returns null if refund is not applicable (delegates to normal flow).\n * Throws error on execution failure or if merchant not registered (facilitator should handle in hook).\n *\n * @param paymentPayload - The payment payload containing authorization and signature\n * @param paymentRequirements - The payment requirements containing refund extension\n * @param signer - The EVM signer for contract interactions\n * @returns SettleResponse on success, null if not applicable\n * @throws Error on execution failure or if merchant not registered\n *\n * @example\n * ```typescript\n * facilitator.onBeforeSettle(async (context) => {\n * try {\n * const result = await settleWithRefundHelper(\n * context.paymentPayload,\n * context.paymentRequirements,\n * signer,\n * );\n *\n * if (result) {\n * return { abort: true, reason: 'handled_by_refund_helper' };\n * }\n * } catch (error) {\n * // Log error but don't abort - let normal settlement proceed\n * console.error('Refund helper settlement failed:', error);\n * }\n *\n * return null; // Proceed with normal settlement\n * });\n * ```\n */\nexport async function settleWithRefundHelper(\n paymentPayload: PaymentPayload,\n paymentRequirements: PaymentRequirements,\n signer: FacilitatorEvmSigner,\n): Promise<SettleResponse | null> {\n // Extract refund info from extension\n const refundInfo = extractRefundInfo(paymentPayload, paymentRequirements);\n if (!refundInfo) {\n return null; // Not refundable, proceed with normal settlement\n }\n\n const factoryAddress = refundInfo.factoryAddress;\n const merchantPayouts = refundInfo.merchantPayouts;\n\n // Check if factory exists (via code check)\n try {\n const factoryCode = await signer.getCode({ address: getAddress(factoryAddress) });\n if (!factoryCode || factoryCode === \"0x\" || factoryCode.length <= 2) {\n throw new Error(\n `Factory contract does not exist at ${factoryAddress}. Invalid refund extension.`,\n );\n }\n } catch (error) {\n throw new Error(\n `Failed to check factory contract: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Get proxy address from payTo\n const proxyAddress = getAddress(paymentRequirements.payTo);\n\n // Check if relay exists (via code check) - do this FIRST before trying to read from it\n const relayCode = await signer.getCode({ address: proxyAddress });\n const relayExists = relayCode && relayCode !== \"0x\" && relayCode.length > 2;\n\n // Get merchantPayout - try from deployed proxy first, then from extension merchantPayouts map if not deployed\n let merchantPayout: string;\n let escrowAddress: string | undefined;\n\n if (relayExists) {\n // Relay is deployed - read merchantPayout and escrow directly from proxy\n try {\n merchantPayout = await readContractWithRetry<string>(signer, {\n address: proxyAddress,\n abi: RELAY_PROXY_ABI,\n functionName: \"MERCHANT_PAYOUT\",\n args: [],\n });\n\n escrowAddress = await readContractWithRetry<string>(signer, {\n address: proxyAddress,\n abi: RELAY_PROXY_ABI,\n functionName: \"ESCROW\",\n args: [],\n });\n } catch (error) {\n // Proxy query failed even though code exists - might not be a refund proxy\n return null;\n }\n } else {\n // Relay not deployed - get merchantPayout from extension's merchantPayouts map\n // The extension contains a map of proxyAddress -> merchantPayout\n\n // Look up merchantPayout in the extension's merchantPayouts map\n // Try both lowercase and original case for the proxy address\n const proxyAddressLower = proxyAddress.toLowerCase();\n merchantPayout = merchantPayouts[proxyAddress] || merchantPayouts[proxyAddressLower];\n\n if (!merchantPayout || merchantPayout === zeroAddress) {\n return null; // Not a refund payment, proceed with normal settlement\n }\n\n // escrowAddress will be set after deployment\n }\n\n // If merchantPayout is zero address, this is not a refund payment\n if (!merchantPayout || merchantPayout === zeroAddress) {\n return null; // Not a refund payment, proceed with normal settlement\n }\n\n // Deploy relay on-demand if needed\n if (!relayExists) {\n try {\n // First, verify the expected address matches what the factory would compute\n const expectedAddress = await readContractWithRetry<string>(signer, {\n address: getAddress(factoryAddress),\n abi: FACTORY_ABI,\n functionName: \"getRelayAddress\",\n args: [getAddress(merchantPayout)],\n });\n\n // Verify addresses match (case-insensitive comparison)\n if (expectedAddress.toLowerCase() !== proxyAddress.toLowerCase()) {\n throw new Error(\n `Address mismatch: Factory computed ${expectedAddress} but expected ${proxyAddress}. ` +\n `This may indicate a version or CreateX address mismatch.`,\n );\n }\n\n // Deploy the relay via factory\n const txHash = await signer.writeContract({\n address: getAddress(factoryAddress),\n abi: FACTORY_ABI,\n functionName: \"deployRelay\",\n args: [getAddress(merchantPayout)],\n });\n\n // Wait for deployment transaction to be mined\n const receipt = await signer.waitForTransactionReceipt({ hash: txHash });\n\n // Verify transaction succeeded\n if (receipt.status !== \"success\") {\n throw new Error(`Relay deployment transaction failed: ${txHash}. Transaction reverted.`);\n }\n\n // Wait a bit for the code to be available (CREATE3 deployments can take a moment)\n // Retry checking for code up to 5 times with increasing delays\n let deployedCode: string | undefined;\n for (let i = 0; i < 5; i++) {\n if (i > 0) {\n const delay = 1000 * i; // 1s, 2s, 3s, 4s\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n deployedCode = await signer.getCode({ address: proxyAddress });\n if (deployedCode && deployedCode !== \"0x\" && deployedCode.length > 2) {\n break;\n }\n }\n\n // Verify the contract was actually deployed at the expected address\n // This is critical for CREATE3 deployments - the address is deterministic\n // but we need to ensure the deployment actually happened\n if (!deployedCode || deployedCode === \"0x\" || deployedCode.length <= 2) {\n // Double-check the factory's computed address\n const actualAddress = await readContractWithRetry<string>(signer, {\n address: getAddress(factoryAddress),\n abi: FACTORY_ABI,\n functionName: \"getRelayAddress\",\n args: [getAddress(merchantPayout)],\n });\n\n throw new Error(\n `Relay deployment completed but contract code not found at ${proxyAddress}. ` +\n `Transaction hash: ${txHash}. Factory computed address: ${actualAddress}. ` +\n `Expected address: ${proxyAddress}. ` +\n `This may indicate a CREATE3 deployment issue, timing problem, or address computation mismatch.`,\n );\n }\n\n // Now read escrow address from the newly deployed proxy\n escrowAddress = await readContractWithRetry<string>(signer, {\n address: proxyAddress,\n abi: RELAY_PROXY_ABI,\n functionName: \"ESCROW\",\n args: [],\n });\n } catch (error) {\n // Check if this is an insufficient funds error\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorString = errorMessage.toLowerCase();\n \n if (\n errorString.includes(\"insufficient funds\") ||\n errorString.includes(\"exceeds the balance\") ||\n errorString.includes(\"insufficient balance\") ||\n errorString.includes(\"the total cost\") ||\n errorString.includes(\"exceeds the balance of the account\")\n ) {\n const facilitatorAddress = signer.getAddresses()[0];\n throw new Error(\n `Failed to deploy relay: Insufficient funds in facilitator account.\\n` +\n `The facilitator account (${facilitatorAddress}) does not have enough ETH to pay for gas to deploy the relay contract.\\n` +\n `Please fund the facilitator account with ETH to cover gas costs.\\n` +\n `Original error: ${errorMessage}`,\n );\n }\n \n throw new Error(\n `Failed to deploy relay: ${errorMessage}`,\n );\n }\n }\n\n // At this point, escrowAddress must be set (either from reading deployed proxy or after deployment)\n if (!escrowAddress) {\n throw new Error(\"Internal error: escrowAddress not set after deployment check\");\n }\n\n // Check if merchant is registered\n let isRegistered: boolean;\n try {\n isRegistered = await readContractWithRetry<boolean>(signer, {\n address: getAddress(escrowAddress),\n abi: ESCROW_ABI,\n functionName: \"registeredMerchants\",\n args: [getAddress(merchantPayout)],\n });\n } catch (error) {\n throw new Error(\n `Failed to check merchant registration: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n if (!isRegistered) {\n throw new Error(\n `Merchant ${merchantPayout} is not registered. Please register at https://app.402r.org to enable refund functionality.`,\n );\n }\n\n // Extract payment parameters from payload\n const payload = paymentPayload.payload as {\n authorization?: {\n from: string;\n to: string;\n value: string;\n validAfter: string;\n validBefore: string;\n nonce: string;\n };\n signature?: string;\n };\n\n if (!payload.authorization || !payload.signature) {\n // Invalid payload structure, delegate to normal flow\n return null;\n }\n\n const { authorization, signature } = payload;\n\n // Verify that authorization.to matches the proxy address\n // The ERC3009 signature must have been signed with to=proxyAddress\n const authTo = getAddress(authorization.to);\n if (authTo !== proxyAddress) {\n throw new Error(\n `Authorization 'to' address (${authTo}) does not match proxy address (${proxyAddress}). ` +\n `The ERC3009 signature must be signed with to=proxyAddress.`,\n );\n }\n\n // Verify proxy can read its own immutables (this tests the _readImmutable function)\n // This helps catch issues before attempting executeDeposit\n try {\n const proxyToken = await readContractWithRetry<string>(signer, {\n address: proxyAddress,\n abi: RELAY_PROXY_ABI,\n functionName: \"TOKEN\",\n args: [],\n });\n\n // CRITICAL: Verify proxy TOKEN matches payment requirements asset\n // If they don't match, transferWithAuthorization will fail\n const proxyTokenNormalized = getAddress(proxyToken);\n const paymentAssetNormalized = getAddress(paymentRequirements.asset);\n\n if (proxyTokenNormalized !== paymentAssetNormalized) {\n const errorMsg =\n `❌ ADDRESS MISMATCH: Proxy has ${proxyTokenNormalized} but payment requires ${paymentAssetNormalized}. ` +\n `The proxy was deployed with the wrong address. ` +\n `This causes transferWithAuthorization to fail. ` +\n `Solution: Redeploy the proxy with the correct address: ${paymentAssetNormalized}`;\n throw new Error(errorMsg);\n }\n\n const proxyEscrow = await readContractWithRetry<string>(signer, {\n address: proxyAddress,\n abi: RELAY_PROXY_ABI,\n functionName: \"ESCROW\",\n args: [],\n });\n\n // Verify these match what we read earlier\n if (proxyEscrow.toLowerCase() !== escrowAddress.toLowerCase()) {\n throw new Error(\n `Proxy ESCROW mismatch: proxy reports ${proxyEscrow} but we read ${escrowAddress} earlier`,\n );\n }\n } catch (error) {\n throw new Error(\n `Failed to read proxy immutables. This may indicate a proxy deployment issue. ` +\n `Error: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Parse signature - handle ERC-6492 if needed\n let parsedSignature: string;\n try {\n const erc6492Result = parseErc6492Signature(signature as `0x${string}`);\n parsedSignature = erc6492Result.signature;\n } catch {\n // Not ERC-6492, use signature as-is\n parsedSignature = signature;\n }\n\n // Extract signature components (v, r, s)\n const signatureLength = parsedSignature.startsWith(\"0x\")\n ? parsedSignature.length - 2\n : parsedSignature.length;\n const isECDSA = signatureLength === 130;\n\n if (!isECDSA) {\n // Non-ECDSA signatures not supported\n return null;\n }\n\n // Parse signature into v, r, s\n const parsedSig = parseSignature(parsedSignature as `0x${string}`);\n const v = (parsedSig.v as number | undefined) ?? parsedSig.yParity ?? 0;\n const r = parsedSig.r;\n const s = parsedSig.s;\n\n // Check if nonce has already been used (ERC3009 tracks this)\n // This helps catch state issues before attempting executeDeposit\n try {\n const tokenAddress = getAddress(paymentRequirements.asset);\n const nonceUsed = await readContractWithRetry<boolean>(signer, {\n address: tokenAddress,\n abi: [\n {\n inputs: [\n { name: \"authorizer\", type: \"address\" },\n { name: \"nonce\", type: \"bytes32\" },\n ],\n name: \"authorizationState\",\n outputs: [{ name: \"\", type: \"bool\" }],\n stateMutability: \"view\",\n type: \"function\",\n },\n ],\n functionName: \"authorizationState\",\n args: [getAddress(authorization.from), authorization.nonce as `0x${string}`],\n });\n\n if (nonceUsed) {\n throw new Error(\n `ERC3009 nonce ${authorization.nonce} has already been used. ` +\n `This authorization cannot be reused.`,\n );\n }\n } catch (error) {\n // If authorizationState doesn't exist or fails, continue\n // Some contracts might not implement this function\n }\n\n // Call proxy.executeDeposit() with retry logic\n let lastError: unknown;\n const maxRetries = 5;\n \n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const txHash = await signer.writeContract({\n address: proxyAddress,\n abi: RELAY_PROXY_ABI,\n functionName: \"executeDeposit\",\n args: [\n getAddress(authorization.from),\n BigInt(authorization.value),\n BigInt(authorization.validAfter),\n BigInt(authorization.validBefore),\n authorization.nonce as `0x${string}`,\n v,\n r,\n s,\n ],\n });\n\n // Wait for transaction confirmation\n const receipt = await signer.waitForTransactionReceipt({ hash: txHash });\n\n if (receipt.status !== \"success\") {\n throw new Error(`Proxy.executeDeposit transaction failed: ${txHash}`);\n }\n\n // Return SettleResponse on success\n return {\n success: true,\n transaction: txHash,\n network: paymentRequirements.network,\n payer: authorization.from,\n };\n } catch (error) {\n lastError = error;\n \n // Don't retry on last attempt\n if (attempt >= maxRetries) {\n break;\n }\n \n // Retry with delays: 1s, 2s, 3s, 4s, 5s\n const delayMs = (attempt + 1) * 1000;\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n }\n \n // All retries exhausted, handle error\n const error = lastError;\n \n // Execution failure - throw error (don't return null)\n // Extract more detailed error information from nested error objects\n let errorMessage = error instanceof Error ? error.message : String(error);\n let revertReason: string | undefined;\n\n // Try to extract revert reason from nested error structure (viem error format)\n if (error && typeof error === \"object\") {\n const errorObj = error as Record<string, unknown>;\n\n // Check for cause.reason (viem ContractFunctionRevertedError)\n if (errorObj.cause && typeof errorObj.cause === \"object\") {\n const cause = errorObj.cause as Record<string, unknown>;\n if (typeof cause.reason === \"string\") {\n revertReason = cause.reason;\n }\n // Check for cause.data which might contain encoded revert reason\n if (cause.data && typeof cause.data === \"string\") {\n // Try to decode as a string if it's a revert with reason string\n // Revert with reason string starts with 0x08c379a0 (Error(string) selector) + offset + length + string\n if (cause.data.startsWith(\"0x08c379a0\")) {\n try {\n // Skip selector (4 bytes) + offset (32 bytes) + length (32 bytes) = 68 chars\n const lengthHex = cause.data.slice(138, 202); // Length is at offset 68\n const length = parseInt(lengthHex, 16);\n const stringHex = cause.data.slice(202, 202 + length * 2);\n const decodedReason = Buffer.from(stringHex, \"hex\")\n .toString(\"utf8\")\n .replace(/\\0/g, \"\");\n if (decodedReason) {\n revertReason = decodedReason;\n }\n } catch (decodeErr) {\n // Failed to decode revert reason\n }\n }\n }\n }\n\n // Check for shortMessage (viem error format)\n if (typeof errorObj.shortMessage === \"string\") {\n if (!revertReason && errorObj.shortMessage.includes(\"reverted\")) {\n // Try to extract from shortMessage\n const match = errorObj.shortMessage.match(/reverted(?:[: ]+)?(.+)/i);\n if (match && match[1]) {\n revertReason = match[1].trim();\n }\n }\n }\n }\n\n // Build detailed error message\n if (revertReason && revertReason !== \"execution reverted\") {\n errorMessage = `Contract reverted: ${revertReason}`;\n } else {\n errorMessage =\n `Contract execution reverted (no specific revert reason available). ` +\n `All pre-checks passed: merchant registered, nonce unused, proxy immutables readable. ` +\n `Possible failure points in executeDeposit: ` +\n `1) _readImmutable staticcall failing (unlikely - we can read immutables directly), ` +\n `2) ERC3009 transferWithAuthorization failing when called through proxy (most likely), ` +\n `3) Transfer to escrow failing, ` +\n `4) Escrow.noteDeposit failing - POOL.supply() may be paused, asset not configured in Aave pool, or pool has restrictions. ` +\n `Check Aave pool status and asset configuration on Base Sepolia. ` +\n `Debugging: Use a transaction trace/debugger on the failed transaction to see exact revert point. ` +\n `The simulation succeeds, so the contract logic is correct - this is likely an execution context issue. ` +\n `Original error: ${errorMessage}`;\n }\n\n throw new Error(`Failed to execute proxy.executeDeposit: ${errorMessage}`);\n}\n"],"mappings":"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","names":["import_viem","error","errorMessage"]}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { PaymentOption, RoutesConfig } from '@x402/core/http';
|
|
2
|
+
import { PaymentPayload, PaymentRequirements, SettleResponse } from '@x402/core/types';
|
|
3
|
+
import { FacilitatorEvmSigner } from '@x402/evm';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type definitions for the Refund Helper Extension
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extension identifier constant for the refund extension
|
|
11
|
+
*/
|
|
12
|
+
declare const REFUND_EXTENSION_KEY = "refund";
|
|
13
|
+
/**
|
|
14
|
+
* Constant for the refund marker key (internal marker)
|
|
15
|
+
* Used to identify refundable payment options
|
|
16
|
+
* The merchantPayout is read directly from the option's payTo field when processing
|
|
17
|
+
*/
|
|
18
|
+
declare const REFUND_MARKER_KEY = "_x402_refund";
|
|
19
|
+
/**
|
|
20
|
+
* Refund extension info structure
|
|
21
|
+
*
|
|
22
|
+
* merchantPayouts: Map of proxy address -> merchantPayout
|
|
23
|
+
* This allows multiple refundable options with different merchantPayouts
|
|
24
|
+
*/
|
|
25
|
+
interface RefundExtensionInfo {
|
|
26
|
+
factoryAddress: string;
|
|
27
|
+
merchantPayouts: Record<string, string>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Refund extension structure (matches extension pattern with info and schema)
|
|
31
|
+
*/
|
|
32
|
+
interface RefundExtension {
|
|
33
|
+
info: RefundExtensionInfo;
|
|
34
|
+
schema: {
|
|
35
|
+
$schema: "https://json-schema.org/draft/2020-12/schema";
|
|
36
|
+
type: "object";
|
|
37
|
+
properties: {
|
|
38
|
+
factoryAddress: {
|
|
39
|
+
type: "string";
|
|
40
|
+
pattern: "^0x[a-fA-F0-9]{40}$";
|
|
41
|
+
description: "The X402DepositRelayFactory contract address";
|
|
42
|
+
};
|
|
43
|
+
merchantPayouts: {
|
|
44
|
+
type: "object";
|
|
45
|
+
additionalProperties: {
|
|
46
|
+
type: "string";
|
|
47
|
+
pattern: "^0x[a-fA-F0-9]{40}$";
|
|
48
|
+
};
|
|
49
|
+
description: "Map of proxy address to merchant payout address";
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
required: ["factoryAddress", "merchantPayouts"];
|
|
53
|
+
additionalProperties: false;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Type guard to check if a payment option is refundable
|
|
58
|
+
* A refundable option has a marker stored in extra
|
|
59
|
+
*
|
|
60
|
+
* @param option - The payment option to check
|
|
61
|
+
* @returns True if the option is refundable
|
|
62
|
+
*/
|
|
63
|
+
declare function isRefundableOption(option: PaymentOption): boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Server-side helpers for the Refund Helper Extension
|
|
67
|
+
*
|
|
68
|
+
* These helpers allow merchants to mark payment options as refundable
|
|
69
|
+
* and process route configurations to route payments to X402DepositRelayProxy contracts.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Declares a refund extension with factory address and merchantPayouts map
|
|
74
|
+
*
|
|
75
|
+
* @param factoryAddress - The X402DepositRelayFactory contract address
|
|
76
|
+
* @param merchantPayouts - Map of proxy address to merchant payout address
|
|
77
|
+
* @returns Refund extension object with info and schema
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* const extension = declareRefundExtension("0xFactory123...", {
|
|
82
|
+
* "0xProxy1...": "0xMerchant1...",
|
|
83
|
+
* "0xProxy2...": "0xMerchant2...",
|
|
84
|
+
* });
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare function declareRefundExtension(factoryAddress: string, merchantPayouts: Record<string, string>): Record<string, RefundExtension>;
|
|
88
|
+
/**
|
|
89
|
+
* Marks a payment option as refundable.
|
|
90
|
+
*
|
|
91
|
+
* This function marks the option as refundable so it can be processed by `withRefund()`.
|
|
92
|
+
* The merchantPayout is read directly from the option's `payTo` field when processing.
|
|
93
|
+
*
|
|
94
|
+
* @param option - The payment option to mark as refundable
|
|
95
|
+
* @returns A new PaymentOption marked as refundable (does not mutate original)
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const refundableOption = refundable({
|
|
100
|
+
* scheme: "exact",
|
|
101
|
+
* payTo: "0xmerchant123...",
|
|
102
|
+
* price: "$0.01",
|
|
103
|
+
* network: "eip155:84532",
|
|
104
|
+
* });
|
|
105
|
+
* // refundableOption.extra._x402_refund = true
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
declare function refundable(option: PaymentOption): PaymentOption;
|
|
109
|
+
/**
|
|
110
|
+
* Processes route configuration to handle refundable payment options.
|
|
111
|
+
*
|
|
112
|
+
* This function finds all payment options marked with `refundable()` and:
|
|
113
|
+
* 1. Computes the proxy address using CREATE3 (no bytecode needed!)
|
|
114
|
+
* 2. Sets `payTo` to the proxy address
|
|
115
|
+
* 3. Adds the refund extension with factory address
|
|
116
|
+
*
|
|
117
|
+
* @param routes - Route configuration (single RouteConfig or Record<string, RouteConfig>)
|
|
118
|
+
* @param factoryAddress - The X402DepositRelayFactory contract address (required)
|
|
119
|
+
* @param createxAddress - The CreateX contract address (optional, will use standard address for network if not provided)
|
|
120
|
+
* @returns A new RoutesConfig with refundable options routed to proxy (deep cloned, does not mutate original)
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* const routes = {
|
|
125
|
+
* "/api": {
|
|
126
|
+
* accepts: refundable({
|
|
127
|
+
* scheme: "exact",
|
|
128
|
+
* payTo: "0xmerchant123...",
|
|
129
|
+
* price: "$0.01",
|
|
130
|
+
* network: "eip155:84532",
|
|
131
|
+
* }),
|
|
132
|
+
* },
|
|
133
|
+
* };
|
|
134
|
+
*
|
|
135
|
+
* // Version is optional - defaults to config file value
|
|
136
|
+
* // CreateX address is optional - uses standard address for the network
|
|
137
|
+
* const processedRoutes = withRefund(routes, "0xFactory123...");
|
|
138
|
+
* // processedRoutes["/api"].accepts.payTo = computed proxy address
|
|
139
|
+
* // processedRoutes["/api"].extensions.refund = { info: { factoryAddress: "0xFactory123..." }, schema: {...} }
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
declare function withRefund(routes: RoutesConfig, factoryAddress: string, createxAddress?: string): RoutesConfig;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Helper to compute CREATE3 address for RelayProxy
|
|
146
|
+
*
|
|
147
|
+
* Uses the CREATE3 formula via CreateX (matching Solidity implementation):
|
|
148
|
+
*
|
|
149
|
+
* Where:
|
|
150
|
+
* - salt = keccak256(abi.encodePacked(factoryAddress, merchantPayout))
|
|
151
|
+
* - guardedSalt = keccak256(abi.encode(salt)) // CreateX guards the salt
|
|
152
|
+
* - createxDeployer = the CreateX contract address
|
|
153
|
+
*
|
|
154
|
+
* CREATE3 is much simpler than CREATE2 - no bytecode needed!
|
|
155
|
+
* The address depends only on the deployer (CreateX) and salt.
|
|
156
|
+
*
|
|
157
|
+
* IMPORTANT: The CreateX address must match the one used by the factory contract.
|
|
158
|
+
* The factory stores its CreateX address and can be queried via factory.getCreateX().
|
|
159
|
+
* This function computes addresses locally without any on-chain calls.
|
|
160
|
+
*/
|
|
161
|
+
/**
|
|
162
|
+
* Computes the CREATE3 address for a merchant's relay proxy
|
|
163
|
+
*
|
|
164
|
+
* This matches the Solidity implementation in DepositRelayFactory.getRelayAddress():
|
|
165
|
+
* 1. salt = keccak256(abi.encodePacked(merchantPayout))
|
|
166
|
+
* 2. guardedSalt = keccak256(abi.encode(salt)) // CreateX guards the salt
|
|
167
|
+
* 3. return CREATEX.computeCreate3Address(guardedSalt)
|
|
168
|
+
*
|
|
169
|
+
* Uses the @whoislewys/predict-deterministic-address library which correctly
|
|
170
|
+
* implements the CREATE3 formula used by CreateX (based on Solady's CREATE3).
|
|
171
|
+
* This ensures the computed address matches the factory's on-chain computation
|
|
172
|
+
* without requiring any on-chain calls.
|
|
173
|
+
*
|
|
174
|
+
* @param createxAddress - The CreateX contract address
|
|
175
|
+
* @param factoryAddress - The DepositRelayFactory contract address
|
|
176
|
+
* @param merchantPayout - The merchant's payout address
|
|
177
|
+
* @returns The deterministic proxy address
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* // No bytecode needed! Computes locally without on-chain calls.
|
|
182
|
+
* const relayAddress = computeRelayAddress(
|
|
183
|
+
* "0xCreateX123...",
|
|
184
|
+
* "0xMerchant123...",
|
|
185
|
+
* 0n // version
|
|
186
|
+
* );
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
declare function computeRelayAddress(createxAddress: string, factoryAddress: string, merchantPayout: string): string;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Facilitator-side helpers for the Refund Helper Extension
|
|
193
|
+
*
|
|
194
|
+
* These helpers allow facilitator operators to validate refund info
|
|
195
|
+
* and handle refund settlements via X402DepositRelayProxy contracts.
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Extracts refund extension info from payment payload or requirements
|
|
200
|
+
*
|
|
201
|
+
* @param paymentPayload - The payment payload (may contain extensions)
|
|
202
|
+
* @param _ - The payment requirements (currently unused, kept for API compatibility)
|
|
203
|
+
* @returns Refund extension info if valid, null otherwise
|
|
204
|
+
*/
|
|
205
|
+
declare function extractRefundInfo(paymentPayload: PaymentPayload, _: PaymentRequirements): {
|
|
206
|
+
factoryAddress: string;
|
|
207
|
+
merchantPayouts: Record<string, string>;
|
|
208
|
+
} | null;
|
|
209
|
+
/**
|
|
210
|
+
* Helper for facilitator operators to handle refund settlements via X402DepositRelayProxy.
|
|
211
|
+
*
|
|
212
|
+
* This function:
|
|
213
|
+
* 1. Extracts refund extension info (factory address)
|
|
214
|
+
* 2. Validates factory exists
|
|
215
|
+
* 3. Reads merchantPayout and escrow directly from proxy storage
|
|
216
|
+
* 4. Checks if merchant is registered
|
|
217
|
+
* 5. Deploys relay on-demand if needed (via factory)
|
|
218
|
+
* 6. Calls proxy.executeDeposit() to deposit funds into escrow
|
|
219
|
+
*
|
|
220
|
+
* Returns null if refund is not applicable (delegates to normal flow).
|
|
221
|
+
* Throws error on execution failure or if merchant not registered (facilitator should handle in hook).
|
|
222
|
+
*
|
|
223
|
+
* @param paymentPayload - The payment payload containing authorization and signature
|
|
224
|
+
* @param paymentRequirements - The payment requirements containing refund extension
|
|
225
|
+
* @param signer - The EVM signer for contract interactions
|
|
226
|
+
* @returns SettleResponse on success, null if not applicable
|
|
227
|
+
* @throws Error on execution failure or if merchant not registered
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* facilitator.onBeforeSettle(async (context) => {
|
|
232
|
+
* try {
|
|
233
|
+
* const result = await settleWithRefundHelper(
|
|
234
|
+
* context.paymentPayload,
|
|
235
|
+
* context.paymentRequirements,
|
|
236
|
+
* signer,
|
|
237
|
+
* );
|
|
238
|
+
*
|
|
239
|
+
* if (result) {
|
|
240
|
+
* return { abort: true, reason: 'handled_by_refund_helper' };
|
|
241
|
+
* }
|
|
242
|
+
* } catch (error) {
|
|
243
|
+
* // Log error but don't abort - let normal settlement proceed
|
|
244
|
+
* console.error('Refund helper settlement failed:', error);
|
|
245
|
+
* }
|
|
246
|
+
*
|
|
247
|
+
* return null; // Proceed with normal settlement
|
|
248
|
+
* });
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
declare function settleWithRefundHelper(paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements, signer: FacilitatorEvmSigner): Promise<SettleResponse | null>;
|
|
252
|
+
|
|
253
|
+
export { REFUND_EXTENSION_KEY, REFUND_MARKER_KEY, type RefundExtension, type RefundExtensionInfo, computeRelayAddress, declareRefundExtension, extractRefundInfo, isRefundableOption, refundable, settleWithRefundHelper, withRefund };
|