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 with08-wasm). The Hub runs the Stellar light client as a wasm contract; Stellar runs a07-tendermintlight 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.hexrequires 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
previousLedgerHashbefore 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(), andcommit_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:
-
the
EXTERNALIZEsignatures over the network id; - that the committing nodes form a quorum containing a slice of the trust root;
-
that the committed value is byte-identical to the header's
scpValue; - that slot N+1 is confirmed and names this ledger as its predecessor;
- continuity with the stored consensus state at N-1, when there is one;
- that close time increases;
-
with a state root: the transaction results hash to
txSetResultHash, and the transaction is a successful call carrying exactly oneibc_rootevent 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;/verifyfor the app's verifier page (ipsa-scp-verifier3.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 /relayhands a transaction to the relayer's gRPCRelay;/relay/status,/relay/transfersand/relay/transfers/{id}/proofsread the relayer's and the proof API's tables. -
Deployment:
GET /configserves every identifier the app uses, so nothing chain-specific is built into the frontend;/chainsand/corridorscome 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:
-
StellarGatewayQueryandStellarGatewayMsg: heights, headers with their SCP evidence, packet commitments, receipts and acknowledgements with ICS-23 proofs, tree siblings (QueryTreeSiblings, used bystellar-to-eth), and unsigned transactions for client updates, receives, acknowledgements and timeouts; -
the proof API's
ProofApiService, which the proof API'sforwardmodule 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:- Reads the Stellar transaction and the router's events from Soroban RPC.
- Refuses, before paying for anything, a packet Sepolia already holds a receipt for.
-
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. - Verifies the evidence natively, and rebuilds the client's trusted state.
- Builds the membership witness from the gateway's siblings.
- Requests the update-client and membership proofs on Succinct.
-
Returns
ICS26Router.multicall(updateClient, recvPacket | ackPacket). Timeouts towards Sepolia and several packets per transaction are not built yet.
-
eth-to-stellar:- Reads the Sepolia receipt.
- Checks the Soroban router's storage.
- Rebuilds the trusted state from the beacon API.
- Waits for a finality update signed by at least two thirds of the sync committee.
- Requests the proofs.
-
Returns Soroban host functions (
update_client× N, thenrecv_packetoracknowledge_packet) for the relayer to sign. It also implementsCreateClient.
-
forward: passesRelayByTxtoipsa-gateway. -
Proof history: with
PROOF_HISTORY_DB_URLset, every proof is recorded inproof_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), whichipsa-api'sPOST /relaycalls. -
For every packet it calls
RelayByTxon 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_hashesin its config. -
It records transfers in Postgres (
ibcv2_transfersand 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 onstellar-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_evidenceorcontradicted. -
Server mode:
--serve 8110exposes/verifywith 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.
-
Pages: the documentation, the consensus verifier, the explorer of transfers and their proofs,
-
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 (
ipsaCLI):ipsa startbrings up the local stack (from itsstack/: Stellar quickstart or testnet, an ibc-gosimdchain, 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
ipsacrate (no_std; modulescore,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:
-
eth-to-stellarwaits for a finality update covering the send. -
It requests proofs of the sync committee update and of the router's storage slot (
eth_getProof). - It returns Soroban host functions.
- The relayer submits them one transaction at a time.
-
The SP1 Ethereum client on Stellar verifies the Groth16 proofs through
stellar-sp1-verifier. - 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.