Skip to content

BlocPaie/frontend

Repository files navigation

BlocPaie Frontend

Next.js dApp for the BlocPaie confidential payroll platform. Two dashboards — one for companies managing payroll, one for contractors collecting salary — connected to on-chain vaults via Porto passkeys and gas-sponsored transactions.

About BlocPaie

BlocPaie is an invoice-to-payment platform where companies create on-chain payroll vaults and pay contractors on Ethereum — while every salary amount, payee identity, and payment status stays fully encrypted on-chain via Zama's Fully Homomorphic Encryption. Nobody on-chain, including the contract itself, can read salary values in plaintext.

The blockchain acts as a verifiability layer — every payment registration, execution, and cancellation is permanently recorded on-chain with a cryptographic commitment, without exposing the underlying amounts or identities. This makes payroll auditable by regulatory authorities without compromising employee privacy.

Architecture Note

The backend stores plaintext invoice metadata — amounts, contractor names, transaction history — so dashboards load instantly without requiring on-chain decryption on every page view. Decryption via Zama's KMS involves on-chain ACL grants and KMS round-trips; caching the results off-chain keeps the UX responsive.

The on-chain contracts never see plaintext. All sensitive values are FHE-encrypted client-side in the browser before any transaction is submitted. The backend is a UX layer — the vault contracts are the source of truth for all payments.

Known Limitations

Ephemeral decrypt keypair — Zama's KMS validates userDecrypt requests via secp256k1 ECDSA signatures. Porto wallets use WebAuthn P-256 passkeys, which the KMS cannot verify directly. As a workaround, BlocPaie generates a short-lived secp256k1 keypair (decryptViewer) in the browser on every decrypt, grants it ACL access on-chain, uses it to sign the KMS request, then discards it. This costs an extra on-chain transaction and briefly materialises a secp256k1 private key in browser memory. This workaround is eliminated if Zama's KMS adds EIP-1271 support — allowing decryption to be authorised via isValidSignature on any smart contract wallet directly.

Stack

  • Framework: Next.js 16 (App Router), React 19
  • Blockchain: Wagmi 3, viem 2, Porto SDK (EIP-7702)
  • FHE: @zama-fhe/relayer-sdk for client-side encryption and decryption
  • Wallet: Porto passkeys (WebAuthn) — no MetaMask required
  • Gas: Ithaca Relay merchant route — all transactions sponsored
  • Styling: Inline CSS (no Tailwind), Lucide icons

Prerequisites

Porto requires HTTPS. Local dev must run over https://localhost:3000. The dev script uses next dev --experimental-https which self-signs a certificate.

Getting Started

1. Install dependencies

npm install

2. Set environment variables

cp .env.example .env.local
Variable Description
NEXT_PUBLIC_API_URL Backend API base URL (e.g. https://blocpaie.onrender.com)
NEXT_PUBLIC_RPC_URL Ethereum Sepolia RPC URL
NEXT_PUBLIC_MERCHANT_URL Full URL to the merchant route (e.g. https://your-ngrok-url/api/porto/merchant). Must be publicly accessible — Porto SDK calls this from the browser.
MERCHANT_ADDRESS Porto merchant account address (used server-side in /api/porto/merchant)
MERCHANT_PRIVATE_KEY Porto merchant private key (server-side only)
JWT_SECRET Same secret as backend — used to verify JWTs in API routes

NEXT_PUBLIC_MERCHANT_URL must be a public HTTPS URL. Porto's iframe calls this endpoint from id.porto.sh. In local dev, expose it via ngrok: ngrok http 3000.

3. Run development server

npm run dev

App runs at https://localhost:3000.

App Structure

app/
├── page.tsx                          # Landing / role selector
├── register/
│   ├── company/page.tsx              # Company registration
│   └── contractor/page.tsx          # Contractor registration
├── company/
│   ├── dashboard/page.tsx           # Main company view — vault stats, deposit, withdraw, invoices
│   ├── vault-setup/page.tsx         # Create vault (ERC-20 or Confidential)
│   ├── invoices/page.tsx            # Invoice management — approve, register, cancel
│   └── transactions/page.tsx        # Full transaction history (all 5 tx types)
└── contractor/
    └── dashboard/page.tsx           # Contractor view — invoices, cheque execution, balances, unwrap
api/
└── porto/
    └── merchant/route.ts            # Gas sponsorship endpoint (server-side)

Hooks

All on-chain interactions are encapsulated in hooks under hooks/. Each hook wraps useSendCalls + waitForCallsStatus and posts confirmation to the backend.

Hook Vault type Action
useCreateVault Both Deploy vault via VaultFactory
useVaultDeposit ERC-20 Deposit USDC into vault
useVaultWithdraw ERC-20 Withdraw USDC from vault, optional forward
useVaultRegisterInvoice ERC-20 Register cheque on-chain
useVaultExecuteCheque ERC-20 Execute cheque (payee collects)
useVaultCancelCheque ERC-20 Cancel cheque
useVaultBalance ERC-20 Read vault USDC balance
useConfidentialVaultDeposit Confidential Encrypt + deposit cUSDC
useConfidentialVaultWithdraw Confidential Withdraw cUSDC + full two-step unwrap
useConfidentialVaultRegisterInvoice Confidential Encrypt payee + amount, register cheque
useConfidentialVaultExecuteCheque Confidential Execute encrypted cheque
useConfidentialVaultCancelCheque Confidential Cancel encrypted cheque
useConfidentialVaultBalance Confidential Read + decrypt vault cUSDC balance
useContractorCusdcBalance Confidential Read + decrypt contractor's wallet cUSDC balance

Key Flows

ERC-20 Vault Payroll

Company: deposit USDC → approve invoice → register cheque (on-chain)
Contractor: execute cheque → USDC lands in Porto wallet
Company: withdraw remaining USDC (optional: forward to another address)

Confidential Vault Payroll

Company: wrap USDC→cUSDC → encrypt + deposit → register encrypted cheque
Contractor: execute cheque → cUSDC lands in Porto wallet (encrypted)
            ↓ optional
            decrypt balance → unwrap cUSDC → publicDecrypt (Zama) → finalizeUnwrap → USDC
Company: withdraw cUSDC (auto-unwraps to USDC)

Confidential Vault — Two-step Unwrap

Unwrapping cUSDC to USDC requires two Porto confirmations:

  1. unwrap(from, to, encryptedAmount, proof) — registers request on-chain, emits UnwrapRequested
  2. Parse UnwrapRequested log → instance.publicDecrypt([handle]) via Zama relayer HTTP
  3. finalizeUnwrap(handle, cleartextAmount, decryptionProof) — transfers underlying USDC

This is handled automatically inside useConfidentialVaultWithdraw and the contractor's Receive Funds modal.

Merchant Route

app/api/porto/merchant/route.ts is the gas sponsorship endpoint. Porto's iframe posts unsigned calls here; the server signs and returns a merchant signature.

The current implementation sponsors all transactions unconditionally. Post-MVP: add an allowlist of contract addresses to prevent misuse.

FHE Operations

All FHE operations run client-side via lib/fhevm.ts (singleton FhevmInstance).

  • Encryption: instance.createEncryptedInput(contractAddr, userAddr) — proof is bound to both addresses
  • Balance decryption: ephemeral keypair via generateKeypair() + EIP-712 signature → userDecrypt()
  • Unwrap decryption: instance.publicDecrypt([handle]) — no user signature required, returns { clearValues, decryptionProof }
  • Error checking: lib/confidentialError.ts — decrypts the vault's getLastError() after each confidential operation

Deployed Contracts (Ethereum Sepolia)

Contract Address
VaultFactory 0x619B322e1D722F86294B4d7dF92B42c89B3456aB
MockUSDC 0xe89D1caF047aEc9F7f0F3623F799F3bc321fFc9c
ConfidentialUSDC (cUSDC) 0x8a486Fa9c123ADc482d383f9fe8A48adaD7fBc17

Constants live in lib/constants.ts.

Directory Structure

frontend/
├── app/                    # Next.js App Router pages + API routes
├── hooks/                  # Wagmi-based on-chain interaction hooks
├── lib/
│   ├── abis/               # Contract ABIs (exported from Hardhat)
│   ├── auth.ts             # JWT storage + getToken()
│   ├── constants.ts        # Contract addresses, chain config
│   ├── fhevm.ts            # FhevmInstance singleton
│   └── confidentialError.ts # Soft-error decryption helper
├── components/
│   └── Navbar.tsx
└── public/

Releases

No releases published

Packages

 
 
 

Contributors