From 659311a0f745412896b52c44774c14dfe0741cc2 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sun, 9 Aug 2026 21:36:36 +0100 Subject: [PATCH] chore(config): make a local setup produce clickable links and a nameable failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things stood between a fresh clone and a payment you can click through. `PAYMENT_LINK_BASE_URL` was documented nowhere. Unset, the API falls back to the production default, so every link created in development came out as https://pay.useroutr.com/ — correct-looking, and not clickable on a machine that has no such host. Now in .env.example, pointed at the checkout app's /l route. `STELLAR_USDC_SAC_TESTNET` shipped as the literal "C...", so everyone re-derived the same public constant. Filled in with the real value, derived from USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 and checked against the live network — calling `symbol()` on it returns "USDC". `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` falls back to the string "placeholder", which Reown answers with a 403. The wallet modal opens empty and the only clue is an unattributed 403 in the console, which reads like a network blip. It now warns once in development naming the variable and where to get one. The value itself still has to come from a human at cloud.reown.com — nothing here can mint it. Note the Stellar payment path does not need it. Two things found on the way: - apps/checkout had no .env.example at all, and could not have had one: the root .gitignore un-ignores `.env.example`, but each app's own .gitignore re-ignores it with a blanket `.env*`, and the more specific file wins. A new template would have been silently skipped by `git add`. Negation added to checkout, dashboard and www. - .env.example advertised CHECKOUT_URL on :3002; the app has been on :3003 since it got its own dev script. Checkout: typecheck clean, 0 lint errors, 8/8 tests. Warning verified to fire exactly once on a fresh page load, and link creation verified end to end — a link made in the dashboard now resolves at http://localhost:3003/l/. Co-Authored-By: Claude Opus 5 --- apps/api/.env.example | 17 ++++++++++-- apps/checkout/.env.example | 23 ++++++++++++++++ apps/checkout/.gitignore | 4 +++ apps/checkout/providers/WalletProviders.tsx | 29 ++++++++++++++++++++- apps/dashboard/.gitignore | 4 +++ apps/www/.gitignore | 4 +++ 6 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 apps/checkout/.env.example diff --git a/apps/api/.env.example b/apps/api/.env.example index 4cebb08..bf20945 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -21,9 +21,18 @@ PORT=3333 API_URL="http://localhost:3333" NODE_ENV="development" FRONTEND_URL="http://localhost:3001" # dashboard origin -CHECKOUT_URL="http://localhost:3002" # hosted checkout (apps/checkout) +CHECKOUT_URL="http://localhost:3003" # hosted checkout (apps/checkout, `next dev --port 3003`) TAVVIO_FEE_BPS=50 # default platform fee = 0.5% +# Base stamped onto every generated payment-link URL. It must point at the +# checkout app's `/l` route — the API appends `/` to it. +# +# Undocumented until now, so local setups fell through to the production +# default and every link created in development came out as +# https://pay.useroutr.com/: correct-looking, and not clickable on a +# machine that has no such host. Set it, or dev links go nowhere. +PAYMENT_LINK_BASE_URL="http://localhost:3003/l" + # ── Auth / crypto ──────────────────────────────────────────────────────── JWT_SECRET="replace-with-256-bit-random" JWT_EXPIRY="7d" @@ -71,7 +80,11 @@ STELLAR_ESCROW_HOLDING_SECRET="S..." # USDC's Stellar Asset Contract id, which the escrow contract moves. Distinct # from the classic asset issuer: Soroban addresses assets by contract id. # Derive with: stellar contract id asset --asset USDC: --network -STELLAR_USDC_SAC_TESTNET="C..." +# Testnet's value is filled in because it is public and deterministic — derived +# from USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5, and +# verified against the live network (calling `symbol()` on it returns "USDC"). +# Leaving it as "C..." meant every developer re-derived the same constant. +STELLAR_USDC_SAC_TESTNET="CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA" STELLAR_USDC_SAC_MAINNET="C..." # ── EVM RPC endpoints ──────────────────────────────────────────────────── diff --git a/apps/checkout/.env.example b/apps/checkout/.env.example new file mode 100644 index 0000000..e53f49e --- /dev/null +++ b/apps/checkout/.env.example @@ -0,0 +1,23 @@ +# Checkout — copy to .env.local for local development. +# +# Both variables are NEXT_PUBLIC_*, so they are inlined into the client bundle +# at build time. Neither is a secret: the API base is public by definition, and +# a Reown project id identifies the app rather than authenticating it. Do not +# add anything here that must stay private. + +# Origin of the Useroutr API. The `/v1` prefix is added by the client — pass the +# origin alone, or with the prefix; `resolveApiBaseUrl` normalises both to +# exactly one `/v1`. The dashboard reads the same variable and agrees on its +# meaning, which it did not always. +# +# Defaults to http://localhost:3333 when unset, which is the local API's port. +NEXT_PUBLIC_API_URL="http://localhost:3333" + +# Reown (formerly WalletConnect) project id, used by RainbowKit for the EVM +# wallet-connect modal. Create one — free — at https://cloud.reown.com. +# +# Without it the code falls back to the string "placeholder", Reown answers 403, +# and the wallet modal opens empty with only an unattributed 403 in the console +# to explain why. The Stellar payment path does not use this and works without +# it; only the EVM chains need it. +NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID="" diff --git a/apps/checkout/.gitignore b/apps/checkout/.gitignore index 5ef6a52..9b105bc 100644 --- a/apps/checkout/.gitignore +++ b/apps/checkout/.gitignore @@ -32,6 +32,10 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +# ...except the committed template. The root .gitignore already un-ignores it, +# but this file's blanket `.env*` is more specific and was winning, so a new +# apps//.env.example silently never got added. +!.env.example # vercel .vercel diff --git a/apps/checkout/providers/WalletProviders.tsx b/apps/checkout/providers/WalletProviders.tsx index 25e3c27..adb9719 100644 --- a/apps/checkout/providers/WalletProviders.tsx +++ b/apps/checkout/providers/WalletProviders.tsx @@ -26,9 +26,36 @@ import { RainbowKitProvider, getDefaultConfig } from "@rainbow-me/rainbowkit"; // STELLAR_NETWORK (testnet → sepolia variants, mainnet → mainnet). The // frontend doesn't know which side the API is on; including both lets // RainbowKit's switcher offer whichever the customer's wallet is on. +const WALLETCONNECT_PROJECT_ID = + process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID ?? "placeholder"; + +// "placeholder" is not a real project id, so Reown answers 403 and the wallet +// modal comes up empty. The only clue was an unattributed 403 in the console, +// which reads like a network blip rather than a missing variable — say what it +// is, in development. +// +// The flag is on `window` rather than module scope because Next re-evaluates +// this module across routes and HMR passes, which printed the same warning +// three times on a single page load. +const WARNED_FLAG = "__useroutrWalletConnectWarned"; + +if ( + process.env.NODE_ENV !== "production" && + WALLETCONNECT_PROJECT_ID === "placeholder" && + typeof window !== "undefined" && + !(window as unknown as Record)[WARNED_FLAG] +) { + (window as unknown as Record)[WARNED_FLAG] = true; + console.warn( + "[checkout] NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID is unset, so wallet " + + "connect will fail with a 403 from Reown. Create a project at " + + "https://cloud.reown.com and set the variable in apps/checkout/.env.local.", + ); +} + const config = getDefaultConfig({ appName: "Useroutr Checkout", - projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID ?? "placeholder", + projectId: WALLETCONNECT_PROJECT_ID, chains: [ mainnet, sepolia, diff --git a/apps/dashboard/.gitignore b/apps/dashboard/.gitignore index 5ef6a52..9b105bc 100644 --- a/apps/dashboard/.gitignore +++ b/apps/dashboard/.gitignore @@ -32,6 +32,10 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +# ...except the committed template. The root .gitignore already un-ignores it, +# but this file's blanket `.env*` is more specific and was winning, so a new +# apps//.env.example silently never got added. +!.env.example # vercel .vercel diff --git a/apps/www/.gitignore b/apps/www/.gitignore index 5ef6a52..9b105bc 100644 --- a/apps/www/.gitignore +++ b/apps/www/.gitignore @@ -32,6 +32,10 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +# ...except the committed template. The root .gitignore already un-ignores it, +# but this file's blanket `.env*` is more specific and was winning, so a new +# apps//.env.example silently never got added. +!.env.example # vercel .vercel