{"id":50927250,"url":"https://github.com/zanni098/onramp","last_synced_at":"2026-06-17T00:04:09.306Z","repository":{"id":354639612,"uuid":"1224515649","full_name":"zanni098/onramp","owner":"zanni098","description":"Non-custodial stablecoin payment gateway — accept USDC \u0026 USDT on Solana and Polygon with instant settlement directly to your wallet.","archived":false,"fork":false,"pushed_at":"2026-06-13T01:43:20.000Z","size":428,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T03:18:07.872Z","etag":null,"topics":["crypto","defi","payment-gateway","payments","polygon","react","solana","stablecoin","supabase","tailwindcss","typescript","usdc","vite","web3"],"latest_commit_sha":null,"homepage":"https://onramp-delta.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zanni098.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-29T11:05:29.000Z","updated_at":"2026-06-13T01:43:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zanni098/onramp","commit_stats":null,"previous_names":["zanni098/onramp"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zanni098/onramp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zanni098%2Fonramp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zanni098%2Fonramp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zanni098%2Fonramp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zanni098%2Fonramp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zanni098","download_url":"https://codeload.github.com/zanni098/onramp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zanni098%2Fonramp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34428199,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crypto","defi","payment-gateway","payments","polygon","react","solana","stablecoin","supabase","tailwindcss","typescript","usdc","vite","web3"],"created_at":"2026-06-17T00:04:01.613Z","updated_at":"2026-06-17T00:04:09.293Z","avatar_url":"https://github.com/zanni098.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/status-live-10B981?style=for-the-badge\u0026labelColor=000000\" alt=\"Status\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-MIT-10B981?style=for-the-badge\u0026labelColor=000000\" alt=\"License\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/react-19-61DAFB?style=for-the-badge\u0026logo=react\u0026labelColor=000000\" alt=\"React\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/typescript-6-3178C6?style=for-the-badge\u0026logo=typescript\u0026labelColor=000000\" alt=\"TypeScript\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/vite-8-646CFF?style=for-the-badge\u0026logo=vite\u0026labelColor=000000\" alt=\"Vite\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/supabase-edge%20functions-3ECF8E?style=for-the-badge\u0026logo=supabase\u0026labelColor=000000\" alt=\"Supabase\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003e\n  \u003cbr /\u003e\n  ⚡ onramp\n  \u003cbr /\u003e\n\u003c/h1\u003e\n\n\u003ch3 align=\"center\"\u003e\n  Non-custodial stablecoin payment gateway — server-authoritative, HMAC-signed, production-deployed.\n\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\n  Accept USDC on Solana and USDT on Polygon, settling directly into the merchant wallet.\u003cbr /\u003e\n  0.5% per transaction (waived during beta) · Zero custody · On-chain verified.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://onramp-delta.vercel.app\"\u003e\u003cstrong\u003eLive demo →\u003c/strong\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## 🧭 Overview\n\n**Onramp** lets merchants generate checkout links, share them with customers, and receive stablecoin payments that settle directly into their own wallets. The system never holds funds — every payment is an on-chain `transfer` from payer to merchant, verified server-side before anything is marked \"paid\".\n\nThe platform is composed of three layers:\n\n1. **Browser SPA** (Vite + React 19) — marketing site, merchant dashboard, customer checkout.\n2. **Supabase backend** — Postgres with a state-machine schema, 16 migrations, Row-Level-Security lockdown, eight Edge Functions (Deno), and `pg_cron` for scheduled jobs.\n3. **Chain layer** — Solana (SPL transfer + Memo-program binding) and Polygon (ERC-20 transfer with a sub-cent reference suffix).\n\nNothing the browser says about price, destination, amount, or status is trusted. Every transition is either produced by the server or verified by the server.\n\n---\n\n## ✨ What's shipped\n\n### Merchant experience\n\n- **Signup + login** with auto-created profile \u0026 rotated API/webhook secrets (server-side trigger).\n- **Product management** — create, list, delete, and copy shareable `/checkout/:productId` links.\n- **Dashboard** — revenue, transaction count, product count, **active in-flight checkout sessions**.\n- **Transactions ledger** — full filterable history, explorer links (Solscan / Polygonscan), confirmed/failed state badges.\n- **Webhook configuration** — set destination URL (HTTPS-only; loopback / RFC-1918 / metadata IPs blocked server-side), reveal API secret on demand, rotate webhook secret with one click, live delivery history.\n- **Settings** — business name, Solana wallet (base58 validated), Polygon wallet (EIP-55 checksum validated).\n\n### Customer checkout\n\n- Phantom-based **Solana USDC** flow with Memo-program binding (`onramp:\u003creference\u003e`), destination-ATA auto-creation when the merchant hasn't received USDC before.\n- MetaMask-based **Polygon USDT** flow with chain-id check (rejects non-137) and **sub-cent reference suffix** so parallel sessions for the same product can never collide.\n- Polls the server verifier every 3 s; the browser never invents a \"confirmed\" state.\n\n### Backend\n\n- **Eight Edge Functions** (`create-checkout-session`, `verify-payment`, `webhook-dispatcher`, `update-merchant-config`, `get-merchant-secrets`, `rotate-webhook-secret`, `solana-rpc`, `v1` REST API).\n- **Public REST API v1** — API-key-authenticated endpoints for checkout sessions and transactions (`sk_live_…` / `sk_test_…`), with idempotency keys and per-merchant rate limits.\n- **Test mode** — `sk_test_` keys drive Solana devnet / Polygon Amoy verification so integrations can be exercised without real funds.\n- **Email receipts** — optional customer email on checkout; queued, retried, idempotent delivery after confirmation.\n- **State-machine DB** — `checkout_sessions.status ∈ {awaiting_payment, confirming, confirmed, failed, expired}` with a trigger that enforces legal transitions.\n- **`pg_cron`** fires the webhook dispatcher every 10 s for delivery + catch-up verification of stuck sessions.\n- **Token-bucket rate limiting** (atomic `rl_consume` RPC) applied per-IP, per-product, and per-session on customer endpoints.\n- **HMAC-SHA256 webhook signatures** (`Onramp-Signature: t=\u003cunix\u003e,v1=\u003chex\u003e`) with replay-window enforcement on the receiver side.\n- **Row-Level Security** — `anon` and `authenticated` have *zero* INSERT/UPDATE/DELETE on payment tables; SELECT scoped to `merchant_id = auth.uid()` where appropriate.\n\n---\n\n## 🏗️ Architecture\n\n```\n┌──────────────────────────────────────────────────────────────────────────┐\n│                               Browser (SPA)                               │\n│     Landing · Login · Register · Dashboard · Products · Transactions      │\n│     · Webhooks · Settings · Checkout(/:id) · Success(?session=)           │\n└─────────────┬────────────────────────────────────────────────────────────┘\n              │ fetch(Edge Function)                                       ▲\n              │                                                             │ merchant webhook\n┌─────────────▼─────────────────────────────────────────────────┐   HMAC   │\n│                Supabase Edge Functions (Deno)                  │          │\n│                                                                │          │\n│  Public / anon-callable:                                       │          │\n│    • create-checkout-session  (server-frozen amount, token,    │          │\n│                                destination, reference)         │          │\n│    • verify-payment           (drives the state machine;       │          │\n│                                idempotent ledger insert)       │          │\n│                                                                │          │\n│  Dashboard / user-JWT + CORS allowlist:                        │          │\n│    • update-merchant-config   (EIP-55, base58, URL validation) │          │\n│    • get-merchant-secrets     (reveal sk_live_… / whsec_… )    │          │\n│    • rotate-webhook-secret                                     │          │\n│                                                                │          │\n│  Scheduler-invoked (service role):                             │          │\n│    • webhook-dispatcher  ──── HMAC-SHA256 signed POST ─────────┘          │\n│                          exponential backoff + jitter                      │\n└─────────────┬──────────────────────────┬─────────────────────────────────┘\n              │ service role              │ pg_cron every 10s\n              ▼                           │\n┌──────────────────────────────────────────────────────────────────────────┐\n│                          Postgres (Supabase)                              │\n│                                                                           │\n│  checkout_sessions  → state machine + trigger-enforced transitions        │\n│  transactions       → append-only ledger (UNIQUE network, tx_hash)        │\n│  webhook_deliveries → retry queue with backoff state                      │\n│  merchant_secrets   → sk_live_ / whsec_ (RLS-locked, never reaches browser)│\n│  profiles, products → merchant data                                       │\n│  rate_limit_buckets → token-bucket state for rl_consume                   │\n│                                                                           │\n│  Extensions: pgcrypto · pg_cron · pg_net                                  │\n└─────────────┬────────────────────────────────────────────────────────────┘\n              │ fetch() from verify-payment\n              ▼\n┌──────────────────────────────────────────────────────────────────────────┐\n│                         Chain verifiers                                   │\n│    Solana RPC (Memo binding check)  ·  Polygon RPC (exact amount check)   │\n└──────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## 🔐 Security model\n\n| Property | How it's enforced |\n|---|---|\n| **Browser cannot choose what it pays** | `create-checkout-session` loads price from `products.price_minor`, destination from `profiles.*_wallet`, freezes them into `checkout_sessions`. The browser receives the frozen row; anything it tampers with is compared server-side on verify. |\n| **On-chain binding of the session to the tx** | Solana: the transaction must include a Memo-program instruction containing `onramp:\u003creference\u003e`. Polygon: the transferred `amount_minor` must exactly match the server-allocated value including a unique sub-cent suffix. A random replay of an unrelated transfer fails both checks. |\n| **Idempotent ledger** | `transactions` has a `UNIQUE (network, tx_hash)` constraint. `verify-payment` upserts; duplicate confirmations are no-ops. |\n| **State-machine enforced by trigger** | `checkout_sessions_enforce_transition` rejects any status update that isn't a legal transition of `awaiting_payment → confirming → confirmed\\|failed\\|expired`. |\n| **Merchant secrets never in the browser** | `merchant_secrets.secret_key` and `webhook_secret` have `REVOKE SELECT` from `anon` + `authenticated`; the dashboard reveals them via the auth-gated `get-merchant-secrets` Edge Function only. |\n| **Webhook payload authenticity** | HMAC-SHA256 over `\u003cts\u003e.\u003craw_body\u003e` with the merchant's `whsec_…`. Signature header is `Onramp-Signature: t=\u003cunix\u003e,v1=\u003chex\u003e`. Receiver example (Node + WebCrypto) ships in the Webhooks page. |\n| **Webhook URL validation** | Server-side: HTTPS only; blocks loopback (`127.0.0.0/8`, `::1`), RFC-1918 ranges, link-local, and cloud-metadata IPs. |\n| **CORS allowlist on dashboard endpoints** | `update-merchant-config`, `get-merchant-secrets`, `rotate-webhook-secret` reject any `Origin` not in `DASHBOARD_ORIGINS`. Customer endpoints stay `*` for embeddability. |\n| **Rate limits** | Per-IP (30/min), per-product (60/min) on session creation; per-IP (120/min) and per-session (12/min) on verification. Atomic token bucket via `rl_consume` RPC. |\n| **Signup trigger** | `SECURITY DEFINER` with locked `search_path = public, pg_temp`; generates `pk_live_…`, `sk_live_…`, `whsec_…` with `extensions.gen_random_bytes`. Browser only supplies `business_name` via signup metadata. |\n| **Password policy** | `password_min_length = 10`, must include upper + lower + digit, enforced by Supabase Auth server-side and mirrored client-side. |\n\nFull Supabase security-advisor output is reviewed in [`supabase/README.md`](./supabase/README.md).\n\n---\n\n## 🛠️ Tech stack\n\n| Layer | Technology | Notes |\n|---|---|---|\n| **Frontend** | React 19, TypeScript 6, Vite 8 | `tsc -b --noEmit` clean |\n| **Styling** | Tailwind CSS 3, Inter + Instrument Serif | OKX-inspired dark design system; `.glow-card`, `.okx-*`, `.liquid-glass` utilities |\n| **Icons** | Lucide React | |\n| **Routing** | React Router 7 | Nested routes under `DashboardLayout` |\n| **Auth \u0026 DB** | Supabase (Postgres 17) | 16 SQL migrations tracked in `supabase/migrations/` |\n| **Edge Functions** | Deno, hosted on Supabase | 8 functions in `supabase/functions/` |\n| **Cron** | `pg_cron` + `pg_net` | 10-second schedule for webhook dispatcher |\n| **Crypto** | `pgcrypto` | All secrets generated server-side via `gen_random_bytes` |\n| **Solana** | `@solana/web3.js` 1.98 + `@solana/spl-token` 0.4 | SPL transfer + Memo-program binding |\n| **Polygon** | `ethers` v6 | ERC-20 `transfer` through `BrowserProvider` (MetaMask RPC) |\n| **Hosting** | Vercel (frontend), Supabase (backend) | Frontend auto-redeploys on push to `main` |\n\n---\n\n## 📂 Project structure\n\n```\nonramp/\n├── public/                              # Static assets\n├── src/\n│   ├── assets/                          # Images\n│   ├── hooks/\n│   │   └── useMobile.ts                 # Breakpoint hook\n│   ├── layouts/\n│   │   └── DashboardLayout.tsx          # Sidebar + outlet shell\n│   ├── lib/\n│   │   ├── supabase.ts                  # Supabase client (fail-loud on missing env)\n│   │   ├── auth.tsx                     # AuthProvider + useAuth\n│   │   └── api.ts                       # Typed client for Edge Functions\n│   ├── pages/\n│   │   ├── Landing.tsx                  # Marketing homepage\n│   │   ├── Login.tsx · Register.tsx     # Merchant auth\n│   │   ├── Dashboard.tsx                # Stats overview\n│   │   ├── Products.tsx                 # Product CRUD + copy link\n│   │   ├── Transactions.tsx             # Ledger viewer\n│   │   ├── Webhooks.tsx                 # URL config, secret rotate, delivery history\n│   │   ├── Settings.tsx                 # Profile + wallet addresses\n│   │   ├── Checkout.tsx                 # Customer payment flow\n│   │   └── Success.tsx                  # Post-payment confirmation\n│   ├── App.tsx · main.tsx · index.css\n│   └── …\n├── supabase/\n│   ├── README.md                        # Backend deploy + apply-order\n│   ├── functions/\n│   │   ├── _shared/                     # auth, cors, db, hmac, validators, ratelimit,\n│   │   │                                # verify-solana, verify-polygon, reference\n│   │   ├── create-checkout-session/\n│   │   ├── verify-payment/\n│   │   ├── webhook-dispatcher/\n│   │   ├── update-merchant-config/\n│   │   ├── get-merchant-secrets/\n│   │   ├── rotate-webhook-secret/\n│   │   └── solana-rpc/                  # method-whitelisted RPC proxy for checkout\n│   └── migrations/                      # 16 migrations, 0002 → 0017\n│       ├── 0002_checkout_sessions.sql   # state machine + RLS lockdown\n│       ├── …                            # webhooks, cron, rate limits, signup\n│       │                                # trigger, security hardening, capability\n│       │                                # reads, idempotency keys, test mode\n│       ├── 0016_email_receipts.sql\n│       └── 0017_backfill_orphan_merchants.sql\n├── .env.example\n├── tailwind.config.js\n├── vite.config.ts\n├── tsconfig*.json\n└── package.json\n```\n\n---\n\n## 🚀 Getting started (frontend only)\n\nPrereqs: Node ≥ 18, npm ≥ 9. You can run the SPA locally pointed at the live Supabase project without deploying anything.\n\n```bash\ngit clone https://github.com/zanni098/onramp.git\ncd onramp\nnpm install\n```\n\nCreate `.env`:\n\n```env\n# Required\nVITE_SUPABASE_URL=https://hkheayotxkyfgxjaoizj.supabase.co\nVITE_SUPABASE_ANON_KEY=\u003canon jwt from Supabase project\u003e\n\n# Optional — client-side Solana RPC override. When unset, checkout routes\n# through the `solana-rpc` Edge Function proxy (server-side Helius key).\nVITE_HELIUS_API_KEY=\n```\n\n```bash\nnpm run dev      # http://localhost:5173\nnpm run build    # type-check + production bundle\nnpm run preview  # serve the production build\nnpm run lint\n```\n\n## 🧱 Deploying the backend yourself\n\nIf you're forking this and want your own Supabase project, see **[`supabase/README.md`](./supabase/README.md)** for:\n\n- Migration apply order (0002 → 0017).\n- Edge Function secrets: `HELIUS_API_KEY`, `ALCHEMY_API_KEY`, `DASHBOARD_ORIGINS`.\n- The auth-config settings that are NOT captured in migrations: `mailer_autoconfirm = true` for dev (flip to `false` + configure a real SMTP before taking real customers), `password_min_length = 10`, `password_required_characters = lower+upper+digit`.\n\nFrontend deploys via Vercel's GitHub integration — every push to `main` builds and promotes. The Supabase anon key is injected at build time via Vercel project env vars.\n\n---\n\n## 🗺️ Route map\n\n| Path | Component | Access | Description |\n|---|---|---|---|\n| `/` | `Landing` | Public | Marketing homepage |\n| `/login` | `Login` | Public | Merchant sign-in |\n| `/register` | `Register` | Public | Merchant registration |\n| `/checkout/:productId` | `Checkout` | Public (capability URL) | Customer payment page |\n| `/success` | `Success` | Public | Re-reads session status from DB; cannot be faked |\n| `/dashboard` | `Dashboard` | Auth | Revenue, txn count, product count, active checkouts |\n| `/products` | `Products` | Auth | Create / delete products, copy checkout link |\n| `/transactions` | `Transactions` | Auth | Filterable ledger (confirmed / failed) |\n| `/webhooks` | `Webhooks` | Auth | URL config, secret rotate, delivery history, verification examples |\n| `/settings` | `Settings` | Auth | Business name + wallet addresses (server-validated) |\n\n---\n\n## 🔮 Roadmap\n\nGenuinely open items (the misleading \"TODO\" roadmap of the previous README covered things that are already shipped — those have been removed):\n\n- [ ] **Custom SMTP provider** (Resend / Postmark / SendGrid) — currently `mailer_autoconfirm = true` so the free Supabase shared sender's 2/hr cap doesn't block signups.\n- [ ] **Sentry (or equivalent) error + cron monitoring** — right now a stopped cron goes undetected until webhook delivery visibly backs up.\n- [ ] **Hosted iframe / embed script** so merchants can drop a checkout widget into their site without the `/checkout/:id` redirect.\n- [ ] **Mobile wallet deep-links** (Phantom / MetaMask app) on the checkout page.\n- [ ] **Scrypt/argon2 hashing of `merchant_secrets.secret_key`** — currently plaintext-equivalent and only ever read through the auth-gated Edge Function, but should move to hashed compare once a public merchant API exists.\n- [ ] **HIBP leaked-password protection** — requires Supabase Pro plan.\n- [ ] **More stablecoins** (USDT on Solana, USDC on Polygon, DAI).\n- [ ] **Platform fee collection** — pricing is announced at 0.5% per transaction, but collection is not yet enforced on-chain (a fee-split requires an extra transfer instruction on Solana and a splitter contract on Polygon). Until that ships, the fee is explicitly waived: merchants keep 100%, and every pricing surface says so.\n- [x] ~~Analytics charts~~ — 30-day revenue chart shipped on the Overview page.\n\n---\n\n## 🤝 Contributing\n\nFork, branch, open a PR. [Conventional Commits](https://www.conventionalcommits.org/) preferred for messages so changelogs are generable.\n\n```bash\ngit checkout -b feat/your-feature\ngit commit -m \"feat: ...\"\ngit push origin feat/your-feature\n```\n\n---\n\n## 📄 License\n\nMIT — see [LICENSE](LICENSE).\n\n---\n\n\u003cp align=\"center\"\u003e\n  Built with ⚡ by \u003ca href=\"https://github.com/zanni098\"\u003ezanni098\u003c/a\u003e · deployed at \u003ca href=\"https://onramp-delta.vercel.app\"\u003eonramp-delta.vercel.app\u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzanni098%2Fonramp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzanni098%2Fonramp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzanni098%2Fonramp/lists"}