Tuo docs

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

EventEmitted 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

EventEmitted 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

EventEmitted 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

EventEmitted 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

EventEmitted 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, RoleAdminChangedStandard 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.

  1. Cancellation is WithdrawalRequested(nftId, 0). There is no separate cancel event and no readiness flag on-chain. A request with sharesBps == 0 clears the pending request.
  2. HlBridgeInboundCompleted does not carry post-state. The vault clamps the bridged-margin attribution to zero, so a projection that subtracts amountReturnedUsdc from its own running total can go negative when a hedge returns more than it was sent. Clamp at zero, and treat finalReturn == true as clearing the attribution regardless of amount.
  3. 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 KeeperLossCharged will drift high; read the budget view for the true state.
  4. 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

QuestionHow to answer it
Who owns itTuoPositionNFT.ownerOf(nftId)
What is it worth on-chainTuoVault.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 payTuoVault.previewWithdraw(nftId) returns the gross, fee and net figures for the requested share, subject to the same revert
Can it be serviced nowCompare 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 stateTuoVault.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.

On this page