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
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,4 @@ dist
.env.example


.claude
.CLAUDE.md
.claude
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Project Overview

PolyPay is a privacy-preserving payroll platform built on Horizen blockchain. It enables organizations, DAOs, and global teams to run payroll privately using zero-knowledge proofs (Noir circuits). Key features: private payments, private multisig approvals, escrow/milestone-based transfers, real-time notifications via WebSocket, and JWT authentication.
PolyPay is a privacy-preserving payroll & multisig platform built on Horizen (primary), Base, and Arbitrum (Stylus). It enables organizations, DAOs, and global teams to run payroll while keeping signer identities private, using zero-knowledge proofs (Noir circuits). Key shipped features: private multisig approvals (signer identities hidden — only a relayer appears on-chain), ZK authentication, single + batch transfers, gasless USDC deposits (x402), real-time notifications via WebSocket, and JWT auth. Note: confidential payment **amounts and recipients** ("private payments") are roadmap, **not yet implemented** — today's privacy covers signer identities only.

## Tech Stack

Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ A privacy-preserving payroll platform built on Horizen. PolyPay enables organiza

## Features

- **Private Payments**: Salary amounts and recipients stay confidential
- **Private Multisig**: Team approvals without exposing signer identities
- **Flexible Payment Logic**: Escrow, milestone-based, and recurring transfers
- **Private Multisig**: Team approvals without exposing signer identities — only a relayer wallet appears on-chain
- **ZK Authentication**: Login via zero-knowledge proof; secrets never leave your device
- **Batch Payroll**: Single and batch transfers

> **Roadmap:** Confidential payment amounts and recipients ("private payments") are in active development — not yet live. Today's privacy guarantee covers signer identities.

## Quick Start

Expand Down
54 changes: 54 additions & 0 deletions REVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# PolyPay PR Review Checklist

Shared standard for reviewing pull requests — used by human reviewers and by the
automated review in `.github/workflows/claude-review.yaml`. Read `CLAUDE.md` for
the repo's architecture and conventions.

## Priority order

Report findings in this order; correctness always outranks cleanup.

1. **Correctness bugs**
2. **Security**
3. **Convention adherence** (`CLAUDE.md`)
4. **Reuse / simplification / efficiency cleanup**

## Security

PolyPay moves USDC and handles multisig approvals, ZK proofs, and auth. Scrutinise
any change that touches:

- **Signatures & payments** — EIP-3009 / x402 payloads, signature assembly and
the `v` recovery byte, nonce generation and replay protection, amount bounds.
- **Key material** — anything that could log, leak, or widen access to private
keys, tokens, or OWS vault flows.
- **Auth & access control** — JWT handling, route guards, `useAuthenticatedQuery`,
multisig signer/relayer logic, ZK proof verification.
- **Secrets** — never approve committed secrets, hardcoded credentials, or
plaintext keys. Secret Manager bindings only (see `CLAUDE.md` deployment notes).

## Convention adherence (see `CLAUDE.md`)

- API contracts via `@polypay/shared` DTOs; all HTTP through `apiClient`.
- Zod schemas for every form; `useAuthenticatedQuery` for authenticated queries.
- Business logic in `hooks/app/`, not components; no hardcoded API URLs
(use `API_BASE_URL`).
- Notifications via the existing `notification` utility / Sonner.
- Zustand stores use `persist` unless state is truly ephemeral.

## Correctness

Bugs a careful reviewer catches in one sitting:

- Inverted or wrong conditions, off-by-one on boundaries.
- Missing `await`, unhandled promise rejections.
- Null / undefined dereferences on reachable paths (error handlers, cold cache,
missing optional fields), falsy-zero treated as missing.
- Errors swallowed in `catch`, copy-paste of the wrong variable.
- Call sites broken by a changed signature, return shape, or new precondition.

## Output

- Top-level summary via `gh pr comment`; line-level issues as inline comments.
- Cite `file:line`. Be specific and actionable.
- If the diff is clean, say so briefly — do not invent issues.
10 changes: 6 additions & 4 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ This lack of privacy prevents businesses from adopting crypto payroll.

### Our Solution

PolyPay uses **zero-knowledge proofs** and **multi-chain deployment** (Horizen and Base) to provide:
PolyPay uses **zero-knowledge proofs** and **multi-chain deployment** (Horizen, Base, and Arbitrum) to provide:

* **Private Payments**: Salary amounts and recipients stay confidential
* **Private Multisig**: Team approvals without exposing signer identities
* **Private Multisig**: Team approvals without exposing signer identities — only a relayer wallet appears on-chain
* **ZK Authentication**: Login via zero-knowledge proof; secrets never leave your device
* **Flexible Payment Logic**: Single and batch transfers
* **Gasless USDC Deposit (x402)**: Fund a multisig on Base with one signature, no ETH required — works for human users and AI agents via the [x402 protocol](https://www.x402.org/)
* **Gasless USDC Deposit (x402)**: Fund a multisig with one signature, no ETH required — works for human users and AI agents via the [x402 protocol](https://www.x402.org/)

> **On the roadmap:** confidential payment amounts and recipients ("private payments"). Today's ZK privacy covers signer identities and authentication; we're actively building toward fully private transfers.

### Who Is It For?

Expand Down
5 changes: 5 additions & 0 deletions usdc-deposit-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
.env
dist/
# Rendered policy contains your multisig + caps; keep the template, ignore the rendered copy.
policy/arbitrum-usdc-deposit.policy.json
139 changes: 139 additions & 0 deletions usdc-deposit-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# PolyPay USDC Deposit Agent — Arbitrum One (OWS-secured)

An autonomous agent that funds an **already-created PolyPay multisig** with USDC on
**Arbitrum One**, gaslessly, via PolyPay's **x402** deposit flow — while keeping the
signing key out of the agent process entirely.

```
agent process ──spawn──▶ OWS signer subprocess ──▶ OWS policy engine ──▶ vault key
(holds token) (holds token) (allow/deny) (~/.ows, encrypted)
│ │
└────────── builds x402 payload, POSTs to PolyPay ◀── signature ────────────┘
```

## Why this design

- **Not Coinbase `awal` / agentic wallet** — it only supports Base/Polygon/Solana, **not Arbitrum**.
- **Not a raw `PRIVATE_KEY` in `.env`** — that key would sit in the agent's heap, reachable by any
tool it runs and one prompt-injection away from signing an attacker's transfer.
- **OWS ([Open Wallet Standard](https://github.com/open-wallet-standard/core))** — key encrypted at
rest (AES-256-GCM / scrypt), the agent holds only a **scoped token**, and a **policy engine**
decides what may be signed *before* the key is ever decrypted.

### What the policy guarantees

Even if this agent is fully compromised or prompt-injected, the scoped token can **only** produce:

- an **EIP-3009 `TransferWithAuthorization`** (not arbitrary transactions),
- for **USDC** (`0xaf88…5831`) — no other token,
- on **Arbitrum One** (`eip155:42161`) — no other chain,
- to **your multisig** — no other recipient,
- **≤ your cap** per signature, until the token **expires**.

It cannot drain the wallet or redirect funds. (`policy/check-deposit.mjs` enforces this; chain +
expiry are also enforced declaratively by OWS.)

## Prerequisites

- Node.js ≥ 20.18.3
- The OWS CLI: `curl -fsSL https://docs.openwallet.sh/install.sh | bash`
- An EOA **funded with USDC on Arbitrum One** (the facilitator sponsors gas; the agent spends USDC).
- The target PolyPay multisig address (must be an **Arbitrum One** multisig).

## Setup

```bash
cd usdc-deposit-agent
npm install # or: yarn
```

### 1. Import your funded key into the OWS vault (one time)

> ⚠️ Run this yourself in your terminal. **Never paste the private key into a chat or commit it.**
> It is read from stdin and encrypted into `~/.ows` — it never enters this project or the agent.

```bash
# you will paste the key at the prompt
ows wallet import --name agent-treasury --private-key
# (or import a mnemonic: ows wallet import --name agent-treasury --mnemonic)
```

Confirm the imported address: `ows wallet list`.

### 2. Render the policy + mint the scoped token

```bash
MULTISIG_ADDRESS=0xYourArbitrumMultisig MAX_USDC=100 EXPIRES_DAYS=30 ./setup.sh
```

This renders the policy for *your* multisig and cap, registers it, and prints a one-time
`ows_key_...` token.

### 3. Configure `.env`

```bash
cp .env.example .env
# set OWS_API_KEY (the ows_key_... token), OWS_WALLET, MULTISIG_ADDRESS, AMOUNT_USDC
```

## Verify the policy (no funds, no setup)

```bash
npm run test:policy
```

Spins up an isolated temp vault and proves the gate: a correct deposit signs, while a wrong
recipient, an over-cap amount, and the wrong chain are all denied.

## Run

```bash
npm run deposit # or: yarn deposit
```

Output:

```
→ PolyPay USDC deposit agent (Arbitrum One, gasless x402)
agent wallet : 0x…
multisig : 0x…
amount : 1 USDC (1000000 base units)
signing : via OWS vault (policy-gated)…
submitting : POST x402 deposit…
✓ Deposit settled
tx hash : 0x…
```

## How it works (flow)

1. **Discover** — `GET /api/x402/deposit/:multisig` returns `402` with the payment requirements
(asset, `payTo`, network `arbitrum`, min/max, EIP-712 domain).
2. **Build** — assembles the EIP-3009 `TransferWithAuthorization` typed data (byte-compatible with
PolyPay's frontend `eip3009.ts`).
3. **Sign** — spawns `signer/ows-signer.mjs`, which calls OWS `signTypedData`. The policy engine
evaluates the typed data; only then is the key decrypted, used, and wiped. The agent's main
process never imports the OWS SDK and never sees the key.
4. **Submit** — base64-encodes the x402 v1 payload, `POST`s it with the `X-PAYMENT` header. PolyPay
settles via the Coinbase CDP facilitator (gasless). Returns `principalTxHash`.

## Operations

- **Rotate / revoke** the agent: `ows key revoke --id <key-id> --confirm` (find it via `ows key list`).
The token instantly becomes useless; the wallet is untouched.
- **Change the cap or expiry**: re-run `setup.sh` with new values, then mint a fresh token.
- **Audit**: every signing request is logged to `~/.ows/logs/audit.jsonl`.

## Troubleshooting

| Symptom | Cause / fix |
|---|---|
| `OWS signer exited … POLICY_DENIED` | The request violated the policy (wrong recipient/amount/chain/expired). Check the deny reason. |
| `Expected HTTP 402 … got 4xx` | Wrong multisig address, or it isn't an Arbitrum One x402 multisig. |
| `Insufficient USDC` | Fund the agent EOA with USDC on Arbitrum One. |
| `Signature check failed` | Domain name/version mismatch — set `ARBITRUM_RPC_URL` so the agent reads them on-chain. |

## Hardening (optional, Layer 2)

This is Layer 1 (single host, key isolated behind a subprocess + policy). To fully isolate the
signer, run OWS in its own container exposing signing over a local socket, with the agent in a
separate container holding only `OWS_API_KEY` — the "two containers, one socket" KMS pattern.
Loading
Loading