ISMP Bridging with Satsuma
We ran two independent satsuma chains — different genesis, different validators — and moved value between them with no multisig, no custodian, no middleman. Each chain verifies the other's GRANDPA finality on-chain, then verifies a state proof. 4 seconds end to end.
In the series we restarted a chain without trusting the restart. This post answers the natural next question: can two running chains talk to each other with the same discipline — cryptographic verification instead of trusted parties?
Yes. We took two satsuma-mini — different genesis, different validator sets, not even peered on the same network — and bridged them with , the Interoperable State Machine Protocol that underpins . No hub chain, no bridge token, no committee. Each chain runs a of the other inside its own : it verifies the other chain’s signatures, then verifies a against the finalized root, and only then acts.
What “trustless” means here — precisely
A bridge has two jobs: know what happened on the other chain, and act on it. Nearly every bridge hack in history shortcut the first job — a , an oracle committee, or a sequencer that simply asserts what happened. Whoever controls that assertion controls the bridge.
Here the first job is done by cryptography alone, on-chain:
- Finality. Chain B stores chain A’s current GRANDPA . The hands B a finality proof — A’s validators’ signatures over a block — and B verifies those signatures in its runtime. Now B knows block #N is final on A, because A’s own says so.
- State. The relayer hands B a ~300-byte Merkle proof that “transfer request X is committed in A’s state at #N”. B checks it against the it just verified.
- Dispatch. Only then does B’s runtime mint.
- − burn 5 UNIT from the sender
- request committed to state (child trie)
- block finalized by A’s validators
- ✓ A’s GRANDPA signatures check out
- ✓ request proven against A’s state root
- + mint 5 UNIT · 4.0 s after send
The relayer that carried the proofs is a courier, not an authority. It can go offline (run two). It cannot lie — we make it try at the end.
To be precise about the remaining trust: chain B trusts chain A’s validator set about chain A — the same honest-supermajority assumption A’s own users already make. The bridge adds no new trusted party. (Same vocabulary discipline as the re-genesis posts: this is the trust the chain already has, minimized to cryptography everywhere else.)
The demo

One command boots the whole thing: two 500ms chains (Alice validates mina,
Bob validates minb), an init step where each chain’s runtime is given the
other’s consensus state (a GRANDPA light client, created via
ismp-grandpa), and our relayer — then:
- A → B: burn 5 UNIT on
mina→ the relayer ships a consensus proof (~1.5 KB) and a request proof (~300 B) →minbverifies both and mints 5 UNIT. 4.02 seconds after the user hit send. - B → A: the mirror image, 4.08s. The bridge is symmetric.
- Tamper test: the relayer forges a transfer of 1,000,000 UNIT that chain A never made, and submits it with the best proof it can obtain — a real proof of A’s state. Chain B’s transaction pool rejects it outright: the forged request’s commitment isn’t in A’s verified state, and no valid proof can exist for a request that isn’t there.
Burn, prove, mint
The product feature on top is a ~320-line
(pallet-xtransfer): on the source,
dispatch an ISMP POST request addressed to the same pallet on the destination,
mint on proven receipt. Timeouts refund the sender automatically —
ISMP’s request lifecycle (request / response / timeout) gives you that for
free, which is exactly the kind of machinery you’d otherwise hand-roll and get
wrong.
Under it sit two Polytope pallets, both pinned to the polkadot-sdk 2503
release train: pallet-ismp (request commitments in a
, unsigned datagram execution) and
ismp-grandpa (the consensus client). The key config
choice: Coprocessor = None. That’s what makes this direct peering — no
Hyperbridge hub in the middle, no Polkadot dependency, just two chains
verifying each other. The same pallets, pointed at Hyperbridge instead, reach
EVM chains and parachains — that’s the upgrade path, not a rewrite.
What it costs
The unsigned-datagram model deserves a highlight. Every proof the relayer submits rides in an unsigned — the verifies the proofs themselves before admitting it. Validity is the admission ticket; there is no fee. Our relayer ran the whole demo with an unfunded account. The chain donates block space to verified bridge traffic (a deliberate choice we control; on a permissioned chain, proof verification is the DoS gate).
The user pays one ordinary fee for the burn extrinsic: 0.0006 UNIT, the same as any local transfer.
Things that bit us (so they don’t bite you)
grandpa_proveFinalitycannot work on a static chain. If the GRANDPA authority set has never changed, the node’sAuthoritySetChangesindex is empty and the RPC returns “Block not covered by authority set changes” for every block, forever. Every standard relayer leans on that RPC. Our fix: scan recent finalized blocks for storedFRNKinstead.- Justification cadence is the bridge’s heartbeat. Nodes persist a GRANDPA
justification every 512 blocks by default — 256 seconds at our block time.
We set
justification_generation_period = 4(one every 2s), which is what makes 4-second delivery possible. - Run nodes with
--enable-offchain-indexing true. ISMP request bodies are persisted through offchain indexing; without the flag the chain works but relayers can’t read what to relay — silently.
Caveats we owe you
- One validator per chain here, so finality proofs are one signature; justifications grow linearly with authority-set size.
- Local loopback: WAN latency adds to delivery time equally for any bridge.
- Challenge period set to zero and no relayer incentives — reasonable when one operator runs both chains; knobs exist for when that’s not true.
Next post: the same bridge rebuilt with Parity’s pallet-bridge-grandpa — the
stack that runs Polkadot↔Kusama — and the head-to-head, including exactly who
pays what.