/** * Passkey-backed x402 Signer * * Client-side module that uses the EncryptID passkey (WebAuthn PRF) to * derive an EOA and sign x402 micropayments. Replaces the EVM_PRIVATE_KEY * env var approach with hardware-backed passkey signing. * * Flow: * 1. Authenticate with passkey (gets PRF output) * 2. Derive EOA via deriveEOAFromPRF() * 3. Create x402 client with EOA as signer * 4. Return wrapped fetch that auto-handles 402 responses * 5. Zero the private key bytes after signing */ import { deriveEOAFromPRF, zeroPrivateKey } from '../../src/encryptid/eoa-derivation'; import type { DerivedKeys } from '../../src/encryptid/key-derivation'; /** * Options for creating a passkey-backed x402 fetch wrapper. */ export interface PasskeySignerOptions { /** Network identifier (default: 'eip155:84532' for Base Sepolia) */ network?: string; /** PRF output from passkey authentication */ prfOutput: Uint8Array; } /** * Result from creating a passkey signer, including cleanup. */ export interface PasskeySignerResult { /** The fetch function with x402 payment handling */ paidFetch: typeof fetch; /** The derived EOA address */ eoaAddress: string; /** Call this to zero out the private key from memory */ cleanup: () => void; } /** * Create a passkey-backed x402 payment signer. * * Derives an EOA from the passkey PRF output and creates a fetch wrapper * that automatically handles 402 Payment Required responses by signing * USDC transfers on the specified network. * * @example * ```ts * const { paidFetch, eoaAddress, cleanup } = await createPasskeySigner({ * prfOutput: prfOutputBytes, * network: 'eip155:84532', * }); * * try { * const res = await paidFetch('https://example.com/paid-endpoint', { * method: 'POST', * body: JSON.stringify({ data: 'test' }), * }); * console.log('Payment + response:', await res.json()); * } finally { * cleanup(); // Zero private key * } * ``` */ export async function createPasskeySigner( options: PasskeySignerOptions, ): Promise { const network = options.network || 'eip155:84532'; // Derive EOA from PRF output const eoa = deriveEOAFromPRF(options.prfOutput); // Dynamic imports for x402 client libraries (only loaded when needed) const [{ x402Client, wrapFetchWithPayment }, { ExactEvmScheme }, { privateKeyToAccount }] = await Promise.all([ import('@x402/fetch'), import('@x402/evm/exact/client'), import('viem/accounts'), ]); // Convert raw private key bytes to viem account const hexKey = ('0x' + Array.from(eoa.privateKey).map(b => b.toString(16).padStart(2, '0')).join('')) as `0x${string}`; const account = privateKeyToAccount(hexKey); // Set up x402 client with the derived EOA // Type cast needed: viem's LocalAccount doesn't include readContract, // but ExactEvmScheme only uses signing methods at runtime const client = new x402Client(); client.register(network as `${string}:${string}`, new ExactEvmScheme(account as any)); const paidFetch = wrapFetchWithPayment(fetch as any, client) as unknown as typeof fetch; return { paidFetch, eoaAddress: eoa.address, cleanup: () => { zeroPrivateKey(eoa.privateKey); }, }; } /** * Create a passkey signer directly from EncryptID DerivedKeys. * * Convenience wrapper when keys are already available from the key manager. */ export async function createPasskeySignerFromKeys( keys: DerivedKeys, network?: string, ): Promise { if (!keys.eoaPrivateKey || !keys.eoaAddress) { return null; } const net = network || 'eip155:84532'; const [{ x402Client, wrapFetchWithPayment }, { ExactEvmScheme }, { privateKeyToAccount }] = await Promise.all([ import('@x402/fetch'), import('@x402/evm/exact/client'), import('viem/accounts'), ]); const hexKey = ('0x' + Array.from(keys.eoaPrivateKey).map(b => b.toString(16).padStart(2, '0')).join('')) as `0x${string}`; const account = privateKeyToAccount(hexKey); const client = new x402Client(); client.register(net as `${string}:${string}`, new ExactEvmScheme(account as any)); const paidFetch = wrapFetchWithPayment(fetch as any, client) as unknown as typeof fetch; return { paidFetch, eoaAddress: keys.eoaAddress, cleanup: () => { if (keys.eoaPrivateKey) { zeroPrivateKey(keys.eoaPrivateKey); } }, }; }