diff --git a/server/index.ts b/server/index.ts index f39250c..189e320 100644 --- a/server/index.ts +++ b/server/index.ts @@ -566,6 +566,99 @@ app.post("/api/internal/provision", async (c) => { return c.json({ status: "created", slug: space }, 201); }); +// POST /api/internal/mint-crdt — called by onramp-service after fiat payment confirmed +app.post("/api/internal/mint-crdt", async (c) => { + const internalKey = c.req.header("X-Internal-Key"); + if (!INTERNAL_API_KEY || internalKey !== INTERNAL_API_KEY) { + return c.json({ error: "Unauthorized" }, 401); + } + + const body = await c.req.json<{ + did?: string; + label?: string; + amountDecimal?: string; + txHash?: string; + network?: string; + }>(); + + const { did, label, amountDecimal, txHash, network } = body; + if (!did || !amountDecimal || !txHash || !network) { + return c.json({ error: "did, amountDecimal, txHash, and network are required" }, 400); + } + + const { mintFromOnChain } = await import("./token-service"); + const success = mintFromOnChain(did, label || "Unknown", amountDecimal, txHash, network); + + if (!success) { + // mintFromOnChain returns false for duplicates or invalid amounts — both are idempotent + return c.json({ ok: false, reason: "already minted or invalid amount" }); + } + + return c.json({ ok: true, minted: amountDecimal, did, txHash }); +}); + +// POST /api/internal/escrow-burn — called by offramp-service to escrow cUSDC before payout +app.post("/api/internal/escrow-burn", async (c) => { + const internalKey = c.req.header("X-Internal-Key"); + if (!INTERNAL_API_KEY || internalKey !== INTERNAL_API_KEY) { + return c.json({ error: "Unauthorized" }, 401); + } + + const body = await c.req.json<{ + did?: string; + label?: string; + amount?: number; + offRampId?: string; + }>(); + + if (!body.did || !body.amount || !body.offRampId) { + return c.json({ error: "did, amount, and offRampId are required" }, 400); + } + + const { burnTokensEscrow, getTokenDoc, getBalance } = await import("./token-service"); + const doc = getTokenDoc("cusdc"); + if (!doc) return c.json({ error: "cUSDC token not found" }, 500); + + const balance = getBalance(doc, body.did); + if (balance < body.amount) { + return c.json({ error: `Insufficient balance: ${balance} < ${body.amount}` }, 400); + } + + const ok = burnTokensEscrow( + "cusdc", body.did, body.label || "", body.amount, body.offRampId, + `Off-ramp withdrawal: ${body.offRampId}`, + ); + if (!ok) return c.json({ error: "Escrow burn failed" }, 500); + + return c.json({ ok: true, escrowed: body.amount, offRampId: body.offRampId }); +}); + +// POST /api/internal/confirm-offramp — called by offramp-service after payout confirmed/failed +app.post("/api/internal/confirm-offramp", async (c) => { + const internalKey = c.req.header("X-Internal-Key"); + if (!INTERNAL_API_KEY || internalKey !== INTERNAL_API_KEY) { + return c.json({ error: "Unauthorized" }, 401); + } + + const body = await c.req.json<{ + offRampId?: string; + status?: "confirmed" | "reversed"; + }>(); + + if (!body.offRampId || !body.status) { + return c.json({ error: "offRampId and status ('confirmed' | 'reversed') required" }, 400); + } + + const { confirmBurn, reverseBurn } = await import("./token-service"); + if (body.status === "confirmed") { + const ok = confirmBurn("cusdc", body.offRampId); + return c.json({ ok, action: "confirmed" }); + } else { + const ok = reverseBurn("cusdc", body.offRampId); + return c.json({ ok, action: "reversed" }); + } +}); + // POST /api/communities/demo/reset app.post("/api/communities/demo/reset", async (c) => { const now = Date.now(); @@ -2227,7 +2320,8 @@ for (const mod of getAllModules()) { || pathname.endsWith("/api/coinbase/webhook") || pathname.endsWith("/api/ramp/webhook") || pathname.includes("/rcart/api/payments") - || pathname.includes("/rcart/pay/"); + || pathname.includes("/rcart/pay/") + || pathname.includes("/rwallet/api/"); if (!isHtmlRequest && !isPublicEndpoint && (vis === "private" || vis === "permissioned")) { const token = extractToken(c.req.raw.headers);