Skip to main content
Glama
tas1337

MCP A2A AP2 Food Delivery & Payments

by tas1337
wallet-service.tsβ€’7.95 kB
// ============================================================================= // USER WALLET SERVICE (Simulates Real User Authorization) // ============================================================================= // // THIS SIMULATES what happens on user's phone/device in real life: // // REAL FLOW: // 1. Agent needs payment β†’ calls this service // 2. User's phone buzzes: "Approve $15.37 to Burger Palace?" // 3. User does Face ID / fingerprint / PIN // 4. Wallet signs mandate with user's private key // 5. Signed mandate returned to agent // // IN THIS DEMO: // - We auto-approve after a short delay (simulating user tap) // - We log what WOULD happen on a real phone // - Private key is stored here (in real life: secure enclave) // // WHY SEPARATE SERVICE? // - Shows mandate creation is NOT in agent's control // - Agent calls this service, can't forge mandates // - User (this service) has the private key, not the agent // ============================================================================= import { createHmac } from 'crypto'; import { createServer } from 'http'; // ============================================================================= // TYPES // ============================================================================= interface MandateRequest { userId: string; agentId: string; action: 'payment' | 'refund'; maxAmount: number; currency: string; orderId: string; merchantName?: string; } interface Mandate { userId: string; agentId: string; action: 'payment' | 'refund'; maxAmount: number; currency: string; orderId: string; issuedAt: string; expiresAt: string; signature: string; } // ============================================================================= // CONFIG // ============================================================================= const PORT = 8006; // User's PRIVATE KEY - only stored here, agent doesn't have it // In real life: stored in phone's secure enclave, never leaves device const USER_PRIVATE_KEY = process.env.USER_SECRET || 'user-secret-key'; // ============================================================================= // MANDATE SIGNING // ============================================================================= function createMandatePayload(m: Omit<Mandate, 'signature'>): string { return JSON.stringify({ userId: m.userId, agentId: m.agentId, action: m.action, maxAmount: m.maxAmount, currency: m.currency, orderId: m.orderId, issuedAt: m.issuedAt, expiresAt: m.expiresAt, }); } function signMandate(data: Omit<Mandate, 'signature'>): Mandate { const payload = createMandatePayload(data); const signature = createHmac('sha256', USER_PRIVATE_KEY).update(payload).digest('hex'); return { ...data, signature }; } // ============================================================================= // SIMULATE USER APPROVAL // ============================================================================= async function simulateUserApproval(request: MandateRequest): Promise<Mandate> { const merchant = request.merchantName || 'Unknown Merchant'; // Log what would appear on user's phone console.log(''); console.log('πŸ“± ════════════════════════════════════════════════════════'); console.log('πŸ“± PAYMENT AUTHORIZATION REQUEST'); console.log('πŸ“± ════════════════════════════════════════════════════════'); console.log(`πŸ“± Merchant: ${merchant}`); console.log(`πŸ“± Amount: $${request.maxAmount.toFixed(2)} ${request.currency}`); console.log(`πŸ“± Order: ${request.orderId}`); console.log('πŸ“± ────────────────────────────────────────────────────────'); console.log('πŸ“± [Simulating Face ID scan...]'); // Simulate user taking 1 second to approve (Face ID / fingerprint) await new Promise(resolve => setTimeout(resolve, 1000)); console.log('πŸ“± βœ… Face ID verified'); console.log('πŸ“± βœ… User approved payment'); console.log('πŸ“± ════════════════════════════════════════════════════════'); console.log(''); // Create the mandate const now = new Date(); const expiresAt = new Date(now.getTime() + 5 * 60 * 1000); // 5 min expiry const mandateData: Omit<Mandate, 'signature'> = { userId: request.userId, agentId: request.agentId, action: request.action, maxAmount: request.maxAmount, currency: request.currency, orderId: request.orderId, issuedAt: now.toISOString(), expiresAt: expiresAt.toISOString(), }; // Sign with user's private key const signedMandate = signMandate(mandateData); console.log('πŸ” Mandate signed with user private key'); console.log(`πŸ” Signature: ${signedMandate.signature.slice(0, 20)}...`); console.log(''); return signedMandate; } // ============================================================================= // HTTP SERVER // ============================================================================= const server = createServer(async (req, res) => { // CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; } // POST /authorize - Request user authorization if (req.method === 'POST' && req.url === '/authorize') { let body = ''; req.on('data', chunk => body += chunk); req.on('end', async () => { try { const request: MandateRequest = JSON.parse(body); console.log(`\nπŸ”” Authorization request received from ${request.agentId}`); // Simulate user approval flow const mandate = await simulateUserApproval(request); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, mandate })); } catch (error) { console.error('❌ Authorization failed:', error); res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: false, error: 'Authorization failed' })); } }); return; } // Health check if (req.method === 'GET' && req.url === '/health') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'ok', service: 'user-wallet' })); return; } res.writeHead(404); res.end(); }); // ============================================================================= // START SERVICE // ============================================================================= server.listen(PORT, '0.0.0.0', () => { console.log(''); console.log('πŸ“± ════════════════════════════════════════════════════════'); console.log('πŸ“± USER WALLET SERVICE'); console.log('πŸ“± ════════════════════════════════════════════════════════'); console.log(`πŸ“± Running on port ${PORT}`); console.log('πŸ“± This simulates user\'s phone/wallet'); console.log('πŸ“± Agent calls POST /authorize to request approval'); console.log('πŸ“± ════════════════════════════════════════════════════════'); console.log(''); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tas1337/mcp-a2a-ap2-im-hungry'

If you have feedback or need assistance with the MCP directory API, please join our Discord server