fix(rwallet): exempt wallet API endpoints from private space access gate

Balance queries, Safe detection, and chain analysis are blockchain
reads that should work for any authenticated user regardless of
space membership. The route handlers enforce their own auth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-23 17:29:37 -07:00
parent 6ab9790373
commit b455e639d7
1 changed files with 95 additions and 1 deletions

View File

@ -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);