Live on Sepolia testnet

Real-world identity,
on-chain attested.

The bridge takes a company's GLEIF-issued vLEI credential — the same identity standard banks and regulators use off-chain — and turns it into an on-chain attestation bound to the company's wallet, so any smart contract on Ethereum can read it permissionlessly.

OFF-CHAIN · GLEIFAcme Bank GmbHGvLEI credentialBRIDGEvLEI → EASverify · sign · attestNext.js · Postgres · FoundryON-CHAIN · ETHEREUMEAS contractattestation
The cast

Eight characters, one story.

Every step in the journey involves some subset of these actors. They each have a single, narrow job — that's what makes the system auditable.

User · violet
The legal entity
Holds an EVM wallet and a vLEI credential they want to bridge.
G
Credential · violet
vLEI
A signed CESR envelope issued under GLEIF's KERI infrastructure.
BRIDGENext.js
Backend · teal
The bridge
A small Next.js app that orchestrates verification and signs attestations.
Sidecar · violet
vLEI verifier
GLEIF's reference checker. Validates KERI signatures cryptographically.
Public API · amber
GLEIF registry
The authoritative source of LEI status and legal name worldwide.
State · teal
Postgres
Stores registrations, replay-protection nonces, and audit logs.
Smart contract · blue
EAS on Sepolia
The Ethereum Attestation Service. Public, permissionless to read.
Solidity · blue
AttesterResolver
A 29-line contract that gates the schema to one immutable wallet.
The journey

From "I am Acme Bank" to a transaction on Sepolia.

Nine steps, end to end. The whole thing finishes in seconds — minus the on-chain confirmation.

https://bridge.example/registerRegister your vLEIStep 1: Connect Your WalletConnect Wallet0x4a3b…f2Sepolia · chainId 11155111ΞWallet0x4a3b…f2address
01

Connect the wallet

src/components/registration-form.tsx · RainbowKit + wagmi

The user — typically a treasury or compliance signer at the legal entity — opens the bridge in their browser and connects an Ethereum wallet. The bridge UI pins the chain to Sepolia; nothing happens here yet except the wallet handing over its address.

// react hook const { address, chainId } = useAccount(); // → { address: "0x4a3b…f2", chainId: 11155111 }
Register your vLEIKERI AIDEKYGGh-FtAphGmSZbsuB…LEI5493001KJTIIGC8Y1R12Credential typeLE ▾DROP CESR HEREGcred.cesr
02

Upload the vLEI credential

src/components/cesr-upload.tsx

The user attaches the CESR file — the cryptographically signed envelope GLEIF issued to them — and fills in the KERI AID, LEI, and credential type. The browser holds it; nothing leaves yet.

// FormData held client-side { cesr: <File>, keriAid: "EKYGGh-FtAphGmSZbsuBs_t4qpsjYJ2ZqvMKluq9OxmY", lei: "5493001KJTIIGC8Y1R12", vleiSaid: "0x...", credType: "LE" }
Wallet · Signature RequestDOMAINname: "vLEI-to-EAS Bridge"version: "1", chainId: 11155111MESSAGE · RegistrationkeriAid: "EKYGGh-FtAphG…"vleiSaid: 0x9a4d…nonce: 1730491284CancelSignEIP-712 signatureSIGNATURE0xa3d8…7f1c(65 bytes)v · r · s
03

Sign with the wallet

src/lib/evm-signature.ts · EIP-712

The wallet pops up an EIP-712 typed-data prompt tying the user's KERI AID and vLEI SAID together with a fresh nonce. Signing this is the user's explicit permission for the bridge to attest about them — without this signature, no attestation gets minted.

domain { name: "vLEI-to-EAS Bridge", version: "1", chainId } types Registration { keriAid, vleiSaid, nonce } message { keriAid, vleiSaid, nonce: Date.now() }
POST /api/registercesr, lei,keriAid…+ signatureBRIDGErecover0x4a3b…f2recovered == evmAddressnonce > stored.nonce✓ both passnoncesSELECT/UPDATE
04

Verify signature & nonce

src/app/api/register/route.ts

The form-data hits POST /api/register. The bridge recovers the signer from the EIP-712 message — it must match the submitted EVM address — and checks the nonce table: each address has a monotonic counter, so an old signature can never be replayed.

recovered = ethers.verifyTypedData(domain, types, msg, sig) assert recovered.toLowerCase() === evmAddress assert nonce > nonces[evmAddress].value
BRIDGEGCESR bytesPUT /presentationsvLEI verifierpoll → "valid"✓ KERI valid
05

Verify the credential cryptographically

src/lib/registration-service.ts → vlei-verifier sidecar

The bridge sends the CESR bytes to the GLEIF reference verifier — a Docker sidecar that knows how to parse and validate KERI signatures. It polls until the verifier returns valid, invalid, or times out. Only valid moves on.

PUT /presentations/{vleiSaid} // hand off CESR GET /authorizations/{keriAid} // poll until decided → "valid"
BRIDGElooking up5493001KJTIIGC8Y1R12in the public registryGET /lei-records/{lei}api.gleif.orglegalName:Acme Bank GmbHISSUED
06

Cross-check with the public registry

src/lib/gleif-client.ts → api.gleif.org

With the credential validated cryptographically, the bridge cross-checks against the public GLEIF registry. This is the canonical "who is this entity, today?" lookup — the same source banks and regulators use.

GET https://api.gleif.org/api/v1/lei-records?filter[lei]={lei} → { legalName: "Acme Bank GmbH", status: "ISSUED" }
BRIDGEall 3 checks ✓sig · vLEI · GLEIFINSERT rowregistrationsSTATUSpending_attestation202 Accepted
07

Persist a pending row, respond fast

prisma/schema.prisma · registrations

All three checks have passed. The bridge writes a row keyed by (evmAddress, chainId) with status pending_attestation, then responds 202 Accepted to the user immediately — they don't wait on Ethereum confirmation in the request.

INSERT registrations { evmAddress, lei, legalName, keriAid, vleiSaid, chainId, status: "pending_attestation", verifiedAt: now() }
BRIDGEΞattester walletsubmit txeas.attest({schema, recipient,data: encoded(...)})tx →attestation fieldslei, legalName, credType,keriAid, vleiSaid, verifiedAtrecipient: 0x4a3b…f2EAS · sepolia
08

The bridge wallet attests on-chain

src/lib/attestation-worker.ts + src/lib/eas-client.ts

Fire-and-forget, the bridge encodes the schema fields and submits an attestation transaction via the EAS SDK, signed by the bridge's attester wallet. The transaction is the only thing that ever moves on-chain.

eas.attest({ schema: SCHEMA_UID, data: { recipient: evmAddress, revocable: true, data: encode(lei, legalName, credType, keriAid, vleiSaid, verifiedAt) } })
txattester:0xBRIDGE…AttesterResolverattester == bridgeWallet ?accepted ✓attestation UIDUPDATE registrations · status=active
09

The resolver gates & the row goes active

contracts/src/AttesterResolver.sol

Inside the EAS contract, the schema's resolver hook fires. It rejects any caller other than the bridge's immutable wallet — that's the on-chain guarantee. Once the receipt lands, the bridge writes the EAS UID back to the row, the audit log gets an "issued" entry, and the user's browser polling unblocks.

// Solidity if (attestation.attester != bridgeWallet) revert UnauthorizedAttester(); // → tx confirmed, easUid stored UPDATE registrations SET status='active', easUid='0x…' WHERE id=$1
The guarantee

Why the bridge can't lie.

A trusted attester is only as good as the rules around it. Two on-chain guarantees keep this one honest.

01 · ON-CHAIN GATE

Only one wallet can ever attest

At deployment, the schema is paired with an AttesterResolver contract that hard-codes a single wallet address. No upgrades, no admin keys, no allowlist edits.

0xBAD…BRIDGEresolver.onAttestattest ✓revert ✗
// AttesterResolver.sol
address public immutable bridgeWallet;

function onAttest(Attestation calldata attestation, uint256)
  internal view override returns (bool)
{
  if (attestation.attester != bridgeWallet)
    revert UnauthorizedAttester();
  return true;
}
02 · USER CONSENT

Every attestation needs a fresh signature

Before the bridge attests anything, the user signs an EIP-712 typed message binding their EVM address to a specific KERI AID and vLEI SAID, with a monotonic nonce. Without this signature, no attestation can be issued.

user · 0x4a3b…f2RegistrationkeriAid: EKYG…vleiSaid: 0x9a4d…nonce: 1730491…sig: 0xa3d8…7f1cBRIDGEconsentverify
// EIP-712 domain
{
  name:    "vLEI-to-EAS Bridge",
  version: "1",
  chainId: 11155111
}
// types
Registration {
  keriAid:  string,
  vleiSaid: bytes32,
  nonce:    uint256
}
net effect

The bridge can refuse to attest, but it can never attest something false — the resolver rejects any tx from a different wallet, and the user signature pins each attestation to a specific vLEI/KERI pair the user actually controls.

The lifecycle

An attestation is a living thing.

Identity changes. Companies merge, lapse, get renamed. Three background loops keep on-chain state honest about off-chain reality.

every 4 hours

vLEI re-verification

For every active registration, re-presents the KERI AID to the verifier. If the credential is no longer valid, the row enters pending_revocation.

every 12 hours

GLEIF status check

Pulls the LEI record from GLEIF. LAPSED, RETIRED, ANNULLED, CANCELLED trigger on-chain revocation. Renames are quietly patched in the DB.

every 30 seconds

Attestation retry

Picks up any stuck pending_attestation or pending_revocation. Up to 3 attempts with backoff before terminal failure.

REGISTRATION STATE MACHINE
pending_attestation→active→pending_revocation→revoked
3× retry exhausted→failed