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
1 change: 1 addition & 0 deletions .github/workflows/frontend-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ jobs:
--platform linux/amd64 \
--build-arg NEXT_PUBLIC_API_URL=${{ vars.NEXT_PUBLIC_API_URL }} \
--build-arg NEXT_PUBLIC_NETWORK=${{ vars.NEXT_PUBLIC_NETWORK }} \
--build-arg NEXT_PUBLIC_FEATURE_X402_DEPOSIT=${{ vars.NEXT_PUBLIC_FEATURE_X402_DEPOSIT }} \
-f docker/frontend.Dockerfile \
-t ${GCP_LOCATION}-docker.pkg.dev/${GCP_PROJECT_ID}/${GCP_REPOSITORY}/${GCP_IMAGE}:${SHORT_SHA} \
.
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/frontend-staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ jobs:
--platform linux/amd64 \
--build-arg NEXT_PUBLIC_API_URL=${{ vars.NEXT_PUBLIC_API_URL }} \
--build-arg NEXT_PUBLIC_NETWORK=${{ vars.NEXT_PUBLIC_NETWORK }} \
--build-arg NEXT_PUBLIC_FEATURE_X402_DEPOSIT=${{ vars.NEXT_PUBLIC_FEATURE_X402_DEPOSIT }} \
-f docker/frontend.Dockerfile \
-t ${GCP_LOCATION}-docker.pkg.dev/${GCP_PROJECT_ID}/${GCP_REPOSITORY}/${GCP_IMAGE}:${SHORT_SHA} \
.
Expand Down
1 change: 1 addition & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ services:
args:
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:4000}
NEXT_PUBLIC_NETWORK: ${NEXT_PUBLIC_NETWORK:-testnet}
NEXT_PUBLIC_FEATURE_X402_DEPOSIT: ${NEXT_PUBLIC_FEATURE_X402_DEPOSIT:-false}
environment:
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:4000}
depends_on:
Expand Down
2 changes: 2 additions & 0 deletions docker/frontend.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@ COPY packages/nextjs ./packages/nextjs
# Build arguments
ARG NEXT_PUBLIC_API_URL
ARG NEXT_PUBLIC_NETWORK
ARG NEXT_PUBLIC_FEATURE_X402_DEPOSIT
ARG STANDALONE=true

ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_NETWORK=$NEXT_PUBLIC_NETWORK
ENV NEXT_PUBLIC_FEATURE_X402_DEPOSIT=$NEXT_PUBLIC_FEATURE_X402_DEPOSIT
ENV STANDALONE=$STANDALONE

# Build shared and frontend packages
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ PolyPay uses **zero-knowledge proofs** and **multi-chain deployment** (Horizen a
* **Private Payments**: Salary amounts and recipients stay confidential
* **Private Multisig**: Team approvals without exposing signer identities
* **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/)

### Who Is It For?

Expand All @@ -40,3 +41,4 @@ PolyPay uses **zero-knowledge proofs** and **multi-chain deployment** (Horizen a
* Fiat on/off-ramp integration
* Escrow and milestone-based payments
* Recurring transfers
* Expand x402 to additional networks and tokens beyond USDC on Base
4 changes: 3 additions & 1 deletion docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

- [Introduction](README.md)
- [How to use Polypay App](how-to-use-polipay-app.md)
- [Quest & Leaderboard](quest-and-leaderboard.md)
<!-- Quest & Leaderboard temporarily hidden — kept for future reuse. -->
<!-- - [Quest & Leaderboard](quest-and-leaderboard.md) -->
- [Privacy Architecture](privacy-architecture.md)
- [Zero-Knowledge Implementation](zero-knowledge-implementation.md)
- [ZK Authentication](zk-authentication.md)
- [zkVerify, Horizen & Base Integration](zkverify-horizen-integration.md)
- [Gasless USDC Deposits (x402)](x402-deposits.md)
- [Architecture](architecture.md)
- [Developer Documentation](developer-documentation/README.md)
- [Getting Started](developer-documentation/getting-started.md)
Expand Down
31 changes: 31 additions & 0 deletions docs/adr/001-dynamic-import-aztec-bb-js.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ADR-001: Dynamic import for @aztec/bb.js

## Status

Accepted

## Context

`@aztec/bb.js` is a heavy WASM-based cryptographic library used for ZK proof generation (UltraHonk/UltraPlonk backends). It is only needed when users perform specific actions: signing transactions, approving votes, or authenticating with ZK proofs.

If imported statically (`import { UltraHonkBackend } from '@aztec/bb.js'`), Next.js bundles the entire library into the initial JS chunk. This significantly increases the first load JS size for every page, even pages that never generate proofs.

## Decision

Use dynamic `import()` for `@aztec/bb.js` (and `@noir-lang/noir_js`) at the point of use inside proof generation hooks:

```typescript
// Instead of top-level static import
const { UltraHonkBackend } = await import("@aztec/bb.js");
const { Noir } = await import("@noir-lang/noir_js");
```

This applies to:
- `packages/nextjs/hooks/app/useGenerateProof.ts`
- `packages/nextjs/hooks/app/useAuthProof.ts`

## Consequences

- First load JS stays small — cryptographic libraries are only fetched when a user initiates a proof generation action
- Slight delay when generating the first proof in a session (library download + WASM initialization), but this is acceptable since proof generation itself already takes seconds
- Must use `await import()` pattern consistently — never add static imports for these packages in frontend code
57 changes: 57 additions & 0 deletions docs/adr/002-ultrahonk-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# ADR-002: UltraPlonk to UltraHonk Migration

## Status

Accepted

## Context

UltraPlonk is deprecated from bbup v0.87.0+. zkVerify supports UltraHonk as replacement. Migration needed across the full stack: frontend proof generation, backend proof submission (Kurier API), and smart contract verification.

## Decision

New accounts (contractVersion >= 2) use UltraHonk. Old accounts (contractVersion 1) continue using UltraPlonk. The `contractVersion` field on the Account model drives all branching.

## Key Findings (not obvious from docs)

### 1. `{ keccak: true }` is mandatory

zkVerify only supports Keccak256 hash for UltraHonk. bb.js defaults to non-keccak. Must pass `{ keccak: true }` to both `generateProof` and `getVerificationKey`:

```typescript
const backend = new UltraHonkBackend(bytecode);
const { proof, publicInputs } = await backend.generateProof(witness, { keccak: true });
const vk = await backend.getVerificationKey({ keccak: true });
```

Without keccak, the VK size is 1825 bytes instead of expected 1760 bytes, and proofs won't verify.

### 2. Variant must be `'Plain'`, not `'ZK'`

bb.js `acirProveUltraKeccakHonk` generates **Plain** (non-ZK) proofs. There is no `--zk` option in the bb.js API. Using `variant: 'ZK'` causes proof verification to fail silently (`optimisticVerify: "failed"`).

### 3. Kurier API format differs completely from UltraPlonk

| Aspect | UltraPlonk | UltraHonk |
|--------|-----------|----------|
| VK encoding | base64 | hex `0x`-prefixed |
| Proof encoding | base64 (public inputs concatenated) | hex `0x`-prefixed |
| Public inputs | Concatenated into proof bytes | Separate `publicSignals` array |
| proofOptions (register-vk) | `{ numberOfPublicInputs }` | `{ variant: 'Plain' }` |
| proofOptions (submit-proof) | `{ numberOfPublicInputs }` | `{ variant: 'Plain' }` |

### 4. VK file naming includes proof type

VK files use the pattern `vkey-{circuitType}-{proofType}.json` to support both systems simultaneously:
- `vkey-transaction-ultraplonk.json` (old accounts)
- `vkey-transaction-ultrahonk.json` (new accounts)

### 5. Smart contract vkHash must match

The `vkHash` in `contracts-config.ts` is used when deploying new multisig contracts. It must match the vkHash from the UltraHonk VK registration on zkVerify. Old deployed contracts are unaffected (vkHash is immutable).

## Consequences

- Both proving systems coexist — no breaking changes for existing accounts
- `contractVersion` must be included in ALL API responses that return account data (including `user.service.ts getAccounts`)
- Future bb.js versions may add ZK support — at that point, switch variant from `'Plain'` to `'ZK'` for better privacy
25 changes: 24 additions & 1 deletion docs/developer-documentation/api-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

PolyPay provides a comprehensive RESTful API for privacy-preserving payroll operations. This guide covers how to interact with the API using various methods.

> The user identifier shown in the PolyPay UI as **Membership ID** is sent and received over the API as the JSON field `commitment`. The field name is preserved for backwards compatibility.

## Quick Links

- **Swagger UI (Interactive)**: `http://localhost:4000/api/swagger`
Expand Down Expand Up @@ -182,6 +184,8 @@ http://localhost:4000/api
|--------|----------|-------------|---------------|
| POST | `/feature-requests` | Submit feature request | Yes |

<!-- Quest, Leaderboard & Claim endpoints temporarily hidden — kept for future reuse. -->
<!--
#### Quests (`/api/quests`)

| Method | Endpoint | Description | Auth Required |
Expand All @@ -198,6 +202,17 @@ http://localhost:4000/api
|--------|----------|-------------|---------------|
| GET | `/claims/summary` | Get claim summary for all weeks | Yes |
| POST | `/claims` | Claim weekly reward | Yes |
-->

#### x402 Gasless USDC Deposit (`/api/x402`)

| Method | Endpoint | Description | Auth Required |
|--------|----------|-------------|---------------|
| GET | `/x402/deposit/:multisigAddress` | Returns HTTP 402 with x402 v1 payment requirements for the multisig | No |
| POST | `/x402/deposit/:multisigAddress` | Settles an EIP-3009 USDC deposit through an x402 facilitator. Requires `X-PAYMENT` header with a base64 x402 v1 payment payload. | No |

See [Gasless USDC Deposits (x402)](../x402-deposits.md) for the full integration guide, request/response shapes, supported networks, limits, and security model.

## Authentication

PolyPay uses JWT (JSON Web Tokens) for authentication.
Expand Down Expand Up @@ -281,14 +296,22 @@ Content-Type: application/json
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request data |
| 401 | Unauthorized | Missing or invalid authentication |
| 402 | Payment Required | x402 discovery response — caller must include a valid `X-PAYMENT` header to retry |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Resource conflict (duplicate) |
| 429 | Too Many Requests | Rate limit exceeded (per IP or per multisig) |
| 500 | Internal Server Error | Server error |

## Rate Limiting

Currently, there is no rate limiting on the API. This may be added in future versions.
Most endpoints are not rate limited. The x402 deposit endpoint applies the following caps:

| Scope | Limit |
|-------|-------|
| `GET /api/x402/deposit/:multisigAddress` per IP | 60 requests / 60s |
| `POST /api/x402/deposit/:multisigAddress` per IP | 10 requests / 60s |
| `POST /api/x402/deposit/:multisigAddress` per multisig | 30 requests / 60s |

## CORS Configuration

Expand Down
14 changes: 8 additions & 6 deletions docs/developer-documentation/circuit-code-walkthrough.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

This page provides a detailed explanation of the [Noir](https://noir-lang.org) circuit used in PolyPay for generating zero-knowledge proofs.

> The user identifier shown in the PolyPay UI as **Membership ID** is the same value referred to in code, contracts, and circuits as `commitment`. Circuit input names below preserve `commitment` for accuracy.

## Overview

The circuit file is located at `packages/nextjs/public/circuit/src/main.nr`. It proves four things in a single proof: transaction hash commitment is correct, ECDSA signature is valid, prover knows the secret for their commitment, and nullifier prevents double-signing.
The circuit file is located at `packages/nextjs/public/circuit/src/main.nr`. It proves four things in a single proof: transaction hash commitment is correct, ECDSA signature is valid, prover knows the secret for their membership ID, and nullifier prevents double-signing.

## Circuit Structure

Expand Down Expand Up @@ -33,7 +35,7 @@ These inputs are visible on-chain and used for verification:
| Input | Type | Description |
|-------|------|-------------|
| tx_hash_commitment | Field | Poseidon hash of tx_hash |
| commitment | Field | hash(secret, secret) - checked against signers list |
| commitment | Field | hash(secret, secret) - the user's membership ID, checked against signers list |
| nullifier | Field | Unique identifier to prevent double-signing |

## Step-by-Step Explanation
Expand All @@ -50,11 +52,11 @@ The circuit reconstructs Ethereum's `personal_sign` prefix `"\x19Ethereum Signed

**Why prefix?** Ethereum wallets always add this prefix when signing. We must match the exact message that was signed.

### Step 3: Verify Commitment Ownership
### Step 3: Verify Membership ID Ownership

The circuit computes commitment from secret using `commitment = hash(secret, secret)`, then compares with public `commitment`.
The circuit computes the membership ID from secret using `commitment = hash(secret, secret)`, then compares with the public `commitment` input.

**How authorization works:** The circuit proves "I know the secret for this commitment". Then the smart contract checks "Is this commitment in the signers list?" This two-step verification ensures only authorized signers can sign transactions.
**How authorization works:** The circuit proves "I know the secret for this membership ID". Then the smart contract checks "Is this membership ID in the signers list?" This two-step verification ensures only authorized signers can sign transactions.

### Step 4: Verify Nullifier

Expand All @@ -77,7 +79,7 @@ Wrapper for [Poseidon](https://www.poseidon-hash.info) hash with 2 inputs.
| Attack | Prevention |
|--------|------------|
| Fake signature | ECDSA verification in circuit |
| Non-member signing | Commitment checked against signers list on-chain |
| Non-member signing | Membership ID checked against signers list on-chain |
| Double signing | Nullifier stored on-chain |
| Transaction tampering | tx_hash_commitment verification |
| Replay attack | Nonce included in tx_hash |
Expand Down
2 changes: 1 addition & 1 deletion docs/developer-documentation/database-connection-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ PolyPay uses **PostgreSQL 16** as its database, managed by **Prisma ORM**.

The database contains 12 tables:

- `users` - User accounts with commitment-based identity
- `users` - User accounts with membership-ID-based identity (the database column is named `commitment` for backwards compatibility)
- `accounts` - Multi-signature accounts
- `account_signers` - Many-to-many relationship between users and accounts
- `transactions` - Transaction records (TRANSFER, BATCH, ADD_SIGNER, etc.)
Expand Down
16 changes: 10 additions & 6 deletions docs/developer-documentation/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,16 @@ yarn start:frontend

### Environment Variables

| Variable | Description |
| -------------------------- | ------------------------------ |
| `DATABASE_URL` | PostgreSQL connection string |
| `RELAYER_WALLET_KEY` | Private key for relayer wallet |
| `RELAYER_ZKVERIFY_API_KEY` | API key from zkVerify |
| `NEXT_PUBLIC_API_URL` | Backend API URL |
| Variable | Description |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `DATABASE_URL` | PostgreSQL connection string |
| `RELAYER_WALLET_KEY` | Private key for relayer wallet |
| `RELAYER_ZKVERIFY_API_KEY` | API key from zkVerify |
| `NEXT_PUBLIC_API_URL` | Backend API URL |
| `FEATURE_X402_DEPOSIT` | `true` to enable the x402 gasless deposit endpoint and UI (default `false`) |
| `X402_FACILITATOR_URL` | x402 facilitator base URL — `https://facilitator.payai.network` (default, supports Base mainnet + Sepolia, no API key) |
| `X402_FACILITATOR_BEARER_TOKEN` | Optional bearer token; leave empty for facilitators that do not require auth (e.g. PayAI) |
| `NEXT_PUBLIC_FEATURE_X402_DEPOSIT`| Frontend flag to render the Deposit button in the Portfolio panel (matches backend flag) |


### User Workflow
Expand Down
16 changes: 11 additions & 5 deletions docs/how-to-use-polipay-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ Polypay app beta v0.1 is an initial release of the Polypay platform. It is a web
- Create and manage a multisig account
- Hide signers(multisig account owners) identities with ZK proofs
- Execute payroll payments: Transfer funds to multiple recipients
- **Deposit USDC into a multisig gaslessly via x402 (Base only)**
- View transaction history
- View multisig account balance
- View multisig account address
- Switch between networks from the sidebar
- Earn points through Quest system
- Compete on weekly Leaderboard
- Claim ZEN token rewards
<!-- Quest & Leaderboard temporarily hidden — kept for future reuse. -->
<!-- - Earn points through Quest system -->
<!-- - Compete on weekly Leaderboard -->
<!-- - Claim ZEN token rewards -->

For details, see [Quest & Leaderboard](quest-and-leaderboard.md).
<!-- For details, see [Quest & Leaderboard](quest-and-leaderboard.md). -->

### User Workflow

Expand All @@ -42,7 +44,7 @@ Overview of your multisig account including balance, pending transactions, and s

#### Create New Account

Create a new multisig account by adding signer commitments and setting the threshold.
Create a new multisig account by adding signer membership IDs and setting the threshold.

![Create New Account](.gitbook/assets/new_account.png)

Expand All @@ -51,3 +53,7 @@ Create a new multisig account by adding signer commitments and setting the thres
Send funds to single or multiple recipients with ZK proof generation.

![Transfer](.gitbook/assets/transfer.png)

### Gasless USDC Deposit (x402)

Open the Portfolio panel of a Base multisig and click **Deposit** to fund the multisig with USDC in a single signature, no ETH required. The flow uses the [x402 protocol](https://www.x402.org/) and works for human users and AI agents alike. See [Gasless USDC Deposits (x402)](x402-deposits.md) for the full guide and developer integration.
Loading
Loading