OmniFi Tranche docs

Covers the single-chain and the multichain version of the Senior/Junior tranche vault: structure, how they work, how to use them, functions and events.

Introduction

OmniFi Tranche splits one yield-source pool into two layers and sells them as products with different risk appetites. Senior takes a fixed annual return first; Junior takes everything left over but absorbs losses first.

There are two versions. Single-chain version keeps the tranche structure and settlement entirely inside one chain. Multichain version extends the same tranche structure into a hub-spoke layout, so one product is sold on several chains. Whichever chain you join from, it is the same tranche at the same price; the ledger and settlement live on the hub chain.

The user surface is the same ERC-7540 async vault in both versions. The sections below on what a tranche is and on the ERC-7540 async vault apply to both.

What a tranche is

A tranche is one yield pool divided into layers of differing priority. This system uses two tranches, Senior and Junior.

SeniorJunior
ReturnTakes a fixed annual APR firstTakes everything left after Senior is paid (residual)
LossOnly takes a loss once Junior is fully wiped outAbsorbs losses first (first-loss)
CharacterFixed return · principal protection firstLeveraged variable return

Waterfall allocation

At every settlement the product total valuation (product NAV) is divided across the tranches. Senior NAV is min(Senior target, product NAV) — the target is principal plus accrued fixed APR. Junior NAV is everything else. Gains fill the Senior target and the whole remainder goes to Junior; losses eat into Junior NAV first.

In the implementation the Senior target accrues at each settlement as target += target × APR × elapsed ÷ 1 year. Interest is applied to the previous target again, so it compounds at the settlement interval. A deposit raises the target by the principal added; a redemption lowers it by the fraction of shares burned.

The waterfall in numbers

Say 1,000 is raised as Senior 800 · Junior 200, the Senior APR is 5%, and the product runs for a year. After a year the Senior target is 840. Whatever the product NAV turns out to be, the rule is the same —everything up to 840 is Senior's, everything above it is Junior's.

Product NAVTotal returnSenior NAVSenior returnJunior NAVJunior return
1,100+10%840+5%260+30%
1,040+4%840+5%2000% (break-even)
1,0000%840+5%160−20%
840−16%840+5%0−100%
800−20%8000%0−100%
700−30%700−12.5%0−100%

One return on one pool of assets splits into two completely different payoff curves. Senior is untouched down to −16% and only starts losing below that; Junior has to clear +4% to make anything, but past that it takes the entire gain. Junior is only 200 of the 1,000, so from Junior's side this is 5x leverage.

Share price

The share price of each tranche is tranche NAV ÷ shares outstanding for that tranche. A deposit gives you deposit ÷ price shares, and a redemption returns share count × price in assets. Deposit principal that has not settled yet is excluded from NAV even once it is deployed, so new money cannot move the price for existing holders.

Risk model

Tranching does not create return — it only re-divides one P&L into two claims ordered by priority. Senior is a threshold structure: capped at a fixed APR, losing nothing until the attachment point is crossed. Junior is leveraged by total ÷ Junior's share of it.

SR attachment −16% JR break-even +4% −40% −30% −20% −10% 0% +10% Asset total return g → −100% −50% 0% +50% Senior Junior Assets themselves (no tranching)
At α=80% (Senior share) and r=5%. Junior (orange) is a straight line of slope 5 that reaches the −100% floor at −16%; Senior (blue) is flat at +5% and only bends below −16%. The weighted average of the two solid lines is always the dashed line (the assets themselves).

Three conclusions matter. ① Senior's risk is non-linear in α (the Senior share): raising α from 80% to 90% multiplies the loss probability by roughly 9. ② Even with no asset loss, if the yield source pays less than α·r the Junior buffer drains at a fixed rate. ③ α here is endogenous — deposits set it — so a Senior investor should watch the current tranche NAV ratio rather than the fixed APR.

The full analysis — the derivation, α·μ·σ sensitivity charts, empirical 2008 structured-finance data (RMBS AAA 0.42% vs CDO AAA 55% losses) and prior on-chain tranching (BarnBridge, Idle, Gro) — is in the separate risk document.

ERC-7540 async vault

In both versions the user entry point is a TrancheVault following the ERC-7540 async vault standard. Deposits and redemptions do not fill immediately; they run in three steps, request → settle → claim.

  1. RequestrequestDeposit / requestRedeem. Assets or shares move into escrow and the request becomes Pending.
  2. Settle — the operator fixes NAV and sets the price. The request becomes Claimable.
  3. Claim — the user calls deposit/mint (receive shares) or withdraw/redeem (receive assets). Any time.

No price is needed at request time. The price is fixed at settlement, and claiming is a local operation that picks up the fixed value. That separation is what lets the multichain version absorb cross-chain round-trip latency.

The share token is an ERC-20 separate from the vault (ERC-7575), with ERC-1404 transfer restrictions, so only whitelisted addresses can hold or transfer it. The vault is only an entry point and has no mint authority; only the Manager can mint or burn shares.

Glossary

GlossaryMeaning
shareTranche ownership token. Senior and Junior shares are separate ERC-20s.
escrowWhere assets and shares sit between request and settlement. The Manager owns it.
waterfallThe rule that divides the product NAV, Senior first and the remainder to Junior.
Yield-source adapterA contract wrapping one yield-source protocol (Compound, Morpho, and so on). Handles deployment, withdrawal and valuation.
settle / settlementThe procedure that fixes NAV and approves pending requests. In multichain it runs in rounds (settlementId).
requestIdIdentifier for one deposit or redeem request. Issued by the Manager.
settlementIdSettlement round number. Increments by 1 on every successful settlement.
NAVNet asset value. Multichain qualifies the layer — adapter NAV (reported by an adapter) → chain NAV (one chain's sum, observed) → product NAV (fixed after adjustment) → tranche NAV (after the waterfall).
hub / spokeThe multichain role split. The hub handles the ledger, NAV and allocation decisions; spokes handle the user surface and execution.
CCCPCross-chain infrastructure that carries a message and assets together. Relayers forward them.
pallet precompileA fixed-address interface for calling the hub chain runtime (pallet) from EVM. It is the source of truth for the multichain ledger.
Single-chain

Definition

In the single-chain version a whole tranche set is self-contained on one chain. User entry, deploying into yield sources, computing NAV and settlement are all contract calls on the same chain. It is the starting point for the multichain version, and the core (Manager · Vault · ShareToken) shares the same lineage across both.

Product policy lives in Valuation. Tranche count, Senior APR, settlement mode and yield-source weights are all Valuation deploy parameters and settings; the core (Manager · Vault · ShareToken) is reused regardless of product.

Structure and components

Entry
Vault SR
ERC-7540
Entry
Vault JR
ERC-7540
Core
Manager
escrow · request state
share mint/burn
Product
Valuation
NAV · waterfall
price · allocation
IAdapter
Adapter ×N
Compound · Morpho …
External
yield source
interest accrues

Behind each Vault sits one ShareToken (ERC-1404) per tranche. Arrows are call delegation; assets flow user → Manager (escrow) → Valuation → Adapter → yield source.

ComponentCountRole
TrancheVault1 per trancheERC-7540/7575/4626 surface entry point. Holds no state and no mint rights — everything is delegated to the Manager.
ShareToken1 per trancheERC-20 + ERC-1404 transfer-restricted share. A stricter regulated form (ERC-3643 surface) is also available.
TrancheManager1 per productEscrow custody, request state, requestId issuance, share mint/burn, claim handling. A general-purpose core that knows nothing about any particular yield source.
Valuation1 per productNAV computation, waterfall, share price fixing (settle), targetWeights allocation, adapter management. This is where product policy lives.
Yield-source adapter1 per yield sourceDeploy (requestSupply), withdraw (requestWithdraw), value (totalAssets). Adding or swapping a yield source is a matter of attaching or detaching one adapter.
WhitelistHookShareableThe whitelist that the ShareToken 1404 hook queries. The vault deposit gate uses the same check.

Every contract is deployed behind an ERC-1967 proxy and can be upgraded. Vault and share are separate (ERC-7575), so replacing the vault logic does not touch holder balances.

Settlement modes — SYNC / ASYNC

Settlement speed is decided at two layers. Adapters declare, separately for deposits and withdrawals, what their yield source actually supports (SYNC = fills immediately, ASYNC = request then wait). Valuation sets, through its deploy parameters (depositMode / redeemMode), the settlement behavior the product promises the user.

There is one constraint: Valuation cannot be faster than its adapters. You cannot promise the user an immediate settlement (SYNC) when the money cannot leave the yield source immediately (ASYNC). With several adapters, the slowest one sets the bound.

In an ASYNC flow, fixing the redemption price and starting the yield-source withdrawal both happen at the operator's settle. If the yield source is ASYNC, two settles are needed — the first fixes the price and starts the withdrawal, and the second, once the withdrawal is ready, makes the request claimable.

How deposits and redemptions work

Deposit

  1. The user calls requestDeposit(assets, controller, owner) — after the 1404 whitelist check, assets move into Manager escrow and, in the same transaction, Valuation deploys them into the adapters. If targetWeights is set it splits by those weights; if not, the whole amount goes to the depositTarget adapter.
  2. The operator calls settle(reqId) — the price is fixed at the NAV of that moment and the share count is set.
  3. The user calls deposit/mint to claim the shares. There is also a keeperClaim path where a keeper claims on the user's behalf (shares always go to the controller).

Redemption

  1. The user calls requestRedeem(shares, controller, owner) — the shares are locked in escrow (not burned).
  2. Operator settle — fixes the price and runs the adapter withdrawal plan, splitting the withdrawal across adapters. The SYNC portion realizes immediately; the ASYNC portion issues tickets.
  3. Once every ASYNC ticket is ready, a batch claim — the user calls withdraw/redeem to receive the assets. The escrowed shares are burned at that point.

Cancellation

User cancellation is not supported. In both versions, calling cancelDepositRequest / cancelRedeemRequest reverts with CancelNotSupported. Once accepted, a request can only be ended by settlement.

The functions are kept so the ERC-7540 surface matches, but they do nothing, so a front end should not show a cancel button. Reversing a request that has already crossed chains can desynchronize the two ledgers, so multichain blocked it first and single-chain was aligned to the same rule.

A request orphaned by a lost cross-chain message is cleared by the operator with adminClearRequest — from the ledger only (compensating the assets is a separate procedure).

How to use it

Users

  1. Get whitelisted (the operator registers you on the WhitelistHook).
  2. Approve the base asset (USDC) to the vault with approve.
  3. Call requestDeposit on the Senior or the Junior vault.
  4. After settle, claim the shares with deposit(assets, receiver). maxDeposit(controller) greater than 0 means there is something to claim.
  5. Redemption is the same order — requestRedeem → wait for settle → redeem.

Operators

  1. pendingRequests() or pendingDeposits()/pendingRedeems() return the pending requests.
  2. isSettleable(reqId) filters them; then call settle or settleBatch.
  3. Change weights with setTargetWeights; move funds between protocols with rebalance. Both of those, plus adapter management and drain, are owner-only; the operator account can only settle.

Demo: Single-chain products →

Functions

TrancheVault — user surface (ERC-7540/7575/4626)

FunctionsDescription
requestDeposit(assets, controller, owner)Request a deposit. Assets move into escrow.
requestRedeem(shares, controller, owner)Request a redemption. Shares are locked in escrow.
deposit / mintClaim the shares of a settled deposit. Full claims only (no partial claim).
withdraw / redeemClaim the assets of a settled redemption.
cancelDepositRequest / cancelRedeemRequestAttempts to cancel. In the current configuration it always reverts (see Cancellation below).
keeperClaim(controller)A keeper performs the claim on the user's behalf. The proceeds always go to the controller.
setOperator(operator, approved)7540 operator approval. Only an approved operator can request on another controller's behalf.
pendingDepositRequest / claimableDepositRequest etc.Request state queries (same shape on the redeem side).
convertToShares / convertToAssets / maxDeposit etc.4626 surface. The preview family deliberately reverts, since the vault is async.

Valuation — operator surface

FunctionsDescription
settle / settleBatch / isSettleableSettle requests. A batch reverts entirely if an unready request is mixed in, so filter before calling.
pendingRequests / pendingDeposits / pendingRedeemsList every pending request (off-chain view only).
setTargetWeights(bps[])Set adapter allocation weights (summing to 10,000). If unset, the whole amount goes to the single depositTarget adapter. Adding or removing an adapter resets the weights, so set them again.
addAdapter / removeAdapter / forceRemoveAdapterRegister or deregister a yield-source adapter. force removes an unresponsive adapter by writing off its remaining assets.
rebalanceMove funds between adapters (valuation unchanged).
initiateDrain / completeDrain / supplyIdlePull funds out of an ASYNC adapter → hold idle → redeploy.
setOperatorDesignate a settlement-only operations account. Only settle/settleBatch can be called from that account; weight changes, adapter management and drain are owner-only (the owner can settle too).

Other

FunctionsDescription
Manager setPaused(bool)Blocks requests and claims entirely. true can be set by the owner or the keeper; clearing it (false) is owner-only.
Manager setKeeper / setVaultReplace the keeper account or the vault.
Adapter requestSupply / requestWithdraw / isReady / receiveReady / totalAssetsIAdapter surface. A SYNC adapter returns a final value at request time. ASYNC tickets are finalized with receiveReady (batched: receiveBatch).

Events

It follows the standard surface events as they are, so it works with general-purpose indexers.

  • ERC-7540DepositRequest / RedeemRequest, operator approval OperatorSet. No cancel event is emitted, since cancellation is unsupported. The standard event's requestId is always 0 and cannot distinguish requests — for a per-request join key use the Manager's DepositRequested(bytes32 requestId, …) and DepositsApproved/RedeemsApproved.
  • ERC-4626 — on claim, Deposit / Withdraw.
  • ERC-7575 — on the share side, VaultUpdate (vault replacement notice).
  • ERC-20 / 1404 — share Transfer (including mint/burn); whitelist changes are hook-contract events.
  • Operations — adapter registration, weight changes, rebalance, drain, pause and the other main operations each have a matching event.
Multi-chain

Definition

The multichain version sells one tranche product across several chains. A tranche is a product-global concept, so whichever chain you join from it is the same Senior and Junior at one price. A chain is only a route in.

The roles split in two. The hub (Bifrost testbed) handles ledger records, NAV aggregation and allocation decisions — only the place where every chain's valuation lands can decide price and allocation. Spokes handle the user surface (vaults) and execution (deploying into and withdrawing from yield sources). The hub can hold vaults and yield sources too, so the hub is sometimes an entry chain itself.

CCCP handles chain-to-chain communication. It carries the message and the assets (USDC) in one request, so there is no separate token bridge. The ledger's source of truth is the pallet in the hub runtime, and contracts read and write it through pallet precompiles at fixed addresses.

Structure and components

Spoke · Sepolia
Entry
Vault SR / JR
ERC-7540 + 1404 share
Core
Manager
escrow · CCCP handler
Exec
MultichainAdapter
yield-source adapter fan-out
Hub · Testbed
Endpoint
Orchestrator
inbound routing · outbound proxy (one global)
Engine
Valuation
NAV aggregation · waterfall · price · allocation (one per product)
Ledger
Pallet Precompile
0x200 config · 0x201 ledger · 0x202 permissions
Entry · Exec
Manager + Vault + Adapter
hub-side user surface and yield sources

Hub and spoke are symmetric — both hold a Manager, vaults and yield sources. Spoke messages ride the CCCP bridge; a request originating on the hub is handled in the same transaction with no bridge. An off-chain relayer watches send events and executes them on the receiving chain.

ComponentLocationRole
TrancheVault / ShareTokenper issuing chainThe same user surface as single-chain. One vault + one share per tranche.
MultichainTrancheManager1 per chain × productEscrow, requestId issuance, share mint/burn, CCCP message send and receive handlers. The multichain edition of the single-chain Manager.
MultichainAdapter1 per chain × productFan-out to that chain's yield-source adapters and aggregation of their valuations. Holds the local allocation weight cache (LocalAllocation).
Orchestrator(Hub)1 global on the hubThe hub's CCCP endpoint. Routes inbound messages to the right product's Valuation and proxies outbound sends.
ValuationDiamond1 per product on the hubThe settlement engine — aggregates chain NAVs, runs the waterfall, fixes prices, produces the allocation plan and is the caller that writes it to the pallet.
3 precompilesfixed hub addressesTrancheSystem(0x200) product/tranche/adapter configuration · Investments(0x201) request, approval and settlement ledger · TranchePermissions(0x202) source of truth for investor eligibility.
settle keeperoff-chainCalls tryUpdateNAV once per settle interval to trigger the settlement cycle.
Relayersoff-chainWatches CCCP events → signs → executes on the receiving chain.
The source of truth for a product's static information (tranche layout, APR, vault addresses, chain and adapter weights) is the pallet. Hub contracts keep no copy; they query TrancheSystem (0x200) every time.

ID scheme

productId — uint64

The top 32 bits are the product class prefix, the low 32 bits the sequence within that class. Always written in hex.

prefixClassExample
0x00000001single-chain0x0000000100000002
0x00000002multichain0x0000000200000009 (current product)

requestId — bytes32

requestId = productId (64 bits) | chainId (64 bits) | seq (128 bits)

Issued by the Manager. Cutting off the top 64 bits gives the full productId including its prefix; the next 64 bits are the EVM chainId of the requesting chain, and the low 128 bits are that Manager's local incrementing sequence. The (productId, chainId) prefix guarantees global uniqueness.

settlementId — uint256

The settlement round number. It increments by 1 on every successful settlement and is the join key for settlement-family events and records. A request that arrives while a settlement is collecting rolls over to the next round.

Tranche key — (chainId, vault)

The pallet identifies a tranche by the pair (chain ID, vault address). There is no separate tranche index field. For the same tranche type the share price is identical across chains; only the supply differs per vault.

Deposit flow

The principle is to centralize allocation decisions on the hub. Requested funds are sent to the hub in full, and each chain receives its own portion back and deploys it according to the plan the hub set.

Previously the requesting chain deployed its own portion first, which mixed funds that had not become shares yet into that chain's NAV and inflated that round's price by other people's money (fixed 2026-08-21). Now the requesting chain goes through the hub like every other chain to get its portion — one more bridge hop, but an accurate price.

  1. Request (entry chain, user tx)requestDeposit → whitelist check → Manager escrow → requestId issued. The requested amount, in full, is sent to the hub as a DEPOSIT_REQUEST (kind 1) message with the assets attached. (Each product has a minimum request amount; below it the call reverts with BelowMinRequest.)
  2. Record and allocate (hub) — the Orchestrator receives it and routes it to Valuation. It writes the pallet ledger with record_investment_request, computes the allocation plan, deploys the hub's portion immediately, and propagates each spoke's portion as an asset-carrying ADAPTER_SUPPLY (kind 7). A deposit that arrives while a settlement is collecting is held by the Orchestrator and deployed only after that round's price is fixed.
  3. Deploy (each spoke) — on receipt each chain deploys its portion into local yield sources. That includes the requesting chain.
  4. Settle · claim — share issuance and price fixing happen in the settle cycle, and the user can claim any time after that.

Joining from the hub delivers message 1 directly inside the same transaction, with no bridge — the event structure is identical.

Redeem flow

Symmetrically with deposits, withdrawal starts as soon as the request is made. That absorbs an ASYNC yield source's lead time (request → ready → claim) into the gap between request and settlement. The start is immediate; the asset movement is batched at settlement.

  1. Request (entry chain)requestRedeem → shares are locked in escrow → the expected payout is computed at the last fixed price → the local portion's withdrawal starts immediately → reported to the hub as REDEEM_REQUEST (kind 2), including the amount started.
  2. Delegate withdrawal (hub) — after writing the ledger, it sends ADAPTER_WITHDRAW (kind 8, flag=1) to every spoke except the requesting chain to start their portion's withdrawal. Each receiving chain computes its own localShareBps of the total expected payout. The hub's portion and any remainder start locally, with no message.
  3. Realized amounts collected (at settlement) — each chain sends what it realized to the hub as assets attached to SETTLEMENT_RESPONSE (kind 4).
  4. Payout (settlement fixed) — per-request payouts are set at the fixed price, and the per-chain payout allocation is sent down attached to SETTLEMENT_FINALIZE (kind 5). The receiving Manager credits it to the payout reserve (payoutReserve) and flips the requests to claimable.
  5. Claim — the user calls withdraw/redeem to receive them. The escrowed shares are burned when the user claims.
The payout is based on the fixed settlement price, not an estimate. If realized withdrawals fall short by settlement time, the requests that were not approved roll over to the next round. The payout reserve (payoutReserve) is shared across the product, so claims go out in the order withdraw was called — request order is not enforced.

settle cycle

The keeper calls the hub Valuation's tryUpdateNAV once per settle interval to start the cycle (if the interval has not elapsed it returns without doing anything). Redemption withdrawals have already started outside this cycle, at request time. settle covers price fixing, share distribution, redemption payouts, and deploying deposits that arrived and were held during collection.

  1. Collection starts (hub) — snapshots the hub's own valuation and sends SETTLEMENT_COLLECT (kind 3) to every registered spoke. The work for this round is fixed to the pending queue as of that moment.
  2. Response (each spoke) — aggregates its yield-source adapter valuations and replies with the chain NAV and a per-source breakdown (AdapterValuation) in SETTLEMENT_RESPONSE (kind 4). Assets realized from redemption withdrawals are attached here too.
  3. Finalize (hub) — collect every chain's response → adjust out unsettled deposit principal → waterfall → compute the fixed price per tranche. It increments the round number first (0 is genesis; real settlements start at 1) and records the adapter breakdown, the tranche settlement and the per-request approvals to the pallet under that number.
  4. Distribute (hub → each spoke) — sends the approval list and the fixed price down in SETTLEMENT_FINALIZE (kind 5), chunked if the list is large. The receiving Manager flips those requests to claimable.

Hub state becomes Claimable the moment the approval is recorded (optimistically). If spoke delivery fails, the state is not rolled back — the message is re-sent until it converges, and receive handling is idempotent.

CCCP messages

Every message payload is one CccpPayload struct passed through abi.encode. It decodes with the same ABI regardless of kind, and unused fields are 0 or empty. The second field is always uint64 productId, so any message can be identified by kind and product from a (uint8, uint64) decode alone.

struct CccpPayload {
    uint8   kind;          // 1~10
    uint64  productId;     // uint32 prefix | uint32 seq
    bytes32 requestId;     // 0 outside the request family
    uint256 settlementId;  // 0 outside the settlement family
    uint8   flag;          // kind 8 usage · kind 9 grant/revoke
    address account;       // investor · permission subject
    address vault;
    uint256 amount;
    uint256 aux;
    address[] addrs;
    uint256[] nums;
    bytes   data;          // nested struct arrays (kind 4·5 only, 2-stage encoding)
}
kindNameDirectionAttached assetsRole
1DEPOSIT_REQUESTspoke→hubfull request amountDeposit request report
2REDEEM_REQUESTspoke→hubRedeem request report (includes the locally started amount)
3SETTLEMENT_COLLECThub→spokeSettlement collection instruction (carries the last fixed price)
4SETTLEMENT_RESPONSEspoke→hubrealized withdrawalsChain NAV + per-source breakdown reply
5SETTLEMENT_FINALIZEhub→spokepayout allocation (first chunk)Distribute the approval list and the fixed price
6REBALANCE_BRIDGEspoke→hubwithdrawn amountRebalance withdrawals collected at the hub
7ADAPTER_SUPPLYhub→spokedeployment amountAsset delivery → deployed on receipt
8ADAPTER_WITHDRAWhub→spokeWithdrawal instruction — flag 0 = rebalance · 1 = redemption funding
9WHITELIST_SYNChub→spokePropagate investor eligibility (nonce ordering guaranteed)
10ALLOCATION_SYNChub→spokePropagate allocation weights (as soon as a weight changes)

At both the send and the receive end, CccpSent / CccpReceived events record the full original message, so an indexer can trace every cross-chain leg from those two events alone. If the declared send amount differs from the amount actually received, that is the bridge fee gap, and the fee sponsor covers it.

Whitelist · rebalance · allocation propagation

Whitelist

The source of truth for investor eligibility is the hub's TranchePermissions (0x202). When an admin makes one grant_permission call, the pallet records it and calls the Orchestrator directly to send WHITELIST_SYNC (kind 9) to the target vault's chain. Once the receiving chain's WhitelistHook is updated, share transfers and the vault deposit gate open. Revoke takes the same path.

CCCP does not guarantee delivery order, so messages carry a propagation nonce. The receiver compares it against the latest nonce per (vault, target address) and discards stale messages — a revoke that arrives before its grant still ends in the right final state.

Rebalance

User ledgers and shares are untouched; only fund capital moves. Moving between chains goes ADAPTER_WITHDRAW (kind 8, flag=0) to the overweight chain → the withdrawn amount collects at the hub via REBALANCE_BRIDGE (kind 6) → redeployed to the underweight chain with ADAPTER_SUPPLY (kind 7).

Allocation weight propagation

Each spoke caches only its own weights (localShareBps and the local adapter split). To change them the operator calls pushAllocation (onlyOwner) on the hub, which propagates ALLOCATION_SYNC (kind 10), and a version counter (weightsVersion) detects staleness. Even if a spoke deployed at the old weights, the hub knows the gap between target and actual and corrects it at the next rebalance.

How to use it

Users

  1. Pick an entry chain — hub (testbed) or spoke (Sepolia); it is the same product either way.
  2. Get test assets (USDC) from the faucet, and get whitelisted.
  3. Call requestDeposit on the vault — from there it is the same 7540 surface as single-chain.
  4. Once a settlement round comes around (a few minutes when entering from a spoke), call deposit to claim the shares.
  5. Redemption is the same — requestRedeem → wait for settlement → withdraw.
The multichain version does not support cancelling a request yet — cancelDepositRequest / cancelRedeemRequest reverts. Atomically cancelling a request that has already spread across chains is needed first, so it is deferred to v2.

Operations

  1. Create the product — register the Valuation address, the tranche layout (APR · priority · vault) and the chain and adapter weights with the pallet's create_product.
  2. Grant eligibility — one grant_permission call both records the source of truth and propagates to every chain.
  3. Settlement is triggered by the keeper on an interval. Watch progress through events and pallet queries.
  4. Weight changes and rebalancing are set_multichain_adapters (replacing the pallet source of truth) followed by starting a rebalance.

Demo: Multichain products → — entry-chain selection, deposit and redeem, a cross-chain tracker, a faucet and an operations panel.

Functions

User surface

The same TrancheVault (ERC-7540/7575/4626) as single-chain — requestDeposit / requestRedeem / deposit / redeem/ view functions are all identical. The only difference is that cancellation is unsupported.

MultichainTrancheManager

FunctionsDescription
requestDeposit / requestRedeemHandles requests delegated from the vault — escrow, requestId issuance, starting local deployment and withdrawal, sending kinds 1 and 2.
receiveMessage(...)CCCP receive entry point. Branches by kind — applying a settlement, deploying, starting a withdrawal, applying a whitelist change, and so on.
sweepDust(to, amount)Recovers only residue outside the accounting (bridge rounding and the like). User assets (poolCash, the payout reserve) are protected by a cap.
setWhitelistHook / setFeeSponsorHook / setPaused etc.Operational wiring. Every state-changing function emits an event.

ValuationDiamond

FunctionsDescription
tryUpdateNAV()Settlement cycle entry point — called by the keeper. Advances the stages from starting collection through finalizing and distributing.
onDepositReq / onRedeemReq / onNavResponse etc.The message handlers the Orchestrator routes to. Write the ledger and execute the allocation and withdrawal plans.
rebalanceCrossChain(...)Start a cross-chain rebalance.
lastPriceOf(tranche) / settlementId()Read the fixed price and the round — the price source for the UI and indexers.

Pallet precompiles (hub)

AddressMain functions
0x200 TrancheSystemcreate_product · set_multichain_adapters · get_tranches · get_multichain_adapters — the source of truth for product configuration.
0x201 Investmentsrecord_investment_request / approval · record_adapter_valuations · record_settlement · get_last_settlement · get_pending_requests — the ledger. Only a product's own Valuation may write to it.
0x202 TranchePermissionsgrant_permission / revoke_permission — the source of truth for investor eligibility, plus the cross-chain propagation trigger.

Every product_id parameter is uint64 (converted 2026-08-18).

Events

Every external state-changing function emits an event, and an indexer can reconstruct the whole system state from events alone. The request lifecycle joins on requestId (standardized as the first indexed parameter); the settlement lifecycle joins on settlementId.

Wire chokepoints

EventsDescription
CccpSent(dstChainId, to, amount, message)Every outbound leg. message = the raw payload.
CccpReceived(srcChainId, sender, declared, received, message)Every inbound leg. declared ≠ received means a fee gap.

Requests · claims

EventsEmitted byDescription
DepositRequested / RedeemRequestedentry-chain ManagerRequest accepted — the amount, including the locally deployed and started portions.
DepositQueued / RedeemQueuedhub ValuationArrival at the hub and ledger write — the round it belongs to, whether it rolled over, and the allocation and funding chain list (adapterChainIds — guaranteed one entry per chain).
InvestmentRequested / InvestmentApprovedpallet 0x201Ledger write and approval.
DepositsApproved / RedeemsApprovedhub ValuationOne array per settlement — per-request entries of (requestId, amount in, amount out, fixed price).
DepositReceived / RedeemReceivedclaiming chain's ManagerUser claim (productId uint64 indexed). It sums everything claimable at that moment, so join with Approved for per-request values.

Deployment · withdrawal

EventsDescription
Supplied / SupplyReceivedDeployed into a yield source (requestId=0 means a deployment not tied to a request).
WithdrawRequestedWithdrawal started — the requested amount and the amount realized immediately.
PayoutRealizedASYNC withdrawal realized and received — waiting to be attached to the next settlement response.

Settle

EventsDescription
SettleStartedRound started — hub NAV, the chains to collect from and the chains scheduled for distribution.
NavReported / NavReceivedChain NAV reply (sent by a spoke / received by the hub).
SettledRound finalized — product NAV and per-tranche NAV (after the settlement is applied).
SettleAppliedApplied on the spoke — the number of deposits and redemptions processed.
RedeemDeferredA redemption rolled over to the next round because the funds were not realized.

Eligibility · allocation · operations

EventsDescription
WhitelistRequested / WhitelistApplied / WhitelistStaleEligibility propagation sent, applied, and discarded when out of order. Carries the nonce.
AllocationPushed / AllocationSyncedAllocation weight propagation and application.
RebalanceStarted / RebalanceCollectedRebalance started and collected.
DustAccrued / Sponsored / DustSweptMessage fee funding accrued · fee gap covered · residue recovered.

Full signatures and the per-action emission order are in docs/multichain/events.md in the repository (the source of truth handed to the indexer and node teams).

Appendix

Single vs multichain

single-chainmultichain
User surfaceERC-7540 interface + 1404 share (institutional products use a 3643 share and SYNC deposits)ERC-7540 async + 1404 share
Tranche · waterfallIdentical — Senior's fixed APR first, Junior residual and first-loss
Ledger · price fixingValuation (price) + TrancheLedger (ledger, same ABI as the 0x201 pallet)Hub pallet (source of truth) + ValuationDiamond (engine)
Settlement triggerOperator settle (per request or batched)Keeper-driven rounds on an interval (settlementId)
Asset deploymentSplit across adapters on the same chainTwo-stage split — chain weight, then adapter weight within the chain; propagated as soon as the request lands
Redemption fundingSYNC products withdraw at request time; only ASYNC redemptions withdraw at settlementEach chain starts at request time → moved in one batch at settlement
WhitelistSet directly on the WhitelistHook (institutional products use the 3643 IdentityRegistry and Compliance)Hub source of truth (0x202) → propagated to every chain (nonce ordering guaranteed)
CancellationUnsupported (CancelNotSupported)Unsupported (CancelNotSupported)
productIdprefix 0x00000001prefix 0x00000002

Current deployments

Four products are running at once. Two are single-chain and two are multichain.

StructureCharacterproductId
AMultichain — hub (Bifrost testbed) + spoke (ETH Sepolia)Plain USDC tranche, 60/40 allocation0x0000000200000009
BSingle-chain — ETH SepoliaJPYC collateral, two yield sources in parallel (USDC rewards + Morpho Blue)0x0000000100000004
CSingle-chain — Bifrost testbedInstitutional lending, ERC-3643 whitelist0x0000000100000005
DMultichain — hub (Bifrost testbed) + spoke (Robinhood testnet)Mirror basket of the top 8 Robinhood QQQ names0x000000020000000b

A — multichain USDC

Hub testbed (49088) · spoke Sepolia (11155111), 60/40 allocation. This generation was cleanly redeployed with a SettleFacet that reflects settlementId numbering (0 is genesis; real settlements start at 1). The Orchestrator is reused as it is, since the CCCPClient interface did not change (deployed and activated 2026-08-28).

ComponentChainAddress
OrchestratorThe hub0xb90933d2707EE5452A87888Ae1b5c353c391a261 (deployed 2026-08-27, still shared by A and D)
ValuationDiamondThe hub0xDeE470bA416AeA3E08829467169b03fdF7056F9d
ManagerThe hub0x71B6Fd1f500D2350e2ebBa79b9dAC7C0993194cc
Vault SR / JRThe hub0x8917815653085A8A00C07FEB39e139C2B819a110 / 0xB5d830Abd8902Efc66DBeAD8cb29feCa00BE86ee
ManagerSpokes0xD38C81f57796c1C9269294118Ee55c3ACd91A314
Vault SR / JRSpokes0x5c1F2eAAA1c20f7babdA63dCFB944B9542FA5362 / 0xAa573F5fB545Aab1f3750655Ae8819AB7E6D9DE0
PrecompileThe hubTrancheSystem 0x…0200 · Investments 0x…0201 · TranchePermissions 0x…0202

The full address book is in the multichain/deployments/ deployment records in the repository.

D — multichain basket

Hub testbed (49088) · spoke Robinhood testnet (46630), 60/40 allocation. The structure is the same hub-spoke pattern as A (the Orchestrator is reused); only the Robinhood spoke adapter is swapped for RobinhoodBasketAdapter so that at settlement only the net inflow/outflow difference is traded in one batch on Uniswap V3 (0.05% tier, full-range liquidity) into a mirror basket of the top 8 QQQ names (NVDA, AAPL, GOOGL, MSFT, MU, AMZN, AMD, TSLA) — deployed, pool seeded and registered with the pallet on 2026-08-31. The full address book is in the multichain/deployments/ deployment records in the repository.

Both multichain products, A and D, put a common floor of 10 USDC(minRequest) on deposit assets and redeem shares, and the CCCP maxTxFee is 1 USDC (applied 2026-09-03). Allocation weights split a request into several legs, and this guard keeps even the smallest leg above the bridge fee cap.

B — single-chain JPYC

ETH Sepolia (11155111), base asset JPYC (18 decimals). Two yield-source adapters run in parallel at 50/50 — an in-house source that pays USDC rewards, and Morpho Blue. Deposit and redeem are both SYNC, so a request becomes claimable immediately.

C — single-chain institutional lending

Hub testbed (49088), base asset USDC. One institutional lending escrow adapter (APR 7%, Senior fixed 3%). Deposits are SYNC and redemptions ASYNC (FIFO, in the order loan repayments arrive). Investor eligibility is filtered by an ERC-3643 IdentityRegistry/Compliance pair rather than a WhitelistHook.

The full address book for the two single-chain products is in singlechain/deployments/.