Skip to content
Open
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
117 changes: 117 additions & 0 deletions typescript/bnb-QR-invoice-payments/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# QR Stablecoin Payments (BNB Chain)

Live demo: [https://bnbchainqrpayments.netlify.app](https://bnbchainqrpayments.netlify.app)
PaymentProcessor (BNB Chain testnet): `0x369bdf926Eaaf7170Be7E7BD30c7Efa2aA72A3EA`

## Feature showcase
- **Merchant console** – connect wallet, register payout address, mint QR invoices, auto-sync with Supabase, and share short links.
- **Customer pay portal** – scan QR, review invoice details, auto-check settlement status, and approve/pay USDT without editing payloads.
- **Invoice dashboard** – list Supabase invoices, show on-chain settlement via `paymentStatus(invoiceId)`, and display totals.
- **Test payment helper** – merchants can simulate a customer payment (skips approval when allowance exists and blocks re-payment).
- **Supabase + API** – `/api/invoices` manages storage; `/api/invoices/status` reads `paymentStatus` server-side using `BSC_RPC_URL`.
- **Built-in USDT faucet** – header button hits `/api/faucet` to mint 10 mock USDT per request via the provided private key.

## Architecture
```
contracts/ PaymentProcessor + USDYMock (mock USDT)
frontend/ Next.js app (merchant, pay, dashboard, API routes)
supabase/migrations/ SQL schema for invoices table
```

## Deployment guide

### 1. Deploy PaymentProcessor via Remix (BNB Chain testnet)
1. Visit [Remix](https://remix.ethereum.org/) and import `contracts/src/PaymentProcessor.sol`.
2. Compile with Solidity 0.8.25+.
3. In the “Deploy & Run” tab, select the injected Web3 provider (MetaMask pointing to BNB Testnet).
4. Deploy `PaymentProcessor` passing the stablecoin address (e.g., your `USDYMock` deployment). The live demo uses `0x369bdf926Eaaf7170Be7E7BD30c7Efa2aA72A3EA`.
5. (Optional) Deploy `USDYMock.sol` and mint test balances to customer wallets.

### 2. Provision Supabase
1. Create a new Supabase project.
2. Apply the migration:
```bash
cd supabase
supabase db push # or supabase migration up
```
This creates the `public.invoices` table with the schema defined in `supabase/migrations/001_create_invoices.sql` (`invoice_id`, `nonce`, status, timestamps, etc.).
3. Note the project URL and Service Role key (Settings → API).

### 3. Configure environment variables
In `frontend/.env.local` (copy from `.env.example`), set:
```
NEXT_PUBLIC_PAYMENT_PROCESSOR_ADDRESS=0x369bdf926Eaaf7170Be7E7BD30c7Efa2aA72A3EA
NEXT_PUBLIC_STABLECOIN_ADDRESS=<USDYMock or other stablecoin>
NEXT_PUBLIC_BSC_RPC=https://bsc-testnet.drpc.org
BSC_RPC_URL=https://bsc-testnet.drpc.org
SUPABASE_URL=<your Supabase URL>
SUPABASE_SERVICE_ROLE_KEY=<service role key>
USDY_FAUCET_PRIVATE_KEY=<private key that owns/mints USDYMock>
```
- `NEXT_PUBLIC_*` values are exposed to the browser.
- `BSC_RPC_URL` is only consumed by API routes (server-side) when checking settlement.
- `USDY_FAUCET_PRIVATE_KEY` is used by the `/api/faucet` route to mint 10 USDT per request (the hackathon key provided is `0x1578...006a`—store it securely in envs, never hard-code in client bundles).

### 4. Install & run frontend
```bash
cd frontend
npm install
npm run dev # visit http://localhost:3000
```
Set `npm run build && npm start` (or deploy via Netlify/Vercel) for production; the live site uses Netlify.

### 5. Merchant workflow
1. Navigate to `/merchant`.
2. Connect MetaMask → register payout address (on-chain `registerMerchant`).
3. Fill amount/memo → click “Generate invoice” (saves invoice hash + nonce to Supabase and renders QR + share link).
4. Share the link/QR or click “Invoices” in the header to see Supabase records.

### 6. Customer workflow
1. Open `/pay?payload=...` (from QR link).
2. Connect wallet; the UI verifies the invoice on-chain via `/api/invoices/status`.
3. Click “Approve & Pay” (skips re-approval if allowance is sufficient). Settled invoices cannot be paid twice.

## Repo scripts
```bash
# Contracts
cd contracts
forge test # run Foundry tests

# Frontend
cd frontend
npm run dev # dev server
npm run lint # lint + typecheck
npm run build && npm start # production mode
```

## Supabase schema (for reference)
```sql
create table if not exists public.invoices (
id uuid primary key default gen_random_uuid(),
merchant text not null,
amount text not null,
memo text,
processor text not null,
token text not null,
chain_id text not null default '97',
invoice_id text unique not null,
nonce text not null,
status text not null default 'pending',
created_at timestamptz not null default now(),
settled_at timestamptz,
tx_hash text,
payer text
);

create index if not exists invoices_merchant_idx on public.invoices (merchant);
create index if not exists invoices_status_idx on public.invoices (status);
```

> Paste the block above into Supabase's SQL editor if you prefer creating the table manually.

## Notes
- Stablecoin: `USDYMock.sol` acts as a USDT mock with `mint` exposed; ensure customers mint or receive tokens on BNB Testnet.
- Settlement check: both the merchant dashboard and pay portal call `/api/invoices/status`, which uses server-side RPC to read `paymentStatus(invoiceId)` from `PaymentProcessor`.
- Wallet persistence: merchant/pay pages store the last connected account locally and rehydrate on page load.

Feel free to extend with Moralis streams, NFC hooks, or production-grade storage. The current stack is tuned for hackathon demos with rapid QR issuance and Supabase-backed tracking.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

env:
FOUNDRY_PROFILE: ci

jobs:
check:
name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Show Forge version
run: |
forge --version
- name: Run Forge fmt
run: |
forge fmt --check
id: fmt

- name: Run Forge build
run: |
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
14 changes: 14 additions & 0 deletions typescript/bnb-QR-invoice-payments/contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
3 changes: 3 additions & 0 deletions typescript/bnb-QR-invoice-payments/contracts/.gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
32 changes: 32 additions & 0 deletions typescript/bnb-QR-invoice-payments/contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Contracts

Solidity smart contracts are managed with [Foundry](https://book.getfoundry.sh/). The MVP ships two contracts:

- `PaymentProcessor.sol` – receives an ERC-20 stablecoin allowance and forwards payments to registered merchant payout wallets while emitting settlement events.
- `USDYMock.sol` – minimal mock token we treat as USDT for local testing on BNB Chain testnet.

## Quick start

```bash
cd contracts
forge install # (only needed the first time)
forge build # compile
forge test # run the test suite in test/PaymentProcessor.t.sol
```

## Local deployment script

```
forge script script/DeployPaymentProcessor.s.sol:DeployPaymentProcessor \
--rpc-url $BSC_TESTNET_RPC \
--private-key $DEPLOYER_KEY \
--broadcast \
--verify --etherscan-api-key $BSCSCAN_KEY
```

Supply the deployed USDT mock (`USDYMock`) address as the `stablecoin` argument and an optional payout address for the deployer merchant. After deployment merchants can call `registerMerchant(newPayout)` and customers pay with `pay(merchant, amount, memo, invoiceId)` using the invoice hash `keccak256(abi.encode(merchant, amount, memo, nonce))`.

## Testing notes

- Tests live in `test/PaymentProcessor.t.sol` and cover successful settlement and error paths such as reused invoice IDs.
- `forge test` mints mock USDT (via USDYMock), registers merchants, and confirms the payment struct persists as expected. Feel free to expand the suite with new flows (refunds, fee logic, etc.).
6 changes: 6 additions & 0 deletions typescript/bnb-QR-invoice-payments/contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/Vm.sol linguist-generated
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @danipopes @klkvr @mattsse @grandizzy @yash-atreya @zerosnacks @onbjerg @0xrusowsky
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: CI

permissions: {}

on:
workflow_dispatch:
pull_request:
push:
branches:
- master

jobs:
build:
name: build +${{ matrix.toolchain }} ${{ matrix.flags }}
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly]
flags:
- ""
- --via-ir
- --use solc:0.8.17 --via-ir
- --use solc:0.8.17
- --use solc:0.8.0
- --use solc:0.7.6
- --use solc:0.7.0
- --use solc:0.6.2
- --use solc:0.6.12
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: foundry-rs/foundry-toolchain@v1
- run: forge --version
- run: |
case "${{ matrix.flags }}" in
*"solc:0.8.0"* | *"solc:0.7"* | *"solc:0.6"*)
forge build --skip test --skip Config --skip StdConfig --skip LibVariable --deny-warnings ${{ matrix.flags }}
;;
*)
forge build --skip test --deny-warnings ${{ matrix.flags }}
;;
esac
# via-ir compilation time checks.
- if: contains(matrix.flags, '--via-ir')
run: forge build --skip test --deny-warnings ${{ matrix.flags }} --contracts 'test/compilation/*'

test:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly]
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: foundry-rs/foundry-toolchain@v1
with:
version: ${{ matrix.toolchain }}
- run: forge --version
- run: forge test -vvv

fmt:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: foundry-rs/foundry-toolchain@v1
- run: forge --version
- run: forge fmt --check

typos:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: crate-ci/typos@626c4bedb751ce0b7f03262ca97ddda9a076ae1c # v1

codeql:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"

ci-success:
runs-on: ubuntu-latest
if: always()
needs:
- build
- test
- fmt
- typos
- codeql
timeout-minutes: 10
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
with:
jobs: ${{ toJSON(needs) }}
Loading