For the complete documentation index, see llms.txt. This page is also available as Markdown.

Nexus Mutual Staking

An assisted adapter that reports an account's staked NXM across Nexus Mutual V2 staking pools, per StakingNFT position the owner has submitted.

An assisted adapter that reports an account's staked NXM across Nexus Mutual V2 staking pools, emitted per StakingNFT position as NXM-denominated Staking and Rewards legs. Each stake is an ERC-721 StakingNFT; the NFT is not owner-enumerable and the StakingViewer exposes no owner→tokenIds view, so a pure-view adapter cannot discover which positions belong to an account. The owner feeds the StakingNFT tokenId itself (submit(uint256), keyed on msg.sender); the adapter then values it entirely from live on-chain reads, re-verifying ownership on every read.

The cache stores only tokenIds, never amounts, so the feeder cannot fabricate value — at worst it omits an id (NAV under-counts), never over-counts. See Assisted balance adapters for the family trust model.


Positions returned

Up to two legs per StakingNFT the account holds (a holder can have several, across several pools):

Leg
PositionKind
isDebt
isLocked
Description

Stake

Staking

false

false

activeStake + expiredStake — staked NXM principal (expired tranches no longer earn but remain withdrawable)

Rewards

Rewards

false

false

Accrued, unclaimed NXM rewards for that position

A leg with a zero amount is skipped. Positions are denominated in NXM and dropped if NXM is not registered in NAVCalculator. isLocked is false for both legs.


Feed surface & assistant

The adapter exposes a small, permissionless, msg.sender-keyed surface (no roles, no account argument):

Function
Purpose

submit(uint256 tokenId)

Track tokenId for the caller (idempotent). Reverts CoordinateNotOwned if the caller doesn't own it, CoordinateCacheFull past MAX_TOKEN_IDS (256)

remove(uint256 tokenId)

Stop tracking tokenId for the caller. Reverts CoordinateNotCached if untracked, CoordinateStillLive if the position is still live

tokenIdsOf(address account)

View the candidate tokenIds cached for account (display/debug; not ownership-filtered)

clearAccount()

Drop all of the caller's cached ids (unconditional self-service GC)

The assistant (NexusMutualStakingPoolsAssistant)

Use the assistant in place of the Nexus staking-pool contract. Instead of staking directly by calling the pool's depositTo (which would leave the resulting StakingNFT invisible to NAV), you delegatecall the assistant: it forwards the same depositTo to the pool and, in the same transaction, submits the minted tokenId to the adapter so the stake is tracked. Withdrawing works the same way through withdraw. It is a thin wrapper over Nexus's own staking channel — same action, plus the bookkeeping that makes the position visible to NAV.

Entrypoint
Does
Adapter call

depositTo(poolAddress, amount, trancheId, requestTokenId)

Approves the Nexus TokenController for the Safe's own NXM and stakes it (destination = the Safe), minting/topping-up a StakingNFT owned by the Safe

submit(tokenId)

withdraw(poolAddress, tokenId, withdrawStake, withdrawRewards, trancheIds[])

Withdraws stake and/or rewards to the Safe, then tries to remove(tokenId)

remove(tokenId) — see below

Both are onlyDelegateCall. Because the module runs in the Safe's context, the Safe holds the NXM, is the Nexus member, and owns the minted NFT — no custody or asset-forwarding. On withdraw the id is untracked best-effort — removal never blocks the withdraw itself: a partial / rewards-only withdraw leaves live stake (adapter reverts CoordinateStillLive → keep it tracked), and an id that isn't tracked reverts CoordinateNotCached (nothing to untrack); the assistant swallows both and re-throws anything else. So the id is effectively dropped only once the position is fully emptied.

Flow to stake into Nexus: the Manager Safe drives the Portfolio Safe to delegatecall the assistant, which stakes NXM into the pool and submits the resulting StakingNFT tokenId to the adapter.
Staking. The Safe delegatecalls the assistant, which stakes its own NXM into the pool (minting/topping-up a StakingNFT) and submits the returned tokenId to the adapter — in one transaction.
Flow to withdraw from Nexus: the assistant withdraws stake/rewards to the Safe then best-effort removes the tokenId — kept if still staked (CoordinateStillLive), dropped once fully emptied.
Withdrawing. The assistant withdraws stake and/or rewards to the Safe, then best-effort removes the tokenId: a partial withdraw keeps it tracked (CoordinateStillLive); it is dropped only once the position is fully emptied.

Balance calculation

Flowchart of the Nexus Mutual Staking balance adapter: an assistant-submitted StakingNFT tokenId is ownership-verified, then valued via the StakingViewer into Staking and Rewards PositionBalance entries.
Nexus Mutual Staking adapter — how the underlying balances reported to the NAV Calculator are derived.

For each cached tokenId, the adapter values the position via the StakingViewer and re-verifies ownership against the StakingNFT:

Ownership is re-checked every read via StakingNFT.tokenInfo, so a stale or transferred id contributes nothing. Stake and rewards are read live from StakingViewer.getTokens; the cache holds no amounts. Each id is valued behind the base's per-coordinate self-staticcall (fail-open), so a revert in one position's live reads — Nexus's getTokens is revert-prone — discards just that id and never drops the account's whole Nexus stake.


Identity

  • positionId: abi.encode(address stakingPool) where stakingPool = StakingViewer.stakingPool(poolId) — the pool address. Both legs of a position share the same key. The NFT tokenId is not part of the positionId — it rides in positionInstanceId.

  • positionKind: Staking · Rewards

  • positionInstanceId: bytes32(tokenId) (the per-position StakingNFT id; ephemeral — both legs of a position carry the same instance id)

  • Labels: the adapter implements positionLabels(positionId), returning ["Staking Pools", "Pool #<poolId>"] — the numeric pool id read live via IStakingPool.getPoolId() (falling back to the pool's "0x…" address if that read reverts), matching the Nexus UI's pool numbering (surfaced only on the verbose read path; full breadcrumb = ["Nexus Mutual", "Staking Pools", "Pool #<poolId>"]). The NFT id is not in the label — it rides in positionInstanceId.

An account's NFTs in the same pool share that pool's positionId. The reconciliation key is keccak256(abi.encode(chainId, protocolSubId, positionId, balanceAsset.asset, positionKind)) — the Staking and Rewards legs reconcile independently via positionKind (and positionInstanceId is excluded, so multiple NFTs in one pool reconcile into one coordinate per kind).


Constructor

Parameter
Description

stakingNft_

Nexus Mutual StakingNFT (ERC-721) — source of ownership re-verification via tokenInfo. Must be a contract

stakingViewer_

Nexus Mutual StakingViewer — per-token active/expired stake + rewards, and stakingPool(poolId) for the positionId. Must be a contract

underlyingToken_

Asset positions are denominated in — NXM. Must be non-zero

navCalculator_

NAVCalculator address, used for asset-registry metadata. Must be a contract

There is no admin_ / assistant_ argument — the family is permissionless. The paired NexusMutualStakingPoolsAssistant is deployed separately, pinned to this adapter (plus the NXM token and Nexus TokenController).


Registration

Registered as a plain adapter with addBalanceAdapters([adapter]) and removed with removeBalanceAdapters([adapter]). The coordinate cache is then kept current by the position owner's own transactions — typically via the assistant, delegatecalled inside the same depositTo / withdraw.

Last updated