Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Browser RPCs (the site proxies mainnet reads through /api/rpc; these are for local anvil forks)
NEXT_PUBLIC_RPC_URL_BASE=http://127.0.0.1:8545
NEXT_PUBLIC_RPC_URL_ROBINHOOD=http://127.0.0.1:8546
# NEXT_PUBLIC_RPC_URL_ARC=http://127.0.0.1:8547

# Server RPCs (indexer + read proxy). Alchemy works for both chains, but cannot execute Base's B20
# stock tokens — BASE_B20_RPC_URL is the fallback node for those (default: base-rpc.publicnode.com).
BASE_RPC_URL=
ROBINHOOD_RPC_URL=
# Arc (bridge-only). The official public node rate-limits bursts; use a keyed provider in production
# (Alchemy arc-mainnet, dRPC rpc.drpc.mainnet.arc.io, Blockdaemon rpc.blockdaemon.mainnet.arc.io, QuickNode).
ARC_RPC_URL=
# BASE_B20_RPC_URL=https://base-rpc.publicnode.com

# Postgres (schema in db/schema.sql; `npm run migrate` applies it idempotently)
Expand Down
7 changes: 7 additions & 0 deletions app/public/brand/arc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions app/public/brand/base.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions app/public/brand/ethereum.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions app/public/brand/robinhood-black.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions app/public/brand/robinhood-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions app/public/brand/usdc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions app/src/app/api/bridge/quote/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { rateLimited } from "@/lib/launchpad/editServer";
import { BRIDGE_PRIVATE_HEADERS, bridgeErrorResponse, getBridgeQuote, readBridgeJson } from "@/lib/bridge/relay";
import { BridgeApiError, parseBridgeRequest } from "@/lib/bridge/validation";

export const dynamic = "force-dynamic";
export const runtime = "nodejs";

export async function POST(req: Request) {
const ip = (req.headers.get("fly-client-ip") || req.headers.get("x-forwarded-for") || "").split(",")[0].trim() || "0.0.0.0";
if (rateLimited(`bridge:quote:ip:${ip}`, 20)) return bridgeErrorResponse(new BridgeApiError("Too many quotes. Please wait a moment.", 429));
try {
if (req.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase() !== "application/json") throw new BridgeApiError("Send a JSON request.", 415);
const length = req.headers.get("content-length");
if (length !== null && (!/^\d+$/.test(length) || Number(length) > 2048)) throw new BridgeApiError("The request is too large.", 413);
const input = parseBridgeRequest(await readBridgeJson(req.body, 2048));
return Response.json(await getBridgeQuote(input), { headers: BRIDGE_PRIVATE_HEADERS });
} catch (error) { return bridgeErrorResponse(error); }
}
16 changes: 16 additions & 0 deletions app/src/app/api/bridge/status/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { rateLimited } from "@/lib/launchpad/editServer";
import { BRIDGE_PRIVATE_HEADERS, bridgeErrorResponse, getBridgeStatus } from "@/lib/bridge/relay";
import { BridgeApiError } from "@/lib/bridge/validation";

export const dynamic = "force-dynamic";
export const runtime = "nodejs";

export async function GET(req: Request) {
const ip = (req.headers.get("fly-client-ip") || req.headers.get("x-forwarded-for") || "").split(",")[0].trim() || "0.0.0.0";
if (rateLimited(`bridge:status:ip:${ip}`, 100)) return bridgeErrorResponse(new BridgeApiError("Too many status requests. Please wait a moment.", 429));
try {
const params = new URL(req.url).searchParams;
if (params.size !== 1 || !params.has("requestId")) throw new BridgeApiError("Invalid bridge request ID.", 400);
return Response.json(await getBridgeStatus(params.get("requestId")!), { headers: BRIDGE_PRIVATE_HEADERS });
} catch (error) { return bridgeErrorResponse(error); }
}
Loading
Loading