Orbit: liquid staking for the self-stake incentive layer, not just nomination

Mind you this is a side project, no one should send it real DOT. I’ve been working on this because June 2026 staking split left a hole, and I wanted to see if a small parachain could sit in it.

Every liquid staking token on Polkadot wraps nomination. You deposit DOT, it gets nominated, you get a receipt, you earn the nomination rate. That’s the whole category. Fine product. Also the only product, which is how you know there’s an obvious second one sitting there unused.

Since Referendum 1909 the reward split is two buckets. Nomination pays a low rate, and Referendum 1910 made nominators unslashable. Validator self-stake pays a higher rate out of a fixed incentive pot, and that bond can be slashed. You only earn the second one by bonding your own DOT as a validator’s self-stake and running the node, or paying someone who will. No current LST touches it. They were all designed before the bucket existed.

Orbit is my attempt, and the tokens work the way any liquid staking token works. You hand over DOT. The protocol stakes it. You get a receipt in your wallet. The number of tokens you hold stays the same. Rewards go into the pool, so the exchange rate moves and each token is worth more DOT than when you minted it. That is the yield. You can hold it. Later, once someone lists a pool, you can swap it for DOT or post it as collateral without waiting out an unbond. When you want the underlying back from the protocol itself, you redeem and wait.

  • oDOT is the plain one. It wraps nomination, the low rate, and a Hub slash does not reduce it. This is the token you would hold, swap, or use as collateral. Same job as vDOT or stETH, pointed at nomination.

  • eDOT is that same receipt pointed at validator self-stake. The DOT is bonded as the validator’s own stake, an operator runs the node through a StakingOperator proxy, and that operator cannot move the funds. The extra incentive shows up in the exchange rate, same as oDOT. The difference is the slash: it comes out of the eDOT you are holding, so the token redeems for less DOT. You would not drop this into a stable pool on day one. You hold it if you want the higher rate and you accept that the token itself can go down.

Two tokens, because the target mix is about 0.69% self-stake. Blend that into one receipt and the high rate disappears under a pile of nomination capital. You’d have built a staking token with extra steps. The longer version of why the curve is concave, and why many small slots beat one large one, is in the Spec. This post is what I have actually run.

What I ran

Today I tested the loop on a local Zombienet network: deposit, the mix ceiling refusing an eDOT mint, then the same mint succeeding after more oDOT, then a redeem landing in the unbond queue.

Pallets

Four custom pallets, plus the usual parachain-template set (parachain-system, aura, collator-selection, session).

pallet-odot and pallet-edot are ERC-4626-shaped vaults. rate = V/S. A deposit mints shares at the live rate. A protocol redeem queues shares for an unbonding period and pays out at the claim-time rate. On this test runtime that delay is 10 blocks, so I can finish a demo before lunch. Production would have to track Hub’s real unbonding period. eDOT also applies Hub slashes via do_apply_slash, and it refuses to slash into the dead-share floor, so a duplicated feed event can’t quietly eat a loss and then pretend it was applied.

Both vaults mint 1,000 dead units into the pallet account at genesis. A virtual-only offset breaks slash-plus-redeem: a live claim can ask for more than the vault holds.

pallet-hub-feed is how rewards and slashes get in. An oracle FeedOrigin reports nomination rewards, self-stake rewards, and eDOT slashes. Each report carries a hub_event_id. Replay the same id and the pallet ignores it. On the test runtime that oracle is a single dev key. That’s fine for Zombienet and absurd for anything with real money.

pallet-hub-bridge moves balances between Asset Hub and Orbit. Staking and balances live on Hub after the November 2025 consolidation. The vault math lives on this parachain. Same oracle, same hub_event_id, new event type. Three legs:

  1. Someone transfers DOT to Orbit’s Hub receiving account. Ordinary transfer, no XCM from the depositor. The oracle sees it and calls report_bridge_deposit, which mints the matching balance on Orbit. Until that report lands, the depositor has sent DOT and holds nothing. The relayer is the product.
  2. An operator bonds that capital onto Hub validator stashes. Rewards and slashes from those stashes come back through pallet-hub-feed.
  3. Burning shares queues a withdrawal with the amount and the user’s Hub address. The operator pays it on Hub. The oracle reports fulfillment.

The mix ceiling

If eDOT can mint faster than oDOT can get those validators elected, the vault is printing a self-stake yield nothing is earning. ensure_within_mix_ceiling runs on every eDOT deposit.

The numbers in the runtime are placeholders, not parameters I derived from live Hub election config:

  • EdotSelfStakeFloor = 10,000. Minimum self-stake for one slot.
  • EdotElectionThreshold = 1,440,000. Total stake I assumed a slot needs in order to clear election, self-stake included. Nomination per slot is the difference, 1,430,000.
  • MaxPhi = 10_000 / 1_440_000 ≈ 0.69%.

The check computes φ = Σσ / (Σσ + Σν) with live oDOT backing and rejects the mint with MixCeilingExceeded if the deposit would push φ over that.

Equal genesis dead shares (1,000 on each vault) start φ at 50%. The ceiling is 0.69%. So the first eDOT deposit bounced, which is what you want from a fresh chain that has no nomination pile yet. I funded a much larger oDOT balance, φ dropped under the ceiling, and the same deposit minted. I queued a redeem right after.

Custody

v1 is not trustless, and I don’t want to imply it is. StakingOperator means the person running the node can’t transfer the stash. Someone still holds the stash key. There isn’t a clean way for a pallet to own a validator stash and call staking by itself, so v1 puts those keys on a threshold multisig (3-of-5 is the sketch, signers are not chosen). Stash addresses would be published on-chain. NAV is supposed to be checked against Hub’s staking ledger, not against whatever the multisig says it bonded.

If the Hub view goes stale past a freshness bound, mints and instant redemptions pause. Queued redeems can still complete once the feed is fresh. Nothing mints against a reward the oracle hasn’t reported.

The local oracle being Alice’s key is part of the same picture. A missed deposit report and a missed slash report are the same trust decision wearing different hats.

Paseo

Orbit is registered on Paseo as para 2002. It has produced zero blocks there. The collator syncs. The relay won’t schedule it.

On-demand coretime on Paseo looks broken chain-wide. The traffic multiplier used for spot price is pinned at u128::MAX, and the order queue still has requests sitting in it from unrelated parachains, days old. I filed that once I could see it on-chain: paseo-network/support#170. Separate from that, Asset Hub Paseo traps (unreachable) on an ordinary signed transfer because of a transaction-extension mismatch. A maintainer already root-caused it: paseo-network/runtimes#434.

So the demo above is Zombienet. It gives the parachain a slot instead of making me buy one from a market that isn’t clearing. That covers the pallets. It does not cover the bridge against Asset Hub. Next step is a healthy public network, or my own persistent node.

Try it

cargo +1.93.1 build -p parachain-template-node --release
npx --yes @zombienet/cli --dir /tmp/zn-run --provider native spawn zombienet.toml

You get a relay and an Orbit parachain producing blocks. The README has the toolchain pin, the macOS note (Parity doesn’t ship a relay binary, so you build one), and the deposit walkthrough: fund a test account, point the dashboard at the local node, run the bridge relayer. Talisman, SubWallet, and the Polkadot.js extension connect through injectedWeb3.

Still unfinished

The deposit leg of scripts/hub-bridge-relay.ts subscribes to live Hub events and does not catch up if it misses one. The withdrawal leg does sweep pending withdrawals on startup. I found the deposit gap by watching a deposit not show up. A relayer that can skip a deposit is not something you point funds at.

Also still true: unaudited, the self-stake weight function in the whitepaper is a model rather than a derivation from the live staking pallet, φ ≈ 0.69% is a design point from those placeholder constants, and the multisig signers don’t exist. Unbonding on the demo runtime is 10 blocks. The oracle is one dev key.

What I’d like pushed on

Do 10,000 self-stake and 1,440,000 total stake-to-get-elected look anything like current Hub election reality, or did I invent a nice fraction? And is an oracle-attested three-leg bridge a reasonable MVP, or should this have been Hub-adjacent accounting from the start so there is no second chain to bridge to? The whitepaper already leans that way later (§11.4, §11.6). A side project can carry a parachain. If the end state is “don’t have one,” a product probably shouldn’t.

Spec: WHITEPAPER.md. Repo: github.com/Kanasjnr/Orbit.

I would really love feedback and comments on this..
Thanks