Vault contracts

The ERC-7540 vault interface, fees, roles and events, plus the three contracts that hide its two step flow.

Why ERC-7540

The vaults implement ERC-7540, an extension of ERC-4626 that adds asynchronous deposits and redeems. ERC-4626 vaults settle deposits and withdrawals in one synchronous call, which limits their ability to:

  • Execute crosschain strategies
  • Integrate real-world assets (RWA) that need settlement time
  • Unwind positions as part of risk management
  • Support institutional compliance requirements

ERC-7540 replaces the single call with two steps:

Deposit:  requestDeposit() → [wait for fulfillment] → claimDeposit()
Redeem:   requestRedeem()  → [wait for fulfillment] → claimRedeem()

Deployed vaults

The user facing vaults are six per-tier EIP-2535 Diamonds (ERC-7540 async) on Base Sepolia (chainId 84532).

  • Denominated in tUSDC, a mintable test USDC available from a faucet.
  • Each tier is its own single-vault Diamond.
  • Every vault has two buckets: a principal protected Floor bucket and an Active Trading bucket.
  • The addresses below are the dApp configuration set.
  • The vaults hold seeded test funds. No capital has been deployed to strategies yet.
VaultAddressRiskStrategy Profile
Conservative0x49f747BBac988Cd628a5D79AAdF865EBaDe6a34fLowCapital preservation
Moderate0x4B183AfF072C7DFf09bc8eB4B7f59eB3D030EEdcMediumBalanced growth
Aggressive0x1444Fb96961fD9b8Fc14Aa4D9A7198465b7d8CA3HighMaximum growth
Hedged0xDDa7655AD3c53f0F7EC5CCbc346c18baB53C5a92LowDownside-protected
Inverse0xE2E4d181f2eAd5B380026Ab71F49FEBCb93f641cHighInverse/short exposure
Configurable0x53A2EfFaDE21055b8f81e1dE96dC5cfc29B75a83MediumDAO-configurable

Each vault has its own target APY band, listed in Core Concepts. The bands are targets, not guaranteed, and no capital is deployed on testnet.

The 8–28% range is the combined vault plus node target, available when a depositor also stakes NODR and runs a node. Node rewards are a revenue share, not a fixed APY. It is never a vault only figure. Higher APYs cited for individual underlying protocols reflect external protocol potential, not Noderr vault depositor yield.

ERC-7540 interface

Request deposit

function requestDeposit(
uint256 assets,
address controller,
address owner
) external returns (uint256 requestId);

Starts an async deposit request. Assets move from the owner to the vault.

Parameters:

  • assets: Amount of underlying tokens to deposit
  • controller: Address that can claim the deposit (usually same as owner)
  • owner: Address that owns the assets being deposited

Returns:requestId - Unique identifier for tracking the request

Claim deposit

function claimDeposit(
uint256 assets,
address receiver,
address controller
) external returns (uint256 shares);

Claims shares after a deposit request has been fulfilled.

Parameters:

  • assets: Amount of assets to claim shares for
  • receiver: Address to receive the vault shares
  • controller: Address that controls the request

Returns:shares - Number of vault shares minted

Request redeem

function requestRedeem(
uint256 shares,
address controller,
address owner
) external returns (uint256 requestId);

Starts an async redeem request. Shares move from the owner to the vault.

Claim redeem

function claimRedeem(
uint256 shares,
address receiver,
address controller
) external returns (uint256 assets);

Claims underlying assets after a redeem request has been fulfilled.

Status functions

// Check pending deposit amount
function pendingDepositRequest(uint256 requestId, address controller)
external view returns (uint256);
// Check claimable deposit amount
function claimableDepositRequest(uint256 requestId, address controller)
external view returns (uint256);
// Check pending redeem amount
function pendingRedeemRequest(uint256 requestId, address controller)
external view returns (uint256);
// Check claimable redeem amount
function claimableRedeemRequest(uint256 requestId, address controller)
external view returns (uint256);

Operator management

// Set an operator who can claim on your behalf
function setOperator(address operator, bool approved) external returns (bool);
// Check if an address is an operator for a controller
function isOperator(address controller, address operator) external view returns (bool);

Vault configuration

Values are read onchain from all six vaults:

ParameterDescriptionValue
fulfillmentDelayMinimum time before requests can be fulfilled0 (no minimum wait)
minDepositMinimum deposit amount0 (no minimum)
managementFeeAnnual management fee0% (every vault)
performanceFeeHigh-water-mark fee on profits, charged only from the Trading bucket10% to 15% on the six deployed vaults (see Fee Structure)
hurdleRatePer-vault soft hurdle return that must be exceeded before the performance fee applies4% to 8% on the six deployed vaults (see Fee Structure)

Async flow

User                    Vault                   Keeper/Fulfiller
|                       |                           |
|-- requestDeposit() -->|                           |
|<-- requestId ---------|                           |
|                       |                           |
|        [assets held in vault, request pending]    |
|                       |                           |
|                       |<-- fulfillDeposit() ------|
|                       |   (after delay elapsed)   |
|                       |                           |
|-- claimDeposit() ---->|                           |
|<-- shares ------------|                           |
|                       |                           |

Integration examples

JavaScript/TypeScript with viem

import { parseUnits } from'viem';
// Request a depositconst requestId = await vaultContract.write.requestDeposit([
parseUnits('1000', 18),  // 1000 tUSDC (underlying asset, 18 decimals)
userAddress,              // controller
userAddress               // owner
]);
// Check if claimable (after fulfillment)const claimable = await vaultContract.read.claimableDepositRequest([
requestId,
userAddress
]);
// Claim sharesif (claimable > 0n) {
const shares = await vaultContract.write.claimDeposit([
claimable,
userAddress,  // receiver
userAddress   // controller
]);
}

Solidity

import "./interfaces/IERC7540.sol";
contract VaultIntegration {
IERC7540 public vault;
// This contract holds the tUSDC, so it is the owner and controller of the request
function depositToVault(uint256 amount) external {
IERC20(vault.asset()).transferFrom(msg.sender, address(this), amount);
IERC20(vault.asset()).approve(address(vault), amount);
vault.requestDeposit(amount, address(this), address(this));
}
function claimFromVault() external {
uint256 claimable = vault.claimableDepositRequest(0, address(this));
require(claimable > 0, "Nothing to claim");
vault.claimDeposit(claimable, address(this), address(this));
}
}

Fees

Every vault charges 0% management fee. The only fee is a performance fee:

  • High-water mark based. It is charged only when NAV per share rises above its prior peak. A depositor is never charged twice for the same gains.
  • Taken strictly from the Trading bucket. The principal protected Floor bucket is never touched.
  • Subject to a per-vault soft hurdle. It applies only on returns above the vault's hurdle rate.
VaultPerformance FeeSoft Hurdle
Conservative10%4%
Moderate10%6%
Aggressive15%8%
Hedged12%4%
Inverse15%8%
Configurable10%6%
PredictionMarkets25%15%

PredictionMarkets is planned and not yet deployed. Its parameters are shown for reference.

Performance fees go to the protocol treasury wallet. Every vault's feeCollector() returns that wallet.

Fee TypeRateDescription
Management Fee0%No annual management fee, on every vault
Deposit Fee0%No fee to deposit
Withdrawal Fee0%No fee to withdraw

Example (Moderate vault), illustrative:

  • User deposits: 10,000 tUSDC
  • The Trading bucket gains enough to lift NAV/share to a new high-water mark. Net of the 6% soft hurdle, fee eligible profit is 1,000 tUSDC
  • Performance fee (10% of the fee eligible profit): 100 tUSDC
  • Net return to user: 10,900 tUSDC

Security

Access control

RolePermissions
DEFAULT_ADMIN_ROLEGrant/revoke roles, emergency functions
FULFILLER_ROLECall fulfillDeposit/fulfillRedeem
STRATEGY_MANAGER_ROLEUpdate strategy allocations
PAUSER_ROLEPause/unpause vault operations

DEFAULT_ADMIN_ROLE and upgrade authority resolve to a MultiSigTimelock (7-day delay) driven by a 2-of-3 Gnosis Safe. There is no offchain (e.g. Snapshot) governance path. See Governance.

Emergency functions

// Pause all vault operations
function pause() external onlyRole(PAUSER_ROLE);
// Unpause vault operations
function unpause() external onlyRole(PAUSER_ROLE);

The vaults have no emergencyWithdraw function.

Audit status

The ERC-7540 vault contracts pass 24/24 internal tests. They are internally audited. An independent third-party audit is planned for Q1 2027.

Events

// Emitted when a deposit is requested
event DepositRequest(
address indexed controller,
address indexed owner,
uint256 indexed requestId,
address sender,
uint256 assets
);
// Emitted when a deposit is claimed
event DepositClaim(
address indexed controller,
address indexed receiver,
uint256 indexed requestId,
uint256 assets,
uint256 shares
);
// Emitted when a redeem is requested
event RedeemRequest(
address indexed controller,
address indexed owner,
uint256 indexed requestId,
address sender,
uint256 shares
);
// Emitted when a redeem is claimed
event RedeemClaim(
address indexed controller,
address indexed receiver,
uint256 indexed requestId,
uint256 shares,
uint256 assets
);
// Emitted when an operator is set
event OperatorSet(
address indexed controller,
address indexed operator,
bool approved
);

Vault UX contracts

Three contracts hide ERC-7540's two step flow: VaultRouter, AutoClaimKeeper and InstantLiquidityPool. Their addresses are in Contract Addresses. The fee, premium and gas figures here are testnet parameters and can change.

ContractPurposeKey Benefit
VaultRouterBundled UX with auto-claimSingle-transaction experience
AutoClaimKeeperKeeper auto-claim serviceHands-free claim execution
InstantLiquidityPoolInstant liquidity AMMSkip the wait entirely

VaultRouter

The main entry point for users. It handles request submission, fulfillment monitoring and claim execution, so the user acts once.

  • Auto-claim: claims run automatically when a request is fulfilled
  • Batch operations: deposit or redeem across several vaults in one transaction
  • Gasless transactions: EIP-712 signatures
  • Slippage protection: configurable minimum output
  • Emergency controls: pause for security incidents
User → VaultRouter → ERC-7540 Vault
↓
AutoClaimKeeper
↓
Automatic Claim

Request deposit with auto-claim

function requestDeposit(
address vault,
uint256 assets,
uint256 minShares,
address receiver,
bool autoClaim
) external returns (uint256 requestId);

Parameters:

  • vault: The ERC-7540 vault address
  • assets: Amount of underlying tokens to deposit
  • minShares: Minimum shares to receive (slippage protection)
  • receiver: Address to receive the vault shares
  • autoClaim: Enable automatic claim when fulfilled (0.1% fee)
// Deposit 1000 tUSDC with auto-claim enabledawait vaultRouter.requestDeposit(
conservativeVaultAddress,
parseUnits('1000', 18),
parseUnits('990', 18),  // Accept up to 1% slippage
userAddress,
true// Enable auto-claim
);

Batch deposit

function batchDeposit(
BatchDepositParams[] calldata params,
address receiver,
bool autoClaim
) external;
struct BatchDepositParams {
address vault;
uint256 assets;
uint256 minShares;
}

Deposits to several vaults in one transaction, saving 50-70% on gas costs.

// Deposit to 3 vaults at onceawait vaultRouter.batchDeposit([
{ vault: conservativeVault, assets: parseUnits('500', 18), minShares: parseUnits('495', 18) },
{ vault: moderateVault, assets: parseUnits('300', 18), minShares: parseUnits('297', 18) },
{ vault: aggressiveVault, assets: parseUnits('200', 18), minShares: parseUnits('196', 18) }
], userAddress, true);

Gasless deposit (EIP-712)

function depositWithPermit(
address vault,
uint256 assets,
uint256 minShares,
address receiver,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 requestId);

Users sign a message offchain. Relayers submit the transaction and pay gas.

Router fees

ServiceFeeDescription
Standard Request0%No fee for manual claim
Auto-Claim0.1%Deducted from claimed amount
Gasless2%+Covers gas + relayer profit

Router configuration

ParameterDefaultDescription
defaultTimeout1 hourRequest expiration time
autoClaimFeeBps10 (0.1%)Fee for auto-claim service

AutoClaimKeeper

Keeper nodes earn rewards by executing auto-claims for users. Rewards scale with the keeper's TrustFingerprint.

  • Registration: stake NODR to become a keeper
  • Batch execution: process several claims at once
  • Performance tracking: success rate, gas usage, rewards
  • Slashing: penalties for failed executions

Become a keeper

function registerKeeper(uint256 stake) external payable;

Requirements:

  • Minimum TrustFingerprint: MIN_TRUST_SCORE = 700 on a 0 to 10,000 scale
  • Stake: any amount sent as msg.value (no enforced minimum)

Execute claims

// Execute single deposit claim
function executeDepositClaim(
address user,
address vault,
address receiver
) external;
// Execute single redeem claim
function executeRedeemClaim(
address user,
address vault,
address receiver
) external;
// Batch execute multiple claims
function batchExecuteClaims(
ClaimParams[] calldata deposits,
ClaimParams[] calldata redeems
) external;

Reward calculation

Base Reward = claimValue × baseRewardBps / 10000
Trust Bonus = Base Reward × (trustScore / 100) × trustBonusBps / 10000
Total Reward = Base Reward + Trust Bonus

Example:

  • Claim value: 10,000 tUSDC
  • Base reward rate: 0.1%
  • TrustFingerprint: 0.85
  • Trust bonus rate: 0.05%
Base Reward = 10,000 × 0.001 = 10 tUSDC
Trust Bonus = 10 × 0.85 × 0.0005 = 0.00425 tUSDC
Total Reward = 10.00425 tUSDC

Keeper metrics

struct KeeperMetrics {
uint256 totalClaims;      // Total claims executed
uint256 successfulClaims; // Successful executions
uint256 failedClaims;     // Failed executions
uint256 totalGasUsed;     // Cumulative gas usage
uint256 totalRewards;     // Cumulative rewards earned
uint256 lastClaimTime;    // Timestamp of last claim
}

Slashing conditions

ConditionPenalty
Failed execution0.1% of stake
>10% failure rateTemporary suspension
Malicious behaviorFull stake slash

InstantLiquidityPool

Deposits and withdrawals that execute at once, for users who prefer speed over cost. LPs earn the fees.

  • Instant deposits: skip the keeper fulfilment step
  • Instant redemptions: immediate asset return
  • Dynamic pricing: 2-5% fee based on utilization
  • LP rewards: earn fees by providing liquidity
  • Automated rebalancing: the pool maintains optimal levels
Standard Flow (until a keeper fulfils):
User → requestDeposit → [wait] → claimDeposit → Shares
Instant Flow (immediate):
User → instantDeposit → Shares (pay 2-5% premium)

Instant deposit

function instantDeposit(
address vault,
uint256 assets,
uint256 minShares,
address receiver
) external returns (uint256 shares);
// Instant deposit with 3% feeconst shares = await instantLiquidityPool.instantDeposit(
conservativeVaultAddress,
parseUnits('1000', 18),
parseUnits('950', 18),  // Accept up to 5% slippage (includes fee)
userAddress
);

Instant redeem

function instantRedeem(
address vault,
uint256 shares,
uint256 minAssets,
address receiver
) external returns (uint256 assets);

Add liquidity

function addLiquidity(
address vault,
uint256 assets,
uint256 shares
) external;

LPs can provide:

  • Asset liquidity: tUSDC (the vault underlying asset) for instant deposits
  • Share liquidity: Vault shares for instant redemptions

Remove liquidity

function removeLiquidity(
address vault,
uint256 assets,
uint256 shares
) external;

Claim fees

function claimFees(address vault) external;

Instant fee

The fee is a linear formula of pool utilization:

feeBps = MIN_FEE_BPS + ((MAX_FEE_BPS - MIN_FEE_BPS) × utilization) / 10000
= 200 + (300 × utilization / 10000)
UtilizationFee (approx.)
0%2.0%
25%2.75%
50%3.5%
75%4.25%
100%5.0%

The fee scales continuously and linearly, not in discrete steps. 90% goes to LPs in proportion to stake and 10% to the protocol treasury.

LP economics

Important: Returns to instant-liquidity LPs are fee income driven entirely by instant-flow volume, fee tier, pool size, and competition among LPs. They are not a protocol yield and are separate from Noderr's per-vault target bands and the combined vault + node target of 8–28%. Node rewards are a revenue-share, not a fixed APY. The figures below are illustrative of the fee-share mechanic only and are not projected, expected, or guaranteed returns.

Illustrative mechanic (not a yield projection):

  • LP provides 10,000 tUSDC liquidity
  • Suppose the pool processes 50,000 tUSDC/day of instant flow at a 3% average fee
  • Gross daily fees: 1,500 tUSDC (3% of 50,000)
  • LP share at 90% fee distribution, holding 10% of the pool: 135 tUSDC/day

Realized returns depend on actual instant-flow volume, the prevailing utilization-based fee, the LP's share of the pool, and competition. Sustained high volume at these assumptions would be exceptional. In practice instant-flow demand is variable and may be low, so LPs should not assume the illustrative throughput above.

Choosing a flow

1. Check instant liquidity availability
2. If available and user prefers speed → Use InstantLiquidityPool
3. If user prefers cost savings → Use VaultRouter with auto-claim
4. If user wants full control → Use VaultRouter without auto-claim

TypeScript

import { parseUnits } from'viem';
// Option 1: Instant deposit (immediate, 2-5% fee)const instantShares = await instantLiquidityPool.instantDeposit(
vaultAddress,
parseUnits('1000', 18),
parseUnits('950', 18),
userAddress
);
// Option 2: Auto-claim deposit (after keeper fulfilment, 0.1% fee)await vaultRouter.requestDeposit(
vaultAddress,
parseUnits('1000', 18),
parseUnits('990', 18),
userAddress,
true// auto-claim
);
// Option 3: Manual deposit (after keeper fulfilment, 0% fee)await vaultRouter.requestDeposit(
vaultAddress,
parseUnits('1000', 18),
parseUnits('990', 18),
userAddress,
false// manual claim
);
// Later: manually claimconst shares = await vaultRouter.claimDeposit(vaultAddress, userAddress);

Solidity

import "./interfaces/IVaultRouter.sol";
import "./interfaces/IInstantLiquidityPool.sol";
contract MyProtocol {
IVaultRouter public vaultRouter;
IInstantLiquidityPool public instantPool;
function smartDeposit(
address vault,
uint256 amount,
bool preferSpeed
) external {
if (preferSpeed && instantPool.hasLiquidity(vault, amount)) {
// Instant deposit
instantPool.instantDeposit(vault, amount, 0, msg.sender);
} else {
// Auto-claim deposit
vaultRouter.requestDeposit(vault, amount, 0, msg.sender, true);
}
}
}

UX contract security

ContractRolePermissions
VaultRouterOPERATOR_ROLEExecute auto-claims
VaultRouterPAUSER_ROLEEmergency pause
AutoClaimKeeperKEEPER_ROLEExecute claims
InstantLiquidityPoolREBALANCER_ROLETrigger rebalancing

All three contracts implement:

  • pause() / unpause() - Halt operations
  • emergencyWithdraw() - Admin asset recovery
  • UUPS upgradeable pattern for security patches

These contracts have not had a third-party security audit. Use them on testnet only until audits are complete.

Next

Last updated: December 28, 2025. Version 1.0.

results matching ""
    No results matching ""