Skip to content

Interfaces

Two interfaces define the boundaries that keep the Liquidity Hub layered. The Hub depends only on IYieldGroupBase; a YieldGroup depends only on IResourceAdapter. Neither the Hub nor a YieldGroup ever reaches past its boundary into the layer below.

  • IYieldGroupBase — the Hub ↔ YieldGroup boundary.
  • IResourceAdapter — the YieldGroup ↔ adapter boundary.

The Hub’s own external interface, IHub, is documented on the Hub page.

The boundary between the Hub and the concrete typed YieldGroups: the generic YieldGroup (which serves the Core-vToken, Fluid-fToken and Spoke-vToken deployments), YieldGroupFRV and YieldGroupCentrifuge. The Hub depends only on this interface; adding a new Source family never touches the Hub.

Three leaf interfaces extend it with family-specific surface:

  • IYieldGroup — the per-resource deposit cap setters and setBlocksPerYear, implemented by the Core, Flux and Spoke deployments.
  • IYieldGroupFRV — forceRemoveResource, implemented by the FRV deployment.
  • IYieldGroupCentrifuge (IYieldGroupBase, INavGuard): the asynchronous request and claim surface (requestRedeem, cancelDepositRequest, cancelRedeemRequest, the four claims), forceRemoveResource (same signature as FRV’s, but with no zero-value gate and only while the Hub is paused), and setSpotAPYBps / resourceSpotAPYBps because a fund publishes its yield rather than exposing a rate to read. See Centrifuge lifecycle.

There is no ISource type in the code. Earlier drafts of this page used that name; the Solidity uses YieldGroup end to end.

  • All amounts are denominated in asset() units — there is no share conversion at this layer.
  • deposit / withdraw / depositResource / withdrawResource are onlyHub-gated (any other caller reverts NotHub) and nonReentrant.
  • deposit and depositResource pull from msg.sender via safeTransferFrom, so the Hub approves the YieldGroup immediately before calling.
  • withdraw pushes the underlying to the supplied to address (today the Hub passes address(this)), leaving room for a future Hub optimization that routes straight to the end receiver without an interface revision.
  • deposit returns the actually-placed amount, which may be less than amount when the inner deposit queue cannot absorb it all. The implementer MUST refund the unplaced remainder to the Hub and return the placed amount; the Hub’s cascade routes that remainder to the next YieldGroup rather than reverting. Fee-on-transfer underlyings are a separate matter — they are unsupported and fail closed inside the deposit.
  • withdraw has no return value: it MUST deliver exactly amount to to or revert. The Hub defensively measures the resulting balance delta rather than trusting a self-reported number.
  • A Source may hold assets across multiple internal resources and route via its own inner queue; that detail is opaque to the Hub.
function deposit(uint256 amount) external returns (uint256 deposited);
function withdraw(uint256 amount, address to) external;
function depositResource(address resource, uint256 amount) external returns (uint256 deposited);
function withdrawResource(address resource, uint256 amount, address to) external;
function accrue() external;
  • deposit — move amount from the caller into the Source’s resources via the inner deposit queue. deposited may be less than amount whenever the inner queue cannot absorb the full amount (per-resource caps, protocol supply caps, or paused / rejecting resources). The Source refunds the unplaced remainder to the Hub, which routes it onward; the Hub does not revert on a short return here.
  • withdraw — pull amount via the inner withdraw queue and send it to to; must deliver exactly amount or revert (no partial fill).
  • depositResource / withdrawResource — target one specific resource, bypassing the inner queue. They exist solely for Hub-orchestrated reallocate. The Hub treats resource as an opaque address it does not interpret; the Source validates it against its own registry. The full amount moves into / out of that one resource or the call reverts. withdrawResource does not consume idle first and does not cascade; pulling from a paused resource is permitted (wind-down).
  • accrue — onlyHub. The Hub calls it on every registered Source before reading NAV, so the management fee is charged on interest-current value. Every family overrides the base no-op: Core, Flux and Spoke share the YieldGroup loop that calls IResourceAdapter.accrue per resource (real work for Core and Spoke), YieldGroupFRV pokes each vault’s updateVaultState(), and YieldGroupCentrifuge only re-anchors each vault’s NAV band. The generic YieldGroup and FRV skip a failed resource poke and emit ResourceAccrualFailed. Centrifuge instead keeps its band anchor unchanged when a NAV observation is unreadable, without emitting that event; a subsequent totalAssets() read still fails closed if the adapter cannot value the resource. A Source implementing only the four asset-moving functions would revert the Hub’s fee accrual and therefore brick deposits and withdrawals.
function asset() external view returns (address underlying);
function totalAssets() external view returns (uint256 total);
function maxDeposit() external view returns (uint256 capacity);
function maxWithdraw() external view returns (uint256 liquid);
function spotAPYBps() external view returns (uint64 apyBps);
  • asset — the ERC-20 the Source accepts; must equal Hub.asset().
  • totalAssets — sum of asset held across all of the Source’s resources; the Hub sums this over its registry.
  • maxDeposit — aggregate remaining deposit capacity. The honesty contract requires that deposit(maxDeposit()) does not revert.
  • maxWithdraw — aggregate currently-withdrawable amount. withdraw(maxWithdraw(), to) must not revert (subject to upstream liquidity).
  • spotAPYBps — TVL-weighted spot supply-side APY across the Source’s resources, in BPS. This is an instantaneous reading; the Source layer has no notion of historical realization, and the Hub does not currently aggregate or smooth it.

The boundary between a YieldGroup and a specific yield-protocol ABI. A single deployment of each implementation is shared across every YieldGroup that registers a resource of the matching family.

IAdapterCentrifuge is IResourceAdapter extends it for the async family: the request and claim calls the YieldGroup forwards, plus depositRoom and pricePerShare. The adapters themselves are on the Adapters page.

  • The asset-moving mutating functions (deposit, withdraw) MUST be invoked via delegatecall from the YieldGroup, so they execute in the YieldGroup’s storage context and receipt-token credits / debits land on the YieldGroup. Implementations enforce this with an address(this) != _ADAPTER_SELF guard.
  • accrue is invoked via normal call, not delegatecall, and deliberately carries no guard: it settles the resource’s own global interest state rather than the holder’s position, so it needs neither the YieldGroup’s storage context nor a delegatecall.
  • View functions are invoked via normal call / staticcall and take an explicit holder parameter where the answer depends on whose position is queried.

The adapters follow these implementation constraints. Audit coverage depends on the report and reviewed revision; see Security and audits for published scopes.

  1. The contract declares zero storage variables; only immutable and constant values are permitted (both live in bytecode, not storage slots).
  2. No inline assembly performs sstore.
  3. External calls go only to the supplied resource and its trusted, chain-fixed dependencies — the resource’s underlying() / asset() / comptroller() and (for Flux) the Fluid LendingResolver — never an arbitrary user-supplied address.
  4. The delegatecall-dispatched mutating functions (deposit, withdraw) revert when called outside a delegatecall context. accrue is the deliberate exception — it is invoked by plain call and has no such guard.
function deposit(address resource, uint256 amount) external returns (uint256 deposited);
function withdraw(address resource, uint256 amount, address to) external;
function accrue(address resource) external;
  • deposit — delegatecalled; the YieldGroup already holds amount of underlying. The full amount of underlying leaves the YieldGroup into resource. Synchronous families credit receipt tokens immediately; Centrifuge creates a pending deposit request for the YieldGroup, with shares collected only after settlement through claimDeposit. A successful Centrifuge deposit therefore does not imply an immediate increase in its share-token balance. Because the code runs in the YieldGroup’s storage context, the adapter contract itself never custodies funds.
  • withdraw — delegatecalled; redeems at least amount and transfers exactly amount to to. Any surplus stays as idle on the YieldGroup, counted in totalAssets and consumed idle-first on the next withdrawal. AdapterCoreV1 produces surplus from two sources: the sub-one-vToken dust bump, and — only if Venus governance later enables it — the Comptroller treasuryPercent gross-up.
  • accrue — invoked via normal call, NOT delegatecall; settles resource’s own global interest state so a following totalAssets read prices the position at a fresh rate. Only block-lazy adapters do real work: AdapterCoreV1 and AdapterSpokeV1 call their vToken’s accrueInterest(), while AdapterFlux, AdapterFRV and AdapterCentrifuge are no-ops.
function asset(address resource) external view returns (address underlying);
function totalAssets(address resource, address holder) external view returns (uint256 value);
function maxDeposit(address resource) external view returns (uint256 capacity);
function maxWithdraw(address resource, address holder) external view returns (uint256 liquid);
function spotAPYBps(address resource, uint256 blocksPerYear) external view returns (uint64 apyBps);
function receiptBalance(address resource, address holder) external view returns (uint256 shares);
function resourceName(address resource) external view returns (string memory name);
function validateRegistration(address resource) external view;
  • asset — the underlying accepted by resource; the YieldGroup uses it to validate a resource matches its own asset at registration.
  • totalAssets — the adapter’s accounting value of holder’s position in resource, in underlying units; this is not a guarantee of recoverable or immediately withdrawable assets. The basis is per-adapter: AdapterCoreV1 uses the stale (non-accruing) exchangeRateStored, net of any Comptroller treasuryPercent; AdapterFlux uses the fToken’s live previewRedeem; AdapterFRV uses a time-based linear accrual (principal plus the coupon accrued so far over the lock); AdapterSpokeV1 uses balance × exchangeRateStored, including the market’s badDebt in the exchange rate; AdapterCentrifuge adds held shares and every pending or claimable request, assets at face and shares at the vault’s share price. The Hub aggregates these marks into NAV; use maxWithdraw for current liquidity.
  • maxDeposit — spare deposit headroom on resource right now (before its own caps or pause reject a mint).
  • maxWithdraw — underlying holder can withdraw right now, net of any redeem-time protocol fee, bounded by the lesser of the position value and the resource’s available cash.
  • spotAPYBps — spot supply-side APY in BPS; annualization uses the blocksPerYear the YieldGroup passes (chain-dependent).
  • receiptBalance — raw receipt-token balance (vToken / fToken / FRV shares), NOT underlying value. Used by removeResource as a share-based emptiness gate so a value-based check can’t round a small balance to zero and orphan tokens. AdapterCentrifuge returns held shares plus its six request counters, which mix share and asset units, so it is read only as zero or non-zero.
  • resourceName: display name of resource, for off-chain consumers. Each adapter reads it from wherever its protocol exposes it, since not every resource is an ERC-20 with its own name().
  • validateRegistration — reverts if resource fails a protocol-specific precondition. AdapterCoreV1 rejects a vToken whose Comptroller charges a non-zero treasuryPercent; AdapterSpokeV1 rejects a market not listed by its Comptroller or one whose supply allowlist excludes the calling YieldGroup; AdapterCentrifuge rejects a vault that does not expose the share token, request manager, investment state and price reads it needs; AdapterFlux and AdapterFRV implement it as a no-op.

Two more interfaces ship with the package but describe contracts outside the Hub ↔ YieldGroup ↔ adapter chain, so they are documented where that contract is:

  • INavGuard: the per-resource NAV band, with the NavBand struct, setNavGuardRate / setNavGuardSnapshot / setNavGuardEnabled, and the navGuard / navGuardStatus views. Folded into IYieldGroupCentrifuge and described under NAV band.
  • IHubLens: the read-only capacity and APY lens. It is not a boundary: HubLens is a standalone periphery contract that takes the Hub as a parameter, and nothing on chain points at it.
function maxDeposit(address hub) external view returns (uint256 assets);
function maxMint(address hub) external view returns (uint256 shares);
function depositCapacityBreakdown(address hub) external view returns (address[] memory groups, uint256[] memory room);
function spotAPYBps(address hub) external view returns (uint64 apyBps);

What each one returns, and how the accrual margin and the paused cases behave, is on Sizing a deposit. IHubLens.sol is not in a published @venusprotocol/liquidity-hub release yet.