Integrate with the contracts

How to read from and write to the Noderr contracts on Base Sepolia, with interfaces and working examples.

Testnet. Addresses and behavior are for Base Sepolia. See Testnet Status.

What you need

  • A Web3 development environment such as Hardhat or Foundry, to compile, test and deploy contracts.
  • ABIs. The interfaces are below.
  • Addresses. See Contract Addresses.
  • ethers.js v6 for the JavaScript examples. The Noderr dApp uses ethers.js v6. The v5 API (ethers.providers.*, ethers.utils.*) is not compatible.

What you can do

Reads call view or pure functions and cost no gas:

  • Vault balances and realized (observed) APYs
  • Accrued staking rewards for an address
  • The status and voting results of a governance proposal
  • The TrustFingerprint score of a node operator

Writes are transactions and cost gas:

  • Deposit into and withdraw from the vaults
  • Stake and unstake NODR
  • Cast votes on active governance proposals

How a vault deposit works

The six vaults are ERC-7540 EIP-2535 diamonds. The asset is tUSDC (18 decimals). Deposits are asynchronous:

  1. Approve the vault to pull your tUSDC.
  2. Call requestDeposit.
  3. Wait for a keeper to fulfil the request.
  4. Call claimDeposit to receive your shares.

Redeeming follows the same pattern with requestRedeem and claimRedeem. There is no deposit or withdraw function on the vaults. The full interface is in Vault Contracts.

Interfaces

IVault.sol

Every function below is on all six vaults on Base Sepolia.

interface IVault {
// Deposit: approve tUSDC, request, wait for a keeper to fulfil, then claim shares
function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId);
function claimDeposit(uint256 assets, address receiver, address controller) external returns (uint256 shares);
// Redeem: request with shares, wait for a keeper to fulfil, then claim tUSDC
function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId);
function claimRedeem(uint256 shares, address receiver, address controller) external returns (uint256 assets);
function pendingDepositRequest(uint256 requestId, address controller) external view returns (uint256);
function claimableDepositRequest(uint256 requestId, address controller) external view returns (uint256);
function pendingRedeemRequest(uint256 requestId, address controller) external view returns (uint256);
function claimableRedeemRequest(uint256 requestId, address controller) external view returns (uint256);
function setOperator(address operator, bool approved) external returns (bool);
function asset() external view returns (address);
function balanceOf(address account) external view returns (uint256);
function convertToAssets(uint256 shares) external view returns (uint256);
}

IStakingManager.sol

interface IStakingManager {
function stake(uint256 amount) external;
function unstake(uint256 amount) external;
function claimRewards() external;
}

These are the most commonly used interfaces. The deployed contracts and their BaseScan links are in Contract Addresses.

Examples

These examples are simplified.

JavaScript with ethers.js v6

Set up a read only provider and a vault contract:

import { ethers } from'ethers'; // ethers ^6.x// Read-only providerconst provider = new ethers.JsonRpcProvider('https://sepolia.base.org');
const vaultAbi = [
'function requestDeposit(uint256 assets, address controller, address owner) returns (uint256)',
'function claimDeposit(uint256 assets, address receiver, address controller) returns (uint256)',
'function claimableDepositRequest(uint256 requestId, address controller) view returns (uint256)',
'function balanceOf(address account) view returns (uint256)',
'function asset() view returns (address)',
];
const vaultAddress = '0x49f747BBac988Cd628a5D79AAdF865EBaDe6a34f'; // Conservative vault, see Contract Addressesconst vaultContract = new ethers.Contract(vaultAddress, vaultAbi, provider);

Read a balance:

asyncfunctiongetVaultBalance(userAddress) {
const balance = await vaultContract.balanceOf(userAddress);
// ethers v6: formatUnits is a top-level function, not ethers.utils.formatUnitsconsole.log('Vault Balance:', ethers.formatUnits(balance, 18));
}

Request a deposit, then claim the shares:

asyncfunctiondepositToVault(amount) {
// ethers v6: BrowserProvider replaces Web3Provider; getSigner() is asyncconst browserProvider = new ethers.BrowserProvider(window.ethereum);
const signer = await browserProvider.getSigner();
const vaultWithSigner = vaultContract.connect(signer);
const user = await signer.getAddress();
// ethers v6: parseUnits is a top-level function, not ethers.utils.parseUnitsconst assets = ethers.parseUnits(amount, 18); // tUSDC has 18 decimals// 1. Approve the vault to pull tUSDCconst tusdc = new ethers.Contract(await vaultContract.asset(), ['function approve(address spender, uint256 amount) returns (bool)'], signer);
await (await tusdc.approve(vaultAddress, assets)).wait();
// 2. Request the deposit (ERC-7540 is asynchronous)await (await vaultWithSigner.requestDeposit(assets, user, user)).wait();
console.log('Deposit requested. Claim once a keeper fulfils it.');
}
asyncfunctionclaimShares() {
const browserProvider = new ethers.BrowserProvider(window.ethereum);
const signer = await browserProvider.getSigner();
const user = await signer.getAddress();
// 3. Claim the shares after fulfilmentconst claimable = await vaultContract.claimableDepositRequest(0, user);
if (claimable > 0n) {
await (await vaultContract.connect(signer).claimDeposit(claimable, user, user)).wait();
}
}

The same read with an ABI file:

import { ethers } from"ethers";
import VaultABI from"./abis/IVault.json";
const provider = new ethers.JsonRpcProvider("https://sepolia.base.org");
const vaultAddress = "0x...";
const vault = new ethers.Contract(vaultAddress, VaultABI, provider);
asyncfunctiongetVaultBalance(userAddress) {
const balance = await vault.balanceOf(userAddress);
console.log(`Vault Balance: ${ethers.formatEther(balance)}`);
}

Solidity

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IVault.sol";
contract MyDApp {
IVault vault = IVault(0x49f747BBac988Cd628a5D79AAdF865EBaDe6a34f); // Conservative vault
// Step 1: take tUSDC from the caller and request a deposit. This contract is owner and controller.
function requestVaultDeposit(uint256 assets) external {
IERC20 tusdc = IERC20(vault.asset());
tusdc.transferFrom(msg.sender, address(this), assets);
tusdc.approve(address(vault), assets);
vault.requestDeposit(assets, address(this), address(this));
}
// Step 2: after a keeper fulfils the request, claim the shares to this contract.
function claimVaultShares() external {
uint256 claimable = vault.claimableDepositRequest(0, address(this));
vault.claimDeposit(claimable, address(this), address(this));
}
}

Python with Web3.py

from web3 import Web3
web3 = Web3(Web3.HTTPProvider('https://sepolia.base.org'))
vault_abi = [...]
vault_address = '0x...'
vault_contract = web3.eth.contract(address=vault_address, abi=vault_abi)
defget_vault_balance(user_address):
balance = vault_contract.functions.balanceOf(user_address).call()
print(f'Vault Balance: {Web3.from_wei(balance, "ether")}')

Good practice

  1. Use the provided interfaces. They keep your code compatible with future protocol upgrades.
  2. Handle errors. Wrap transactions in try/catch blocks and handle any exception thrown.
  3. Estimate gas first. Some calls are gas intensive. Estimate the cost before sending and show it to your users.
  4. Stay up to date. Follow Noderr on Twitter and join the Discord to hear about upcoming changes and upgrades.

Next

results matching ""
    No results matching ""