{"id":50586204,"url":"https://github.com/effectstream/payment-app","last_synced_at":"2026-06-05T06:30:52.646Z","repository":{"id":358728009,"uuid":"1242691475","full_name":"effectstream/payment-app","owner":"effectstream","description":"Example payment platform on ETH built with Effectstream and Transak","archived":false,"fork":false,"pushed_at":"2026-05-18T19:59:56.000Z","size":199,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-18T22:01:26.791Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/effectstream.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-05-18T16:58:12.000Z","updated_at":"2026-05-18T20:00:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"581811bc-7c9e-4f45-bd66-593ec0963d83","html_url":"https://github.com/effectstream/payment-app","commit_stats":null,"previous_names":["effectstream/payment-app"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/effectstream/payment-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/effectstream%2Fpayment-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/effectstream%2Fpayment-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/effectstream%2Fpayment-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/effectstream%2Fpayment-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/effectstream","download_url":"https://codeload.github.com/effectstream/payment-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/effectstream%2Fpayment-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33932048,"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-05T02:00:06.157Z","response_time":120,"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-06-05T06:30:51.828Z","updated_at":"2026-06-05T06:30:52.635Z","avatar_url":"https://github.com/effectstream.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# payment-app\n\nEffectstream template for buying in-game items with fiat — single-chain EVM (Hardhat for dev, Arbitrum Sepolia for mainnet) with a Transak fiat→crypto purchase flow on mainnet.\n\nThis is a migration of the [Paima payments game template](https://github.com/PaimaStudios/paima-game-templates/pull/87) onto the [Effectstream template specification](https://github.com/effectstream/effectstream/blob/v-next-bun-start/templates/effectstream-template-guidelines.md).\n\n---\n\n## Prerequisites\n\n| Tool | Version | Required for |\n|------|---------|--------------|\n| [Bun](https://bun.sh) | ≥ 1.1 | Everything (runtime + package manager) |\n| [Foundry](https://book.getfoundry.sh/getting-started/installation) | latest | `forge build` (EVM artifacts) |\n| Node.js | ≥ 20 | Some Hardhat postinstall scripts |\n| Docker | (optional) | Containerized runs |\n| Browser wallet (MetaMask, Rabby…) | — | Dev mode purchases |\n\nInstall Bun + Foundry on macOS/Linux:\n\n```sh\ncurl -fsSL https://bun.sh/install | bash\ncurl -L https://foundry.paradigm.xyz | bash \u0026\u0026 foundryup\n```\n\n---\n\n## Install\n\nClone (or `cd` to where this repo lives) and install deps from the workspace root:\n\n```sh\ngit clone \u003crepo-url\u003e payment-app\ncd payment-app\nbun install\n```\n\nThen run the two one-time build steps in order:\n\n```sh\nbun run build:evm        # 1. compile PaymentEffectstreamL2.sol + generate packages/contracts-evm/mod.ts\nbun run build:pgtypes    # 2. regenerate packages/database/sql/queries.queries.ts from queries.sql\n```\n\n\u003e **Note on `build:pgtypes`** — a hand-written stub is committed so type-checks pass before the first run, but it is the authoritative version you should regenerate whenever the SQL changes. Port 5432 must be free.\n\n---\n\n## Run — Dev mode (local Hardhat, direct wallet)\n\nDev mode boots the entire stack locally: a Hardhat node on `:8545`, PGLite on `:5432`, the sync engine on `:9999`, a batcher on `:3334`, and the frontend on `:10599`. Transak is **not** used in dev — the wallet signs and submits directly.\n\n```sh\nbun run dev\n```\n\nWait until the orchestrator log shows `frontend-server` is healthy. Then:\n\n1. Open **http://localhost:10599**\n2. Configure your browser wallet for the local Hardhat chain:\n   - Network name: `Hardhat`\n   - RPC URL: `http://localhost:8545`\n   - Chain ID: `31337`\n   - Currency: `ETH`\n3. Import a Hardhat dev account (private key prefilled with funds):\n   - `0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80` (account #0)\n4. Click **Connect Wallet**, pick a weapon, click **Buy** — the wallet pops up to sign.\n5. Inventory polls update within ~30 seconds (the \"Owned: N\" badge appears on the card).\n\nTo stop everything, hit `Ctrl+C` in the orchestrator terminal — it cascades shutdown to all child processes.\n\n### Run only one process (advanced)\n\nThe orchestrator runs everything in one shell. If you want to develop the frontend in isolation with hot reload:\n\n```sh\n# Terminal 1: leave the orchestrator running for chain + sync + batcher\nbun run dev\n\n# Terminal 2: Vite dev server with HMR\nbun run --filter @payment-app/frontend dev\n# → http://localhost:10598\n```\n\n### Verify\n\n```sh\nbun run test\n```\n\nRuns three phases: chain reachability + contract deploy (A), `purchaseItem` STM transition + `/api/items` (B), and frontend build smoke (C). For the optional Playwright render test:\n\n```sh\nRUN_RENDER_TEST=true bun run test\n```\n\n---\n\n## Run — Staging mode (Ethereum Sepolia, Transak STAGING)\n\nStaging mode is a fully-runnable testnet deployment that uses Transak's STAGING environment — **no KYB required, no real money moves**. Useful for end-to-end testing of the fiat-onramp flow with test cards. It runs against Ethereum Sepolia (chain id `11155111`).\n\n### One-time setup\n\n#### 1. Generate a deployer wallet and fund it\n\n```sh\n# Generate a fresh keypair (prints both private key and address)\nbun run packages/contracts-evm/deploy.sepolia.ts --new-wallet\n```\n\nCopy the private key into `.env.staging` (at the repo root — create from `.env.staging.example` if it doesn't exist):\n\n```sh\ncp .env.staging.example .env.staging\n# Set BATCHER_EVM_SECRET_KEY=0x... in .env.staging\n```\n\nPrint the wallet address and fund it via a Sepolia faucet (e.g. [alchemy.com/faucets/ethereum-sepolia](https://www.alchemy.com/faucets/ethereum-sepolia)):\n\n```sh\nbun run packages/contracts-evm/deploy.sepolia.ts --wallet\n```\n\n#### 2. Deploy the contract\n\n```sh\ncd packages/contracts-evm \u0026\u0026 bun run build      # compile the artifact\ncd ../.. \u0026\u0026 bun run packages/contracts-evm/deploy.sepolia.ts\n```\n\nThe script prints the deployed address, deploy block, and an Etherscan link. Copy:\n- The contract address into `EFFECTSTREAM_L2_ADDRESS` in `.env.staging` (root) and `VITE_EFFECTSTREAM_L2_ADDRESS` in `packages/frontend/.env.staging`\n- (Optional) the deploy block into `EVM_START_BLOCK` if you want to backfill — otherwise the sync node auto-starts from the current Sepolia tip on a fresh DB\n\n#### 3. Bootstrap a local Postgres\n\n```sh\n./init_db.sh                  # installs postgresql@16 via Homebrew (idempotent)\n```\n\nThis creates a `payment_app_staging` database with a `postgres` superuser matching the defaults in `.env.staging.example`. On first run it also builds and installs the [`pg_ivm`](https://github.com/sraoss/pg_ivm) extension from source (required by the `@effectstream/db` system migration); this needs Xcode Command Line Tools (`xcode-select --install`).\n\n#### 4. Register the contract with Transak (and get an API key)\n\n1. Sign up at [dashboard.transak.com](https://dashboard.transak.com) — staging API key is granted on signup (no KYB)\n2. **Products → NFT Checkout → Add Contract**: paste the deployed Sepolia address, upload [PaymentEffectstreamL2.abi.json](PaymentEffectstreamL2.abi.json), set network to Ethereum Sepolia. Auto-approved in ~1 minute on staging.\n3. Capture the staging **API key** and the **contractId**\n4. Set in `packages/frontend/.env.staging`:\n   ```sh\n   VITE_TRANSAK_API_KEY=\u003cstaging API key\u003e\n   # VITE_TRANSAK_CONTRACT_ID=\u003ccontractId\u003e   # only when purchase.ts is updated to NFT-checkout shape\n   ```\n\n### Run it\n\n```sh\nbun run dev:staging\n# Boots sync + batcher + Vite dev server (HMR) via the orchestrator.\n# → http://localhost:10598\n```\n\nThis mirrors `bun run dev` but targets Ethereum Sepolia and the Transak STAGING widget. Postgres must already be running (`./init_db.sh` once is enough — `brew services` keeps it alive across reboots).\n\n### Test purchase\n\n1. Connect a wallet on Ethereum Sepolia (chain id `11155111`)\n2. Click **Buy** on any item — the Transak widget opens\n3. Use a staging test card:\n   - Card number `4242 4242 4242 4242` (most fiat currencies)\n   - Expiration `10/33`, CVV `123`\n   - 3D Secure password `Checkout1!`\n4. After the on-chain transaction confirms, the inventory poll should reflect it within ~1 minute\n\n**Note**: per Transak's staging docs, no real ETH is sent to your contract on staging — the on-chain tx fires (so the sync node will index it) but the contract's balance won't change. Full end-to-end with real funds requires production keys + KYB.\n\n---\n\n## Run — Mainnet mode (Arbitrum Sepolia, Transak fiat)\n\nMainnet mode assumes the chain, database, and frontend are deployed and hosted externally. You only run two processes on your server: the sync node and the batcher.\n\n### One-time setup\n\n#### 1. Deploy the contract to Arbitrum Sepolia\n\nEdit the deploy parameters in [`packages/contracts-evm/deploy.ts`](packages/contracts-evm/deploy.ts) to point at your owner address, then deploy:\n\n```sh\ncd packages/contracts-evm\n\n# Add an arbitrumSepolia network in hardhat.config.ts (or use a separate config),\n# then run Ignition manually. Example:\nbun ./node_modules/.bin/hardhat ignition deploy \\\n  ./ignition/modules/effectstreamL2.ts \\\n  --network arbitrumSepolia \\\n  --parameters ./ignition/parameters.json\n```\n\nCapture the deployed address — you'll need it as `EFFECTSTREAM_L2_ADDRESS`.\n\n#### 2. Provision a Postgres database\n\nAnywhere — managed service (Neon, Supabase, RDS) or self-hosted. The sync node applies its own migrations on startup; no manual schema setup needed. Capture the connection string as `DATABASE_URL`.\n\n#### 3. Obtain a Transak API key\n\nSign up at [transak.com](https://transak.com), create an app, and capture the **API key** (staging or production). You also need to whitelist the contract address in the Transak dashboard so they will route calldata to it.\n\n### Environment variables\n\nCreate a `.env` file at the repo root (or export them in your shell / process manager):\n\n```sh\n# Sync node + batcher\nEVM_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc      # or your private RPC\nEFFECTSTREAM_L2_ADDRESS=0xYourDeployedContract...\nBATCHER_EVM_SECRET_KEY=0xYourBatcherPrivateKey...      # funded with gas on Arbitrum Sepolia\nDATABASE_URL=postgres://user:pass@host:5432/dbname\n\n# Optional tuning\nEVM_START_BLOCK=12345678                                # block at/around contract deploy\nEVM_POLL_MS=2000\nEVM_CONFIRMATION_DEPTH=5\nBATCHER_INTERVAL_MS=2000\nBATCHER_FEE=0\n```\n\nFor the frontend build, in `packages/frontend/.env.mainnet`:\n\n```sh\nVITE_EFFECTSTREAM_NODE_URL=https://api.your-domain.example\nVITE_BATCHER_URL=https://batcher.your-domain.example\nVITE_CHAIN_ID=421614\nVITE_EFFECTSTREAM_L2_ADDRESS=0xYourDeployedContract...\nVITE_TRANSAK_API_KEY=your_transak_api_key            # presence triggers the Transak path\n```\n\n### Build the frontend for mainnet\n\n```sh\nbun run --filter @payment-app/frontend build:mainnet\n# output: packages/frontend/client/dist/\n```\n\nDeploy `packages/frontend/client/dist/` to any static host (Vercel, Netlify, Cloudfront, plain Nginx). Make sure `\u003cscript src=\"https://global.transak.com/sdk/v1.2/widget.js\"\u003e` is loaded — add it to `client/index.html` before deploying if you haven't already.\n\n### Start the sync node + batcher\n\nOn your server (or in CI, a VM, ECS task, etc.):\n\n```sh\nbun install                         # if not already installed on the host\nbun run build:pgtypes               # one-time\nbunx orchestrator start --config start.mainnet.ts\n```\n\nThe orchestrator launches only [`main.mainnet.ts`](packages/node/main.mainnet.ts) and [`batcher.mainnet.ts`](packages/batcher/batcher.mainnet.ts) — both validate the env vars listed above and will fail-fast with a clear message if any are missing.\n\nAlternative: run the node directly without the orchestrator (useful for managed runtimes that handle process supervision themselves):\n\n```sh\nbun run start:mainnet               # sync node only — start the batcher separately\nbun run packages/batcher/batcher.mainnet.ts\n```\n\n### Verify mainnet flow\n\n1. Hit `https://api.your-domain.example/api/health` → expects `{\"ok\":true}`.\n2. From the deployed frontend, connect a wallet on Arbitrum Sepolia.\n3. Click **Buy** on an item — the Transak widget should appear with the contract address and calldata pre-filled.\n4. Complete a sandbox payment (Transak staging mode supports test card numbers).\n5. After the on-chain transaction confirms, the inventory poll should show the item within ~1 minute.\n\n---\n\n## Run — Docker\n\nFor a self-contained image that boots the full dev stack:\n\n```sh\n# Apple Silicon\nexport DOCKER_DEFAULT_PLATFORM=linux/amd64\n\ndocker build -f ./Dockerfile . -t payment-app\n\n# Dev stack inside the container\ndocker run --rm \\\n  -p 4747:4747 -p 9999:9999 -p 10599:10599 -p 3334:3334 -p 8545:8545 -p 5432:5432 \\\n  payment-app\n\n# Run tests inside the container\ndocker run --rm payment-app bun run test\n```\n\n---\n\n## Environments at a glance\n\n| | Dev | Staging | Mainnet |\n|---|-----|---------|---------|\n| Chain | Hardhat (`31337`) | Ethereum Sepolia (`11155111`) | Arbitrum Sepolia (`421614`) |\n| Database | PGLite (in-process) | Local Postgres via `./init_db.sh` | Managed Postgres |\n| Sync entry | [main.dev.ts](packages/node/main.dev.ts) | [main.staging.ts](packages/node/main.staging.ts) | [main.mainnet.ts](packages/node/main.mainnet.ts) |\n| Batcher entry | [batcher.dev.ts](packages/batcher/batcher.dev.ts) | [batcher.staging.ts](packages/batcher/batcher.staging.ts) | [batcher.mainnet.ts](packages/batcher/batcher.mainnet.ts) |\n| Frontend purchase | Direct wallet | Transak STAGING widget | Transak STAGING/PRODUCTION widget |\n| Frontend env file | `packages/frontend/.env.dev` | `packages/frontend/.env.staging` | `packages/frontend/.env.mainnet` |\n| Start command | `bun run dev` | `bun run dev:staging` | `bunx orchestrator start --config start.mainnet.ts` |\n\n---\n\n## Frontend env files\n\nThe Vite client loads a different env file per mode. **All `VITE_*` vars are baked into the bundle at build time and shipped to the browser** — never put a private key in these files.\n\n| Var | Dev (`.env.dev`) | Staging (`.env.staging`) | Mainnet (`.env.mainnet`) |\n|-----|------------------|---------------------------|---------------------------|\n| `VITE_EFFECTSTREAM_NODE_URL` | `http://localhost:9999` | your staging API host | your production API host |\n| `VITE_BATCHER_URL` | `http://localhost:3334` | your staging batcher host | your production batcher host |\n| `VITE_CHAIN_ID` | `31337` | `11155111` | `421614` |\n| `VITE_EFFECTSTREAM_L2_ADDRESS` | (unset — defaults to Hardhat slot) | Sepolia deploy address | Arb-Sepolia deploy address |\n| `VITE_TRANSAK_API_KEY` | (unset — uses direct wallet) | staging key from Transak dashboard | staging or production key |\n| `VITE_TRANSAK_ENVIRONMENT` | n/a | `STAGING` | `STAGING` or `PRODUCTION` |\n| `VITE_TRANSAK_NETWORK` | n/a | `sepolia` or `ethereum` | `arbitrumsepolia` or `arbitrum` |\n\nHow the frontend uses them:\n- If `VITE_TRANSAK_API_KEY` is **unset**, [purchase.ts](packages/frontend/client/src/purchase.ts) falls back to direct wallet signing via the connected wallet. This is the dev-mode path.\n- If `VITE_TRANSAK_API_KEY` is **set**, it opens the Transak widget with `environment` and `network` from the corresponding env vars (defaults: `STAGING` + `arbitrumsepolia` if you don't override them).\n- `VITE_TRANSAK_NETWORK` must match the network you registered the contract under in the Transak dashboard — if the dashboard shows the contract as \"Ethereum\", use `ethereum`; if \"Sepolia\", use `sepolia`.\n\nBuild / dev commands per mode:\n\n```sh\nbun run dev              # dev mode (loads .env.dev, port 10598)\nbun run dev:staging      # staging mode (loads .env.staging, port 10598)\nbun run build:dev        # dev build → packages/frontend/client/dist/\nbun run build:staging    # staging build\nbun run build:mainnet    # mainnet build\n```\n\n(Run these from `packages/frontend/` or prefix with `bun run --filter @payment-app/frontend \u003cscript\u003e`.)\n\n---\n\n## Project Structure\n\n```\npayment-app/\n├── package.json                  # workspaces [\"packages/*\"], effectstream.default = \"start.dev.ts\"\n├── start.dev.ts                  # Orchestrator: PGLite + Hardhat + sync + batcher + frontend\n├── start.staging.ts              # Orchestrator: sync + batcher only (Ethereum Sepolia)\n├── start.mainnet.ts              # Orchestrator: sync + batcher only (Arbitrum Sepolia)\n├── init_db.sh                    # Homebrew Postgres bootstrap for staging\n├── .env.staging.example          # Template for backend/deploy secrets (root)\n├── Dockerfile                    # oven/bun:1 + Foundry + solc 0.8.30 + workspace symlinks\n├── packages/\n│   ├── node/                     # @payment-app/node — sync node\n│   ├── database/                 # @payment-app/database — migrations + pgtyped queries\n│   ├── contracts-evm/            # @payment-app/contracts-evm — PaymentEffectstreamL2.sol\n│   ├── batcher/                  # @payment-app/batcher — EffectstreamL2 adapter\n│   ├── frontend/                 # @payment-app/frontend — Vite + React + Fastify\n│   └── tests/                    # @payment-app/tests — Phase A/B/C\n```\n\n### Package descriptions\n\n**`packages/node`** — the sync engine.\n\n| File | Purpose |\n|------|---------|\n| `grammar.ts` | Defines the `purchaseItem` grammar key (Typebox schema) |\n| `state-machine.ts` | Single STM transition: upsert into `user_items` |\n| `api.ts` | `GET /api/items?wallet=\u003caddr\u003e` (returns inventory rows) |\n| `config.{dev,staging,mainnet}.ts` | ConfigBuilder per environment. Staging auto-starts from chain tip on empty DB. |\n| `main.{dev,staging,mainnet}.ts` | Entry points |\n\n**`packages/database`** — schema and typed queries. `user_items(wallet, item_id, amount)` table; queries: `upsertUserItem`, `getItemsByWallet`, `getAllItems`. Regenerate with `bun run build:pgtypes`.\n\n**`packages/contracts-evm`** — Solidity. `PaymentEffectstreamL2.sol` extends `EffectstreamL2Contract` (no additional logic — the base contract handles `effectstreamSubmitGameInput(bytes)` payable submissions).\n\n**`packages/batcher`** — receives signed inputs from the frontend, batches them, submits to the L2 contract.\n\n**`packages/frontend`** — Vite + React. The 18-weapon catalogue lives in [`client/src/items.ts`](packages/frontend/client/src/items.ts); SVG placeholders are generated at build time by [`client/scripts/gen-items.ts`](packages/frontend/client/scripts/gen-items.ts). [`purchase.ts`](packages/frontend/client/src/purchase.ts) branches on `VITE_TRANSAK_API_KEY`.\n\n---\n\n## Services\n\n| Service | Port | Notes |\n|---------|------|-------|\n| Frontend | 10599 | Fastify static server (built from `client/dist`) |\n| Vite dev server | 10598 | Only when running `bun run --filter @payment-app/frontend dev` |\n| Sync API | 9999 | `/api/items`, `/api/health` |\n| Orchestrator | 4747 | Process management + `/shutdown` |\n| Batcher | 3334 | `/send-input` (frontend posts here when `preferBatchedMode=true`) |\n| Hardhat EVM | 8545 | Dev only |\n| PGLite | 5432 | Dev only |\n\n---\n\n## Game Mechanics\n\n### Grammar inputs\n\n| Input | Fields | Effect |\n|-------|--------|--------|\n| `purchaseItem` | `itemId: int (1..18)`, `amount: int (1..1000)` | Adds `amount` to `user_items(wallet=signer, item_id)` (insert or increment) |\n\nWire format: `[\"purchaseItem\", 3, 1]` — JSON array submitted as bytes to `effectstreamSubmitGameInput`.\n\n### API endpoints\n\n| Method | Path | Response |\n|--------|------|----------|\n| GET | `/api/items?wallet=\u003caddr\u003e` | `{ wallet, items: [{ wallet, item_id, amount }] }` |\n| GET | `/api/health` | `{ ok: true }` |\n\n---\n\n## Env var reference (staging + mainnet)\n\nBackend vars are read at runtime by the sync node, batcher, and deploy script. They live in `.env.staging` (or your shell / process manager) at the **repo root** — not in `packages/frontend/.env.*`, which is for `VITE_*` only.\n\n| Name | Required | Used by | Description |\n|------|----------|---------|-------------|\n| `EVM_RPC_URL` | yes | sync, batcher, deploy | Sepolia / Arbitrum-Sepolia RPC URL |\n| `EFFECTSTREAM_L2_ADDRESS` | yes | sync, batcher | Deployed `PaymentEffectstreamL2` address |\n| `BATCHER_EVM_SECRET_KEY` | yes | batcher, deploy | Hex private key (must hold gas on the target chain) |\n| `DB_HOST` | yes | sync | Postgres host (default `localhost`) |\n| `DB_PORT` | yes | sync | Postgres port (default `5432`) |\n| `DB_USER` | yes | sync | Postgres user (default `postgres`) |\n| `DB_PW` | yes | sync | Postgres password (default `postgres`) |\n| `DB_NAME` | yes | sync | Postgres database (default `postgres`) |\n| `EVM_START_BLOCK` | no | sync | Block to start indexing from. **If unset on staging with an empty DB, auto-starts from the Sepolia tip.** |\n| `EVM_POLL_MS` | no | sync | RPC polling interval ms (default `2000`) |\n| `EVM_CONFIRMATION_DEPTH` | no | sync | Blocks until finality (default `5`) |\n| `BATCHER_INTERVAL_MS` | no | batcher | Batch flush interval ms (default `2000`) |\n| `BATCHER_FEE` | no | batcher | Per-input fee in wei (default `0`) |\n| `CONTRACT_OWNER` | no | deploy | Owner address on `PaymentEffectstreamL2`. Defaults to deployer. |\n| `CONTRACT_FEE` | no | deploy | Constructor `fee` arg (wei). Default `0`. |\n| `VITE_TRANSAK_API_KEY` | frontend | client | Transak SDK API key — presence enables the fiat path |\n| `VITE_TRANSAK_ENVIRONMENT` | frontend | client | `STAGING` or `PRODUCTION` (default `STAGING`) |\n| `VITE_TRANSAK_NETWORK` | frontend | client | Network token Transak registered the contract under (e.g. `sepolia`, `ethereum`, `arbitrumsepolia`) |\n| `VITE_EFFECTSTREAM_L2_ADDRESS` | frontend | client | Same address as `EFFECTSTREAM_L2_ADDRESS` |\n| `VITE_EFFECTSTREAM_NODE_URL` | frontend | client | Sync node URL (`/api/*`) |\n| `VITE_BATCHER_URL` | frontend | client | Batcher URL |\n| `VITE_CHAIN_ID` | frontend | client | Chain id matching the deploy (`11155111` staging, `421614` mainnet) |\n\n---\n\n## Troubleshooting\n\n- **`bun install` fails with workspace resolution errors on Linux** — Bun on Linux doesn't auto-create workspace symlinks. Run the symlink loop from the [Dockerfile](Dockerfile) (the snippet starting with `bun -e \"...\"` after `bun install`).\n- **`build:pgtypes` errors with `port 5432 already in use`** — the script starts its own PGLite. Kill stale processes: `lsof -ti :5432 | xargs kill -9`.\n- **Hardhat node never starts in `bun run dev`** — make sure Foundry is on `$PATH`. Verify with `forge --version`.\n- **Transak widget says \"missing apiKey\"** — `VITE_TRANSAK_API_KEY` is unset at build time. The frontend silently falls back to direct-wallet submission; you need to rebuild with the env var present.\n- **`401 Invalid signature` from `/send-input`** — `EffectstreamConfig.appName` (frontend, `\"\"`) must match `BatcherConfig.namespace` (batcher, `\"\"`). Both empty strings is intentional and required.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feffectstream%2Fpayment-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feffectstream%2Fpayment-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feffectstream%2Fpayment-app/lists"}