ipsa architecture

The ipsa repositories and components, how they fit together, how a packet moves between Stellar, the Cosmos Hub and Sepolia, and how it is built and deployed.

ARCHITECTURE

The repositories of the ipsadev organisation: what each one is, how they fit together, how a packet moves between Stellar and its counterparties, and how it is built, released and deployed. Written on 2026-11-02 from the code, the release notes and the live testnet deployment.

Warning. ipsa is a test implementation on testnets. No third-party audit has been done, the pinned testnet trust root tolerates zero Byzantine faults, and neither deployed light client has been shown to reject a forged proof on-chain.

1. What ipsa is

ipsa is the implementation of IBC v2 (Eureka) for Stellar. It makes Stellar an IBC chain, not a bridge between two chains: Stellar gets the IBC v2 packet lifecycle natively in Soroban contracts, and its counterparties get a light client of Stellar's consensus (SCP) they can run on-chain. Two counterparties run on testnet:

  • The Cosmos Hub testnet (provider, ibc-go with 08-wasm). The Hub runs the Stellar light client as a wasm contract; Stellar runs a 07-tendermint light client of the Hub as a Soroban contract.
  • Ethereum Sepolia. Sepolia runs SP1ICSStellar, which verifies zero-knowledge proofs (SP1, Groth16) that the SCP checks passed; Stellar runs an SP1 light client of Ethereum's sync committee.

Applications on top: ICS-20 token transfers in both directions on both corridors, and ICS-27 general message passing (GMP) from Stellar to Sepolia, which the issuer example uses to control a Stellar asset's ERC-20 on Ethereum.

The defining property: no component holds funds or attests to anything off-chain. Packet authenticity is checked by on-chain light clients; the relayer, the proof API, the gateway and the api are untrusted transport. A malicious off-chain component can delay or censor a packet, never forge one.

2. The repositories

Repository Source Language Role
ipsa-contracts private Rust (Soroban) The on-chain protocol on Stellar: router, ICS-20 transfer app, voucher token, ICS-27 GMP app and accounts, 07-tendermint and SP1 Ethereum light clients, Groth16 verifier
ipsa-backend private Rust ipsa-api (HTTP, everything off-chain reads Stellar through) and ipsa-gateway (keyless gRPC, Stellar proofs and transactions)
ibc-contracts public, fork of cosmos/ibc-contracts Solidity, Rust SP1ICSStellar, the SP1 programs for Stellar and Ethereum, and stellar-proof-api, the relayer's proof endpoint
ibc-relayer public, fork of cosmos/ibc-relayer Go Cosmos Labs' IBC v2 relayer with Stellar added as a chain type; source-available evaluation licence
stellar-light-client public Rust (CosmWasm) The 08-wasm light client of Stellar the Cosmos Hub runs
stellar-consensus-verifier public Rust The I/O-free SCP verification library everything else builds on (crates.io stellar-consensus-verifier)
ipsa-scp-verifier public Rust Step-by-step ledger verification from the history archives: library, CLI and HTTP server (crates.io ipsa-scp-verifier)
ipsa-dev-kit private Rust, Solidity The ipsa crate (crates.io) for building on ipsa, and the example apps
ipsa-tooling private Rust The ipsa CLI: brings the stack up, deploys contracts, creates clients, registers counterparties, sends and relays transfers, runs the ICS flows
ipsa-app private TypeScript (Next.js) app.ipsa.dev: documentation, the consensus verifier page, the explorer and the ICS-20 and ICS-27 demos
ipsa-landing private Vue ipsa.dev, the landing page
ipsa-ibc-core public, archived Rust Former home of the commitment paths, SMT and ICS-23 proofs; now the core module of the ipsa crate

3. System overview

The relayer never builds a proof itself. For every packet it finds, it asks stellar-proof-api for the transaction that relays it (RelayByTx), signs the result with its own key and submits it to the destination chain. The proof API answers in one of three ways:

Pair Module Proof Who verifies it
Stellar → Sepolia stellar-to-eth SP1 proofs (update-client, membership) generated on Succinct SP1ICSStellar on Sepolia
Sepolia → Stellar eth-to-stellar SP1 proofs of the sync committee and the router's storage, generated on Succinct the SP1 Ethereum light client on Stellar
Stellar ↔ Cosmos Hub forward → ipsa-gateway ICS-23 proofs and SCP evidence (Stellar side), Tendermint headers and IAVL proofs (Hub side), no zero-knowledge the 08-wasm client on the Hub, the 07-tendermint client on Stellar

4. Trust model

Component Holds funds Holds keys Trusted for correctness
Light clients, routers and apps (on-chain) the transfer apps' escrow no yes: the verification root
ipsa-api no the testnet faucet's, when enabled no
ipsa-gateway no no no
stellar-core watcher no no no: every SCP message is validator-signed, so it can lag or withhold, never forge
stellar-proof-api no the Succinct requester key, which pays for proofs no: a wrong proof fails on-chain
ibc-relayer no a fee-paying key per chain no: can delay or censor, never forge
Postgres no no no: used for display and retries only

What the light clients of Stellar trust:

  • A quorum set pinned at client creation (the trust root). The testnet trust root is a 2-of-3 set, so two compromised validator keys can forge and one equivocator can fork. The mainnet trust root in ipsa-scp-verifier/trust-roots/mainnet.hex requires 5 of 7 organisations, each 2 of 3 validators.
  • Quorum intersection, which no light client can check from inside (SCP whitepaper §4.1).
  • The slot N+1 binding: Stellar validators never sign the ledger header, so a header is bound through the next slot's transaction set, which names its predecessor. This relies on validators checking previousLedgerHash before voting, a stellar-core rule.

5. On-chain components

5.1 Stellar: ipsa-contracts

One Cargo workspace (v0.2.0, soroban-sdk 26.1.0, OpenZeppelin stellar-access, stellar-macros and stellar-tokens 0.7.2). Each contract is a lib + cdylib built for wasm32v1-none.

Crate Contract Responsibility
ibc-router stellar-ibc-router ICS-26 core: client registry (ICS-02), counterparties, ports (ICS-05), the provable store (ICS-24) and the packet lifecycle (ICS-04)
ibc-transfer stellar-ibc-transfer ICS-20: escrow of Stellar assets through their Stellar Asset Contract, vouchers for foreign denominations, denomination traces, per-denomination daily rate limits, ABI and JSON packet encodings per client
ibc-voucher-token stellar-ibc-voucher-token The SEP-41 token the transfer app deploys for each foreign denomination
ibc-gmp stellar-ibc-gmp ICS-27 (gmpport, ics27-2), with Solidity ABI, protobuf, JSON and Soroban XDR encodings
gmp-account ipsa-gmp-account An account per (client, sender, salt) through which incoming GMP calls execute
light-clients/tendermint stellar-tendermint-light-client Verifies Cosmos Hub headers (adjacent and skipping, across validator set changes, about 152M of the 400M instruction limit) and IAVL ICS-23 proofs
light-clients/sp1-ethereum stellar-sp1-ethereum-light-client Verifies SP1 proofs of Ethereum's sync committee and storage; client type sepolia
sp1-verifier stellar-sp1-verifier Groth16 verification on BN254
testing/light-client ipsa-test-light-client Accept-everything client for tests, never built to wasm

The router's interface:

  • Clients: register_client_type, create_client(type, client_state, consensus_state, height) → "{type}-{N}", update_client, migrate_client, register_counterparty(client_id, counterparty_client_id, prefix).
  • Ports: register_port, reassign_port, remove_port. A port is not removed while packets are in flight.
  • Packets: send_packet(source_client_id, timeout_timestamp, payloads) → sequence (the port's app must authorise), recv_packet (writes the acknowledgement itself), acknowledge_packet, timeout_packet. One payload per packet.
  • App callbacks run through try_invoke_contract: a failing app is rolled back, the receipt is kept and the universal error acknowledgement is written.
  • upgrade, ibc_root(), and commit_root(), which re-emits the root event without changing state.

Provable state. IBC v2 keeps three provable paths, all in one fixed-depth-64 sparse Merkle tree:

Value Path
Packet commitment client ‖ 0x01 ‖ be64(sequence)
Packet receipt client ‖ 0x02 ‖ be64(sequence)
Acknowledgement commitment client ‖ 0x03 ‖ be64(sequence)

A leaf's index is the first 8 bytes of sha256(path); the router records each slot's owner, so a colliding path is refused (ProvableSlotTaken). Branch points cache their subtree hashes, so a write costs about 64 hashes. After every write the router publishes the root as an ibc_root contract event. Since validators never sign the ledger header or the state, counterparties bind that event, through txSetResultHash, to a ledger they verified, and only then check proofs against the root. The router, the gateway and both Stellar light clients must compute this tree identically; shared vectors in the ipsa crate enforce it.

Transfers. initiate_transfer(sender, source_client_id, denom, amount, receiver, timeout, memo) moves a Stellar asset into the app's escrow, or burns a voucher. On receive, the ICS-20 trace rule applies: a returning denomination ({port}/{client}/…) is released from escrow, a foreign one is minted as {port}/{client}/{denom} through a voucher deployed on first receipt. An error acknowledgement or a timeout refunds the sender.

5.2 Cosmos Hub: stellar-light-client

A CosmWasm contract implementing the 08-wasm light-client interface (cosmwasm-std 2.1, ics23 0.12, stellar-consensus-verifier 1.0.1). For each header it checks:

  1. the EXTERNALIZE signatures over the network id;
  2. that the committing nodes form a quorum containing a slice of the trust root;
  3. that the committed value is byte-identical to the header's scpValue;
  4. that slot N+1 is confirmed and names this ledger as its predecessor;
  5. continuity with the stored consensus state at N-1, when there is one;
  6. that close time increases;
  7. with a state root: the transaction results hash to txSetResultHash, and the transaction is a successful call carrying exactly one ibc_root event from the configured router.

Membership and non-membership proofs are ICS-23 messages over the router's tree. The wasm is built reproducibly in Docker (Rust 1.91.1, checksummed wasm-opt 129, SOURCE_DATE_EPOCH=0); CI builds it twice from cold and compares. The on-chain checksum is 26d42704e9a6dfcf38d678419dc419aca29121b9391e72be158e8649bb55002d. On the Hub testnet, storing code is delegated by authz, with a governance proposal as the fallback (docs/ops/store.md). The live client is 08-wasm-464.

5.3 Sepolia: ibc-contracts

The upstream Solidity IBC v2 stack (ICS26Router, ICS20Transfer, ICS27GMP) plus SP1ICSStellar (ibc-solidity/contracts/light-clients/sp1-stellar/), adapted from SP1ICS07Tendermint:

  • It verifies an SP1 Groth16 proof that the SCP checks passed, instead of verifying SCP in Solidity.
  • Its client state pins the network id, the trust root's fingerprint, the Soroban router's contract id and the root event's topic.
  • It anchors membership on the consensus state's transactionSetResultHash, because the root arrives as a contract event.
  • It stores only each consensus state's keccak hash, has no trusting or unbonding period, and uses AccessControl roles for proof submitters and freezers.

The SP1 programs (programs/sp1-stellar-programs/{update-client,membership,uc-and-membership}) run ipsa-scp-verifier inside the zkVM; their verification keys are pinned in vkeys.json, and CI rebuilds the ELFs and checks the keys still match.

6. Off-chain components

6.1 ipsa-api (ipsa-backend)

The HTTP service everything off-chain reads Stellar through, and the only public container.

  • Stellar: ledgers, events, clients, ports, contract data and transfer assets through Soroban RPC; SCP evidence from the watcher service, a stellar-core node with a small reader (ipsa-watcher) that the api queries over HTTP and checks again, falling back to the history archives (/archive/*) on any error or delay; /verify for the app's verifier page (ipsa-scp-verifier 3.0.0).
  • Transactions: prepares unsigned Soroban transactions (/tx/prepare, /stellar/transfer/prepare) and submits signed ones.
  • Cosmos: headers with the trusted validator set (/cosmos/header/{height}), ICS-23 proofs and packets.
  • Relaying: POST /relay hands a transaction to the relayer's gRPC Relay; /relay/status, /relay/transfers and /relay/transfers/{id}/proofs read the relayer's and the proof API's tables.
  • Deployment: GET /config serves every identifier the app uses, so nothing chain-specific is built into the frontend; /chains and /corridors come from the relayer's config.
  • Testnet faucet: ETH and WETH on Sepolia, ATOM through the Hub faucet, with a daily cap.
  • Limits for public callers: the api has no authentication, so it bounds what any caller can make it do (rate limits, request size, time and concurrency, XDR nesting depth) and keeps upstream addresses out of its errors.

6.2 ipsa-gateway (ipsa-backend)

The keyless gRPC service for Stellar. It serves:

  • StellarGatewayQuery and StellarGatewayMsg: heights, headers with their SCP evidence, packet commitments, receipts and acknowledgements with ICS-23 proofs, tree siblings (QueryTreeSiblings, used by stellar-to-eth), and unsigned transactions for client updates, receives, acknowledgements and timeouts;
  • the proof API's ProofApiService, which the proof API's forward module calls for the Stellar ↔ Hub pairs.

It keeps its own copy of the router's tree. On start it seeds the tree from the router's storage and accepts the seed only if it rebuilds the root the router stored; a follower brings it to the latest ledger every 60 s and saves it to Postgres; a two-day journal lets it rewind to any packet's ledger. Each rewound tree must match the root the router published at that ledger. When SCP evidence is not available yet it answers UNAVAILABLE, and the relayer retries.

6.3 stellar-proof-api (ibc-contracts)

programs/stellar-proof-api registers three modules with upstream's ProofApiBuilder and serves ProofApiService on port 3100; upstream's own programs/proof-api is untouched.

  • stellar-to-eth:
    1. Reads the Stellar transaction and the router's events from Soroban RPC.
    2. Refuses, before paying for anything, a packet Sepolia already holds a receipt for.
    3. Takes SCP evidence from ipsa-api (/archive/scp, /archive/ledger-header, /archive/tx-set, /ledger), or from the archives, waiting up to about 5.5 minutes for a checkpoint.
    4. Verifies the evidence natively, and rebuilds the client's trusted state.
    5. Builds the membership witness from the gateway's siblings.
    6. Requests the update-client and membership proofs on Succinct.
    7. Returns ICS26Router.multicall(updateClient, recvPacket | ackPacket). Timeouts towards Sepolia and several packets per transaction are not built yet.
  • eth-to-stellar:
    1. Reads the Sepolia receipt.
    2. Checks the Soroban router's storage.
    3. Rebuilds the trusted state from the beacon API.
    4. Waits for a finality update signed by at least two thirds of the sync committee.
    5. Requests the proofs.
    6. Returns Soroban host functions (update_client × N, then recv_packet or acknowledge_packet) for the relayer to sign. It also implements CreateClient.
  • forward: passes RelayByTx to ipsa-gateway.
  • Proof history: with PROOF_HISTORY_DB_URL set, every proof is recorded in proof_requests, one row per proof however often it is retried. A row holds the program, the Succinct request, cycles, PGUs, its cost in PROVE, the status and the number of attempts. Recording never blocks a relay. A proof costs about 0.4 PROVE.

6.4 ibc-relayer

Cosmos Labs' IBC v2 relayer, with a Stellar bridge client (shared/bridges/ibcv2/stellar_*.go, using go-stellar-sdk).

  • It watches every chain for packets, and also takes requests through its gRPC RelayerApiService (Relay, Status), which ipsa-api's POST /relay calls.
  • For every packet it calls RelayByTx on the proof API, and caches a proof it has paid for across retries (30 minutes, 5 uses).
  • On Stellar it submits each host function as its own transaction: simulate, set the fee, sign with its seed, send, and wait for inclusion before the next.
  • It checks the quorum sets it is served against pinned_quorum_set_hashes in its config.
  • It records transfers in Postgres (ibcv2_transfers and the submission tables), which the api reads.
  • Licence: the Cosmos Labs source-available evaluation licence allows evaluation, testing and research only, not production or commercial use, and it extends to this fork. The Stellar work is open as PR #1 (feat/stellar-chain-type), and images are built on stellar-v* tags.

6.5 The verifiers

  • stellar-consensus-verifier: the core library, with no network, storage or clock, and a hand-written XDR reader that treats all input as untrusted. Its modules cover envelopes and signatures, ballots, quorum sets and the quorum predicate, ledger headers and hashes, chains of ancestry, transaction sets and results. CI also builds it for wasm32-unknown-unknown. It is published to crates.io from tags.
  • ipsa-scp-verifier: verifies one ledger end to end from the public history archives, trusting no archive, Horizon or RPC provider:
    • The checks: fetching the evidence; signatures; statements; quorum-set preimages; the quorum predicate; agreement on one value; the value matching the header and the header's hash; the slot N+1 binding; and the results hash.
    • Verdicts: verified, verified_unanchored, insufficient, invalid_evidence or contradicted.
    • Server mode: --serve 8110 exposes /verify with mutation options, which demonstrate which check catches which tampering.
    • Tests: it is tested against brute force over 20,000 random federations, and against stellar-core's own quorum code over 2,000 cases.

6.6 Clients and tools

  • ipsa-app (app.ipsa.dev, Next.js static export on Cloudflare Pages):
    • Pages: the documentation, the consensus verifier, the explorer of transfers and their proofs, /transfer (ICS-20 on both corridors) and /issuer (ICS-27).
    • Wallets: Stellar Wallets Kit, MetaMask and Keplr, or generated test keys funded by friendbot and the api's faucet.
    • Data: it reads everything chain-specific from GET /config.
  • ipsa-landing (ipsa.dev, Vue 3 on Cloudflare Pages): one page covering the project, the eight consensus checks, the applications and progress; it links to the app.
  • ipsa-tooling (ipsa CLI): ipsa start brings up the local stack (from its stack/: Stellar quickstart or testnet, an ibc-go simd chain, the api, the gateway, the watcher, Postgres, the relayer, and the proof API on the host). It also:
    • deploys contracts from a checksummed release or a local build, creates clients and registers counterparties (client lifecycle lives here, never in the relayer);
    • sends and relays transfers and GMP calls, and runs the issuer example;
    • runs the ICS integration flows (ipsa test).
  • ipsa-dev-kit: the ipsa crate (no_std; modules core, types, app, gmp, ics20) for contracts that build on ipsa, and examples:
    • a governance dispatcher over GMP;
    • a remote treasury controlled from another chain;
    • payouts over ICS-20;
    • the issuer console and its ERC-20. The examples test against the ipsa-contracts release wasm, verified against SHA256SUMS.

7. Flows

7.1 Stellar → Cosmos Hub (ICS-20)

7.2 Cosmos Hub → Stellar

The user signs a MsgTransfer in Keplr. The gateway builds unsigned update_client (a Tendermint header with the trusted validator set, read through the api) and recv_packet transactions. The relayer signs and submits them to Stellar, where the 07-tendermint client verifies the header and the IAVL proof, and the transfer app releases the escrowed asset or mints a voucher. The acknowledgement goes back to the Hub with a Stellar ICS-23 proof, verified by the 08-wasm client.

7.3 Stellar → Sepolia (ICS-20, ICS-27)

A Stellar → Sepolia transfer arrives in about two minutes, most of it proving. A round trip needs four proofs, about 1.6 PROVE.

7.4 Sepolia → Stellar

The user signs ICS20Transfer.sendTransfer in MetaMask, after an approval when needed. Then:

  1. eth-to-stellar waits for a finality update covering the send.
  2. It requests proofs of the sync committee update and of the router's storage slot (eth_getProof).
  3. It returns Soroban host functions.
  4. The relayer submits them one transaction at a time.
  5. The SP1 Ethereum client on Stellar verifies the Groth16 proofs through stellar-sp1-verifier.
  6. The transfer app mints the voucher, for example XLM's or WETH's.

7.5 Timeouts

A packet sent from Stellar that the destination did not receive before its timeout is refunded: the relayer proves the receipt's absence (ICS-23 non-membership) and submits timeout_packet, and the transfer app returns the escrow. This is verified for packets sent from Stellar to the Hub; timeouts are not built in the stellar-to-eth module yet.

8. Build, release and deploy

Repository CI Release
ipsa-contracts fmt, clippy, tests, audit v* tag = workspace version: wasm, SHA256SUMS, testnet deployment, GitHub release
ipsa-backend fmt, clippy, tests, audit; secret scan vX.Y.Z tag: multi-arch images and the OpenAPI document; latest on every push to main
ibc-contracts upstream untouched, reproducible ELFs and vkeys, differential and negative tests, Solidity client tests proof API image
ibc-relayer upstream image on stellar-v* tags
stellar-light-client double reproducible build v* tag: wasm, .wasm.gz, checksums
stellar-consensus-verifier fmt, clippy, tests, docs, wasm build, audit crates.io on tags, refusing a version from a different commit
ipsa-scp-verifier fmt, clippy, tests, audit, a live mainnet ledger crates.io on tags
ipsa-dev-kit fmt, contracts, clippy, tests, Foundry, audit crates.io on tags
ipsa-tooling fmt, clippy, tests, audit none; installed with cargo install --path .
ipsa-app type check, lint, formatting, tests, static build Cloudflare Pages on every push; previews per PR
ipsa-landing pnpm check Cloudflare Pages

The off-chain services run as containers behind api.ipsa.dev, and the app and the landing page are static sites.

Live identifiers on testnet (the app reads them from GET https://api.ipsa.dev/config):

Chain Item Value
Stellar testnet router CBA7QRXNLV2VGXNB7JALC4ZRPD4Q3SFAJRXFS3NTUJBFLLIDT6AEECSS
Stellar testnet transfer app CBGZLIITTVINCMCJ2AQR4JZPALRKU5Z3ZDGEJ6UBJFDWYYCHO5ELT2LO
Stellar ↔ Hub clients 07-tendermint-1 on Stellar ↔ 08-wasm-464 on provider
Stellar ↔ Sepolia clients sepolia-0 on Stellar ↔ stellar-testnet-2 on Sepolia
Sepolia ICS26Router 0x3f09B9D0E8453C91FBA256F8e2A39A7200D7604B
Sepolia ICS20Transfer 0x28b94Ea9eb1f91686AcEA5c46A1e9331E51CC7D4
Sepolia SP1ICSStellar 0xf3b1f636aE281dCac3F5c98fE26b489529EA64b0
Sepolia ICS27GMP 0xC0dF53871B8E5De14298b49C6C3Fe4e635cA3FF0
Sepolia WETH9 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14
Issuer example BRLA console (Stellar) and token (Sepolia) CAGPGBOJ…JLDM and 0xd379209c…A32b

9. Repository dependencies

Repositories depend on each other only through published crates, release assets and images, never through path dependencies. Repositories marked private are not public yet.