Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ FACILITATOR_PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE

# Token addresses on Beam (verify on BeamScan before mainnet)
USDC_BEAM_ADDRESS=0xYourUsdcBeamAddress
BEAM_TOKEN_ADDRESS=0x4200000000000000000000000000000000000023
BEAM_TOKEN_ADDRESS=0xF65B6f9c94187276C7d91F4F74134751d248bFeA

# Permit2 (universal address on all EVM chains)
PERMIT2_ADDRESS=0x000000000022D473030F116dDEE9F6B43aC78BA3
Expand Down
73 changes: 73 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build-and-test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20, 22]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build packages
run: pnpm build

- name: Run tests
run: pnpm test

build-demo:
runs-on: ubuntu-latest
needs: build-and-test

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10

- name: Setup Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build packages
run: pnpm build

- name: Build demo app
run: pnpm --filter @raygate/demo build
env:
NEXT_PUBLIC_USER_WALLET: "0x0000000000000000000000000000000000000000"
NEXT_PUBLIC_MERCHANT_WALLET: "0x0000000000000000000000000000000000000000"
NEXT_PUBLIC_FACILITATOR_WALLET: "0x0000000000000000000000000000000000000000"
NEXT_PUBLIC_USDC_ADDRESS: "0x007Fdc86FD12924C9116025C7F594843087397E3"
NEXT_PUBLIC_BEAM_TOKEN_ADDRESS: "0xF65B6f9c94187276C7d91F4F74134751d248bFeA"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules/
dist/
.next/
.env
.env.local
*.tsbuildinfo
Expand Down
76 changes: 61 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,51 @@ Client (AI Agent / Browser) Resource Server Raygate Facili

## Why Permit2, Not EIP-3009

The standard x402 EVM scheme uses `transferWithAuthorization` (EIP-3009), which USDC on Base natively supports. But Beam uses LayerZero-bridged USDC an OFT wrapper that doesn't implement EIP-3009.
The standard x402 EVM scheme uses `transferWithAuthorization` (EIP-3009), which USDC on Base natively supports. But Beam uses LayerZero-bridged USDC an OFT wrapper that doesn't implement EIP-3009.

[Permit2](https://github.com/Uniswap/permit2) (Uniswap's universal token allowance contract) solves this cleanly:

- Works with **any ERC-20** USDC, BEAM token, any future Beam ecosystem token
- **Already deployed** on Beam at `0x000000000022D473030F116dDEE9F6B43aC78BA3`
- **No contract deployment** required
- Works with **any ERC-20** — USDC, WBEAM (WMC), USDT, or any future Beam ecosystem token
- **Deployed by Raygate** at the canonical address [`0x000000000022D473030F116dDEE9F6B43aC78BA3`](https://subnets-test.avax.network/beam/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) on Beam testnet via deterministic CREATE2
- Nonce bitmap provides **on-chain replay protection**
- One-time `approve(Permit2, maxUint256)` per token, then every payment is a pure off-chain signature
- Facilitator pays gas — payers never submit transactions

### Permit2 on Beam

Permit2 was not previously deployed on Beam. As part of building Raygate, we deployed it on Beam testnet using the deterministic CREATE2 factory (`0x4e59b44847b379578588920cA78FbF26c0B4956C`) that was already present on the chain. By replaying the exact deployment calldata used on Ethereum and Avalanche C-Chain, Permit2 now lives at the same canonical address on Beam testnet — meaning any tooling or SDK that works with Permit2 on other chains works identically on Beam.

View the deployed contract: [Permit2 on Beam Testnet Explorer](https://subnets-test.avax.network/beam/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)

Mainnet deployment will follow before production launch.

### What's Next: User as `from` via ERC-4337

Today, the facilitator wallet appears as `from` on every settlement transaction — matching the standard x402 design used by Coinbase on Base and other chains. But Beam already has ERC-4337 infrastructure deployed (EntryPoint + Paymaster), which opens a path to a stronger model.

In the production Go implementation, we plan to integrate ERC-4337 Account Abstraction so that **the user appears as `from`** on each settlement while the **facilitator's Paymaster sponsors the gas**. This means:

- On-chain history shows which users are calling which APIs — a richer activity graph
- The facilitator still covers gas, preserving the gasless payer experience
- Beam gets a genuinely unique x402 implementation that no other chain's facilitator has

This is documented in the [Raygate Whitepaper](./WHITEPAPER.md) as a Phase 2 milestone.

### Token Approval (One-Time Setup)

Before Permit2 can transfer tokens on a payer's behalf, the payer must approve Permit2 to spend each token. This is a **one-time on-chain transaction** per token per wallet:

```typescript
import { approvePermit2 } from "@raygate/client";

// Approve Permit2 for USDC (one-time)
await approvePermit2(walletClient, publicClient, USDC_ADDRESS);

// Approve Permit2 for WBEAM (one-time)
await approvePermit2(walletClient, publicClient, WBEAM_ADDRESS);
```

After approval, every subsequent payment is a gasless off-chain EIP-712 signature.

## Packages

Expand All @@ -84,7 +120,7 @@ The standard x402 EVM scheme uses `transferWithAuthorization` (EIP-3009), which
| [`@raygate/core`](./packages/core) | Shared types, Permit2 ABI, Beam chain definitions, encoding utilities |
| [`@raygate/facilitator`](./packages/facilitator) | Standalone Express service `/verify`, `/settle`, `/capabilities`, `/health` |
| [`@raygate/express`](./packages/express) | One-line Express middleware for gating any route with x402 payments |
| [`@raygate/client`](./packages/client) | Client library `beamFetch()` handles the full 402 → sign → retry loop |
| [`@raygate/client`](./packages/client) | Client library `beamFetch()` for the 402 → sign → retry loop, `approvePermit2()` for one-time token approval |

## Quick Start

Expand Down Expand Up @@ -184,7 +220,10 @@ raygate/
│ ├── express/ # Drop-in payment middleware
│ └── client/ # beamFetch() + Permit2 signing
├── apps/
│ └── demo/ # Demo application (coming soon)
│ └── demo/ # Interactive demo (Next.js) — live on Beam testnet
├── scripts/
│ ├── deploy-permit2.ts # Deploy Permit2 on Beam via deterministic CREATE2
│ └── permit2-calldata.hex
├── pnpm-workspace.yaml
└── vitest.config.ts
```
Expand Down Expand Up @@ -214,19 +253,22 @@ pnpm --filter @raygate/facilitator dev
| Phase | Milestone | Status |
|---|---|---|
| 1 | Core packages + facilitator + middleware + client | Done |
| 2 | Demo application (gated Beam API + frontend) | Next |
| 3 | npm publish + documentation + demo video | Planned |
| 4 | Beam Foundation pitch + x402.org ecosystem listing | Planned |
| 5 | Go production implementation | Planned |
| 2 | Deploy Permit2 on Beam testnet | Done |
| 3 | Demo application (playground + docs) | Done |
| 4 | npm publish + documentation + demo video | Planned |
| 5 | Beam Foundation pitch + x402.org ecosystem listing | Planned |
| 6 | Go production implementation | Planned |

## Token Support

| Token | Decimals | Example Price | Amount String |
|---|---|---|---|
| USDC (LayerZero-bridged) | 6 | $0.10 | `"100000"` |
| BEAM | 18 | 0.001 BEAM | `"1000000000000000"` |
| Token | Symbol | Decimals | Testnet Address | Mainnet Address |
|---|---|---|---|---|
| USD Coin | USDC | 6 | `0x007Fdc86FD12924C9116025C7F594843087397E3` | `0x76BF5E7d2Bcb06b1444C0a2742780051D8D0E304` |
| Wrapped BEAM | WBEAM (WMC) | 18 | `0xF65B6f9c94187276C7d91F4F74134751d248bFeA` | `0xD51BFa777609213A653a2CD067c9A0132a2D316A` |

**Note:** BEAM is the native gas token (like ETH). WBEAM is the ERC-20 wrapped version (on-chain symbol: `WMC` — Wrapped Merit Circle, from Beam's original name). Permit2 requires ERC-20 tokens, so payments in BEAM use the WBEAM wrapper.

Additional Beam ecosystem ERC-20 tokens can be supported without code changes Permit2 works with any ERC-20.
Additional Beam ecosystem ERC-20 tokens can be supported without code changes Permit2 works with any ERC-20.

## Security

Expand All @@ -236,6 +278,10 @@ Additional Beam ecosystem ERC-20 tokens can be supported without code changes Pe
- **Deadlines** are enforced both off-chain (30s grace for clock skew) and on-chain by the Permit2 contract
- **Rate limiting** on `/settle` prevents gas-draining attacks

## Whitepaper

For the full technical story — the problem, the Permit2 decision, architecture, security model, what we learned deploying on Beam, and the roadmap including ERC-4337 integration — read the [Raygate Whitepaper](./WHITEPAPER.md).

## Contributing

Contributions are welcome. Please open an issue first to discuss what you'd like to change.
Expand Down
Loading
Loading