@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.
- package/dist/client/index.cjs +70 -8
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +70 -8
- package/dist/client/index.js.map +1 -1
- package/dist/index.cjs +70 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +70 -8
- package/dist/index.js.map +1 -1
- package/dist/{session-wrapper-Cf7U8ObX.d.cts → session-wrapper-CcQU6BOI.d.cts} +93 -18
- package/dist/{session-wrapper-Cf7U8ObX.d.ts → session-wrapper-CcQU6BOI.d.ts} +93 -18
- package/package.json +13 -14
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../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 * 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":["getAddress","toHex","toHex","getAddress"]}
|
|
1
|
+
{"version":3,"sources":["../../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 * 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":["getAddress","toHex","toHex","getAddress"]}
|
package/dist/index.cjs
CHANGED
|
@@ -95,7 +95,7 @@ var BaseStorage = class {
|
|
|
95
95
|
get(network, receiver) {
|
|
96
96
|
const now = Date.now() / 1e3;
|
|
97
97
|
for (const session of this.sessions.values()) {
|
|
98
|
-
if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now) {
|
|
98
|
+
if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now && session.status === "active") {
|
|
99
99
|
return session;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -106,7 +106,7 @@ var BaseStorage = class {
|
|
|
106
106
|
let best = null;
|
|
107
107
|
let bestBalance = 0n;
|
|
108
108
|
for (const session of this.sessions.values()) {
|
|
109
|
-
if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now) {
|
|
109
|
+
if (session.network === network && session.receiver.toLowerCase() === receiver.toLowerCase() && session.authorizationExpiry > now && session.status === "active") {
|
|
110
110
|
const balance = BigInt(session.balance);
|
|
111
111
|
if (balance >= minAmount && balance > bestBalance) {
|
|
112
112
|
best = session;
|
|
@@ -189,7 +189,16 @@ var SessionManager = class {
|
|
|
189
189
|
* Store a session from escrow settlement response.
|
|
190
190
|
*/
|
|
191
191
|
store(session) {
|
|
192
|
-
this.storage.set({ ...session, createdAt: Date.now() });
|
|
192
|
+
this.storage.set({ ...session, createdAt: Date.now(), status: "active" });
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Update session status (e.g., mark as inactive when reclaimed).
|
|
196
|
+
*/
|
|
197
|
+
setStatus(sessionId, status) {
|
|
198
|
+
const session = this.storage.list().find((s) => s.sessionId === sessionId);
|
|
199
|
+
if (session) {
|
|
200
|
+
this.storage.set({ ...session, status });
|
|
201
|
+
}
|
|
193
202
|
}
|
|
194
203
|
/**
|
|
195
204
|
* Get session for a specific receiver.
|
|
@@ -222,6 +231,21 @@ var SessionManager = class {
|
|
|
222
231
|
getAll() {
|
|
223
232
|
return this.storage.list();
|
|
224
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Get all active sessions for a specific receiver.
|
|
236
|
+
*/
|
|
237
|
+
getAllForReceiver(receiver) {
|
|
238
|
+
const now = Date.now() / 1e3;
|
|
239
|
+
return this.storage.list().filter(
|
|
240
|
+
(s) => s.network === this.network && s.receiver.toLowerCase() === receiver.toLowerCase() && s.authorizationExpiry > now && s.status === "active"
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Get session by ID.
|
|
245
|
+
*/
|
|
246
|
+
getById(sessionId) {
|
|
247
|
+
return this.storage.list().find((s) => s.sessionId === sessionId) || null;
|
|
248
|
+
}
|
|
225
249
|
/**
|
|
226
250
|
* Remove a specific session.
|
|
227
251
|
*/
|
|
@@ -305,6 +329,8 @@ async function signERC3009(wallet, authorization, domain) {
|
|
|
305
329
|
var EscrowScheme = class {
|
|
306
330
|
constructor(walletClient, options = {}) {
|
|
307
331
|
this.scheme = "escrow";
|
|
332
|
+
/** Force new session on next createPaymentPayload call (resets after use) */
|
|
333
|
+
this.forceNewSession = false;
|
|
308
334
|
if (!walletClient.account) {
|
|
309
335
|
throw new Error("WalletClient must have an account");
|
|
310
336
|
}
|
|
@@ -329,13 +355,39 @@ var EscrowScheme = class {
|
|
|
329
355
|
/**
|
|
330
356
|
* Creates payment payload for escrow scheme.
|
|
331
357
|
* Auto-detects whether to create new session or use existing one.
|
|
358
|
+
*
|
|
359
|
+
* @param x402Version - Protocol version
|
|
360
|
+
* @param paymentRequirements - Payment requirements from 402 response
|
|
361
|
+
* @param options - Optional payload options
|
|
362
|
+
* @param options.forceNew - Skip session lookup, always create new session
|
|
332
363
|
*/
|
|
333
|
-
async createPaymentPayload(x402Version, paymentRequirements) {
|
|
364
|
+
async createPaymentPayload(x402Version, paymentRequirements, options) {
|
|
334
365
|
const receiver = (0, import_viem2.getAddress)(paymentRequirements.payTo);
|
|
335
366
|
const amount = BigInt(paymentRequirements.amount);
|
|
336
|
-
const
|
|
337
|
-
if (
|
|
338
|
-
|
|
367
|
+
const shouldForceNew = options?.forceNew || this.forceNewSession;
|
|
368
|
+
if (this.forceNewSession) {
|
|
369
|
+
this.forceNewSession = false;
|
|
370
|
+
}
|
|
371
|
+
const targetSessionId = options?.sessionId || this.selectedSessionId;
|
|
372
|
+
if (this.selectedSessionId) {
|
|
373
|
+
this.selectedSessionId = void 0;
|
|
374
|
+
}
|
|
375
|
+
if (!shouldForceNew) {
|
|
376
|
+
let existingSession;
|
|
377
|
+
if (targetSessionId) {
|
|
378
|
+
existingSession = this.sessions.getById(targetSessionId);
|
|
379
|
+
if (existingSession) {
|
|
380
|
+
const now = Date.now() / 1e3;
|
|
381
|
+
if (existingSession.authorizationExpiry <= now || BigInt(existingSession.balance) < amount) {
|
|
382
|
+
existingSession = null;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
existingSession = this.sessions.findBest(receiver, amount);
|
|
387
|
+
}
|
|
388
|
+
if (existingSession) {
|
|
389
|
+
return this.createUsagePayload(x402Version, existingSession, paymentRequirements.amount);
|
|
390
|
+
}
|
|
339
391
|
}
|
|
340
392
|
return this.createCreationPayload(x402Version, paymentRequirements);
|
|
341
393
|
}
|
|
@@ -457,8 +509,18 @@ function createEscrowFetch(walletClient, options) {
|
|
|
457
509
|
const x402 = new import_client.x402Client().register(scheme.network, scheme);
|
|
458
510
|
const baseFetch = options?.fetch ?? globalThis.fetch;
|
|
459
511
|
const paidFetch = (0, import_fetch.wrapFetchWithPayment)(baseFetch, x402);
|
|
512
|
+
const wrappedFetch = withSessionExtraction(paidFetch, scheme);
|
|
513
|
+
const escrowFetch = async (input, init) => {
|
|
514
|
+
const { session, ...fetchInit } = init || {};
|
|
515
|
+
if (session === "new") {
|
|
516
|
+
scheme.forceNewSession = true;
|
|
517
|
+
} else if (session && session !== "auto") {
|
|
518
|
+
scheme.selectedSessionId = session;
|
|
519
|
+
}
|
|
520
|
+
return wrappedFetch(input, fetchInit);
|
|
521
|
+
};
|
|
460
522
|
return {
|
|
461
|
-
fetch:
|
|
523
|
+
fetch: escrowFetch,
|
|
462
524
|
scheme,
|
|
463
525
|
x402
|
|
464
526
|
// Expose for adding hooks
|