Developers
Cross-chain with LayerZero V2
How EVM Positions reach the Solana ledger and back.
simETH vault · PositionVaultsimHYPE vault · PositionVaultOAppGemas · Debt · Matches · BackingsPositions stay on their home chains while Gemas, Debt, Matches and Backings live on Solana, so four messages cross chains. ADR 0002 picks LayerZero V2 on Solana devnet, Ethereum Hoodi and HyperEVM testnet: the only transport verified to reach all three with managed relaying. A trusted Tesoro relayer was rejected in favour of real infrastructure.
The four messages
| # | Message | Direction | Effect |
|---|---|---|---|
| 1 | PositionLocked | EVM vault to Solana | A Position now exists: owner, LST, Principal, Horizon. The bridge applies it to tesoro_core. |
| 2 | YieldAccrued(cumulative) | EVM vault to Solana | Cumulative yield. tesoro_core repays Debt first; the rest is free yield. Applied once per sequence. |
| 3 | Unlock | Solana to EVM vault | Debt is zero and the owner unlocked: the vault releases Principal to the owner. |
| 4 | PayYield(to, amount) | Solana to EVM vault | A holder redeemed Gemas Sourced from this Position: the vault pays that amount in LST shares, never more than accrued yield. |
Wire format (identical in Solidity and Rust, abi.encodePacked, big-endian): header msgType u8 | sourceChain u32 (eid) | positionId bytes32 | seq u64 (45 bytes), then the body. The positionId is the vault address (20 bytes), the vault's Position id (u64) and 4 zero bytes, so two vaults on one chain never collide and a vault can speak only for its own Positions. Every handler is idempotent per sequence number.
Pieces
| Piece | Role |
|---|---|
PositionVaultOApp | A PositionVault that is a LayerZero OApp. openPosition sends PositionLocked, reportYield sends YieldAccrued, and _lzReceive feeds Unlock and PayYield to the vault. |
tesoro_bridge | Solana OApp: an inbox for inbound facts, permissionless CPIs into tesoro_core, permissionless outbound sends. |
SourceAuthority | A tesoro_core PDA per source chain naming the signer allowed to report PositionLocked and YieldAccrued. The bridge authority PDA is registered for Ethereum and HyperEVM, so an Ethereum authority cannot touch a HyperEVM Position. |
Flow and who pays
- Link, once. The Account's Solana wallet calls
tesoro_bridge::link_evmwith an EIP-191 signature from the EVM wallet overTesoro: link EVM address to Account <TesoroAccount PDA>. The bridge recovers the signer on chain withsecp256k1_recover, so nobody can claim someone else's address. - Inbox record. Anyone creates the record with
init_position(eid, positionId)before the message (about 0.003 SOL), because the default executors cap the native drop to Solana at 0 and rent cannot be paid insidelz_receive. - openPosition(lstAmount, months). The caller pays the LayerZero fee in the chain's native coin (the app reads
quotePositionLocked()and adds about 20%; the excess is refunded). - apply_locked. Permissionless: the bridge CPIs
open_position_from_source, signed by its PDA.PeerConfigper vault supplies decimals and rate. Principal must fitu64, so up to about 18 ETH; the vault refuses larger deposits. - Advance. The oracle path in tesoro_core: the owner and the oracle co-sign, the oracle supplies the issuance price.
- reportYield, then apply_yield. The vault accrues first, so the cumulative figure includes everything earned up to that block. Debt falls automatically.
- Redeem. The holder calls
tesoro_bridge::redeem_to_evm, which CPIsredeem_from_sourceand records the units in aClaim. Anyone then callssend_pay_yield, which advancesunits_sentin the same transaction so a unit is paid once. Plainredeemrefuses EVM-sourced Sources (audit F-07). - Unlock. After
tesoro_core::unlock, anyone callssend_unlock. Yield still owed to redeemers stays in the vault.
lz_receive deliberately only records the fact: its account list is derived from the message alone and cannot include the Account, the open Source or rent-funded accounts. Applying is a second, permissionless transaction.
Endpoints and latency
| Chain | Chain id | LayerZero eid | Observed delivery |
|---|---|---|---|
| Solana devnet | n/a | 40168 | Solana to Hoodi about 4 to 5 minutes |
| Ethereum Hoodi | 560048 | 40449 | Hoodi to Solana about 2 minutes |
| HyperEVM testnet | 998 | 40362 | Both directions delivered in roughly 40 to 80 seconds |
The default pathway configuration uses LayerZero Labs as the single required DVN. Fees seen: about 0.0001 ETH per Hoodi send, about 0.0074 HYPE per HyperEVM send (so a wallet needs roughly 0.02 HYPE to open a Position), and about 0.0003 SOL per Solana send.
HyperEVM testnet gas
There is no HYPE faucet to relay, so the app server sends a small top-up (0.03 HYPE if the wallet holds under 0.02, once a day per wallet, with a global hourly limit) from a low-balance relayer key.
Failure behaviour
- An out-of-order delivery could once strand an Unlock or PayYield (audit F-04). Vaults now record each sequence as handled, so order does not matter, and the vaults were redeployed.
- A PayYield larger than the vault's accrued yield reverts, and a reverted receive rolls back its handled mark, so LayerZero can retry it once yield catches up (F-13).
- The vault always caps a payout at its own accrued yield, so a lying DVN could report false yield but could not drain Principal through PayYield.