Events and integration
The public event surface of the Tuo vault, the projection rules an indexer must follow, and how to read a position's state.
Everything that changes a position emits an event, and every event is declared on the vault's interface. This page is for anyone building an indexer, a dashboard or a monitor on top of Tuo.
Only interact with the addresses on Deployed addresses. The Sepolia rehearsal build predates the current ABI and does not emit the event set below.
Events by lifecycle
Deposits and positions
| Event | Emitted when |
|---|---|
PositionOpened(nftId, owner, productCode, depositToken, depositAmount, usdcCredited) | A new position is opened. usdcCredited is the basis |
PositionToppedUp(nftId, depositToken, depositAmount, usdcCredited, newBasisUsdc) | An existing position receives more capital |
PositionBurned(nftId) | The NFT is burned after a full withdrawal or an emergency exit |
Withdrawals
| Event | Emitted when |
|---|---|
WithdrawalRequested(nftId, sharesBps) | The owner requests an exit of sharesBps of the position. sharesBps == 0 is a cancellation |
PositionWithdrawn(nftId, sharesBps, grossUsdc, feeUsdc, netUsdc, withdrawalToken, tokenAmountOut, newBasisUsdc) | The owner completes a withdrawal. grossUsdc == feeUsdc + netUsdc exactly |
EmergencyWithdrawn(nftId, feeUsdc, hlClaimUsdc) | The owner takes the emergency exit. hlClaimUsdc is margin still on Hyperliquid, now a claim ticket |
EmergencyTokenPaid(nftId, token, amount) | One in-kind payout during an emergency exit, one event per token |
HlClaimSettled(nftId, amountUsdc) | The keeper pays out a claim ticket after the hedge margin returns |
Keeper actions
| Event | Emitted when |
|---|---|
IdleSwapped(nftId, tokenIn, tokenOut, amountIn, amountOut) | The keeper swaps an idle balance through an allowlisted aggregator |
LpMinted(nftId, lpTokenId, pool, tickLower, tickUpper, liquidity, amount0, amount1) | A concentrated liquidity range is opened |
LpBurned(nftId, lpTokenId, amount0, amount1, fees0, fees1) | A range is closed. fees0 and fees1 are the trading fees collected |
HlBridgeOutbound(nftId, operator, amountUsdc) | Hedge margin leaves the vault for the position's bound operator |
HlBridgeInboundCompleted(nftId, amountReturnedUsdc, finalReturn) | Margin comes back, or the keeper attests that no more will |
KeeperLossCharged(nftId, lossUsdc, windowLossUsdc, ceilingUsdc) | A keeper action produced a measured loss. Carries the full window state |
Fees
| Event | Emitted when |
|---|---|
FeesAccrued(nftId, feeUsdc, totalAccruedUsdc) | A performance fee is taken in USDC on a withdrawal |
TokenFeesAccrued(nftId, token, amount, totalAccrued) | A performance fee is withheld in kind on an emergency exit |
FeesClaimed(recipient, amountUsdc) | The treasury sweeps accrued USDC fees |
TokenFeesClaimed(token, recipient, amount) | The treasury sweeps accrued fees in another token |
Administration
| Event | Emitted when |
|---|---|
HlOperatorUpdated(operator, allowed) | The treasury adds or removes a Hyperliquid operator. The only on-chain signal that the set of addresses customer margin may leave toward has changed |
ProductRegistered(productCode, label, maxHedgeBps, maxLpCount) | A product is added to the registry |
ProductOpenUpdated(productCode, openForDeposits) | A product is opened or closed for new positions |
PerNftCapUpdated(perNftCapUsdc) | The per-position cap changes |
FeeRecipientUpdated(feeRecipient) | The fee recipient changes |
DepositsPauseUpdated(paused) | Deposits are paused or unpaused |
RoleGranted, RoleRevoked, RoleAdminChanged | Standard OpenZeppelin AccessControl events |
The router emits SwapExecuted, AggregatorAdded and AggregatorRemoved. The NFT emits the
standard ERC-721 Transfer.
Projection rules
Anyone rebuilding position state from events should follow these rules. Each one has caught a real indexing bug.
- Cancellation is
WithdrawalRequested(nftId, 0). There is no separate cancel event and no readiness flag on-chain. A request withsharesBps == 0clears the pending request. HlBridgeInboundCompleteddoes not carry post-state. The vault clamps the bridged-margin attribution to zero, so a projection that subtractsamountReturnedUsdcfrom its own running total can go negative when a hedge returns more than it was sent. Clamp at zero, and treatfinalReturn == trueas clearing the attribution regardless of amount.- Withdrawals reduce charged keeper loss without an event. A partial withdrawal scales the
position's charged loss down pro-rata so a full exit never leaves a charge against a zero
ceiling. A projection that only follows
KeeperLossChargedwill drift high; read the budget view for the true state. - Key raw events on
(chainId, txHash, logIndex)so a replay is idempotent, and index from finalized blocks if you cannot handle reorgs.
Reading a position
| Question | How to answer it |
|---|---|
| Who owns it | TuoPositionNFT.ownerOf(nftId) |
| What is it worth on-chain | TuoVault.settlementNav(nftId). Reverts with HlMarginOutstanding while hedge margin is bridged out; that is the normal state of a hedged position, not an error |
| What would a withdrawal pay | TuoVault.previewWithdraw(nftId) returns the gross, fee and net figures for the requested share, subject to the same revert |
| Can it be serviced now | Compare the preview against TuoVault.idleBalance(nftId, USDC). A withdrawal needs the settlement figure to resolve and the share to be covered by idle USDC |
| Keeper budget state | TuoVault.keeperBudgetOf(nftId) |
Subgraph and ABIs
A public subgraph indexes the raw events. During the guarded launch it targets the Sepolia
rehearsal build under the Subgraph Studio slug tuo-arbitrum-sepolia; the Arbitrum One subgraph
will be listed here at deployment. The subgraph is a convenience for reading events. It is never
authoritative for valuation, which only the on-chain views and the off-chain NAV service provide.
Generated TypeScript ABIs are published from the repository as the @tuo/abis package once the
mainnet deployment is live.