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.
IYieldGroupBase
Section titled “IYieldGroupBase”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 andsetBlocksPerYear, 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), andsetSpotAPYBps/resourceSpotAPYBpsbecause a fund publishes its yield rather than exposing a rate to read. See Centrifuge lifecycle.
There is no
ISourcetype in the code. Earlier drafts of this page used that name; the Solidity uses YieldGroup end to end.
Conventions
Section titled “Conventions”- All amounts are denominated in
asset()units — there is no share conversion at this layer. deposit/withdraw/depositResource/withdrawResourceareonlyHub-gated (any other caller revertsNotHub) andnonReentrant.depositanddepositResourcepull frommsg.senderviasafeTransferFrom, so the Hub approves the YieldGroup immediately before calling.withdrawpushes the underlying to the suppliedtoaddress (today the Hub passesaddress(this)), leaving room for a future Hub optimization that routes straight to the end receiver without an interface revision.depositreturns the actually-placed amount, which may be less thanamountwhen 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.withdrawhas no return value: it MUST deliver exactlyamounttotoor 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.
Mutating
Section titled “Mutating”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— moveamountfrom the caller into the Source’s resources via the inner deposit queue.depositedmay be less thanamountwhenever 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— pullamountvia the inner withdraw queue and send it toto; must deliver exactlyamountor revert (no partial fill).depositResource/withdrawResource— target one specific resource, bypassing the inner queue. They exist solely for Hub-orchestratedreallocate. The Hub treatsresourceas an opaque address it does not interpret; the Source validates it against its own registry. The fullamountmoves into / out of that one resource or the call reverts.withdrawResourcedoes 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 theYieldGrouploop that callsIResourceAdapter.accrueper resource (real work for Core and Spoke),YieldGroupFRVpokes each vault’supdateVaultState(), andYieldGroupCentrifugeonly re-anchors each vault’s NAV band. The genericYieldGroupand FRV skip a failed resource poke and emitResourceAccrualFailed. Centrifuge instead keeps its band anchor unchanged when a NAV observation is unreadable, without emitting that event; a subsequenttotalAssets()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 equalHub.asset().totalAssets— sum ofassetheld across all of the Source’s resources; the Hub sums this over its registry.maxDeposit— aggregate remaining deposit capacity. The honesty contract requires thatdeposit(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.
IResourceAdapter
Section titled “IResourceAdapter”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.
Dispatch model
Section titled “Dispatch model”- The asset-moving mutating functions (
deposit,withdraw) MUST be invoked viadelegatecallfrom the YieldGroup, so they execute in the YieldGroup’s storage context and receipt-token credits / debits land on the YieldGroup. Implementations enforce this with anaddress(this) != _ADAPTER_SELFguard. accrueis invoked via normalcall, notdelegatecall, 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/staticcalland take an explicitholderparameter where the answer depends on whose position is queried.
Implementation invariants
Section titled “Implementation invariants”The adapters follow these implementation constraints. Audit coverage depends on the report and reviewed revision; see Security and audits for published scopes.
- The contract declares zero storage variables; only
immutableandconstantvalues are permitted (both live in bytecode, not storage slots). - No inline assembly performs
sstore. - External calls go only to the supplied
resourceand its trusted, chain-fixed dependencies — the resource’sunderlying()/asset()/comptroller()and (for Flux) the FluidLendingResolver— never an arbitrary user-supplied address. - The delegatecall-dispatched mutating functions (
deposit,withdraw) revert when called outside a delegatecall context.accrueis the deliberate exception — it is invoked by plaincalland has no such guard.
Mutating
Section titled “Mutating”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 holdsamountof underlying. The fullamountof underlying leaves the YieldGroup intoresource. Synchronous families credit receipt tokens immediately; Centrifuge creates a pending deposit request for the YieldGroup, with shares collected only after settlement throughclaimDeposit. 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 leastamountand transfers exactlyamounttoto. Any surplus stays as idle on the YieldGroup, counted intotalAssetsand consumed idle-first on the next withdrawal.AdapterCoreV1produces surplus from two sources: the sub-one-vToken dust bump, and — only if Venus governance later enables it — the ComptrollertreasuryPercentgross-up.accrue— invoked via normalcall, NOT delegatecall; settlesresource’s own global interest state so a followingtotalAssetsread prices the position at a fresh rate. Only block-lazy adapters do real work:AdapterCoreV1andAdapterSpokeV1call their vToken’saccrueInterest(), whileAdapterFlux,AdapterFRVandAdapterCentrifugeare 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 byresource; the YieldGroup uses it to validate a resource matches its own asset at registration.totalAssets— the adapter’s accounting value ofholder’s position inresource, in underlying units; this is not a guarantee of recoverable or immediately withdrawable assets. The basis is per-adapter:AdapterCoreV1uses the stale (non-accruing)exchangeRateStored, net of any ComptrollertreasuryPercent;AdapterFluxuses the fToken’s livepreviewRedeem;AdapterFRVuses a time-based linear accrual (principal plus the coupon accrued so far over the lock);AdapterSpokeV1usesbalance × exchangeRateStored, including the market’sbadDebtin the exchange rate;AdapterCentrifugeadds 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; usemaxWithdrawfor current liquidity.maxDeposit— spare deposit headroom onresourceright now (before its own caps or pause reject a mint).maxWithdraw— underlyingholdercan 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 theblocksPerYearthe YieldGroup passes (chain-dependent).receiptBalance— raw receipt-token balance (vToken / fToken / FRV shares), NOT underlying value. Used byremoveResourceas a share-based emptiness gate so a value-based check can’t round a small balance to zero and orphan tokens.AdapterCentrifugereturns 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 ofresource, for off-chain consumers. Each adapter reads it from wherever its protocol exposes it, since not every resource is an ERC-20 with its ownname().validateRegistration— reverts ifresourcefails a protocol-specific precondition.AdapterCoreV1rejects a vToken whose Comptroller charges a non-zerotreasuryPercent;AdapterSpokeV1rejects a market not listed by its Comptroller or one whose supply allowlist excludes the calling YieldGroup;AdapterCentrifugerejects a vault that does not expose the share token, request manager, investment state and price reads it needs;AdapterFluxandAdapterFRVimplement it as a no-op.
Interfaces documented elsewhere
Section titled “Interfaces documented elsewhere”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 theNavBandstruct,setNavGuardRate/setNavGuardSnapshot/setNavGuardEnabled, and thenavGuard/navGuardStatusviews. Folded intoIYieldGroupCentrifugeand described under NAV band.IHubLens: the read-only capacity and APY lens. It is not a boundary:HubLensis 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.