How Iris Works

A non-custodial, gasless link-drop system for sending stablecoins to anyone with a phone number.

Iris lets a sender lock stablecoins in an on-chain escrow and generate a payment link. The recipient opens the link, verifies their phone number, and claims the funds directly to their wallet — without needing to already have one set up. All cryptographic operations happen client-side; the backend never has access to the full claiming credentials.

The Problem

Sending stablecoins today requires the recipient to already have a wallet address. For international remittance, this means the person you want to pay needs to have already navigated wallet setup, seed phrase backup, and chain selection before they can receive a single dollar.

There is no lightweight onboarding path. Existing solutions are either fully custodial (the platform holds the funds) or require both parties to be on-chain before the transfer can happen.

Payment links bridge this gap. The sender locks funds on-chain, and the recipient claims them when they are ready — with identity verification gating access so only the intended recipient can claim.

System Architecture

Four services coordinate to create and claim payment links. The green path shows the happy-path flow of funds and credentials.

Client (Browser)
Keypair generation, encryption, EIP-712 signing
Iris
Payment link CRUD, claimer key storage
Escrow Contract
On-chain fund custody (Celo)
WIMS
Identity attestation (phone to wallet)
Charon
Auth token minting (ephemeral JWT)
Happy path
Supporting service

Create Flow

The sender generates a payment link in seven steps. All cryptographic material is created client-side.

1

Sender connects wallet

The sender connects their wallet via RainbowKit / WalletConnect. This wallet holds the stablecoins to be sent.

2

Client generates two ephemeral keypairs

Claimer keypair(secp256k1) — the derived address is registered with the escrow contract as the authorized claimer.

Encryptor keypair— used to encrypt the claimer private key before sending it to the backend.

3

Client encrypts claimer private key

The claimer private key is encrypted using AES-256-GCM with a shared secret derived via ECDH from the encryptor keypair. The backend will store this ciphertext but can never decrypt it.

4

ERC20 approval

The sender approves the escrow contract to spend the specified token amount via approve(escrow, amount).

5

createEscrow()

Funds are locked on-chain. The contract records the sender, claimer address, token, amount, and expiry. Returns a bytes32 escrowId.

6

POST /iris/v0/payment-links

The backend stores the encrypted claimer key, encryptor public key, and validation requirements (the recipient's phone number). Returns a UUID for the payment link.

7

URL constructed

The shareable payment link is assembled:

URL Anatomy

https://iris.demos.pactlabs.com/claim/a1b2c3d4-...#0x4f3e...9a1b
UUID— Payment link identifier. Transmitted to the server in the URL path.
Encryptor private key — In the URL fragment (after #). Browsers never send fragments to servers. The backend never sees this value.

Claim Flow

The recipient opens the payment link and claims the funds in nine steps. The critical security property is that the encryptor private key (from the URL fragment) never leaves the browser.

1

Recipient opens link

The encryptor private key is parsed from the URL fragment. This happens entirely client-side — the fragment is never sent to the server.

2

Link details fetched

GET /iris/v0/payment-links/{id} returns the payment link metadata: amount, token, expiry, status. This endpoint is public and requires no authentication.

3

Recipient enters phone number + connects wallet

The recipient provides their phone number (must match the sender's specified recipient) and connects a wallet to receive the funds.

4

Identity attestation

WIMS (POST /v0/{wallet}/identifiers) links the recipient's phone number to their wallet address, creating an on-platform identity.

5

Ephemeral token minted

Charon issues an ephemeral JWT (POST /v0/tokens) with the recipient's phone identifier. This token authorizes the claimer key request.

6

Claimer key released

GET /iris/v0/payment-links/{id}/claimer-key validates the JWT's identity against the link's validation requirements (AND semantics). If the phone number matches, the encrypted claimer key is returned.

7

Client decrypts

The encryptor private key from the URL fragment is combined with the encryptor public key (returned alongside the encrypted key) via ECDH to derive the shared secret. The claimer private key is decrypted using AES-256-GCM.

8

EIP-712 signature

The client signs Claim(escrowId, recipient) using the decrypted claimer private key. This typed data signature proves authorization without the claimer key ever touching a server.

9

claim() on-chain

The signed claim is submitted to the escrow contract. The contract verifies the EIP-712 signature against the registered claimer address and transfers the escrowed funds to the recipient's wallet. Gas is covered by the Pact paymaster.

Security Model

The system is designed so that no single party — including the Iris backend — can unilaterally claim funds.

Non-custodial

The backend never sees the claimer private key in plaintext. It stores only the AES-256-GCM ciphertext, which is useless without the encryptor private key.

Split-knowledge

Claiming requires two pieces of information held by different parties: the encryptor private key (in the URL fragment, held by the link recipient) and the encrypted claimer key (on the Iris server). Both are needed.

Identity-gated

The Iris backend only releases the encrypted claimer key after WIMS attestation confirms the caller's phone number matches the sender's specified recipient. Validation requirements use AND semantics.

On-chain escrow

Funds are held by the smart contract, not by any service. The sender can reclaim after expiry. No backend has withdrawal capability.

Gasless claims

The Pact paymaster covers gas for claim transactions, so recipients do not need native tokens to receive funds. This removes the last onboarding friction for new users.

Fragment privacy

URL fragments (the part after #) are defined by RFC 3986 as client-only. Browsers, proxies, and servers never transmit the fragment in HTTP requests. The encryptor private key stays in the recipient's browser.

Key Cryptography

Ephemeral Keypairs (secp256k1)

Two keypairs are generated per payment link using the @noble/curveslibrary. The claimer keypair is a standard secp256k1 keypair — the same curve used for Ethereum accounts. The claimer's EVM address is derived via keccak256 of the uncompressed public key and registered with the escrow contract.

Encryption (AES-256-GCM)

The claimer private key is encrypted using AES-256-GCM with a symmetric key derived from an ECDH shared secret between the encryptor keypair. The sender's client computes sharedSecret = ECDH(encryptorPrivateKey, encryptorPublicKey), derives a 256-bit AES key via HKDF, and encrypts. At claim time, the recipient's client performs the same derivation using the encryptor private key from the URL fragment and the encryptor public key from the backend.

EIP-712 Typed Data Signatures

The claim operation requires an EIP-712 signature from the claimer private key. This is verified on-chain by the escrow contract against the registered claimer address.

Domain {
  name:              "PactClaimablePaymentEscrow"
  version:           "1"
  chainId:           42220
  verifyingContract:  <escrow_address>
}

Type Claim {
  bytes32  escrowId
  address  recipient
}

Escrow ID Derivation

The escrowId is a bytes32 value derived on-chain via keccak256 from the escrow parameters (sender, claimer, token, amount, nonce). This deterministic derivation means the escrow ID is known immediately after the createEscrow() transaction confirms.

Iris is part of the Pact protocol. Try the demo