{"id":51892058,"url":"https://github.com/circlefin/arc-ecommerce-payments","last_synced_at":"2026-07-26T05:01:43.175Z","repository":{"id":371644642,"uuid":"1302188977","full_name":"circlefin/arc-ecommerce-payments","owner":"circlefin","description":"An onchain eCommerce demo on Circle Arc that uses the Commerce Payments Protocol (Coinbase x Shopify) as its payment processor.","archived":false,"fork":false,"pushed_at":"2026-07-16T02:10:12.000Z","size":7675,"stargazers_count":0,"open_issues_count":3,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-07-16T04:10:10.523Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/circlefin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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-07-16T01:32:13.000Z","updated_at":"2026-07-16T02:10:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/circlefin/arc-ecommerce-payments","commit_stats":null,"previous_names":["circlefin/arc-ecommerce-payments"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/circlefin/arc-ecommerce-payments","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circlefin%2Farc-ecommerce-payments","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circlefin%2Farc-ecommerce-payments/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circlefin%2Farc-ecommerce-payments/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circlefin%2Farc-ecommerce-payments/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/circlefin","download_url":"https://codeload.github.com/circlefin/arc-ecommerce-payments/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/circlefin%2Farc-ecommerce-payments/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35901460,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-26T02:00:06.503Z","response_time":89,"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":[],"created_at":"2026-07-26T05:01:43.093Z","updated_at":"2026-07-26T05:01:43.156Z","avatar_url":"https://github.com/circlefin.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Arc eCommerce Payments\n\nAn onchain eCommerce demo on [Arc](https://arc.io) that uses the [Commerce Payments Protocol](https://github.com/base/commerce-payments) (Coinbase x Shopify) as its payment processor. Shoppers browse a normal storefront, add items to a cart, connect a wallet, and pay in USDC or EURC. They sign once, never touch gas, and never see a seed phrase or a hex address. Behind that familiar checkout, the protocol's escrow contract recreates the card-network authorize then capture lifecycle on-chain: funds are reserved at checkout and only captured when the merchant fulfills the order.\n\nA single token transfer cannot express what real commerce needs, reserving funds at checkout, capturing when an order ships, partial captures, voids, and refunds. The Commerce Payments Protocol solves this by placing an escrow between the payer and the merchant. This app demonstrates all six of its operations (authorize, capture, charge, void, reclaim, refund) across both currencies, with an operator service that sponsors gas so the shopper pays nothing beyond the purchase itself.\n\nThe escrow and its ERC-3009 token collector are not natively deployed on Arc Testnet, so `npm run setup` bootstraps them through Circle's [Smart Contract Platform](https://developers.circle.com/contracts). One command provisions the operator and merchant wallets, deploys both pinned contracts, and writes the wallet and contract addresses back into your environment.\n\n\n\n## Table of Contents\n\n- [Prerequisites](#prerequisites)\n- [Getting Started](#getting-started)\n- [How It Works](#how-it-works)\n- [Commerce Payments Protocol Integration](#commerce-payments-protocol-integration)\n- [Payment Operations](#payment-operations)\n- [Order Lifecycle](#order-lifecycle)\n- [Currencies (USDC and EURC)](#currencies-usdc-and-eurc)\n- [Authentication and Roles](#authentication-and-roles)\n- [Environment Variables](#environment-variables)\n- [Project Structure](#project-structure)\n- [Local Development with Webhooks (ngrok)](#local-development-with-webhooks-ngrok)\n- [SCP Webhook Registration](#scp-webhook-registration)\n\n\n\n## Prerequisites\n\n- **Node.js v20+** - install via [nvm](https://github.com/nvm-sh/nvm). Next.js 16 and React 19 require a recent runtime.\n- **A wallet for the shopper** - MetaMask, Coinbase Wallet, Rainbow, or any injected EVM wallet, connected through RainbowKit / WalletConnect. The app prompts a switch to **Arc Testnet** (Chain ID `5042002`) when needed.\n- **Arc Testnet USDC** - obtain from the [Circle faucet](https://faucet.circle.com). USDC is Arc's native gas token, so the deployer wallet needs a balance to deploy, and shoppers need a balance to pay. EURC is available from the same faucet if you want to test the EURC path.\n- **A Circle Console account** - the operator/merchant backend runs on Circle's [Developer-Controlled Wallets](https://developers.circle.com/wallets/dev-controlled) and [Smart Contract Platform](https://developers.circle.com/contracts). You need an API key and a registered entity secret.\n- **A WalletConnect (Reown) project ID** *(optional)* - required only for QR and mobile connectors. Injected browser wallets work without one. Get it from [cloud.reown.com](https://cloud.reown.com).\n- **Supabase** - a hosted project or the local CLI stack, used for authentication and order persistence.\n\n\n\n## Getting Started\n\n1. Clone the repository and install dependencies:\n  ```bash\n   git clone https://github.com/akelani-circle/arc-ecommerce-payments.git\n   cd arc-ecommerce-payments\n   npm install\n  ```\n2. Set up environment variables:\n  ```bash\n   cp .env.example .env.local\n  ```\n   Open `.env.local` and fill in your Supabase keys, your Circle API key and entity secret, and (optionally) your WalletConnect project ID. The Arc Testnet RPC URL is pre-filled. See [Environment Variables](#environment-variables) for the full list. The wallet IDs and the deployed contract addresses are written automatically by the setup script, so leave those blank.\n  \u003e **Register your entity secret first.** Circle's Developer-Controlled Wallets require a one-time entity secret registration before the SDK can sign anything. Follow [the registration guide](https://developers.circle.com/wallets/dev-controlled/register-entity-secret) and store the recovery file **outside** the repository. Set `CIRCLE_API_KEY` and `CIRCLE_ENTITY_SECRET` in `.env.local` afterward.\n3. Set up the onchain side in one command:\n  ```bash\n   npm run setup\n  ```\n   The setup script (`scripts/setup.ts`) is idempotent end to end:\n  1. Ensures the three Developer-Controlled wallets the store needs - **deployer**, **operator** (submits + sponsors gas), and **merchant** (funds receiver) - creating any that are missing and writing their ids/addresses to `.env.local` so reruns reuse them.\n  2. Deploys `AuthCaptureEscrow` (no constructor args, deploys its own `TokenStore`) and `ERC3009PaymentCollector` (escrow + Multicall3 addresses) via the Smart Contract Platform, then writes `NEXT_PUBLIC_ESCROW_ADDRESS` and `NEXT_PUBLIC_TOKEN_COLLECTOR_ADDRESS` back. Skipped if both are already set.\n  3. Seeds the operator with a little Arc gas (USDC) from the deployer, since the operator pays for every checkout transaction.\n    cause Arc uses USDC as native gas, the deployer must hold some before contracts can deploy. If it is empty the script prints its address and the [faucet](https://faucet.circle.com) link, then exits - fund it and re-run. To deploy a fresh pair of contracts, force it:\n    `bash m run setup -- --force` \n    The protocol source is pinned to a specific commit and compiled with the Cancun EVM target. Arc Testnet runs a newer hardfork, so no source changes are needed. Provenance and the compatibility notes live in `[contracts/README.md](contracts/README.md)`.\n4. Start the development server:\n  ```bash\n   npm run dev\n  ```\n   The app runs at `http://localhost:3000`. A demo admin account is seeded automatically on boot (see [Authentication and Roles](#authentication-and-roles)).\n\n\n\n## How It Works\n\n- Built with the [Next.js 16](https://nextjs.org) App Router, [React 19](https://react.dev), and TypeScript, styled with [Tailwind CSS v4](https://tailwindcss.com) and [shadcn/ui](https://ui.shadcn.com).\n- Shopper wallets connect through [wagmi](https://wagmi.sh) + [viem](https://viem.sh) + [RainbowKit](https://www.rainbowkit.com) on Arc Testnet. The operator and merchant backend runs on [Circle Developer-Controlled Wallets](https://developers.circle.com/wallets/dev-controlled).\n- Payments are processed by the Commerce Payments Protocol escrow, deployed to Arc through the Smart Contract Platform and called by ABI. The operator builds and signs a `PaymentInfo` intent and submits each operation.\n- Checkout is gasless for the shopper. The shopper signs an off-chain ERC-3009 `receiveWithAuthorization` (EIP-712) in their own wallet, and the operator relays it on-chain and sponsors the gas.\n- Both USDC and EURC are supported end to end. Every price renders in the selected currency, and each order settles in the token it was paid in.\n- Authentication and order history run on [Supabase](https://supabase.com) with server-controlled role claims. A route guard gates the account, admin, and checkout areas.\n- Arc uses USDC as its native gas token, so no separate ETH is needed for transaction fees.\n\n\n\n## Commerce Payments Protocol Integration\n\nThe protocol places an escrow contract between payer and merchant. The shared key across every operation is `PaymentInfo`, a signed intent describing the payer, receiver, token, amount, operator, fee, and deadlines (`lib/payments/types.ts`). The operator has bounded power: it submits transactions and earns a fee, but it cannot redirect funds.\n\nThe token collector is the piece that pulls funds into escrow. This app uses the ERC-3009 collector: the shopper signs an off-chain `receiveWithAuthorization` (the collector is the authorized recipient, so only it can redeem the signature) and the operator submits it, which keeps the authorize step gasless for the shopper. Both USDC and EURC support ERC-3009 for EOA wallets, so no ERC-1271 smart-account signatures are needed.\n\n### Deployed contracts (Arc Testnet)\n\n\n| Contract                  | Address                                                                                                                        | Purpose                                                                              |\n| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ |\n| `AuthCaptureEscrow`       | `[0xa5b4fa1890619cf03b8d6b11e0c680345b1881d8](https://testnet.arcscan.app/address/0xa5b4fa1890619cf03b8d6b11e0c680345b1881d8)` | The escrow that holds reserved funds and releases them on capture                    |\n| `ERC3009PaymentCollector` | `[0x01e39d4a0b8ffeac8ae1618dbf316d15a8ee867c](https://testnet.arcscan.app/address/0x01e39d4a0b8ffeac8ae1618dbf316d15a8ee867c)` | Pulls USDC/EURC into escrow from the shopper's signed ERC-3009 authorization         |\n| Multicall3                | `[0xcA11bde05977b3631167028862bE2a173976CA11](https://testnet.arcscan.app/address/0xcA11bde05977b3631167028862bE2a173976CA11)` | Canonical helper, a constructor dependency of the collector (already present on Arc) |\n\n\nThese are the addresses produced by `npm run setup`. Your own deployment will have different addresses, written into `.env.local` and read centrally from `lib/contracts/index.ts`. `npm run setup` also deploys an `OperatorRefundCollector` (pinned to `NEXT_PUBLIC_REFUND_COLLECTOR_ADDRESS`) used by the refund path below.\n\n\u003e **Implementation status.** All six operations are wired end-to-end. The shopper signs the ERC-3009 authorization at checkout; the operator relays **Authorize** or **Charge** on-chain sponsoring gas. The admin queue submits **Capture**, **Void**, and **Refund** via `POST /api/admin/orders/[id]/`*. **Refund** requires no signature from the merchant: it routes through the `OperatorRefundCollector`, which pulls the refund amount from the operator wallet (via a standing USDC + EURC allowance granted in `npm run setup`) back to the payer. Captured funds have already left the escrow to the merchant, so the operator fronts refunds out of its own balance rather than clawing back from the merchant. **Reclaim** is shopper-triggered from `/account` via `POST /api/account/reclaim` once `authorizationExpiry` has passed.\n\n\n\n## Payment Operations\n\nThe protocol exposes six operations, all keyed off the same `PaymentInfo`. The demo exercises each one across both currencies.\n\n\n| Operation     | What it does                                                        | In the store                                              |\n| ------------- | ------------------------------------------------------------------- | --------------------------------------------------------- |\n| **Authorize** | Reserves funds in escrow                                            | Checkout for goods. Order shows \"Payment reserved\"        |\n| **Capture**   | Releases escrowed funds to the merchant, supports multiple partials | Merchant fulfills the order                               |\n| **Charge**    | Authorize and capture in one atomic step                            | In-stock and digital items at checkout                    |\n| **Void**      | Cancels an authorization and returns funds to the payer             | Shopper cancels before fulfillment, or merchant cancels   |\n| **Reclaim**   | Payer self-recovers funds after the authorization expires           | Auto-release if the order has not shipped by the deadline |\n| **Refund**    | Returns captured funds to the payer, full or partial                | Merchant refunds a paid order, in the order's currency    |\n\n\n\n\n## Order Lifecycle\n\n\n| Step | Operation                                                    | Order state                        |\n| ---- | ------------------------------------------------------------ | ---------------------------------- |\n| 1    | Shopper signs the ERC-3009 authorization, operator relays it | Reserved (Authorize)               |\n| 2    | Merchant fulfills                                            | Paid (Capture)                     |\n| -    | In-stock or digital item at checkout                         | Paid (Charge, single step)         |\n| -    | Shopper or merchant cancels before fulfillment               | Canceled (Void)                    |\n| -    | Authorization expires before fulfillment                     | Expired (Reclaim available)        |\n| -    | Merchant refunds a paid order                                | Refunded (Refund, full or partial) |\n\n\nThe merchant admin shows each order's currency, the operator fee, and the net amount to the merchant.\n\n## Currencies (USDC and EURC)\n\nPrices render in the shopper's chosen currency via a toggle, and every order settles in the token it was paid in. There is no FX or swapping at settlement.\n\n\n| Field    | Value                                                     |\n| -------- | --------------------------------------------------------- |\n| Chain ID | `5042002` (hex `0x4CEF52`), ships as `arcTestnet` in viem |\n| RPC      | `https://rpc.testnet.arc.network`                         |\n| Explorer | [testnet.arcscan.app](https://testnet.arcscan.app)        |\n| Faucet   | [faucet.circle.com](https://faucet.circle.com)            |\n| USDC     | `0x3600000000000000000000000000000000000000` (6 decimals) |\n| EURC     | `0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a` (6 decimals) |\n\n\nUSDC is Arc's native gas token, which uses 18 decimals for native gas and 6 decimals for the ERC-20. The operator sponsors gas, so shoppers never pay it.\n\n## Authentication and Roles\n\nAuthentication is traditional email and password through Supabase, with email confirmation disabled for the demo, so sign-up returns an active session immediately. Routes are guarded by `proxy.ts` and re-verified server-side in protected layouts as defense in depth.\n\nAuthorization is keyed off `app_metadata.role`, not the email address. `app_metadata` is server-controlled and not user-editable, which makes it a real authorization boundary.\n\n\n| Route                          | Access                                                    |\n| ------------------------------ | --------------------------------------------------------- |\n| `/`, `/products/[id]`          | public                                                    |\n| `/auth/login`, `/auth/sign-up` | public, redirect away if signed in                        |\n| `/checkout`                    | signed-in shoppers with a non-empty cart (admins bounced) |\n| `/account`                     | signed-in regular users (admins bounced to `/admin`)      |\n| `/admin`                       | `app_metadata.role === \"admin\"` only                      |\n\n\n\n\n### Demo admin (auto-seeded)\n\nA single admin account is bootstrapped on server boot (`instrumentation.ts` runs `lib/supabase/seed-admin.ts`). It is idempotent: created if missing, otherwise its `admin` role claim is ensured. This requires `SUPABASE_SECRET_KEY` (the service-role key) and is skipped with a warning if that is unset.\n\n\n| Field    | Value             |\n| -------- | ----------------- |\n| Email    | `admin@admin.com` |\n| Password | `123456`          |\n\n\nThese credentials are hardcoded on purpose. This is a demo where zero setup matters more than secrecy. Disable email confirmation in the Supabase dashboard under **Authentication, Sign In / Providers, Email, \"Confirm email\" off.**\n\n## Environment Variables\n\nAll variables live in `.env.local`. The setup script writes the wallet IDs/addresses and the contract addresses automatically, so you only fill in the credentials.\n\n\n| Variable                                                             | Purpose                                      | How to get it                                                                |\n| -------------------------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------- |\n| `NEXT_PUBLIC_SUPABASE_URL` / `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`  | Auth and orders                              | Supabase project settings, or `npm run db:status` for local                  |\n| `SUPABASE_SECRET_KEY`                                                | Admin auto-seed, server only                 | Service-role key. Never expose to the browser                                |\n| `NEXT_PUBLIC_ARC_RPC_URL`                                            | Wallet and reads                             | Defaults to `https://rpc.testnet.arc.network`                                |\n| `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID`                               | RainbowKit QR / mobile                       | [cloud.reown.com](https://cloud.reown.com). Injected wallets work without it |\n| `CIRCLE_API_KEY` / `CIRCLE_ENTITY_SECRET`                            | Operator backend, setup script               | Circle Console. Backend only, never commit                                   |\n| `DEPLOYER_WALLET_SET_ID` / `DEPLOYER_WALLET_ID`                      | Setup script                                 | Written automatically on first setup                                         |\n| `OPERATOR_WALLET_ID` / `OPERATOR_ADDRESS`                            | Operator (submits ops, sponsors gas)         | Written automatically by `npm run setup`                                     |\n| `MERCHANT_WALLET_ID` / `MERCHANT_ADDRESS`                            | Merchant (funds receiver)                    | Written automatically by `npm run setup`                                     |\n| `NEXT_PUBLIC_ESCROW_ADDRESS` / `NEXT_PUBLIC_TOKEN_COLLECTOR_ADDRESS` | On-chain settlement                          | Written automatically by `npm run setup`                                     |\n| `WEBHOOK_SECRET`                                                     | HMAC-SHA256 verification for SCP push events | Any strong random string; register it with the Circle SCP webhook API        |\n\n\n\n\n### Local Supabase (optional)\n\nThe Supabase CLI is wired into npm scripts for running Postgres and auth locally:\n\n```bash\nnpm run db:start     # start the local stack\nnpm run db:status    # print local URLs and keys (use these in .env.local)\nnpm run db:stop      # stop the stack\nnpm run db:reset     # reset the local database\n```\n\n\n\n## Project Structure\n\n```\napp/\n  page.tsx                  public storefront (catalog + currency toggle)\n  products/[id]/page.tsx    product detail (statically generated per product)\n  checkout/page.tsx         wallet-connect checkout\n  account/                  authenticated order history (layout guards session)\n  admin/                    admin-only console (layout guards role claim)\n  auth/login, auth/sign-up  email auth screens\n  layout.tsx                root layout (theme + web3 providers)\ncomponents/\n  storefront.tsx            catalog grid + USDC/EURC toggle\n  product-detail.tsx        detail view + add to cart\n  checkout/checkout.tsx     wallet connect + checkout flow\n  cart-provider.tsx         localStorage cart, cookie count mirror, cross-tab sync\n  cart-button.tsx           header cart popover\n  add-to-cart-button.tsx\n  site-header.tsx           role-aware header\n  web3-provider.tsx         wagmi + react-query + RainbowKit\n  theme-provider.tsx, mode-toggle.tsx   light/dark\n  login-form.tsx, sign-up-form.tsx, logout-button.tsx\n  ui/                       shadcn primitives\ncontracts/\n  README.md                 protocol provenance + Arc/Cancun compatibility notes\n  artifacts/                pinned ABI + bytecode (AuthCaptureEscrow, ERC3009PaymentCollector)\nlib/\n  products.ts               catalog data + currency formatting\n  orders.ts                 order lifecycle states + placeholder history\n  arc/chain.ts              Arc Testnet constants (chain id, RPC, explorer, faucet)\n  arc/tokens.ts             USDC/EURC config, ERC-20 + ERC-3009 ABI / EIP-712\n  arc/public-client.ts      shared read-only viem client for Arc (server-side reads)\n  contracts/index.ts        deployed addresses + ABIs (single source of truth)\n  payments/types.ts         PaymentInfo + the six operations + signed authorization\n  payments/payment-info.ts  builds PaymentInfo for an order (pure)\n  payments/authorization.ts ERC-3009 typed-data + payer-agnostic nonce\n  payments/intent-store.ts  short-lived server hold of the built PaymentInfo\n  operator/operations.ts    operator service (all six operations wired)\n  operator/config.ts        operator + merchant addresses (server-only)\n  checkout/submit.ts        real checkout: intent -\u003e sign -\u003e relay\n  circle/client.ts          Developer-Controlled Wallets client (backend-only)\n  supabase/                 client (browser), server (RSC), proxy (session), seed-admin\n  use-hydrated.ts           hydration-safe client flag\n  wagmi.ts                  RainbowKit config (Arc Testnet, WalletConnect)\nscripts/\n  setup.ts                  ensures wallets, deploys escrow + collectors, seeds operator gas, grants refund allowance\napp/api/\n  checkout/intent/          builds + holds PaymentInfo, returns what the shopper signs\n  checkout/authorize/       relays the signed authorization (Authorize or Charge)\n  admin/orders/[id]/\n    capture/                POST - operator submits escrow.capture, updates DB\n    void/                   POST - operator submits escrow.void, updates DB\n    refund/                 POST - operator submits escrow.refund via OperatorRefundCollector\n  account/reclaim/          POST - shopper: operator submits escrow.reclaim after expiry\n  webhooks/payments/        POST - SCP event push -\u003e order status updates\ninstrumentation.ts          boots admin seeding on server start\nproxy.ts                    route guard entry + matcher config\n```\n\n\n\n## Local Development with Webhooks (ngrok)\n\nThe SCP webhook endpoint (`/api/webhooks/payments`) must be publicly reachable for Circle to push contract events to it. During local development you need a tunnel - [ngrok](https://ngrok.com) is the easiest option.\n\n1. **Install ngrok:**\n  ```bash\n   # macOS\n   brew install ngrok\n\n   # Linux / Windows - download from https://ngrok.com/download and add to PATH\n  ```\n2. **Authenticate once** (free account required):\n  ```bash\n   ngrok config add-authtoken \u003cyour-ngrok-authtoken\u003e\n  ```\n   Get your token from [dashboard.ngrok.com/authtokens](https://dashboard.ngrok.com/authtokens).\n3. **Start a tunnel** while the dev server is running on port 3000:\n  ```bash\n   ngrok http 3000\n  ```\n   ngrok prints a public HTTPS URL such as `https://abc123.ngrok-free.app`. Every request to that URL is forwarded to your local `localhost:3000`.\n4. **Register the webhook** using your ngrok URL (see [SCP Webhook Registration](#scp-webhook-registration) below). Restart ngrok when you need a new session - the URL changes each time on the free plan, so re-register accordingly.\n  \u003e **Tip:** The [ngrok paid plans](https://ngrok.com/pricing) offer stable custom domains if you need a persistent URL across sessions.\n\n\n\n## SCP Webhook Registration\n\n`POST /api/webhooks/payments` receives Circle Smart Contract Platform event-monitoring push notifications for the escrow contract and updates order status without polling.\n\nTo register it after deployment:\n\n```bash\ncurl -X POST https://api.circle.com/v1/w3s/contracts/event-monitors \\\n  -H \"Authorization: Bearer $CIRCLE_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"contractAddress\": \"'$NEXT_PUBLIC_ESCROW_ADDRESS'\",\n    \"endpointUrl\": \"https://\u003cyour-public-domain\u003e/api/webhooks/payments\",\n    \"secret\": \"\u003cyour-WEBHOOK_SECRET\u003e\"\n  }'\n```\n\nSet `WEBHOOK_SECRET=\u003cyour-WEBHOOK_SECRET\u003e` in `.env.local`, then uncomment the HMAC-SHA256 signature verification block at the top of `app/api/webhooks/payments/route.ts`. Circle signs each push request with the shared secret; the handler verifies it before processing.\n\n## **Legal disclaimer**\n\nSample apps provided for demonstration and educational purposes only, intended for Arc testnet use only, and not production-ready. See ++[Arc.io](http://Arc.io)++ for more.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcirclefin%2Farc-ecommerce-payments","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcirclefin%2Farc-ecommerce-payments","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcirclefin%2Farc-ecommerce-payments/lists"}