@agentokratia/x402-escrow 2.0.0 → 2.0.1

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.
@@ -85,7 +85,7 @@ var BaseStorage = class {
85
85
  get(network, receiver) {
86
86
  const now = Date.now() / 1e3;
87
87
  for (const session of this.sessions.values()) {
88
- if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now) {
88
+ if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now && session.status === "active") {
89
89
  return session;
90
90
  }
91
91
  }
@@ -96,7 +96,7 @@ var BaseStorage = class {
96
96
  let best = null;
97
97
  let bestBalance = 0n;
98
98
  for (const session of this.sessions.values()) {
99
- if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now) {
99
+ if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now && session.status === "active") {
100
100
  const balance = BigInt(session.balance);
101
101
  if (balance >= minAmount && balance > bestBalance) {
102
102
  best = session;
@@ -179,7 +179,16 @@ var SessionManager = class {
179
179
  * Store a session from escrow settlement response.
180
180
  */
181
181
  store(session) {
182
- this.storage.set({ ...session, createdAt: Date.now() });
182
+ this.storage.set({ ...session, createdAt: Date.now(), status: "active" });
183
+ }
184
+ /**
185
+ * Update session status (e.g., mark as inactive when reclaimed).
186
+ */
187
+ setStatus(sessionId, status) {
188
+ const session = this.storage.list().find((s) => s.sessionId === sessionId);
189
+ if (session) {
190
+ this.storage.set({ ...session, status });
191
+ }
183
192
  }
184
193
  /**
185
194
  * Get session for a specific receiver.
@@ -212,6 +221,21 @@ var SessionManager = class {
212
221
  getAll() {
213
222
  return this.storage.list();
214
223
  }
224
+ /**
225
+ * Get all active sessions for a specific receiver.
226
+ */
227
+ getAllForReceiver(receiver) {
228
+ const now = Date.now() / 1e3;
229
+ return this.storage.list().filter(
230
+ (s) => s.network === this.network && s.receiver.toLowerCase() === receiver.toLowerCase() && s.authorizationExpiry > now && s.status === "active"
231
+ );
232
+ }
233
+ /**
234
+ * Get session by ID.
235
+ */
236
+ getById(sessionId) {
237
+ return this.storage.list().find((s) => s.sessionId === sessionId) || null;
238
+ }
215
239
  /**
216
240
  * Remove a specific session.
217
241
  */
@@ -295,6 +319,8 @@ async function signERC3009(wallet, authorization, domain) {
295
319
  var EscrowScheme = class {
296
320
  constructor(walletClient, options = {}) {
297
321
  this.scheme = "escrow";
322
+ /** Force new session on next createPaymentPayload call (resets after use) */
323
+ this.forceNewSession = false;
298
324
  if (!walletClient.account) {
299
325
  throw new Error("WalletClient must have an account");
300
326
  }
@@ -319,13 +345,39 @@ var EscrowScheme = class {
319
345
  /**
320
346
  * Creates payment payload for escrow scheme.
321
347
  * Auto-detects whether to create new session or use existing one.
348
+ *
349
+ * @param x402Version - Protocol version
350
+ * @param paymentRequirements - Payment requirements from 402 response
351
+ * @param options - Optional payload options
352
+ * @param options.forceNew - Skip session lookup, always create new session
322
353
  */
323
- async createPaymentPayload(x402Version, paymentRequirements) {
354
+ async createPaymentPayload(x402Version, paymentRequirements, options) {
324
355
  const receiver = (0, import_viem2.getAddress)(paymentRequirements.payTo);
325
356
  const amount = BigInt(paymentRequirements.amount);
326
- const existingSession = this.sessions.findBest(receiver, amount);
327
- if (existingSession) {
328
- return this.createUsagePayload(x402Version, existingSession, paymentRequirements.amount);
357
+ const shouldForceNew = options?.forceNew || this.forceNewSession;
358
+ if (this.forceNewSession) {
359
+ this.forceNewSession = false;
360
+ }
361
+ const targetSessionId = options?.sessionId || this.selectedSessionId;
362
+ if (this.selectedSessionId) {
363
+ this.selectedSessionId = void 0;
364
+ }
365
+ if (!shouldForceNew) {
366
+ let existingSession;
367
+ if (targetSessionId) {
368
+ existingSession = this.sessions.getById(targetSessionId);
369
+ if (existingSession) {
370
+ const now = Date.now() / 1e3;
371
+ if (existingSession.authorizationExpiry <= now || BigInt(existingSession.balance) < amount) {
372
+ existingSession = null;
373
+ }
374
+ }
375
+ } else {
376
+ existingSession = this.sessions.findBest(receiver, amount);
377
+ }
378
+ if (existingSession) {
379
+ return this.createUsagePayload(x402Version, existingSession, paymentRequirements.amount);
380
+ }
329
381
  }
330
382
  return this.createCreationPayload(x402Version, paymentRequirements);
331
383
  }
@@ -447,8 +499,18 @@ function createEscrowFetch(walletClient, options) {
447
499
  const x402 = new import_client.x402Client().register(scheme.network, scheme);
448
500
  const baseFetch = options?.fetch ?? globalThis.fetch;
449
501
  const paidFetch = (0, import_fetch.wrapFetchWithPayment)(baseFetch, x402);
502
+ const wrappedFetch = withSessionExtraction(paidFetch, scheme);
503
+ const escrowFetch = async (input, init) => {
504
+ const { session, ...fetchInit } = init || {};
505
+ if (session === "new") {
506
+ scheme.forceNewSession = true;
507
+ } else if (session && session !== "auto") {
508
+ scheme.selectedSessionId = session;
509
+ }
510
+ return wrappedFetch(input, fetchInit);
511
+ };
450
512
  return {
451
- fetch: withSessionExtraction(paidFetch, scheme),
513
+ fetch: escrowFetch,
452
514
  scheme,
453
515
  x402
454
516
  // Expose for adding hooks
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/index.ts","../../src/client/session-wrapper.ts","../../src/client/escrow.ts","../../src/types.ts","../../src/constants.ts","../../src/client/storage.ts","../../src/client/session-manager.ts","../../src/client/eip712.ts"],"sourcesContent":["/**\n * x402 Escrow Client\n *\n * Unified escrow scheme that handles both session creation and usage.\n * - First call: Creates session with wallet signature (EIP-712)\n * - Subsequent calls: Uses stored session token (no signature needed)\n *\n * @example Simple (recommended)\n * ```typescript\n * import { createEscrowFetch } from '@agentokratia/x402-escrow/client';\n *\n * const { fetch: escrowFetch, scheme, x402 } = createEscrowFetch(walletClient);\n * const response = await escrowFetch('https://api.example.com/premium');\n *\n * // Add hooks (user has control)\n * x402.onAfterPaymentCreation(async (ctx) => {\n * console.log('Payment created:', ctx.paymentPayload);\n * });\n * ```\n *\n * @example With custom fetch\n * ```typescript\n * const { fetch: escrowFetch } = createEscrowFetch(walletClient, {\n * fetch: ky, // Use ky, undici, node-fetch, etc.\n * });\n * ```\n *\n * @example Fully composable (manual setup)\n * ```typescript\n * import { x402Client } from '@x402/core/client';\n * import { wrapFetchWithPayment } from '@x402/fetch';\n * import { EscrowScheme, withSessionExtraction } from '@agentokratia/x402-escrow/client';\n *\n * const escrowScheme = new EscrowScheme(walletClient);\n * const x402 = new x402Client()\n * .register('eip155:84532', escrowScheme)\n * .onAfterPaymentCreation(ctx => console.log('Payment:', ctx));\n * const paidFetch = wrapFetchWithPayment(fetch, x402);\n * const escrowFetch = withSessionExtraction(paidFetch, escrowScheme);\n * ```\n */\n\n// Simple API (recommended)\nexport {\n createEscrowFetch,\n type EscrowFetchResult,\n type CreateEscrowFetchOptions,\n} from './session-wrapper';\n\n// Core scheme (for advanced use with x402Client)\nexport { EscrowScheme, type EscrowSchemeOptions } from './escrow';\n\n// Session wrappers for x402 integration\nexport { withSessionExtraction, withAxiosSessionExtraction } from './session-wrapper';\n\n// Components (for advanced customization)\nexport { SessionManager, type SessionManagerOptions, type StoredSession } from './session-manager';\nexport {\n type SessionStorage,\n InMemoryStorage,\n BrowserLocalStorage,\n createStorage,\n} from './storage';\nexport {\n signERC3009,\n computeEscrowNonce,\n type EIP712Domain,\n type ERC3009Authorization,\n} from './eip712';\n\n// Re-export version constant\nexport { X402_VERSION } from '../types';\n","/**\n * Session Extraction Wrappers\n *\n * Thin wrappers that extract sessions from x402 responses and store them.\n *\n * @example Simple (recommended)\n * ```typescript\n * const { fetch: escrowFetch, scheme, x402 } = createEscrowFetch(walletClient);\n * const response = await escrowFetch('https://api.example.com/premium');\n *\n * // Add hooks (user has control)\n * x402.onAfterPaymentCreation(async (ctx) => {\n * console.log('Payment created:', ctx.paymentPayload);\n * });\n * ```\n *\n * @example With custom fetch\n * ```typescript\n * const { fetch: escrowFetch } = createEscrowFetch(walletClient, {\n * fetch: ky, // Use ky, undici, node-fetch, etc.\n * });\n * ```\n *\n * @example Fully composable (manual setup)\n * ```typescript\n * const escrowScheme = new EscrowScheme(walletClient);\n * const x402 = new x402Client().register('eip155:84532', escrowScheme);\n * const paidFetch = wrapFetchWithPayment(fetch, x402);\n * const escrowFetch = withSessionExtraction(paidFetch, escrowScheme);\n * ```\n */\n\nimport { isAddress, getAddress, type WalletClient } from 'viem';\nimport { x402Client } from '@x402/core/client';\nimport { wrapFetchWithPayment } from '@x402/fetch';\nimport { EscrowScheme, type EscrowSchemeOptions } from './escrow';\nimport { fromBase64 } from '../types';\n\n// =============================================================================\n// Types\n// =============================================================================\n\ntype FetchLike = typeof globalThis.fetch;\n\n/** Options for createEscrowFetch */\nexport interface CreateEscrowFetchOptions extends EscrowSchemeOptions {\n /** Custom fetch implementation (default: globalThis.fetch) */\n fetch?: FetchLike;\n}\n\n/** Result from createEscrowFetch */\nexport interface EscrowFetchResult {\n /** Fetch function with automatic payment + session handling */\n fetch: FetchLike;\n /** Access to underlying scheme for session management */\n scheme: EscrowScheme;\n /** Access to x402Client for adding hooks (onBeforePaymentCreation, etc.) */\n x402: x402Client;\n}\n\n// =============================================================================\n// Convenience wrapper (Simple API)\n// =============================================================================\n\n/**\n * Creates a fetch function with automatic escrow payment and session handling.\n * This is the simplest way to integrate x402 escrow payments.\n *\n * @example Simple usage\n * ```typescript\n * const { fetch: escrowFetch, scheme, x402 } = createEscrowFetch(walletClient);\n * const response = await escrowFetch('https://api.example.com/premium');\n *\n * // Access sessions\n * scheme.sessions.getAll();\n * scheme.sessions.hasValid(receiverAddress, '10000');\n * ```\n *\n * @example With custom fetch and hooks\n * ```typescript\n * const { fetch: escrowFetch, x402 } = createEscrowFetch(walletClient, {\n * fetch: customFetch, // Use ky, undici, node-fetch, etc.\n * });\n *\n * // Add hooks for user control\n * x402.onBeforePaymentCreation(async (ctx) => {\n * console.log('About to pay:', ctx.paymentRequirements);\n * });\n * ```\n */\nexport function createEscrowFetch(\n walletClient: WalletClient,\n options?: CreateEscrowFetchOptions\n): EscrowFetchResult {\n const scheme = new EscrowScheme(walletClient, options);\n const x402 = new x402Client().register(scheme.network, scheme);\n\n // Use custom fetch or default to globalThis.fetch\n const baseFetch = options?.fetch ?? globalThis.fetch;\n const paidFetch = wrapFetchWithPayment(baseFetch, x402) as FetchLike;\n\n return {\n fetch: withSessionExtraction(paidFetch, scheme),\n scheme,\n x402, // Expose for adding hooks\n };\n}\n\n// =============================================================================\n// Core extraction logic (header-agnostic)\n// =============================================================================\n\ntype HeaderGetter = (name: string) => string | null | undefined;\n\n/**\n * Core session extraction - works with any header accessor.\n * Extracts session from PAYMENT-RESPONSE header (x402 standard).\n *\n * The facilitator returns session data in SettleResponse, which x402 encodes\n * into the PAYMENT-RESPONSE header as: { ...settleResponse, requirements }\n */\nfunction extractSession(getHeader: HeaderGetter, escrowScheme: EscrowScheme): void {\n const paymentResponseHeader = getHeader('PAYMENT-RESPONSE') || getHeader('payment-response');\n if (!paymentResponseHeader) return;\n\n try {\n const data = JSON.parse(fromBase64(paymentResponseHeader));\n\n // Must have session id at minimum\n if (!data.session?.id) return;\n\n // Check for SESSION USAGE first (no token, just balance update)\n if (!data.session.token) {\n // SESSION USAGE: Update balance only (token already stored from creation)\n if (data.session.balance !== undefined) {\n escrowScheme.sessions.updateBalance(data.session.id, data.session.balance);\n }\n return;\n }\n\n // SESSION CREATION: Has token → Store new session\n // Receiver is in data.requirements.payTo or data.receiver\n const receiver = data.requirements?.payTo || data.receiver;\n if (!receiver) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[x402] Session missing receiver - cannot store');\n }\n return;\n }\n\n // Validate receiver address\n if (!isAddress(receiver)) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[x402] Invalid receiver address in session:', receiver);\n }\n return;\n }\n\n escrowScheme.sessions.store({\n sessionId: data.session.id,\n sessionToken: data.session.token,\n network: escrowScheme.network,\n payer: escrowScheme.address,\n receiver: getAddress(receiver),\n balance: data.session.balance || '0',\n authorizationExpiry: data.session.expiresAt || 0,\n });\n } catch (error) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[x402] Failed to parse PAYMENT-RESPONSE:', error);\n }\n }\n}\n\n// =============================================================================\n// Fetch wrapper (Advanced API)\n// =============================================================================\n\n/**\n * Wraps a paid fetch to automatically extract and store sessions.\n * Use with wrapFetchWithPayment from @x402/fetch.\n *\n * @example\n * ```typescript\n * const escrowScheme = new EscrowScheme(walletClient);\n * const x402 = new x402Client().register('eip155:84532', escrowScheme);\n * const paidFetch = wrapFetchWithPayment(fetch, x402);\n * const escrowFetch = withSessionExtraction(paidFetch, escrowScheme);\n *\n * const response = await escrowFetch('https://api.example.com/premium');\n * // Session automatically stored if present in response\n * ```\n */\nexport function withSessionExtraction(paidFetch: FetchLike, escrowScheme: EscrowScheme): FetchLike {\n return async (input, init) => {\n const response = await paidFetch(input, init);\n extractSession((name) => response.headers.get(name), escrowScheme);\n return response;\n };\n}\n\n// =============================================================================\n// Axios wrapper\n// =============================================================================\n\ninterface AxiosResponseLike {\n headers: Record<string, string | undefined>;\n [key: string]: unknown;\n}\n\n/**\n * Returns an Axios response interceptor that extracts and stores sessions.\n * Use with wrapAxiosWithPayment from @x402/axios.\n *\n * @example\n * ```typescript\n * const escrowScheme = new EscrowScheme(walletClient);\n * const x402 = new x402Client().register('eip155:84532', escrowScheme);\n * const paidAxios = wrapAxiosWithPayment(axios.create(), x402);\n * paidAxios.interceptors.response.use(withAxiosSessionExtraction(escrowScheme));\n *\n * const response = await paidAxios.get('https://api.example.com/premium');\n * // Session automatically stored if present in response\n * ```\n */\nexport function withAxiosSessionExtraction(escrowScheme: EscrowScheme) {\n return <T extends AxiosResponseLike>(response: T): T => {\n extractSession((name) => response.headers[name.toLowerCase()], escrowScheme);\n return response;\n };\n}\n","/**\n * Escrow Client Scheme (Unified)\n *\n * Thin orchestrator that delegates to:\n * - SessionManager: Session lifecycle management\n * - Signer (eip712.ts): EIP-712 signing for session creation\n *\n * Handles both session CREATION and session USAGE in a single scheme.\n * - First call: Creates session with wallet signature (EIP-712)\n * - Subsequent calls: Uses stored session token (no signature needed)\n */\n\nimport { toHex, getAddress, type WalletClient, type Address, type Hex } from 'viem';\nimport {\n generateRequestId,\n generateRandomBytes,\n type PaymentRequirements,\n type Network,\n} from '../types';\nimport { ZERO_ADDRESS, DEFAULT_SESSION_DURATION, DEFAULT_REFUND_WINDOW } from '../constants';\nimport { SessionManager } from './session-manager';\nimport { signERC3009, computeEscrowNonce, type EIP712Domain } from './eip712';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface PaymentPayload {\n x402Version: number;\n resource?: { url: string; description?: string; mimeType?: string };\n accepted?: PaymentRequirements;\n payload: Record<string, unknown>;\n extensions?: Record<string, unknown>;\n}\n\ninterface SchemeNetworkClient {\n readonly scheme: string;\n createPaymentPayload(\n x402Version: number,\n paymentRequirements: PaymentRequirements\n ): Promise<Pick<PaymentPayload, 'x402Version' | 'payload'>>;\n}\n\ninterface EscrowExtra {\n escrowContract: Address;\n facilitator: Address;\n tokenCollector: Address;\n name: string;\n version: string;\n minDeposit?: string;\n maxDeposit?: string;\n}\n\n// ============================================================================\n// Options\n// ============================================================================\n\nexport interface EscrowSchemeOptions {\n /** Session duration in seconds (default: 1 hour) */\n sessionDuration?: number;\n /** Refund window after session expires (default: 24 hours) */\n refundWindow?: number;\n /** Storage type: 'memory' (default) or 'localStorage' */\n storage?: 'memory' | 'localStorage';\n /** localStorage key (default: 'x402-sessions') */\n storageKey?: string;\n /**\n * Custom deposit amount in atomic units (e.g., \"10000000\" for $10 USDC).\n * Must be between minDeposit and maxDeposit from the 402 response.\n * If not specified, defaults to maxDeposit for maximum flexibility.\n */\n depositAmount?: string;\n}\n\n// ============================================================================\n// Escrow Scheme\n// ============================================================================\n\n/**\n * Escrow payment scheme for x402 (Unified).\n *\n * Orchestrates:\n * - SessionManager for session lookup and storage\n * - EIP-712 signer for session creation signatures\n *\n * Auto-preference: If a valid session exists for the receiver,\n * it will be used automatically (no wallet interaction required).\n */\nexport class EscrowScheme implements SchemeNetworkClient {\n readonly scheme = 'escrow';\n\n /** Session manager - use directly for session operations */\n readonly sessions: SessionManager;\n\n private readonly wallet: WalletClient;\n private readonly chainId: number;\n readonly network: Network;\n private readonly sessionDuration: number;\n private readonly refundWindow: number;\n private readonly customDepositAmount?: bigint;\n\n constructor(walletClient: WalletClient, options: EscrowSchemeOptions = {}) {\n if (!walletClient.account) {\n throw new Error('WalletClient must have an account');\n }\n if (!walletClient.chain) {\n throw new Error('WalletClient must have a chain');\n }\n\n this.wallet = walletClient;\n this.chainId = walletClient.chain.id;\n this.network = `eip155:${walletClient.chain.id}`;\n this.sessionDuration = options.sessionDuration ?? DEFAULT_SESSION_DURATION;\n this.refundWindow = options.refundWindow ?? DEFAULT_REFUND_WINDOW;\n this.customDepositAmount = options.depositAmount ? BigInt(options.depositAmount) : undefined;\n this.sessions = new SessionManager(this.network, {\n storage: options.storage,\n storageKey: options.storageKey,\n });\n }\n\n get address(): Address {\n return this.wallet.account!.address;\n }\n\n // ========== Payment Payload Creation ==========\n\n /**\n * Creates payment payload for escrow scheme.\n * Auto-detects whether to create new session or use existing one.\n */\n async createPaymentPayload(\n x402Version: number,\n paymentRequirements: PaymentRequirements\n ): Promise<Pick<PaymentPayload, 'x402Version' | 'payload'>> {\n const receiver = getAddress(paymentRequirements.payTo);\n const amount = BigInt(paymentRequirements.amount);\n\n // Check for existing session (auto-preference)\n const existingSession = this.sessions.findBest(receiver, amount);\n\n if (existingSession) {\n return this.createUsagePayload(x402Version, existingSession, paymentRequirements.amount);\n }\n\n return this.createCreationPayload(x402Version, paymentRequirements);\n }\n\n // ========== Private: Payload Builders ==========\n\n /**\n * Session USAGE payload - uses existing session (no signature).\n */\n private createUsagePayload(\n x402Version: number,\n session: { sessionId: string; sessionToken: string },\n amount: string\n ): Pick<PaymentPayload, 'x402Version' | 'payload'> {\n return {\n x402Version,\n payload: {\n session: {\n id: session.sessionId,\n token: session.sessionToken,\n },\n amount,\n requestId: generateRequestId(),\n },\n };\n }\n\n /**\n * Session CREATION payload - requires wallet signature.\n */\n private async createCreationPayload(\n x402Version: number,\n paymentRequirements: PaymentRequirements\n ): Promise<Pick<PaymentPayload, 'x402Version' | 'payload'>> {\n const extra = paymentRequirements.extra as unknown as EscrowExtra;\n\n if (!extra.escrowContract || !extra.facilitator || !extra.tokenCollector) {\n throw new Error('Missing required escrow configuration in payment requirements');\n }\n\n // Normalize addresses\n const escrowContract = getAddress(extra.escrowContract);\n const facilitator = getAddress(extra.facilitator);\n const tokenCollector = getAddress(extra.tokenCollector);\n const receiver = getAddress(paymentRequirements.payTo);\n const token = getAddress(paymentRequirements.asset);\n\n // Compute session parameters\n const now = Math.floor(Date.now() / 1000);\n const salt = this.generateSalt();\n const authorizationExpiry = now + this.sessionDuration;\n const refundExpiry = authorizationExpiry + this.refundWindow;\n\n // Determine deposit amount with validation\n const minDeposit = extra.minDeposit\n ? BigInt(extra.minDeposit)\n : BigInt(paymentRequirements.amount);\n const maxDeposit = extra.maxDeposit ? BigInt(extra.maxDeposit) : minDeposit;\n\n let amount: bigint;\n if (this.customDepositAmount !== undefined) {\n // User specified custom deposit - validate against bounds\n if (this.customDepositAmount < minDeposit) {\n throw new Error(\n `Deposit amount ${this.customDepositAmount} is below minimum ${minDeposit}`\n );\n }\n if (this.customDepositAmount > maxDeposit) {\n throw new Error(`Deposit amount ${this.customDepositAmount} exceeds maximum ${maxDeposit}`);\n }\n amount = this.customDepositAmount;\n } else {\n // Default to maxDeposit for maximum flexibility\n amount = maxDeposit;\n }\n\n const validAfter = 0n;\n const validBefore = BigInt(authorizationExpiry);\n\n // Compute nonce (payer-agnostic)\n const nonce = computeEscrowNonce(this.chainId, escrowContract, {\n operator: facilitator,\n payer: this.address,\n receiver,\n token,\n maxAmount: amount,\n preApprovalExpiry: authorizationExpiry,\n authorizationExpiry,\n refundExpiry,\n minFeeBps: 0,\n maxFeeBps: 0,\n feeReceiver: ZERO_ADDRESS,\n salt: BigInt(salt),\n });\n\n // Sign ERC-3009 authorization\n const domain: EIP712Domain = {\n name: extra.name,\n version: extra.version,\n chainId: this.chainId,\n verifyingContract: token,\n };\n\n const signature = await signERC3009(\n this.wallet,\n { from: this.address, to: tokenCollector, value: amount, validAfter, validBefore, nonce },\n domain\n );\n\n // Build payload\n const payload: Record<string, unknown> = {\n signature,\n authorization: {\n from: this.address,\n to: tokenCollector,\n value: amount.toString(),\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n },\n sessionParams: {\n salt,\n authorizationExpiry,\n refundExpiry,\n },\n };\n\n // Only add requestId for escrow scheme (not exact-escrow)\n if (paymentRequirements.scheme === 'escrow') {\n payload.requestId = generateRequestId();\n }\n\n // Build accepted block for server routing\n const accepted = {\n scheme: paymentRequirements.scheme,\n network: paymentRequirements.network,\n asset: paymentRequirements.asset,\n amount: paymentRequirements.amount,\n payTo: paymentRequirements.payTo,\n maxTimeoutSeconds: paymentRequirements.maxTimeoutSeconds,\n extra: { ...paymentRequirements.extra, facilitator, escrowContract, tokenCollector },\n };\n\n return { x402Version, accepted, payload } as Pick<PaymentPayload, 'x402Version' | 'payload'>;\n }\n\n private generateSalt(): Hex {\n return toHex(generateRandomBytes(32));\n }\n}\n","/**\n * x402 Protocol Types for @agentokratia/x402-escrow\n *\n * Subset of types needed for the escrow scheme.\n */\n\n/** Current x402 protocol version */\nexport const X402_VERSION = 2;\n\n/** Network identifier in CAIP-2 format (e.g., \"eip155:8453\" for Base) */\nexport type Network = `${string}:${string}`;\n\n/**\n * PaymentRequirements - defines a single payment option\n * Base interface that each scheme extends\n */\nexport interface PaymentRequirements {\n scheme: string;\n network: Network;\n amount: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds: number;\n extra: Record<string, unknown>;\n}\n\n/**\n * x402 Facilitator settle response (JSON body)\n */\nexport interface SettleResponse {\n success: boolean;\n errorReason?: string;\n payer?: string;\n transaction: string;\n network: string;\n session?: {\n id: string;\n balance: string;\n token?: string;\n expiresAt?: number;\n };\n}\n\n/** Header name constants (x402 v2) */\nexport const X402_HEADERS = {\n /** Server → Client: Payment options (402 response) - base64 JSON */\n PAYMENT_REQUIRED: 'PAYMENT-REQUIRED',\n /** Client → Server: Payment payload - base64 JSON */\n PAYMENT_SIGNATURE: 'PAYMENT-SIGNATURE',\n /** Server → Client: Settlement result - base64 JSON (includes session data) */\n PAYMENT_RESPONSE: 'PAYMENT-RESPONSE',\n} as const;\n\n/** PAYMENT-RESPONSE header data (all schemes) */\nexport interface PaymentResponseData {\n success: boolean;\n transaction: string;\n network: string;\n payer?: string;\n receiver?: string;\n errorReason?: string;\n session?: {\n id: string;\n balance: string;\n token?: string;\n expiresAt?: number;\n };\n requirements?: PaymentRequirements;\n}\n\n/**\n * Base64 encode (works in both browser and Node.js)\n */\nexport function toBase64(str: string): string {\n try {\n if (typeof btoa !== 'undefined') {\n return btoa(unescape(encodeURIComponent(str)));\n }\n return Buffer.from(str, 'utf-8').toString('base64');\n } catch {\n return Buffer.from(str, 'utf-8').toString('base64');\n }\n}\n\n/**\n * Base64 decode (works in both browser and Node.js)\n */\nexport function fromBase64(str: string): string {\n try {\n if (typeof atob !== 'undefined') {\n return decodeURIComponent(escape(atob(str)));\n }\n return Buffer.from(str, 'base64').toString('utf-8');\n } catch {\n return Buffer.from(str, 'base64').toString('utf-8');\n }\n}\n\n/**\n * Generate a unique request ID\n */\nexport function generateRequestId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}`;\n}\n\n/**\n * Generate random bytes\n */\nexport function generateRandomBytes(length: number): Uint8Array {\n const bytes = new Uint8Array(length);\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < length; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return bytes;\n}\n\n/**\n * Parse PAYMENT-RESPONSE header.\n */\nexport function parsePaymentResponseHeader(header: string): PaymentResponseData | null {\n try {\n return JSON.parse(fromBase64(header));\n } catch {\n return null;\n }\n}\n","/**\n * Shared Constants for @agentokratia/x402-escrow\n */\n\n// Zero address constant\nexport const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as const;\n\n// Session defaults (in seconds)\nexport const DEFAULT_SESSION_DURATION = 3600; // 1 hour\nexport const DEFAULT_REFUND_WINDOW = 86400; // 24 hours\n","/**\n * Session Storage for x402 Escrow Scheme\n *\n * Provides session persistence with two implementations:\n * - InMemoryStorage: For server-side or ephemeral usage\n * - LocalStorage: For browser persistence across page loads\n */\n\nimport type { Address } from 'viem';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface StoredSession {\n sessionId: string;\n sessionToken: string;\n network: string;\n payer: Address;\n receiver: Address;\n balance: string;\n authorizationExpiry: number;\n createdAt: number;\n}\n\nexport interface SessionStorage {\n get(network: string, receiver: Address): StoredSession | null;\n findBest(network: string, receiver: Address, minAmount: bigint): StoredSession | null;\n set(session: StoredSession): void;\n update(sessionId: string, balance: string): void;\n list(): StoredSession[];\n remove(sessionId: string): void;\n}\n\n// ============================================================================\n// Base Storage (shared logic)\n// ============================================================================\n\nabstract class BaseStorage implements SessionStorage {\n protected sessions = new Map<string, StoredSession>();\n\n get(network: string, receiver: Address): StoredSession | null {\n const now = Date.now() / 1000;\n for (const session of this.sessions.values()) {\n if (\n session.network === network &&\n session.receiver.toLowerCase() === receiver.toLowerCase() &&\n session.authorizationExpiry > now\n ) {\n return session;\n }\n }\n return null;\n }\n\n findBest(network: string, receiver: Address, minAmount: bigint): StoredSession | null {\n const now = Date.now() / 1000;\n let best: StoredSession | null = null;\n let bestBalance = 0n;\n\n for (const session of this.sessions.values()) {\n if (\n session.network === network &&\n session.receiver.toLowerCase() === receiver.toLowerCase() &&\n session.authorizationExpiry > now\n ) {\n const balance = BigInt(session.balance);\n if (balance >= minAmount && balance > bestBalance) {\n best = session;\n bestBalance = balance;\n }\n }\n }\n return best;\n }\n\n set(session: StoredSession): void {\n this.sessions.set(session.sessionId, session);\n this.onUpdate();\n }\n\n update(sessionId: string, balance: string): void {\n const session = this.sessions.get(sessionId);\n if (session) {\n this.sessions.set(sessionId, { ...session, balance });\n this.onUpdate();\n }\n }\n\n list(): StoredSession[] {\n return Array.from(this.sessions.values());\n }\n\n remove(sessionId: string): void {\n this.sessions.delete(sessionId);\n this.onUpdate();\n }\n\n /** Override in subclasses for persistence */\n protected onUpdate(): void {}\n}\n\n// ============================================================================\n// In-Memory Storage\n// ============================================================================\n\nexport class InMemoryStorage extends BaseStorage {\n // No persistence needed\n}\n\n// ============================================================================\n// LocalStorage (Browser)\n// ============================================================================\n\nexport class BrowserLocalStorage extends BaseStorage {\n private key: string;\n\n constructor(key = 'x402-sessions') {\n super();\n this.key = key;\n this.load();\n }\n\n protected override onUpdate(): void {\n this.save();\n }\n\n private load(): void {\n if (typeof localStorage === 'undefined') return;\n try {\n const data = localStorage.getItem(this.key);\n if (data) {\n const sessions: StoredSession[] = JSON.parse(data);\n for (const s of sessions) this.sessions.set(s.sessionId, s);\n }\n } catch {\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[x402] Failed to load sessions from localStorage');\n }\n }\n }\n\n private save(): void {\n if (typeof localStorage === 'undefined') return;\n try {\n localStorage.setItem(this.key, JSON.stringify(Array.from(this.sessions.values())));\n } catch {\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[x402] Failed to save sessions to localStorage');\n }\n }\n }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\nexport function createStorage(\n type: 'memory' | 'localStorage',\n storageKey?: string\n): SessionStorage {\n return type === 'localStorage' ? new BrowserLocalStorage(storageKey) : new InMemoryStorage();\n}\n","/**\n * Session Manager for x402 Escrow Scheme\n *\n * Handles session lifecycle: lookup, storage, and validation.\n * Uses the SessionStorage interface for persistence.\n */\n\nimport type { Address } from 'viem';\nimport type { Network } from '../types';\nimport { createStorage, type SessionStorage, type StoredSession } from './storage';\n\n// Re-export for convenience\nexport type { StoredSession } from './storage';\n\nexport interface SessionManagerOptions {\n /** Storage type: 'memory' (default) or 'localStorage' */\n storage?: 'memory' | 'localStorage';\n /** localStorage key (default: 'x402-sessions') */\n storageKey?: string;\n}\n\n/**\n * Manages session lifecycle for the escrow scheme.\n *\n * Responsibilities:\n * - Store sessions from escrow settlement responses\n * - Find best session for a receiver/amount\n * - Update session balances after debits\n * - Validate session expiry\n */\nexport class SessionManager {\n private readonly storage: SessionStorage;\n private readonly network: Network;\n\n constructor(network: Network, options: SessionManagerOptions = {}) {\n this.network = network;\n this.storage = createStorage(options.storage ?? 'memory', options.storageKey);\n }\n\n /**\n * Store a session from escrow settlement response.\n */\n store(session: Omit<StoredSession, 'createdAt'>): void {\n this.storage.set({ ...session, createdAt: Date.now() });\n }\n\n /**\n * Get session for a specific receiver.\n */\n getForReceiver(receiver: Address): StoredSession | null {\n return this.storage.get(this.network, receiver);\n }\n\n /**\n * Find best session for receiver with minimum balance.\n */\n findBest(receiver: Address, minAmount: bigint): StoredSession | null {\n return this.storage.findBest(this.network, receiver, minAmount);\n }\n\n /**\n * Check if valid session exists for receiver.\n */\n hasValid(receiver: Address, minAmount?: string): boolean {\n const session = minAmount\n ? this.storage.findBest(this.network, receiver, BigInt(minAmount))\n : this.storage.get(this.network, receiver);\n return session !== null;\n }\n\n /**\n * Update session balance after debit.\n */\n updateBalance(sessionId: string, newBalance: string): void {\n this.storage.update(sessionId, newBalance);\n }\n\n /**\n * Get all stored sessions.\n */\n getAll(): StoredSession[] {\n return this.storage.list();\n }\n\n /**\n * Remove a specific session.\n */\n remove(sessionId: string): void {\n this.storage.remove(sessionId);\n }\n\n /**\n * Clear all sessions.\n */\n clear(): void {\n for (const session of this.storage.list()) {\n this.storage.remove(session.sessionId);\n }\n }\n}\n","/**\n * EIP-712 Signing Utilities for x402 Escrow\n *\n * Handles ERC-3009 ReceiveWithAuthorization signing and nonce computation\n * for the escrow contract.\n */\n\nimport {\n keccak256,\n encodeAbiParameters,\n parseAbiParameters,\n toHex,\n type WalletClient,\n type Address,\n type Hex,\n} from 'viem';\nimport { ZERO_ADDRESS } from '../constants';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface EIP712Domain {\n name: string;\n version: string;\n chainId: number;\n verifyingContract: Address;\n}\n\nexport interface ERC3009Authorization {\n from: Address;\n to: Address;\n value: bigint;\n validAfter: bigint;\n validBefore: bigint;\n nonce: Hex;\n}\n\nexport interface PaymentInfoParams {\n operator: Address;\n payer: Address;\n receiver: Address;\n token: Address;\n maxAmount: bigint;\n preApprovalExpiry: number;\n authorizationExpiry: number;\n refundExpiry: number;\n minFeeBps: number;\n maxFeeBps: number;\n feeReceiver: Address;\n salt: bigint;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** EIP-712 type string for PaymentInfo struct */\nconst PAYMENT_INFO_TYPE =\n 'PaymentInfo(address operator,address payer,address receiver,address token,uint120 maxAmount,uint48 preApprovalExpiry,uint48 authorizationExpiry,uint48 refundExpiry,uint16 minFeeBps,uint16 maxFeeBps,address feeReceiver,uint256 salt)';\n\n/** Pre-computed typehash for PaymentInfo */\nconst PAYMENT_INFO_TYPEHASH = keccak256(toHex(new TextEncoder().encode(PAYMENT_INFO_TYPE)));\n\n/** ABI parameter encoding for PaymentInfo struct */\nconst PAYMENT_INFO_ABI_PARAMS =\n 'bytes32, address, address, address, address, uint120, uint48, uint48, uint48, uint16, uint16, address, uint256';\n\n/** ABI parameter encoding for nonce computation */\nconst NONCE_ABI_PARAMS = 'uint256, address, bytes32';\n\n/** ERC-3009 ReceiveWithAuthorization type definition */\nconst ERC3009_TYPES = {\n ReceiveWithAuthorization: [\n { name: 'from', type: 'address' },\n { name: 'to', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'validAfter', type: 'uint256' },\n { name: 'validBefore', type: 'uint256' },\n { name: 'nonce', type: 'bytes32' },\n ],\n} as const;\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Compute the nonce for ERC-3009 authorization.\n * Uses payer=0 for payer-agnostic hash (allows any payer to use this session).\n */\nexport function computeEscrowNonce(\n chainId: number,\n escrowContract: Address,\n paymentInfo: PaymentInfoParams\n): Hex {\n // Use payer=0 for payer-agnostic hash\n const paymentInfoHash = keccak256(\n encodeAbiParameters(parseAbiParameters(PAYMENT_INFO_ABI_PARAMS), [\n PAYMENT_INFO_TYPEHASH,\n paymentInfo.operator,\n ZERO_ADDRESS, // payer = 0 for payer-agnostic\n paymentInfo.receiver,\n paymentInfo.token,\n paymentInfo.maxAmount,\n paymentInfo.preApprovalExpiry,\n paymentInfo.authorizationExpiry,\n paymentInfo.refundExpiry,\n paymentInfo.minFeeBps,\n paymentInfo.maxFeeBps,\n paymentInfo.feeReceiver,\n paymentInfo.salt,\n ])\n );\n\n return keccak256(\n encodeAbiParameters(parseAbiParameters(NONCE_ABI_PARAMS), [\n BigInt(chainId),\n escrowContract,\n paymentInfoHash,\n ])\n );\n}\n\n/**\n * Sign an ERC-3009 ReceiveWithAuthorization message.\n * This is required by USDC FiatTokenV2 for gasless transfers.\n */\nexport async function signERC3009(\n wallet: WalletClient,\n authorization: ERC3009Authorization,\n domain: EIP712Domain\n): Promise<Hex> {\n if (!wallet.account) {\n throw new Error('WalletClient must have an account');\n }\n\n return wallet.signTypedData({\n account: wallet.account,\n domain,\n types: ERC3009_TYPES,\n primaryType: 'ReceiveWithAuthorization',\n message: {\n from: authorization.from,\n to: authorization.to,\n value: authorization.value,\n validAfter: authorization.validAfter,\n validBefore: authorization.validBefore,\n nonce: authorization.nonce,\n },\n });\n}\n"],"mappings":"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","names":["import_viem","import_viem"]}
1
+ {"version":3,"sources":["../../src/client/index.ts","../../src/client/session-wrapper.ts","../../src/client/escrow.ts","../../src/types.ts","../../src/constants.ts","../../src/client/storage.ts","../../src/client/session-manager.ts","../../src/client/eip712.ts"],"sourcesContent":["/**\n * x402 Escrow Client\n *\n * Unified escrow scheme that handles both session creation and usage.\n * - First call: Creates session with wallet signature (EIP-712)\n * - Subsequent calls: Uses stored session token (no signature needed)\n *\n * @example Simple (recommended)\n * ```typescript\n * import { createEscrowFetch } from '@agentokratia/x402-escrow/client';\n *\n * const { fetch: escrowFetch, scheme, x402 } = createEscrowFetch(walletClient);\n * const response = await escrowFetch('https://api.example.com/premium');\n *\n * // Add hooks (user has control)\n * x402.onAfterPaymentCreation(async (ctx) => {\n * console.log('Payment created:', ctx.paymentPayload);\n * });\n * ```\n *\n * @example With custom fetch\n * ```typescript\n * const { fetch: escrowFetch } = createEscrowFetch(walletClient, {\n * fetch: ky, // Use ky, undici, node-fetch, etc.\n * });\n * ```\n *\n * @example Fully composable (manual setup)\n * ```typescript\n * import { x402Client } from '@x402/core/client';\n * import { wrapFetchWithPayment } from '@x402/fetch';\n * import { EscrowScheme, withSessionExtraction } from '@agentokratia/x402-escrow/client';\n *\n * const escrowScheme = new EscrowScheme(walletClient);\n * const x402 = new x402Client()\n * .register('eip155:84532', escrowScheme)\n * .onAfterPaymentCreation(ctx => console.log('Payment:', ctx));\n * const paidFetch = wrapFetchWithPayment(fetch, x402);\n * const escrowFetch = withSessionExtraction(paidFetch, escrowScheme);\n * ```\n */\n\n// Simple API (recommended)\nexport {\n createEscrowFetch,\n type EscrowFetch,\n type EscrowFetchResult,\n type EscrowRequestInit,\n type CreateEscrowFetchOptions,\n} from './session-wrapper';\n\n// Core scheme (for advanced use with x402Client)\nexport { EscrowScheme, type EscrowSchemeOptions, type PayloadOptions } from './escrow';\n\n// Session wrappers for x402 integration\nexport { withSessionExtraction, withAxiosSessionExtraction } from './session-wrapper';\n\n// Components (for advanced customization)\nexport { SessionManager, type SessionManagerOptions, type StoredSession } from './session-manager';\nexport {\n type SessionStorage,\n InMemoryStorage,\n BrowserLocalStorage,\n createStorage,\n} from './storage';\nexport {\n signERC3009,\n computeEscrowNonce,\n type EIP712Domain,\n type ERC3009Authorization,\n} from './eip712';\n\n// Re-export version constant\nexport { X402_VERSION } from '../types';\n","/**\n * Session Extraction Wrappers\n *\n * Thin wrappers that extract sessions from x402 responses and store them.\n *\n * @example Simple (recommended)\n * ```typescript\n * const { fetch: escrowFetch, scheme, x402 } = createEscrowFetch(walletClient);\n * const response = await escrowFetch('https://api.example.com/premium');\n *\n * // Add hooks (user has control)\n * x402.onAfterPaymentCreation(async (ctx) => {\n * console.log('Payment created:', ctx.paymentPayload);\n * });\n * ```\n *\n * @example With custom fetch\n * ```typescript\n * const { fetch: escrowFetch } = createEscrowFetch(walletClient, {\n * fetch: ky, // Use ky, undici, node-fetch, etc.\n * });\n * ```\n *\n * @example Fully composable (manual setup)\n * ```typescript\n * const escrowScheme = new EscrowScheme(walletClient);\n * const x402 = new x402Client().register('eip155:84532', escrowScheme);\n * const paidFetch = wrapFetchWithPayment(fetch, x402);\n * const escrowFetch = withSessionExtraction(paidFetch, escrowScheme);\n * ```\n */\n\nimport { isAddress, getAddress, type WalletClient } from 'viem';\nimport { x402Client } from '@x402/core/client';\nimport { wrapFetchWithPayment } from '@x402/fetch';\nimport { EscrowScheme, type EscrowSchemeOptions } from './escrow';\nimport { fromBase64 } from '../types';\n\n// =============================================================================\n// Types\n// =============================================================================\n\ntype FetchLike = typeof globalThis.fetch;\n\n/**\n * Extended RequestInit with session selection option.\n */\nexport interface EscrowRequestInit extends RequestInit {\n /**\n * Session selection mode:\n * - 'auto' (default): Auto-select best available session for the receiver\n * - 'new': Force create new session (ignores existing sessions)\n * - string (session ID): Use a specific session by ID\n *\n * @example\n * ```typescript\n * // Auto-select best session (default)\n * await escrowFetch(url);\n * await escrowFetch(url, { session: 'auto' });\n *\n * // Force new session creation\n * await escrowFetch(url, { session: 'new' });\n *\n * // Use specific session\n * await escrowFetch(url, { session: 'abc-123-def' });\n * ```\n */\n session?: 'auto' | 'new' | string;\n}\n\n/**\n * Escrow-aware fetch function with session options.\n */\nexport type EscrowFetch = (input: RequestInfo | URL, init?: EscrowRequestInit) => Promise<Response>;\n\n/** Options for createEscrowFetch */\nexport interface CreateEscrowFetchOptions extends EscrowSchemeOptions {\n /** Custom fetch implementation (default: globalThis.fetch) */\n fetch?: FetchLike;\n}\n\n/** Result from createEscrowFetch */\nexport interface EscrowFetchResult {\n /** Fetch function with automatic payment + session handling */\n fetch: EscrowFetch;\n /** Access to underlying scheme for session management */\n scheme: EscrowScheme;\n /** Access to x402Client for adding hooks (onBeforePaymentCreation, etc.) */\n x402: x402Client;\n}\n\n// =============================================================================\n// Convenience wrapper (Simple API)\n// =============================================================================\n\n/**\n * Creates a fetch function with automatic escrow payment and session handling.\n * This is the simplest way to integrate x402 escrow payments.\n *\n * @example Basic usage (auto-selects best session)\n * ```typescript\n * const { fetch: escrowFetch, scheme } = createEscrowFetch(walletClient);\n * const response = await escrowFetch('https://api.example.com/premium');\n * ```\n *\n * @example Session selection options\n * ```typescript\n * // Auto-select best session (default)\n * await escrowFetch(url);\n * await escrowFetch(url, { session: 'auto' });\n *\n * // Force new session creation\n * await escrowFetch(url, { session: 'new' });\n *\n * // Use specific session by ID\n * await escrowFetch(url, { session: 'session-abc-123' });\n * ```\n *\n * @example Access session manager\n * ```typescript\n * const { fetch: escrowFetch, scheme } = createEscrowFetch(walletClient);\n *\n * // List all sessions for a receiver\n * const sessions = scheme.sessions.getAllForReceiver(receiverAddress);\n *\n * // Check if valid session exists\n * scheme.sessions.hasValid(receiverAddress, '10000');\n * ```\n */\nexport function createEscrowFetch(\n walletClient: WalletClient,\n options?: CreateEscrowFetchOptions\n): EscrowFetchResult {\n const scheme = new EscrowScheme(walletClient, options);\n const x402 = new x402Client().register(scheme.network, scheme);\n\n // Use custom fetch or default to globalThis.fetch\n const baseFetch = options?.fetch ?? globalThis.fetch;\n const paidFetch = wrapFetchWithPayment(baseFetch, x402) as FetchLike;\n const wrappedFetch = withSessionExtraction(paidFetch, scheme);\n\n // Create escrow-aware fetch that handles session option\n const escrowFetch: EscrowFetch = async (input, init) => {\n // Extract session option and pass rest to underlying fetch\n const { session, ...fetchInit } = init || {};\n\n // Set session mode on scheme before fetch\n if (session === 'new') {\n scheme.forceNewSession = true;\n } else if (session && session !== 'auto') {\n // Specific session ID\n scheme.selectedSessionId = session;\n }\n // 'auto' or undefined = default behavior (findBest)\n\n return wrappedFetch(input, fetchInit);\n };\n\n return {\n fetch: escrowFetch,\n scheme,\n x402, // Expose for adding hooks\n };\n}\n\n// =============================================================================\n// Core extraction logic (header-agnostic)\n// =============================================================================\n\ntype HeaderGetter = (name: string) => string | null | undefined;\n\n/**\n * Core session extraction - works with any header accessor.\n * Extracts session from PAYMENT-RESPONSE header (x402 standard).\n *\n * The facilitator returns session data in SettleResponse, which x402 encodes\n * into the PAYMENT-RESPONSE header as: { ...settleResponse, requirements }\n */\nfunction extractSession(getHeader: HeaderGetter, escrowScheme: EscrowScheme): void {\n const paymentResponseHeader = getHeader('PAYMENT-RESPONSE') || getHeader('payment-response');\n if (!paymentResponseHeader) return;\n\n try {\n const data = JSON.parse(fromBase64(paymentResponseHeader));\n\n // Must have session id at minimum\n if (!data.session?.id) return;\n\n // Check for SESSION USAGE first (no token, just balance update)\n if (!data.session.token) {\n // SESSION USAGE: Update balance only (token already stored from creation)\n if (data.session.balance !== undefined) {\n escrowScheme.sessions.updateBalance(data.session.id, data.session.balance);\n }\n return;\n }\n\n // SESSION CREATION: Has token → Store new session\n // Receiver is in data.requirements.payTo or data.receiver\n const receiver = data.requirements?.payTo || data.receiver;\n if (!receiver) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[x402] Session missing receiver - cannot store');\n }\n return;\n }\n\n // Validate receiver address\n if (!isAddress(receiver)) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[x402] Invalid receiver address in session:', receiver);\n }\n return;\n }\n\n escrowScheme.sessions.store({\n sessionId: data.session.id,\n sessionToken: data.session.token,\n network: escrowScheme.network,\n payer: escrowScheme.address,\n receiver: getAddress(receiver),\n balance: data.session.balance || '0',\n authorizationExpiry: data.session.expiresAt || 0,\n });\n } catch (error) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[x402] Failed to parse PAYMENT-RESPONSE:', error);\n }\n }\n}\n\n// =============================================================================\n// Fetch wrapper (Advanced API)\n// =============================================================================\n\n/**\n * Wraps a paid fetch to automatically extract and store sessions.\n * Use with wrapFetchWithPayment from @x402/fetch.\n *\n * @example\n * ```typescript\n * const escrowScheme = new EscrowScheme(walletClient);\n * const x402 = new x402Client().register('eip155:84532', escrowScheme);\n * const paidFetch = wrapFetchWithPayment(fetch, x402);\n * const escrowFetch = withSessionExtraction(paidFetch, escrowScheme);\n *\n * const response = await escrowFetch('https://api.example.com/premium');\n * // Session automatically stored if present in response\n * ```\n */\nexport function withSessionExtraction(paidFetch: FetchLike, escrowScheme: EscrowScheme): FetchLike {\n return async (input, init) => {\n const response = await paidFetch(input, init);\n extractSession((name) => response.headers.get(name), escrowScheme);\n return response;\n };\n}\n\n// =============================================================================\n// Axios wrapper\n// =============================================================================\n\ninterface AxiosResponseLike {\n headers: Record<string, string | undefined>;\n [key: string]: unknown;\n}\n\n/**\n * Returns an Axios response interceptor that extracts and stores sessions.\n * Use with wrapAxiosWithPayment from @x402/axios.\n *\n * @example\n * ```typescript\n * const escrowScheme = new EscrowScheme(walletClient);\n * const x402 = new x402Client().register('eip155:84532', escrowScheme);\n * const paidAxios = wrapAxiosWithPayment(axios.create(), x402);\n * paidAxios.interceptors.response.use(withAxiosSessionExtraction(escrowScheme));\n *\n * const response = await paidAxios.get('https://api.example.com/premium');\n * // Session automatically stored if present in response\n * ```\n */\nexport function withAxiosSessionExtraction(escrowScheme: EscrowScheme) {\n return <T extends AxiosResponseLike>(response: T): T => {\n extractSession((name) => response.headers[name.toLowerCase()], escrowScheme);\n return response;\n };\n}\n","/**\n * Escrow Client Scheme (Unified)\n *\n * Thin orchestrator that delegates to:\n * - SessionManager: Session lifecycle management\n * - Signer (eip712.ts): EIP-712 signing for session creation\n *\n * Handles both session CREATION and session USAGE in a single scheme.\n * - First call: Creates session with wallet signature (EIP-712)\n * - Subsequent calls: Uses stored session token (no signature needed)\n */\n\nimport { toHex, getAddress, type WalletClient, type Address, type Hex } from 'viem';\nimport {\n generateRequestId,\n generateRandomBytes,\n type PaymentRequirements,\n type Network,\n} from '../types';\nimport { ZERO_ADDRESS, DEFAULT_SESSION_DURATION, DEFAULT_REFUND_WINDOW } from '../constants';\nimport { SessionManager } from './session-manager';\nimport { signERC3009, computeEscrowNonce, type EIP712Domain } from './eip712';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface PaymentPayload {\n x402Version: number;\n resource?: { url: string; description?: string; mimeType?: string };\n accepted?: PaymentRequirements;\n payload: Record<string, unknown>;\n extensions?: Record<string, unknown>;\n}\n\ninterface SchemeNetworkClient {\n readonly scheme: string;\n createPaymentPayload(\n x402Version: number,\n paymentRequirements: PaymentRequirements,\n options?: PayloadOptions\n ): Promise<Pick<PaymentPayload, 'x402Version' | 'payload'>>;\n}\n\n/**\n * Options for createPaymentPayload\n */\nexport interface PayloadOptions {\n /**\n * Force creation of a new session even if a valid existing session exists.\n * Useful when the user explicitly wants a fresh session with new deposit.\n */\n forceNew?: boolean;\n /**\n * Use a specific session by ID instead of auto-selecting the best one.\n * If the session doesn't exist or is expired, falls back to creating new session.\n */\n sessionId?: string;\n}\n\ninterface EscrowExtra {\n escrowContract: Address;\n facilitator: Address;\n tokenCollector: Address;\n name: string;\n version: string;\n minDeposit?: string;\n maxDeposit?: string;\n}\n\n// ============================================================================\n// Options\n// ============================================================================\n\nexport interface EscrowSchemeOptions {\n /** Session duration in seconds (default: 1 hour) */\n sessionDuration?: number;\n /** Refund window after session expires (default: 24 hours) */\n refundWindow?: number;\n /** Storage type: 'memory' (default) or 'localStorage' */\n storage?: 'memory' | 'localStorage';\n /** localStorage key (default: 'x402-sessions') */\n storageKey?: string;\n /**\n * Custom deposit amount in atomic units (e.g., \"10000000\" for $10 USDC).\n * Must be between minDeposit and maxDeposit from the 402 response.\n * If not specified, defaults to maxDeposit for maximum flexibility.\n */\n depositAmount?: string;\n}\n\n// ============================================================================\n// Escrow Scheme\n// ============================================================================\n\n/**\n * Escrow payment scheme for x402 (Unified).\n *\n * Orchestrates:\n * - SessionManager for session lookup and storage\n * - EIP-712 signer for session creation signatures\n *\n * Auto-preference: If a valid session exists for the receiver,\n * it will be used automatically (no wallet interaction required).\n */\nexport class EscrowScheme implements SchemeNetworkClient {\n readonly scheme = 'escrow';\n\n /** Session manager - use directly for session operations */\n readonly sessions: SessionManager;\n\n private readonly wallet: WalletClient;\n private readonly chainId: number;\n readonly network: Network;\n private readonly sessionDuration: number;\n private readonly refundWindow: number;\n private readonly customDepositAmount?: bigint;\n\n /** Force new session on next createPaymentPayload call (resets after use) */\n forceNewSession = false;\n /** Use specific session ID on next createPaymentPayload call (resets after use) */\n selectedSessionId?: string;\n\n constructor(walletClient: WalletClient, options: EscrowSchemeOptions = {}) {\n if (!walletClient.account) {\n throw new Error('WalletClient must have an account');\n }\n if (!walletClient.chain) {\n throw new Error('WalletClient must have a chain');\n }\n\n this.wallet = walletClient;\n this.chainId = walletClient.chain.id;\n this.network = `eip155:${walletClient.chain.id}`;\n this.sessionDuration = options.sessionDuration ?? DEFAULT_SESSION_DURATION;\n this.refundWindow = options.refundWindow ?? DEFAULT_REFUND_WINDOW;\n this.customDepositAmount = options.depositAmount ? BigInt(options.depositAmount) : undefined;\n this.sessions = new SessionManager(this.network, {\n storage: options.storage,\n storageKey: options.storageKey,\n });\n }\n\n get address(): Address {\n return this.wallet.account!.address;\n }\n\n // ========== Payment Payload Creation ==========\n\n /**\n * Creates payment payload for escrow scheme.\n * Auto-detects whether to create new session or use existing one.\n *\n * @param x402Version - Protocol version\n * @param paymentRequirements - Payment requirements from 402 response\n * @param options - Optional payload options\n * @param options.forceNew - Skip session lookup, always create new session\n */\n async createPaymentPayload(\n x402Version: number,\n paymentRequirements: PaymentRequirements,\n options?: PayloadOptions\n ): Promise<Pick<PaymentPayload, 'x402Version' | 'payload'>> {\n const receiver = getAddress(paymentRequirements.payTo);\n const amount = BigInt(paymentRequirements.amount);\n\n // Check forceNew from options or instance flag (instance flag resets after use)\n const shouldForceNew = options?.forceNew || this.forceNewSession;\n if (this.forceNewSession) {\n this.forceNewSession = false; // Reset after use\n }\n\n // Check for specific session ID from options or instance flag\n const targetSessionId = options?.sessionId || this.selectedSessionId;\n if (this.selectedSessionId) {\n this.selectedSessionId = undefined; // Reset after use\n }\n\n // Skip session lookup if forceNew is true\n if (!shouldForceNew) {\n let existingSession;\n\n // If specific session ID requested, use that\n if (targetSessionId) {\n existingSession = this.sessions.getById(targetSessionId);\n // Validate session is still usable\n if (existingSession) {\n const now = Date.now() / 1000;\n if (\n existingSession.authorizationExpiry <= now ||\n BigInt(existingSession.balance) < amount\n ) {\n existingSession = null; // Session expired or insufficient balance\n }\n }\n } else {\n // Auto-select best session\n existingSession = this.sessions.findBest(receiver, amount);\n }\n\n if (existingSession) {\n return this.createUsagePayload(x402Version, existingSession, paymentRequirements.amount);\n }\n }\n\n return this.createCreationPayload(x402Version, paymentRequirements);\n }\n\n // ========== Private: Payload Builders ==========\n\n /**\n * Session USAGE payload - uses existing session (no signature).\n */\n private createUsagePayload(\n x402Version: number,\n session: { sessionId: string; sessionToken: string },\n amount: string\n ): Pick<PaymentPayload, 'x402Version' | 'payload'> {\n return {\n x402Version,\n payload: {\n session: {\n id: session.sessionId,\n token: session.sessionToken,\n },\n amount,\n requestId: generateRequestId(),\n },\n };\n }\n\n /**\n * Session CREATION payload - requires wallet signature.\n */\n private async createCreationPayload(\n x402Version: number,\n paymentRequirements: PaymentRequirements\n ): Promise<Pick<PaymentPayload, 'x402Version' | 'payload'>> {\n const extra = paymentRequirements.extra as unknown as EscrowExtra;\n\n if (!extra.escrowContract || !extra.facilitator || !extra.tokenCollector) {\n throw new Error('Missing required escrow configuration in payment requirements');\n }\n\n // Normalize addresses\n const escrowContract = getAddress(extra.escrowContract);\n const facilitator = getAddress(extra.facilitator);\n const tokenCollector = getAddress(extra.tokenCollector);\n const receiver = getAddress(paymentRequirements.payTo);\n const token = getAddress(paymentRequirements.asset);\n\n // Compute session parameters\n const now = Math.floor(Date.now() / 1000);\n const salt = this.generateSalt();\n const authorizationExpiry = now + this.sessionDuration;\n const refundExpiry = authorizationExpiry + this.refundWindow;\n\n // Determine deposit amount with validation\n const minDeposit = extra.minDeposit\n ? BigInt(extra.minDeposit)\n : BigInt(paymentRequirements.amount);\n const maxDeposit = extra.maxDeposit ? BigInt(extra.maxDeposit) : minDeposit;\n\n let amount: bigint;\n if (this.customDepositAmount !== undefined) {\n // User specified custom deposit - validate against bounds\n if (this.customDepositAmount < minDeposit) {\n throw new Error(\n `Deposit amount ${this.customDepositAmount} is below minimum ${minDeposit}`\n );\n }\n if (this.customDepositAmount > maxDeposit) {\n throw new Error(`Deposit amount ${this.customDepositAmount} exceeds maximum ${maxDeposit}`);\n }\n amount = this.customDepositAmount;\n } else {\n // Default to maxDeposit for maximum flexibility\n amount = maxDeposit;\n }\n\n const validAfter = 0n;\n const validBefore = BigInt(authorizationExpiry);\n\n // Compute nonce (payer-agnostic)\n const nonce = computeEscrowNonce(this.chainId, escrowContract, {\n operator: facilitator,\n payer: this.address,\n receiver,\n token,\n maxAmount: amount,\n preApprovalExpiry: authorizationExpiry,\n authorizationExpiry,\n refundExpiry,\n minFeeBps: 0,\n maxFeeBps: 0,\n feeReceiver: ZERO_ADDRESS,\n salt: BigInt(salt),\n });\n\n // Sign ERC-3009 authorization\n const domain: EIP712Domain = {\n name: extra.name,\n version: extra.version,\n chainId: this.chainId,\n verifyingContract: token,\n };\n\n const signature = await signERC3009(\n this.wallet,\n { from: this.address, to: tokenCollector, value: amount, validAfter, validBefore, nonce },\n domain\n );\n\n // Build payload\n const payload: Record<string, unknown> = {\n signature,\n authorization: {\n from: this.address,\n to: tokenCollector,\n value: amount.toString(),\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n },\n sessionParams: {\n salt,\n authorizationExpiry,\n refundExpiry,\n },\n };\n\n // Only add requestId for escrow scheme (not exact-escrow)\n if (paymentRequirements.scheme === 'escrow') {\n payload.requestId = generateRequestId();\n }\n\n // Build accepted block for server routing\n const accepted = {\n scheme: paymentRequirements.scheme,\n network: paymentRequirements.network,\n asset: paymentRequirements.asset,\n amount: paymentRequirements.amount,\n payTo: paymentRequirements.payTo,\n maxTimeoutSeconds: paymentRequirements.maxTimeoutSeconds,\n extra: { ...paymentRequirements.extra, facilitator, escrowContract, tokenCollector },\n };\n\n return { x402Version, accepted, payload } as Pick<PaymentPayload, 'x402Version' | 'payload'>;\n }\n\n private generateSalt(): Hex {\n return toHex(generateRandomBytes(32));\n }\n}\n","/**\n * x402 Protocol Types for @agentokratia/x402-escrow\n *\n * Subset of types needed for the escrow scheme.\n */\n\n/** Current x402 protocol version */\nexport const X402_VERSION = 2;\n\n/** Network identifier in CAIP-2 format (e.g., \"eip155:8453\" for Base) */\nexport type Network = `${string}:${string}`;\n\n/**\n * PaymentRequirements - defines a single payment option\n * Base interface that each scheme extends\n */\nexport interface PaymentRequirements {\n scheme: string;\n network: Network;\n amount: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds: number;\n extra: Record<string, unknown>;\n}\n\n/**\n * x402 Facilitator settle response (JSON body)\n */\nexport interface SettleResponse {\n success: boolean;\n errorReason?: string;\n payer?: string;\n transaction: string;\n network: string;\n session?: {\n id: string;\n balance: string;\n token?: string;\n expiresAt?: number;\n };\n}\n\n/** Header name constants (x402 v2) */\nexport const X402_HEADERS = {\n /** Server → Client: Payment options (402 response) - base64 JSON */\n PAYMENT_REQUIRED: 'PAYMENT-REQUIRED',\n /** Client → Server: Payment payload - base64 JSON */\n PAYMENT_SIGNATURE: 'PAYMENT-SIGNATURE',\n /** Server → Client: Settlement result - base64 JSON (includes session data) */\n PAYMENT_RESPONSE: 'PAYMENT-RESPONSE',\n} as const;\n\n/** PAYMENT-RESPONSE header data (all schemes) */\nexport interface PaymentResponseData {\n success: boolean;\n transaction: string;\n network: string;\n payer?: string;\n receiver?: string;\n errorReason?: string;\n session?: {\n id: string;\n balance: string;\n token?: string;\n expiresAt?: number;\n };\n requirements?: PaymentRequirements;\n}\n\n/**\n * Base64 encode (works in both browser and Node.js)\n */\nexport function toBase64(str: string): string {\n try {\n if (typeof btoa !== 'undefined') {\n return btoa(unescape(encodeURIComponent(str)));\n }\n return Buffer.from(str, 'utf-8').toString('base64');\n } catch {\n return Buffer.from(str, 'utf-8').toString('base64');\n }\n}\n\n/**\n * Base64 decode (works in both browser and Node.js)\n */\nexport function fromBase64(str: string): string {\n try {\n if (typeof atob !== 'undefined') {\n return decodeURIComponent(escape(atob(str)));\n }\n return Buffer.from(str, 'base64').toString('utf-8');\n } catch {\n return Buffer.from(str, 'base64').toString('utf-8');\n }\n}\n\n/**\n * Generate a unique request ID\n */\nexport function generateRequestId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}`;\n}\n\n/**\n * Generate random bytes\n */\nexport function generateRandomBytes(length: number): Uint8Array {\n const bytes = new Uint8Array(length);\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < length; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return bytes;\n}\n\n/**\n * Parse PAYMENT-RESPONSE header.\n */\nexport function parsePaymentResponseHeader(header: string): PaymentResponseData | null {\n try {\n return JSON.parse(fromBase64(header));\n } catch {\n return null;\n }\n}\n","/**\n * Shared Constants for @agentokratia/x402-escrow\n */\n\n// Zero address constant\nexport const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as const;\n\n// Session defaults (in seconds)\nexport const DEFAULT_SESSION_DURATION = 3600; // 1 hour\nexport const DEFAULT_REFUND_WINDOW = 86400; // 24 hours\n","/**\n * Session Storage for x402 Escrow Scheme\n *\n * Provides session persistence with two implementations:\n * - InMemoryStorage: For server-side or ephemeral usage\n * - LocalStorage: For browser persistence across page loads\n */\n\nimport type { Address } from 'viem';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type SessionStatus = 'active' | 'inactive' | 'expired';\n\nexport interface StoredSession {\n sessionId: string;\n sessionToken: string;\n network: string;\n payer: Address;\n receiver: Address;\n balance: string;\n authorizationExpiry: number;\n createdAt: number;\n status: SessionStatus;\n}\n\nexport interface SessionStorage {\n get(network: string, receiver: Address): StoredSession | null;\n findBest(network: string, receiver: Address, minAmount: bigint): StoredSession | null;\n set(session: StoredSession): void;\n update(sessionId: string, balance: string): void;\n list(): StoredSession[];\n remove(sessionId: string): void;\n}\n\n// ============================================================================\n// Base Storage (shared logic)\n// ============================================================================\n\nabstract class BaseStorage implements SessionStorage {\n protected sessions = new Map<string, StoredSession>();\n\n get(network: string, receiver: Address): StoredSession | null {\n const now = Date.now() / 1000;\n for (const session of this.sessions.values()) {\n if (\n session.network === network &&\n session.receiver.toLowerCase() === receiver.toLowerCase() &&\n session.authorizationExpiry > now &&\n session.status === 'active'\n ) {\n return session;\n }\n }\n return null;\n }\n\n findBest(network: string, receiver: Address, minAmount: bigint): StoredSession | null {\n const now = Date.now() / 1000;\n let best: StoredSession | null = null;\n let bestBalance = 0n;\n\n for (const session of this.sessions.values()) {\n if (\n session.network === network &&\n session.receiver.toLowerCase() === receiver.toLowerCase() &&\n session.authorizationExpiry > now &&\n session.status === 'active'\n ) {\n const balance = BigInt(session.balance);\n if (balance >= minAmount && balance > bestBalance) {\n best = session;\n bestBalance = balance;\n }\n }\n }\n return best;\n }\n\n set(session: StoredSession): void {\n this.sessions.set(session.sessionId, session);\n this.onUpdate();\n }\n\n update(sessionId: string, balance: string): void {\n const session = this.sessions.get(sessionId);\n if (session) {\n this.sessions.set(sessionId, { ...session, balance });\n this.onUpdate();\n }\n }\n\n list(): StoredSession[] {\n return Array.from(this.sessions.values());\n }\n\n remove(sessionId: string): void {\n this.sessions.delete(sessionId);\n this.onUpdate();\n }\n\n /** Override in subclasses for persistence */\n protected onUpdate(): void {}\n}\n\n// ============================================================================\n// In-Memory Storage\n// ============================================================================\n\nexport class InMemoryStorage extends BaseStorage {\n // No persistence needed\n}\n\n// ============================================================================\n// LocalStorage (Browser)\n// ============================================================================\n\nexport class BrowserLocalStorage extends BaseStorage {\n private key: string;\n\n constructor(key = 'x402-sessions') {\n super();\n this.key = key;\n this.load();\n }\n\n protected override onUpdate(): void {\n this.save();\n }\n\n private load(): void {\n if (typeof localStorage === 'undefined') return;\n try {\n const data = localStorage.getItem(this.key);\n if (data) {\n const sessions: StoredSession[] = JSON.parse(data);\n for (const s of sessions) this.sessions.set(s.sessionId, s);\n }\n } catch {\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[x402] Failed to load sessions from localStorage');\n }\n }\n }\n\n private save(): void {\n if (typeof localStorage === 'undefined') return;\n try {\n localStorage.setItem(this.key, JSON.stringify(Array.from(this.sessions.values())));\n } catch {\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[x402] Failed to save sessions to localStorage');\n }\n }\n }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\nexport function createStorage(\n type: 'memory' | 'localStorage',\n storageKey?: string\n): SessionStorage {\n return type === 'localStorage' ? new BrowserLocalStorage(storageKey) : new InMemoryStorage();\n}\n","/**\n * Session Manager for x402 Escrow Scheme\n *\n * Handles session lifecycle: lookup, storage, and validation.\n * Uses the SessionStorage interface for persistence.\n */\n\nimport type { Address } from 'viem';\nimport type { Network } from '../types';\nimport {\n createStorage,\n type SessionStorage,\n type StoredSession,\n type SessionStatus,\n} from './storage';\n\n// Re-export for convenience\nexport type { StoredSession, SessionStatus } from './storage';\n\nexport interface SessionManagerOptions {\n /** Storage type: 'memory' (default) or 'localStorage' */\n storage?: 'memory' | 'localStorage';\n /** localStorage key (default: 'x402-sessions') */\n storageKey?: string;\n}\n\n/**\n * Manages session lifecycle for the escrow scheme.\n *\n * Responsibilities:\n * - Store sessions from escrow settlement responses\n * - Find best session for a receiver/amount\n * - Update session balances after debits\n * - Validate session expiry\n */\nexport class SessionManager {\n private readonly storage: SessionStorage;\n private readonly network: Network;\n\n constructor(network: Network, options: SessionManagerOptions = {}) {\n this.network = network;\n this.storage = createStorage(options.storage ?? 'memory', options.storageKey);\n }\n\n /**\n * Store a session from escrow settlement response.\n */\n store(session: Omit<StoredSession, 'createdAt' | 'status'>): void {\n this.storage.set({ ...session, createdAt: Date.now(), status: 'active' });\n }\n\n /**\n * Update session status (e.g., mark as inactive when reclaimed).\n */\n setStatus(sessionId: string, status: SessionStatus): void {\n const session = this.storage.list().find((s) => s.sessionId === sessionId);\n if (session) {\n this.storage.set({ ...session, status });\n }\n }\n\n /**\n * Get session for a specific receiver.\n */\n getForReceiver(receiver: Address): StoredSession | null {\n return this.storage.get(this.network, receiver);\n }\n\n /**\n * Find best session for receiver with minimum balance.\n */\n findBest(receiver: Address, minAmount: bigint): StoredSession | null {\n return this.storage.findBest(this.network, receiver, minAmount);\n }\n\n /**\n * Check if valid session exists for receiver.\n */\n hasValid(receiver: Address, minAmount?: string): boolean {\n const session = minAmount\n ? this.storage.findBest(this.network, receiver, BigInt(minAmount))\n : this.storage.get(this.network, receiver);\n return session !== null;\n }\n\n /**\n * Update session balance after debit.\n */\n updateBalance(sessionId: string, newBalance: string): void {\n this.storage.update(sessionId, newBalance);\n }\n\n /**\n * Get all stored sessions.\n */\n getAll(): StoredSession[] {\n return this.storage.list();\n }\n\n /**\n * Get all active sessions for a specific receiver.\n */\n getAllForReceiver(receiver: Address): StoredSession[] {\n const now = Date.now() / 1000;\n return this.storage\n .list()\n .filter(\n (s) =>\n s.network === this.network &&\n s.receiver.toLowerCase() === receiver.toLowerCase() &&\n s.authorizationExpiry > now &&\n s.status === 'active'\n );\n }\n\n /**\n * Get session by ID.\n */\n getById(sessionId: string): StoredSession | null {\n return this.storage.list().find((s) => s.sessionId === sessionId) || null;\n }\n\n /**\n * Remove a specific session.\n */\n remove(sessionId: string): void {\n this.storage.remove(sessionId);\n }\n\n /**\n * Clear all sessions.\n */\n clear(): void {\n for (const session of this.storage.list()) {\n this.storage.remove(session.sessionId);\n }\n }\n}\n","/**\n * EIP-712 Signing Utilities for x402 Escrow\n *\n * Handles ERC-3009 ReceiveWithAuthorization signing and nonce computation\n * for the escrow contract.\n */\n\nimport {\n keccak256,\n encodeAbiParameters,\n parseAbiParameters,\n toHex,\n type WalletClient,\n type Address,\n type Hex,\n} from 'viem';\nimport { ZERO_ADDRESS } from '../constants';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface EIP712Domain {\n name: string;\n version: string;\n chainId: number;\n verifyingContract: Address;\n}\n\nexport interface ERC3009Authorization {\n from: Address;\n to: Address;\n value: bigint;\n validAfter: bigint;\n validBefore: bigint;\n nonce: Hex;\n}\n\nexport interface PaymentInfoParams {\n operator: Address;\n payer: Address;\n receiver: Address;\n token: Address;\n maxAmount: bigint;\n preApprovalExpiry: number;\n authorizationExpiry: number;\n refundExpiry: number;\n minFeeBps: number;\n maxFeeBps: number;\n feeReceiver: Address;\n salt: bigint;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** EIP-712 type string for PaymentInfo struct */\nconst PAYMENT_INFO_TYPE =\n 'PaymentInfo(address operator,address payer,address receiver,address token,uint120 maxAmount,uint48 preApprovalExpiry,uint48 authorizationExpiry,uint48 refundExpiry,uint16 minFeeBps,uint16 maxFeeBps,address feeReceiver,uint256 salt)';\n\n/** Pre-computed typehash for PaymentInfo */\nconst PAYMENT_INFO_TYPEHASH = keccak256(toHex(new TextEncoder().encode(PAYMENT_INFO_TYPE)));\n\n/** ABI parameter encoding for PaymentInfo struct */\nconst PAYMENT_INFO_ABI_PARAMS =\n 'bytes32, address, address, address, address, uint120, uint48, uint48, uint48, uint16, uint16, address, uint256';\n\n/** ABI parameter encoding for nonce computation */\nconst NONCE_ABI_PARAMS = 'uint256, address, bytes32';\n\n/** ERC-3009 ReceiveWithAuthorization type definition */\nconst ERC3009_TYPES = {\n ReceiveWithAuthorization: [\n { name: 'from', type: 'address' },\n { name: 'to', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'validAfter', type: 'uint256' },\n { name: 'validBefore', type: 'uint256' },\n { name: 'nonce', type: 'bytes32' },\n ],\n} as const;\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Compute the nonce for ERC-3009 authorization.\n * Uses payer=0 for payer-agnostic hash (allows any payer to use this session).\n */\nexport function computeEscrowNonce(\n chainId: number,\n escrowContract: Address,\n paymentInfo: PaymentInfoParams\n): Hex {\n // Use payer=0 for payer-agnostic hash\n const paymentInfoHash = keccak256(\n encodeAbiParameters(parseAbiParameters(PAYMENT_INFO_ABI_PARAMS), [\n PAYMENT_INFO_TYPEHASH,\n paymentInfo.operator,\n ZERO_ADDRESS, // payer = 0 for payer-agnostic\n paymentInfo.receiver,\n paymentInfo.token,\n paymentInfo.maxAmount,\n paymentInfo.preApprovalExpiry,\n paymentInfo.authorizationExpiry,\n paymentInfo.refundExpiry,\n paymentInfo.minFeeBps,\n paymentInfo.maxFeeBps,\n paymentInfo.feeReceiver,\n paymentInfo.salt,\n ])\n );\n\n return keccak256(\n encodeAbiParameters(parseAbiParameters(NONCE_ABI_PARAMS), [\n BigInt(chainId),\n escrowContract,\n paymentInfoHash,\n ])\n );\n}\n\n/**\n * Sign an ERC-3009 ReceiveWithAuthorization message.\n * This is required by USDC FiatTokenV2 for gasless transfers.\n */\nexport async function signERC3009(\n wallet: WalletClient,\n authorization: ERC3009Authorization,\n domain: EIP712Domain\n): Promise<Hex> {\n if (!wallet.account) {\n throw new Error('WalletClient must have an account');\n }\n\n return wallet.signTypedData({\n account: wallet.account,\n domain,\n types: ERC3009_TYPES,\n primaryType: 'ReceiveWithAuthorization',\n message: {\n from: authorization.from,\n to: authorization.to,\n value: authorization.value,\n validAfter: authorization.validAfter,\n validBefore: authorization.validBefore,\n nonce: authorization.nonce,\n },\n });\n}\n"],"mappings":"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","names":["import_viem","import_viem"]}
@@ -1,4 +1,4 @@
1
- export { B as BrowserLocalStorage, C as CreateEscrowFetchOptions, E as EscrowFetchResult, a as EscrowScheme, b as EscrowSchemeOptions, I as InMemoryStorage, f as SessionManager, g as SessionManagerOptions, h as SessionStorage, S as StoredSession, j as X402_VERSION, c as createEscrowFetch, i as createStorage, d as withAxiosSessionExtraction, w as withSessionExtraction } from '../session-wrapper-Cf7U8ObX.cjs';
1
+ export { B as BrowserLocalStorage, C as CreateEscrowFetchOptions, f as EscrowFetch, E as EscrowFetchResult, g as EscrowRequestInit, a as EscrowScheme, b as EscrowSchemeOptions, I as InMemoryStorage, h as PayloadOptions, i as SessionManager, j as SessionManagerOptions, k as SessionStorage, S as StoredSession, m as X402_VERSION, c as createEscrowFetch, l as createStorage, d as withAxiosSessionExtraction, w as withSessionExtraction } from '../session-wrapper-CcQU6BOI.cjs';
2
2
  import { WalletClient, Address, Hex } from 'viem';
3
3
  import '@x402/core/client';
4
4
 
@@ -1,4 +1,4 @@
1
- export { B as BrowserLocalStorage, C as CreateEscrowFetchOptions, E as EscrowFetchResult, a as EscrowScheme, b as EscrowSchemeOptions, I as InMemoryStorage, f as SessionManager, g as SessionManagerOptions, h as SessionStorage, S as StoredSession, j as X402_VERSION, c as createEscrowFetch, i as createStorage, d as withAxiosSessionExtraction, w as withSessionExtraction } from '../session-wrapper-Cf7U8ObX.js';
1
+ export { B as BrowserLocalStorage, C as CreateEscrowFetchOptions, f as EscrowFetch, E as EscrowFetchResult, g as EscrowRequestInit, a as EscrowScheme, b as EscrowSchemeOptions, I as InMemoryStorage, h as PayloadOptions, i as SessionManager, j as SessionManagerOptions, k as SessionStorage, S as StoredSession, m as X402_VERSION, c as createEscrowFetch, l as createStorage, d as withAxiosSessionExtraction, w as withSessionExtraction } from '../session-wrapper-CcQU6BOI.js';
2
2
  import { WalletClient, Address, Hex } from 'viem';
3
3
  import '@x402/core/client';
4
4
 
@@ -49,7 +49,7 @@ var BaseStorage = class {
49
49
  get(network, receiver) {
50
50
  const now = Date.now() / 1e3;
51
51
  for (const session of this.sessions.values()) {
52
- if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now) {
52
+ if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now && session.status === "active") {
53
53
  return session;
54
54
  }
55
55
  }
@@ -60,7 +60,7 @@ var BaseStorage = class {
60
60
  let best = null;
61
61
  let bestBalance = 0n;
62
62
  for (const session of this.sessions.values()) {
63
- if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now) {
63
+ if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now && session.status === "active") {
64
64
  const balance = BigInt(session.balance);
65
65
  if (balance >= minAmount && balance > bestBalance) {
66
66
  best = session;
@@ -143,7 +143,16 @@ var SessionManager = class {
143
143
  * Store a session from escrow settlement response.
144
144
  */
145
145
  store(session) {
146
- this.storage.set({ ...session, createdAt: Date.now() });
146
+ this.storage.set({ ...session, createdAt: Date.now(), status: "active" });
147
+ }
148
+ /**
149
+ * Update session status (e.g., mark as inactive when reclaimed).
150
+ */
151
+ setStatus(sessionId, status) {
152
+ const session = this.storage.list().find((s) => s.sessionId === sessionId);
153
+ if (session) {
154
+ this.storage.set({ ...session, status });
155
+ }
147
156
  }
148
157
  /**
149
158
  * Get session for a specific receiver.
@@ -176,6 +185,21 @@ var SessionManager = class {
176
185
  getAll() {
177
186
  return this.storage.list();
178
187
  }
188
+ /**
189
+ * Get all active sessions for a specific receiver.
190
+ */
191
+ getAllForReceiver(receiver) {
192
+ const now = Date.now() / 1e3;
193
+ return this.storage.list().filter(
194
+ (s) => s.network === this.network && s.receiver.toLowerCase() === receiver.toLowerCase() && s.authorizationExpiry > now && s.status === "active"
195
+ );
196
+ }
197
+ /**
198
+ * Get session by ID.
199
+ */
200
+ getById(sessionId) {
201
+ return this.storage.list().find((s) => s.sessionId === sessionId) || null;
202
+ }
179
203
  /**
180
204
  * Remove a specific session.
181
205
  */
@@ -264,6 +288,8 @@ async function signERC3009(wallet, authorization, domain) {
264
288
  var EscrowScheme = class {
265
289
  constructor(walletClient, options = {}) {
266
290
  this.scheme = "escrow";
291
+ /** Force new session on next createPaymentPayload call (resets after use) */
292
+ this.forceNewSession = false;
267
293
  if (!walletClient.account) {
268
294
  throw new Error("WalletClient must have an account");
269
295
  }
@@ -288,13 +314,39 @@ var EscrowScheme = class {
288
314
  /**
289
315
  * Creates payment payload for escrow scheme.
290
316
  * Auto-detects whether to create new session or use existing one.
317
+ *
318
+ * @param x402Version - Protocol version
319
+ * @param paymentRequirements - Payment requirements from 402 response
320
+ * @param options - Optional payload options
321
+ * @param options.forceNew - Skip session lookup, always create new session
291
322
  */
292
- async createPaymentPayload(x402Version, paymentRequirements) {
323
+ async createPaymentPayload(x402Version, paymentRequirements, options) {
293
324
  const receiver = getAddress(paymentRequirements.payTo);
294
325
  const amount = BigInt(paymentRequirements.amount);
295
- const existingSession = this.sessions.findBest(receiver, amount);
296
- if (existingSession) {
297
- return this.createUsagePayload(x402Version, existingSession, paymentRequirements.amount);
326
+ const shouldForceNew = options?.forceNew || this.forceNewSession;
327
+ if (this.forceNewSession) {
328
+ this.forceNewSession = false;
329
+ }
330
+ const targetSessionId = options?.sessionId || this.selectedSessionId;
331
+ if (this.selectedSessionId) {
332
+ this.selectedSessionId = void 0;
333
+ }
334
+ if (!shouldForceNew) {
335
+ let existingSession;
336
+ if (targetSessionId) {
337
+ existingSession = this.sessions.getById(targetSessionId);
338
+ if (existingSession) {
339
+ const now = Date.now() / 1e3;
340
+ if (existingSession.authorizationExpiry <= now || BigInt(existingSession.balance) < amount) {
341
+ existingSession = null;
342
+ }
343
+ }
344
+ } else {
345
+ existingSession = this.sessions.findBest(receiver, amount);
346
+ }
347
+ if (existingSession) {
348
+ return this.createUsagePayload(x402Version, existingSession, paymentRequirements.amount);
349
+ }
298
350
  }
299
351
  return this.createCreationPayload(x402Version, paymentRequirements);
300
352
  }
@@ -416,8 +468,18 @@ function createEscrowFetch(walletClient, options) {
416
468
  const x402 = new x402Client().register(scheme.network, scheme);
417
469
  const baseFetch = options?.fetch ?? globalThis.fetch;
418
470
  const paidFetch = wrapFetchWithPayment(baseFetch, x402);
471
+ const wrappedFetch = withSessionExtraction(paidFetch, scheme);
472
+ const escrowFetch = async (input, init) => {
473
+ const { session, ...fetchInit } = init || {};
474
+ if (session === "new") {
475
+ scheme.forceNewSession = true;
476
+ } else if (session && session !== "auto") {
477
+ scheme.selectedSessionId = session;
478
+ }
479
+ return wrappedFetch(input, fetchInit);
480
+ };
419
481
  return {
420
- fetch: withSessionExtraction(paidFetch, scheme),
482
+ fetch: escrowFetch,
421
483
  scheme,
422
484
  x402
423
485
  // Expose for adding hooks