import type { DelegationRequest } from './governance.js'; // ── State ────────────────────────────────────────────── let xmtpReady = false; let notifyAddress: string | null = null; let xmtpClient: any = null; let ownerAddress: string | null = null; // ── Init ─────────────────────────────────────────────── export async function initXmtp(): Promise { try { const { Client } = await import('@xmtp/node-sdk'); const { createWallet, getWallet, exportWallet } = await import('@open-wallet-standard/core'); const { mnemonicToAccount } = await import('viem/accounts'); const { toBytes } = await import('viem'); const { getRandomValues } = await import('node:crypto'); // Use a separate notify wallet — treasury keys never touch XMTP const NOTIFY_WALLET = 'spendos-notify'; const OWS_VAULT = process.env.OWS_VAULT_PATH; try { getWallet(NOTIFY_WALLET, OWS_VAULT); } catch { createWallet(NOTIFY_WALLET, undefined, undefined, OWS_VAULT); } const mnemonic = exportWallet(NOTIFY_WALLET, undefined, OWS_VAULT); const account = mnemonicToAccount(mnemonic); notifyAddress = account.address; // Get owner (treasury) address to send notifications TO try { const treasuryMnemonic = exportWallet('spendos-treasury', process.env.OWS_PASSPHRASE, OWS_VAULT); const treasuryAccount = mnemonicToAccount(treasuryMnemonic); ownerAddress = treasuryAccount.address; } catch { ownerAddress = null; } const signer = { type: 'EOA' as const, getIdentifier: () => ({ identifier: account.address, identifierKind: 0 }), signMessage: async (message: string) => { const sig = await account.signMessage({ message }); return toBytes(sig); }, }; const dbEncryptionKey = getRandomValues(new Uint8Array(32)); xmtpClient = await Client.create(signer, { dbEncryptionKey }); xmtpReady = true; console.log(`[XMTP] Client ready: ${notifyAddress} → notifications to ${ownerAddress ?? 'none'}`); return notifyAddress; } catch (err: any) { console.log(`[XMTP] Init failed: ${err.message ?? err}`); if (err.cause) console.log(`[XMTP] Cause: ${err.cause}`); return null; } } // ── Send XMTP Message ───────────────────────────────── async function sendXmtpMessage(text: string): Promise { if (!xmtpReady || !xmtpClient || !ownerAddress) return false; try { // Create or get conversation with the owner wallet const conversation = await xmtpClient.conversations.newDm(ownerAddress); await conversation.send(text); console.log(`[XMTP] Sent to ${ownerAddress}: ${text.slice(0, 80)}`); return true; } catch (err: any) { console.log(`[XMTP] Send failed: ${err.message ?? err}`); return false; } } // ── Notifications ───────────────────────────────────── export async function notifyDelegationRequest(d: DelegationRequest): Promise { const text = [ `🔔 SpendOS Delegation Request`, ``, `Agent: ${d.agentAddress}`, `Reason: ${d.reason}`, `Chains: ${d.chains.join(', ')}`, `Budget: $${d.totalBudget}`, `Risk: ${d.aiInterpretation?.riskLevel?.toUpperCase() ?? 'unknown'}`, ``, `Approve at https://spendos.xyz`, ].join('\n'); const sent = await sendXmtpMessage(text); if (!sent) console.log(`[XMTP/log] ${text.replace(/\n/g, ' | ')}`); return sent; } export async function notifyDelegationDecision( d: DelegationRequest, action: 'approved' | 'rejected' | 'revoked' | 'expired', ): Promise { const emoji = { approved: '✅', rejected: '❌', revoked: '🔄', expired: '⏰' }[action]; const text = `${emoji} Delegation ${action.toUpperCase()}: ${d.reason}`; const sent = await sendXmtpMessage(text); if (!sent) console.log(`[XMTP/log] ${text}`); return sent; } export function getXmtpAddress(): string | null { return notifyAddress; }