// =============================================================================
// 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('');
});