Skip to content

Repository files navigation

RelayPay

Trustless crypto-to-PIX payments. A depositor locks USDC in an escrow contract; a relayer pays the Brazilian recipient in BRL via PIX; a TLSNotary proof of the payment API response settles the contract on-chain — no custodian, no bridge, no trusted third party.

Status: End-to-end production tested on Ethereum Sepolia with real USDC and real PIX transfers (Asaas payment provider). Base mainnet deployment in progress.


How it works

Depositor                    Escrow (Solidity)               Relayer
    │                              │                             │
    ├── createOrder() ────────────►│  locks USDC                 │
    │                              │◄── claimOrder() ────────────┤
    │                              │                             │
    │                              │         [pays PIX via API]  │
    │                              │                             │
    │                              │◄── submitProof() ───────────┤
    │                              │    (TLSNotary attestation)  │
    │                              │                             │
    │                              ├── releases USDC ───────────►│
    │                              │    (minus protocol fee)     │
  1. Depositor calls createOrder on the escrow, specifying a PIX key, fiat amount, and USDC amount. USDC is locked in the contract.
  2. Relayer claims the order and sends a PIX transfer via the payment provider API (currently Asaas).
  3. Attestation service runs a TLSNotary MPC-TLS session against api.asaas.com, verifying the transfer response, and signs an EIP-712 PaymentDetails attestation.
  4. Relayer submits the attestation on-chain. The escrow verifies the EIP-712 signature against the registered notary and releases USDC to the relayer.

The fiat payment is proven, not trusted. The relayer cannot forge a PIX receipt without compromising the TLS session — and the TLS server's key is confirmed by the TLSNotary protocol.


Live deployment (Ethereum Sepolia)

Contract Address
RelayPayEscrow 0x53a9a73F3FafD65e550079CDD65795E9920b9529
EIP712PaymentVerifier 0x4A95b94A625DDf447c5E7517Fd7d3b93563f4d6e
NotaryRegistry 0x96D0bb9B1AE592aeF5c0B7bA71DAcF830A1eF773
Token (Circle USDC) 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238

Registered notary: 0x5764C55eb0B8e23E382e4397d65526524fCaA278


Repository structure

contracts/
  RelayPayEscrow.sol          # Core escrow — order lifecycle, proof verification, settlement
  EIP712PaymentVerifier.sol   # Verifies EIP-712 signed payment attestations
  NotaryRegistry.sol          # Manages trusted notary signing keys
  IPaymentVerifier.sol        # Interface for pluggable verifiers

services/
  relayer/                    # Relayer agent — polls escrow, pays PIX, submits proofs
  attestation/                # Attestation service — verifies TLSNotary proofs, signs EIP-712
  shared/payout/              # Payout provider abstraction (Asaas, MercadoPago)

prover/                       # Rust TLSNotary prover (MPC-TLS sessions + selective disclosure)
  crates/examples/asaas/      # Asaas-specific prover (prove, present, verify)
  crates/examples/mercadopago/# MercadoPago-specific prover
  vendor/spansy/              # Patched HTTP span parser (RFC 9112 §6.3 rule 7 fix)

scripts/
  deploy.ts                   # Deploy all contracts and register notary
  live-pix-run.ts             # End-to-end live PIX test
  cancel-order.ts             # Cancel expired orders and recover USDC

test/                         # Hardhat test suite (full order lifecycle)

Quick start (local)

npm install
npm run compile
npm test

# Start local node + deploy
npx hardhat node                     # terminal 1
npm run deploy:local                 # terminal 2

# Run mock end-to-end
npm run services:e2e

Sepolia deployment

# Set in .env:
# DEPLOYER_PRIVATE_KEY, RELAYER_PRIVATE_KEY, WITNESS_PRIVATE_KEY
# SEPOLIA_RPC_URL
# ESCROW_ADDRESS (after deploy)
# TOKEN_ADDRESS=0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238

npx hardhat run scripts/deploy.ts --network sepolia

Live PIX test (Sepolia + real Asaas account)

# Additional .env vars needed:
# PAYOUT_PROVIDER=asaas
# ASAAS_API_KEY=<your key>
# ASAAS_SANDBOX=false
# PIX_KEY=<recipient PIX key>
# PIX_KEY_TYPE=PHONE

# Start attestation service (separate terminal)
npm run services:attestation

# Expose it via cloudflared and set as Asaas webhook
# (see docs/setup-webhook.md)

npm run prover:build:asaas
npx hardhat run scripts/live-pix-run.ts --network sepolia

Cancel expired orders

# Cancel specific orders
ORDER_IDS=1,3 npx hardhat run scripts/cancel-order.ts --network sepolia

# Inspect all orders
npx hardhat run scripts/cancel-order.ts --network sepolia

Architecture notes

Proof model

On-chain verification of TLSNotary presentations is not yet available directly in Solidity (it's on the TLSNotary roadmap). We follow the same pattern as ZKP2P: an off-chain attestation service verifies the TLSNotary presentation in Rust, extracts payment details, and signs an EIP-712 PaymentDetails struct. The on-chain contract recovers the signer and checks it against NotaryRegistry.

Trust model (Phase 1)

The attestation service is the current trust anchor — whoever controls its private key controls proof validity. This is acceptable for a testnet demo. Phase 2 moves the service into a TEE (Intel SGX via Gramine) and includes a TEE attestation in each proof. Phase 3 introduces threshold signatures across independent notaries.

Payout providers

The relayer uses a pluggable PayoutProvider interface. Currently supported:

  • Asaas (default) — Brazilian PSP with PIX transfer API, self-serve KYC
  • MercadoPago — alternative, requires integrator onboarding

New providers only need to implement sendPayout, getStatus, and waitForSettlement, plus a corresponding Rust prover binary that attests the provider's status API.

RFC 9112 §6.3 rule 7

The vendored spansy HTTP parser includes a fix for responses where the body is delimited by connection close (no Content-Length header). This is required for Asaas's transfer API and has been submitted upstream to tlsnotary/tlsn-utils.


Contracts

Contract Purpose
RelayPayEscrow Order creation, claiming, proof submission, settlement, cancellation. Protocol fee (30 bps) sent to treasury on settlement.
EIP712PaymentVerifier Verifies EIP-712 PaymentDetails signatures. Implements IPaymentVerifier.
NotaryRegistry Ownable registry of trusted notary addresses. Escrow delegates signer validation here.
IPaymentVerifier Interface for pluggable verifiers — allows swapping proof systems without redeploying the escrow.

Tech stack

  • Solidity 0.8.24 — smart contracts (EVM cancun)
  • OpenZeppelin v5Ownable, ReentrancyGuard, SafeERC20, ECDSA
  • Hardhat — compile, test, deploy
  • ethers.js v6 + TypeScript — tests, scripts, off-chain services
  • Rust / TLSNotary alpha.14 — MPC-TLS prover, selective disclosure, presentation verification
  • Express — attestation service HTTP server
  • Asaas — PIX payment provider (production-tested)

Documentation


License

MIT

About

▎ Trustless KYC-less crypto-to-PIX payments — USDC locked in escrow, PIX paid by a relayer, settlement proven on-chain via TLSNotary attestation.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages