{"version":3,"sources":["../../src/batch-settlement/authorizerSigner.ts","../../src/batch-settlement/facilitator/utils.ts"],"sourcesContent":["import type { AuthorizerSigner, BatchSettlementVoucherClaim } from \"./types\";\nimport { claimBatchTypes, refundTypes } from \"./constants\";\nimport { computeChannelId, getBatchSettlementEip712Domain } from \"./utils\";\nimport { getEvmChainId } from \"../utils\";\n\n/**\n * Signs a `ClaimBatch` EIP-712 digest for `claimWithSignature()`.\n *\n * @param signer - Authorizer signer holding the `receiverAuthorizer` key.\n * @param claims - Voucher claims to include in the batch.\n * @param network - CAIP-2 network identifier (e.g. `\"eip155:84532\"`).\n * @returns EIP-712 signature over `ClaimBatch(ClaimEntry[] claims)`.\n */\nexport async function signClaimBatch(\n  signer: AuthorizerSigner,\n  claims: BatchSettlementVoucherClaim[],\n  network: string,\n): Promise<`0x${string}`> {\n  const chainId = getEvmChainId(network);\n\n  const claimEntries = claims.map(c => ({\n    channelId: computeChannelId(c.voucher.channel, chainId),\n    maxClaimableAmount: BigInt(c.voucher.maxClaimableAmount),\n    totalClaimed: BigInt(c.totalClaimed),\n  }));\n\n  return signer.signTypedData({\n    domain: getBatchSettlementEip712Domain(chainId),\n    types: claimBatchTypes,\n    primaryType: \"ClaimBatch\",\n    message: {\n      claims: claimEntries,\n    },\n  });\n}\n\n/**\n * Signs a `Refund` EIP-712 digest for `refundWithSignature()`.\n *\n * @param signer - Authorizer signer holding the `receiverAuthorizer` key.\n * @param channelId - Channel to authorize refund for.\n * @param amount - Refund amount (capped to unclaimed escrow onchain).\n * @param nonce - Must match onchain `refundNonce(channelId)`.\n * @param network - CAIP-2 network identifier (e.g. `\"eip155:84532\"`).\n * @returns EIP-712 signature over `Refund(channelId, nonce, amount)`.\n */\nexport async function signRefund(\n  signer: AuthorizerSigner,\n  channelId: `0x${string}`,\n  amount: string,\n  nonce: string,\n  network: string,\n): Promise<`0x${string}`> {\n  const chainId = getEvmChainId(network);\n\n  return signer.signTypedData({\n    domain: getBatchSettlementEip712Domain(chainId),\n    types: refundTypes,\n    primaryType: \"Refund\",\n    message: {\n      channelId,\n      nonce: BigInt(nonce),\n      amount: BigInt(amount),\n    },\n  });\n}\n","import { getAddress, hashTypedData, recoverAddress, isAddressEqual } from \"viem\";\nimport { verifyTypedDataSignature } from \"../../shared/verifySignature\";\nimport type { PaymentRequirements } from \"@x402/core/types\";\nimport { FacilitatorEvmSigner } from \"../../signer\";\nimport { multicall } from \"../../multicall\";\nimport {\n  BATCH_SETTLEMENT_ADDRESS,\n  MIN_WITHDRAW_DELAY,\n  MAX_WITHDRAW_DELAY,\n  voucherTypes,\n} from \"../constants\";\nimport { batchSettlementABI } from \"../abi\";\nimport type {\n  BatchSettlementPaymentRequirementsExtra,\n  ChannelConfig,\n  ChannelState,\n} from \"../types\";\nimport { computeChannelId, getBatchSettlementEip712Domain } from \"../utils\";\nimport * as Errors from \"../errors\";\n\n/**\n * Normalises a {@link ChannelConfig} into the checksummed-address tuple expected by the\n * batch-settlement contract's `deposit` / `refundWithSignature` / `claimWithSignature` calls.\n *\n * @param config - In-memory channel configuration.\n * @returns Channel config tuple with all address fields checksummed via `getAddress`.\n */\nexport function toContractChannelConfig(config: ChannelConfig) {\n  return {\n    payer: getAddress(config.payer),\n    payerAuthorizer: getAddress(config.payerAuthorizer),\n    receiver: getAddress(config.receiver),\n    receiverAuthorizer: getAddress(config.receiverAuthorizer),\n    token: getAddress(config.token),\n    withdrawDelay: config.withdrawDelay,\n    salt: config.salt,\n  };\n}\n\n/**\n * Case-insensitive comparison of two channel id hex strings.\n *\n * @param a - First channel id.\n * @param b - Second channel id (may be any unknown value).\n * @returns `true` when both ids refer to the same channel.\n */\nexport function channelIdsEqual(a: `0x${string}`, b: unknown): boolean {\n  if (typeof b !== \"string\" || b.length === 0) return false;\n  const norm = (x: string) => {\n    let s = x.toLowerCase();\n    if (s.startsWith(\"0x\")) s = s.slice(2);\n    return `0x${s}`;\n  };\n  return norm(a) === norm(b);\n}\n\n/**\n * Validates the time window of an ERC-3009 `ReceiveWithAuthorization`.\n *\n * @param validAfter - Earliest unix timestamp the authorization is valid (in seconds).\n * @param validBefore - Latest unix timestamp before which the authorization is valid.\n * @returns An error code string if the time window is invalid, otherwise `undefined`.\n */\nexport function erc3009AuthorizationTimeInvalidReason(\n  validAfter: bigint,\n  validBefore: bigint,\n): string | undefined {\n  const now = Math.floor(Date.now() / 1000);\n  if (validBefore < BigInt(now + 6)) return Errors.ErrValidBeforeExpired;\n  if (validAfter > BigInt(now)) return Errors.ErrValidAfterInFuture;\n  return undefined;\n}\n\n/**\n * Dual-path voucher signature verification.\n *\n * When `payerAuthorizer` is a non-zero address, the signature is verified off-chain via\n * ECDSA recovery against that address (no RPC call).  When `payerAuthorizer` is `address(0)`,\n * verification falls back to an ERC-1271 `isValidSignature` call against the payer contract\n * (smart-wallet path).\n *\n * @param signer - Facilitator signer providing `verifyTypedData` (may issue RPC for ERC-1271).\n * @param params - Voucher fields and authorizer addresses needed for verification.\n * @param params.channelId - EIP-712 voucher channel id (`bytes32` hex).\n * @param params.maxClaimableAmount - Max cumulative claimable amount as a decimal string.\n * @param params.payerAuthorizer - Address that signed the voucher; zero address selects ERC-1271 verification.\n * @param params.payer - Payer contract address (used for ERC-1271).\n * @param params.signature - EIP-712 signature bytes over the voucher.\n * @param chainId - Numeric EVM chain id for the EIP-712 domain.\n * @returns `true` when the voucher signature is valid.\n */\nexport async function verifyBatchSettlementVoucherTypedData(\n  signer: FacilitatorEvmSigner,\n  params: {\n    channelId: `0x${string}`;\n    maxClaimableAmount: string;\n    payerAuthorizer: `0x${string}`;\n    payer: `0x${string}`;\n    signature: `0x${string}`;\n  },\n  chainId: number,\n): Promise<boolean> {\n  const domain = getBatchSettlementEip712Domain(chainId);\n  const message = {\n    channelId: params.channelId,\n    maxClaimableAmount: BigInt(params.maxClaimableAmount),\n  };\n\n  const zeroAddress = \"0x0000000000000000000000000000000000000000\";\n\n  if (params.payerAuthorizer !== zeroAddress) {\n    // on-chain x402BatchSettlement uses ECDSA.recoverCalldata — pure ecrecover,\n    // no code check, no EIP-1271. Use recoverAddress directly so there is no\n    // ambiguity: this path never issues an RPC call regardless of address state.\n    try {\n      const digest = hashTypedData({\n        domain,\n        types: voucherTypes,\n        primaryType: \"Voucher\",\n        message,\n      });\n      const recovered = await recoverAddress({\n        hash: digest,\n        signature: params.signature as `0x${string}`,\n      });\n      return isAddressEqual(recovered, getAddress(params.payerAuthorizer));\n    } catch {\n      return false;\n    }\n  }\n\n  // payerAuthorizer == 0 path: x402BatchSettlement._processVoucherClaim falls\n  // back to OpenZeppelin's SignatureChecker.isValidSignatureNow(payer, …) which\n  // routes by signer.code.length (ECDSA for EOAs, strict EIP-1271 for contracts\n  // including 7702-delegated EOAs). Mirror that exactly — no ECDSA fallback\n  // for addresses with code.\n  return verifyTypedDataSignature(signer, {\n    address: getAddress(params.payer),\n    domain,\n    types: voucherTypes,\n    primaryType: \"Voucher\",\n    message,\n    signature: params.signature,\n  });\n}\n\n/**\n * Validates that a {@link ChannelConfig} is consistent with the claimed `channelId` and\n * the server's {@link PaymentRequirements}.\n *\n * @param config - The channel configuration from the payload.\n * @param channelId - The `channelId` claimed in the payload.\n * @param requirements - Server payment requirements to cross-check against.\n * @returns An error code string if validation fails, otherwise `undefined`.\n */\nexport function validateChannelConfig(\n  config: ChannelConfig,\n  channelId: `0x${string}`,\n  requirements: PaymentRequirements,\n): string | undefined {\n  const computedId = computeChannelId(config, requirements.network);\n  if (computedId.toLowerCase() !== channelId.toLowerCase()) {\n    return Errors.ErrChannelIdMismatch;\n  }\n\n  if (getAddress(config.receiver) !== getAddress(requirements.payTo)) {\n    return Errors.ErrReceiverMismatch;\n  }\n\n  const extra = requirements.extra as Partial<BatchSettlementPaymentRequirementsExtra> | undefined;\n  const requiredReceiverAuthorizer = extra?.receiverAuthorizer;\n\n  if (\n    !requiredReceiverAuthorizer ||\n    getAddress(requiredReceiverAuthorizer) === \"0x0000000000000000000000000000000000000000\" ||\n    getAddress(config.receiverAuthorizer) !== getAddress(requiredReceiverAuthorizer)\n  ) {\n    return Errors.ErrReceiverAuthorizerMismatch;\n  }\n\n  if (getAddress(config.token) !== getAddress(requirements.asset)) {\n    return Errors.ErrTokenMismatch;\n  }\n\n  if (extra?.withdrawDelay !== undefined && config.withdrawDelay !== Number(extra.withdrawDelay)) {\n    return Errors.ErrWithdrawDelayMismatch;\n  }\n\n  if (config.withdrawDelay < MIN_WITHDRAW_DELAY || config.withdrawDelay > MAX_WITHDRAW_DELAY) {\n    return Errors.ErrWithdrawDelayOutOfRange;\n  }\n\n  return undefined;\n}\n\n/**\n * Reads onchain channel state via a 3-call multicall:\n * `channels(channelId)`, `pendingWithdrawals(channelId)`, `refundNonce(channelId)`.\n *\n * Throws when any sub-call fails so callers can distinguish RPC failures\n * from missing channels (which return zero balance/totalClaimed/refundNonce).\n *\n * @param signer - Facilitator signer for onchain reads.\n * @param channelId - The `bytes32` channel id.\n * @returns Fresh {@link ChannelState}.\n */\nexport async function readChannelState(\n  signer: FacilitatorEvmSigner,\n  channelId: `0x${string}`,\n): Promise<ChannelState> {\n  const target = getAddress(BATCH_SETTLEMENT_ADDRESS);\n  const mcResults = await multicall(signer.readContract.bind(signer), [\n    { address: target, abi: batchSettlementABI, functionName: \"channels\", args: [channelId] },\n    {\n      address: target,\n      abi: batchSettlementABI,\n      functionName: \"pendingWithdrawals\",\n      args: [channelId],\n    },\n    { address: target, abi: batchSettlementABI, functionName: \"refundNonce\", args: [channelId] },\n  ]);\n\n  const [chRes, wdRes, rnRes] = mcResults;\n  if (chRes.status === \"failure\" || wdRes.status === \"failure\" || rnRes.status === \"failure\") {\n    throw new Error(`${Errors.ErrRpcReadFailed}: multicall returned failure for ${channelId}`);\n  }\n\n  const [balance, totalClaimed] = chRes.result as [bigint, bigint];\n  const [, wdInitiatedAt] = wdRes.result as [bigint, bigint];\n  const refundNonce = rnRes.result as bigint;\n\n  return { balance, totalClaimed, withdrawRequestedAt: Number(wdInitiatedAt), refundNonce };\n}\n"],"mappings":"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","names":[]}