Skip to main content
EarnUSDT is a yield‑bearing version of USDT backed by the Super Vault. Users deposit USDT and receive EarnUSDT (vault shares). As yield is generated, the value of each share increases. There are two main ways to integrate EarnUSDT:
  1. Contract integration – you deploy your own smart contract and that contract deposits into SuperEarn.
  2. Frontend‑only integration – your UI calls SuperEarn directly from the user’s wallet.
The on‑chain write flow (deposit / redeem) is slightly different between 1 and 2, but data read via GraphQL is the same – you just change the accountAddress.

Common building blocks

You will need three addresses (See smart contracts page):
  • SUPEREARN_ROUTER – SuperEarn router (ISuperEarnRouter).
  • SUPER_VAULT – the EarnUSDT Super Vault (the EarnUSDT ERC‑20 share token).
  • USDT – Kaia USDT token.
Key router functions (simplified):
  • deposit(address superVault, uint256 amount, uint256 minSharesOut) returns (uint256 shares)
  • deposit(address superVault, uint256 amount, address receiver, uint256 minSharesOut) returns (uint256 shares)
  • depositWithPermit(...) returns (uint256 shares)
  • previewDeposit(address superVault, uint256 amount) view returns (uint256 expectedShares)
  • previewRedeem(address superVault, uint256 shares) view returns (uint256 expectedAssets)
  • redeem(address superVault, uint256 shares, uint256 minAssetsOut) returns (uint256 requestId)
  • redeem(address superVault, uint256 shares, address receiver, uint256 minAssetsOut) returns (uint256 requestId)
Internally, the Super Vault holds CooldownVault shares and the actual cooldown logic lives in the CooldownVault). All user / integrator deposits and withdrawals must go through SuperEarnRouter; CooldownVault deposits/redeems are restricted to protocol contracts. Claims are permissionless but normally handled by protocol keepers to batch requests.
You can get the CooldownVault address from the Super Vault:
address cooldownVault = IVault(SUPER_VAULT).token();

1. Contract integration (your own wrapper contract)

When to use
  • Centralized service, CEX, wallet, or protocol that:
    • Holds users’ funds in its own contract, and
    • Wants to manage EarnUSDT on behalf of users.
Your contract holds the EarnUSDT shares, does accounting, and talks to SuperEarnRouter. Assume:
ISuperEarnRouter constant ROUTER = ISuperEarnRouter(SUPEREARN_ROUTER);
IERC20            constant USDT   = IERC20(USDT_ADDRESS);
IERC20            constant SHARES = IERC20(SUPER_VAULT); // EarnUSDT share token

1-1. Deposit (USDT → EarnUSDT) via your contract

High‑level flow
  1. User sends USDT into your contract (e.g. via transferFrom).
  2. Your contract approves SuperEarnRouter to spend USDT.
  3. Your contract calls ROUTER.deposit(SUPER_VAULT, amount, receiver, minSharesOut).
  4. EarnUSDT shares are minted to receiver (usually your contract).
  5. You update your internal accounting.
Example Solidity
function depositIntoSuperVault(uint256 amountUSDT, address user) external {
    // 1. Pull USDT from the user into THIS contract
    USDT.transferFrom(user, address(this), amountUSDT);

    // 2. Approve SuperEarnRouter
    USDT.approve(address(ROUTER), amountUSDT);

    // 3. Deposit into Super Vault through SuperEarnRouter
    //    - receiver = this contract (wrapper)
    //    - minSharesOut = 0 for simple integrations
    uint256 shares = ROUTER.deposit(
        SUPER_VAULT,
        amountUSDT,
        address(this),
        0
    );

    // 4. Record user's shares in your own storage
    userShares[user] += shares;
}
If you want to show the user an estimate before depositing, call:
uint256 expectedShares = ROUTER.previewDeposit(SUPER_VAULT, amountUSDT);

1-2. Withdraw / redeem (EarnUSDT → USDT) via your contract

Redemption is a 2‑step process under the hood (redeem request → claim after cooldown), but your contract usually only needs to start the request. A keeper / bot normally handles claim. High‑level flow
  1. You reduce the user’s internal share balance.
  2. Your contract approves SuperEarnRouter to spend its EarnUSDT shares.
  3. Call ROUTER.previewRedeem for an estimate (optional).
  4. Call ROUTER.redeem(SUPER_VAULT, shares, receiver, minAssetsOut).
  5. You get a requestId for the pending withdrawal.
  6. After cooldown, USDT is delivered to receiver when the request is claimed.
Example Solidity
function requestWithdrawFromSuperVault(
    uint256 shares,
    address user,
    address receiver
) external returns (uint256 requestId) {
    // 1. Update your internal accounting
    require(userShares[user] >= shares, "insufficient shares");
    userShares[user] -= shares;

    // 2. Approve router to move shares from THIS contract
    SHARES.approve(address(ROUTER), shares);

    // 3. (Optional) Preview how much USDT the user will get
    uint256 expectedAssets = ROUTER.previewRedeem(SUPER_VAULT, shares);
    // You can show this to the user, or enforce your own minAssetsOut logic

    // 4. Create redemption request
    //    - receiver = address that should receive USDT after cooldown
    //    - minAssetsOut = 0 for simple integrations (or set your own)
    requestId = ROUTER.redeem(
        SUPER_VAULT,
        shares,
        receiver,
        0
    );

    // 5. Store requestId if you want to show pending withdrawals
    userRedeemRequests[user].push(requestId);
}
Claims on CooldownVault are handled by protocol keepers; integrators should not call deposit, redeem, or claim on CooldownVault directly. Read access (e.g. redeemRequests) is fine for monitoring.

1-3. Read data via GraphQL (for contract integration)

For more detailed information about Subgraphs, please refer to the Subgraphs page.
For reads, you don’t talk to the contract directly. Instead, use the GraphQL read API (built on top of the subgraph / processed data layer). For contract integration:
  • accountAddress = your wrapper contract address
  • You filter positions by the EarnUSDT Super Vault.

1-3-1. Get vault list (to find EarnUSDT Super Vault)

query GetVaults {
  pdm_vaults {
    contractAddress
    vaultName
    wantAddress
    isVisble
    inManagement
    vaultCreatedAt
  }
}
  • Look for the vault where vaultName or contractAddress matches your EarnUSDT Super Vault.

1-3-2. Get your contract’s EarnUSDT position

query WrapperEarnUsdtPosition($wrapper: String!) {
  pdm_account(
    accountAddress: $wrapper
    forceRefresh: false
  ) {
    id
    positions(forceRefresh: false) {
      vault {
        contractAddress
        vaultName
      }
      shares
      totalValueInUSD
      totalEarningsInUSD
      positionCreatedAt
      positionUpdatedAt
    }
    redeemRequests(forceRefresh: false) {
      # RedeemRequest fields (id, shares, assets, timestamps, etc.)
    }
  }
}
  • Use your wrapper contract address as $wrapper.
  • Filter positions client‑side where vault.contractAddress == SUPER_VAULT.

1-3-3. Optional: account charts

query WrapperBalanceChart($wrapper: String!) {
  pdm_totalBalanceInUSDLineChart(
    accountAddress: $wrapper
    startDateUTCInclusive: "2024-01-01T00:00:00Z"
    endDateUTCInclusive: "2024-12-31T23:59:59Z"
    vaultAddress: null  # or SUPER_VAULT to focus only on EarnUSDT
  ) {
    date
    value
  }
}
Use this to draw balance/earnings charts for your pooled account.

2. Frontend‑only integration (direct from user wallet)

When to use
  • Non‑custodial dApps / wallets.
  • You want the user’s wallet to hold EarnUSDT shares directly.
  • No custom wrapper contract – just frontend and SuperEarn.
Your frontend calls SuperEarnRouter from the user’s wallet using ethers, viem, wagmi, etc. Assume you have:
  • routerAddress = SUPEREARN_ROUTER
  • superVaultAddress = SUPER_VAULT
  • usdtAddress = USDT

2-1. Deposit (USDT → EarnUSDT) directly from frontend

High‑level UX
  1. User connects wallet.
  2. Frontend asks user to approve USDT for SuperEarnRouter.
  3. Frontend calls deposit(superVault, amount, minSharesOut) from the wallet.
  4. User receives EarnUSDT shares (Super Vault shares) in their wallet.
Example (TypeScript + viem/ethers‑style pseudocode)
const router = {
  address: routerAddress,
  abi: ISuperEarnRouterAbi,
} as const;

async function depositEarnUsdt(amountUSDT: bigint, account: `0x${string}`) {
  // 1. Approve USDT
  await writeContract({
    address: usdtAddress,
    abi: erc20Abi,
    functionName: 'approve',
    args: [routerAddress, amountUSDT],
    account,
  });

  // 2. (Optional) preview shares
  const expectedShares = await readContract({
    ...router,
    functionName: 'previewDeposit',
    args: [superVaultAddress, amountUSDT],
  });

  // 3. Deposit
  const txHash = await writeContract({
    ...router,
    functionName: 'deposit',
    args: [superVaultAddress, amountUSDT, 0n], // minSharesOut = 0n for simple case
    account,
  });

  // Track txHash and show confirmation in UI
}
If you support permit, use depositWithPermit to skip the explicit ERC‑20 approval.

2-2. Withdraw / redeem (EarnUSDT → USDT) from frontend

From the user’s point of view:
  1. They pick how much EarnUSDT to redeem.
  2. Your UI shows an estimate using previewRedeem.
  3. You call redeem(superVault, shares, minAssetsOut) from their wallet.
  4. A redemption request is created; after the cooldown, USDT is claimable / delivered.
Example (TypeScript pseudocode)
async function redeemEarnUsdt(shares: bigint, account: `0x${string}`) {
  // 1. (Optional) preview expected USDT
  const expectedAssets = await readContract({
    address: routerAddress,
    abi: ISuperEarnRouterAbi,
    functionName: 'previewRedeem',
    args: [superVaultAddress, shares],
  });

  // 2. Approve router to spend EarnUSDT shares
  await writeContract({
    address: superVaultAddress,
    abi: erc20Abi,
    functionName: 'approve',
    args: [routerAddress, shares],
    account,
  });

  // 3. Create redeem request
  const requestId = await writeContract({
    address: routerAddress,
    abi: ISuperEarnRouterAbi,
    functionName: 'redeem',
    args: [superVaultAddress, shares, 0n], // minAssetsOut = 0n (or your own)
    account,
  });

  // Store requestId in your app state if you want to show “pending withdrawal”
}
A backend keeper normally handles the actual claim on the CooldownVault once the cooldown period ends, so your UI can just track redeemRequests and final balances rather than calling claim itself; end users should not call CooldownVault directly.

2-3. Read user data via GraphQL

For more detailed information about Subgraphs, please refer to the Subgraphs page.
For frontend‑only integration, the account is the user’s wallet address.

2-3-1. Get EarnUSDT vault info

Same pdm_vaults query as before:
query GetVaults {
  pdm_vaults {
    contractAddress
    vaultName
    wantAddress
    isVisble
    inManagement
  }
}
Filter client‑side to the EarnUSDT Super Vault (by contractAddress / vaultName).

2-3-2. Get a user’s EarnUSDT position

query UserEarnUsdtPosition($user: String!) {
  pdm_account(
    accountAddress: $user
    forceRefresh: false
  ) {
    id
    positions(forceRefresh: false) {
      vault {
        contractAddress
        vaultName
      }
      shares
      totalValueInUSD
      totalEarningsInUSD
      positionCreatedAt
      positionUpdatedAt
    }
    redeemRequests(forceRefresh: false) {
      # RedeemRequest fields (requestId, shares, assets, timestamps, etc.)
    }
  }
}
Use the user’s wallet as $user. Filter positions by the EarnUSDT vault.

2-3-3. Get user balance / earnings chart

query UserBalanceChart($user: String!, $vault: String!) {
  pdm_totalBalanceInUSDLineChart(
    accountAddress: $user
    startDateUTCInclusive: "2024-01-01T00:00:00Z"
    endDateUTCInclusive: "2024-12-31T23:59:59Z"
    vaultAddress: $vault  # SUPER_VAULT for EarnUSDT only
  ) {
    date
    value
  }
}
Use this to render “balance over time” and “earnings over time” charts in your UI.

3. Direct contract queries

Most integrations can rely entirely on the subgraph / GraphQL layer. If you need real‑time, raw on‑chain data (e.g. your own indexer, custom monitoring), you can use the view functions on SUPER_VAULT (EarnUSDT yVault) and CooldownVault.

3-1. Get EarnUSDT balances and underlying value

EarnUSDT (the Super Vault) is an ERC‑20 token:
// EarnUSDT share balance
uint256 shares = IERC20(SUPER_VAULT).balanceOf(account);
To convert shares to underlying USDT equivalent, use price per share on the Super Vault:
uint256 assetsValue = shares * IVault(SUPER_VAULT).pricePerShare() / 10 ** IVault(SUPER_VAULT).decimals();
In UI terms: display balance in USDT = convertToAssets(balanceOf(user)).

3-2. Check cooldown redemption details

When you call ROUTER.redeem(...), the router internally creates a CooldownVault RedeemRequest. You can read its state directly from CooldownVault using the requestId returned by the router.
  1. Resolve the CooldownVault address
address cooldownVault = IVault(SUPER_VAULT).token();
  1. Query a specific redemption request
(
  address receiver,
  uint256 assets,
  uint256 cooldownRequestedTime,
  uint256 cooldownPeriod,
  bool claimed
) = ICooldownVault(cooldownVault).redeemRequests(requestId);
With this you can:
  • Compute claimable time:
uint256 claimableAt = cooldownRequestedTime + cooldownPeriod;
bool isClaimable = block.timestamp >= claimableAt && !claimed;
  • Show “pending / claimable / claimed” states per requestId.

3-3. Check global cooldown configuration

You can read the global cooldown period applied to new redemption requests:
uint256 globalCooldownPeriod = ICooldownVault(cooldownVault).cooldownPeriod();
// e.g. convert to days:
uint256 cooldownDays = globalCooldownPeriod / 86400;
This is useful to:
  • Show “estimated withdrawal time” in UI.
  • Enforce minimum UX expectations (e.g. “cooldown ≈ 7 days”).

3-4. System status: emergency & pause

For safety‑aware integrations you may want to halt actions if the system is paused or in emergency mode.
// Super Vault emergency shutdown (deposits & redeems are blocked)
bool isEmergency = IVault(SUPER_VAULT).emergencyShutdown();

// CooldownVault pause state (cooldown flow disabled)
bool isPaused = ICooldownVault(cooldownVault).paused();
Typical pattern:
  • Block new deposits / redeems in your app when isEmergency || isPaused is true.
  • Keep reads enabled for transparency.

4. Events to watch (monitoring & indexing)

If you run your own indexer or monitoring system, you will mainly care about events from ISuperEarnRouter and CooldownVault.

4-1. Router events (user‑facing actions)

Defined in ISuperEarnRouter:
event Deposited(
    address indexed sender,
    address indexed receiver,
    address indexed yVault,
    uint256 underlyingAmount,
    uint256 yShares
);

event Redeemed(
    address indexed sender,
    address indexed receiver,
    address indexed yVault,
    uint256 yShares,
    uint256 ySharesFilled,
    uint256 requestId,
    uint256 underlyingAmount
);
Usage:
  • Deposited
    • Trigger on successful USDT → EarnUSDT deposits.
    • Index (sender, receiver, yVault, underlyingAmount, yShares) to reconstruct deposit history.
  • Redeemed
    • Trigger when a redeem request is created (EarnUSDT → USDT with cooldown).
    • Use requestId to join with CooldownVault.redeemRequests(requestId) for full status.
    • ySharesFilled lets you detect partial fills in advanced scenarios.

4-2. CooldownVault events (cooldown lifecycle)

Core events from ICooldownVault:
event RedeemRequested(
    address indexed caller,
    address indexed receiver,
    uint256 indexed requestId,
    uint256 assets,
    uint256 shares,
    uint256 requestedTime
);

event Claimed(
    address indexed caller,
    uint256 indexed requestId,
    uint256 assets,
    uint256 claimable
);
(There are additional governance / strategy events such as PredepositRequested, DebtRetrieved, etc., but most EarnUSDT integrators can ignore those.) Usage:
  • RedeemRequested
    • Mirrors router Redeemed, but emitted at the CooldownVault level.
    • Good for verifying that a given requestId exists and carries (assets, shares, requestedTime).
  • Claimed
    • Emitted when a cooldown redemption is actually claimed and USDT is transferred.
    • Use it to mark a pending withdrawal as completed in your own indexer or notifications.
In practice:
  • Track user intent via Deposited / Redeemed on the router.
  • Track cooldown state + completion via RedeemRequested / Claimed on CooldownVault.

5. Interface reference (abridged)

This section summarizes only the pieces of the on‑chain interfaces that most integrators actually need. Strategy‑level interfaces such as IStrategyCooldownAware are intentionally omitted.

5-1. ISuperEarnRouter

Full interface (simplified to the relevant parts):
interface ISuperEarnRouter {
    // EVENTS
    event Deposited(
        address indexed sender,
        address indexed receiver,
        address indexed yVault,
        uint256 underlyingAmount,
        uint256 yShares
    );

    event Redeemed(
        address indexed sender,
        address indexed receiver,
        address indexed yVault,
        uint256 yShares,
        uint256 ySharesFilled,
        uint256 requestId,
        uint256 underlyingAmount
    );

    // VIEW
    function registry() external view returns (address);
    function previewRedeem(address yVault, uint256 yShares) external view returns (uint256);
    function endorsedVault(address token) external view returns (address);

    // DEPOSIT
    function deposit(
        address yVault,
        uint256 amount,
        uint256 minSharesOut
    ) external returns (uint256);

    function deposit(
        address yVault,
        uint256 amount,
        address receiver,
        uint256 minSharesOut
    ) external returns (uint256);

    function depositWithPermit(
        address yVault,
        uint256 amount,
        address receiver,
        uint256 minSharesOut,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256);

    // REDEEM (cooldown‑based)
    function redeem(
        address yVault,
        uint256 yShares,
        uint256 minAssetsOut
    ) external returns (uint256 requestId);

    function redeem(
        address yVault,
        uint256 yShares,
        address receiver,
        uint256 minAssetsOut
    ) external returns (uint256 requestId);
}
Rule of thumb:
  • For writes, you only need deposit, depositWithPermit, redeem.
  • For reads / UX, you mainly need previewRedeem.

5-2. ICooldownVault (high‑level subset)

CooldownVault is a vault with built‑in cooldown and loss‑limit logic. Below are the main functions relevant to EarnUSDT integrators.
interface ICooldownVault {
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);
    function mint(uint256 shares, address receiver) external returns (uint256 assets);
    function previewDeposit(uint256 assets) external view returns (uint256 shares);
    function previewMint(uint256 shares) external view returns (uint256 assets);

    // Cooldown redemption
    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) external returns (uint256 assets);

    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) external returns (uint256 shares);

    // Global cooldown config
    function cooldownPeriod() external view returns (uint256 period);

    // Individual redemption request
    function redeemRequests(uint256 requestId)
        external
        view
        returns (
            address receiver,
            uint256 assets,
            uint256 cooldownRequestedTime,
            uint256 cooldownPeriod,
            bool claimed
        );

    // Claim after cooldown
    function claim(uint256 requestId, uint256 maxLossBps) external returns (uint256 claimable);

    // Status
    function paused() external view returns (bool);
}
As an integrator:
  • Do not call deposit or redeem on CooldownVault — these are restricted to protocol contracts and go through SuperEarnRouter.
  • Claims are permissionless but typically executed by protocol keepers to batch/optimise redemptions.
  • Helpful reads:
    • cooldownPeriod() – to show global cooldown.
    • redeemRequests(requestId) – to inspect a specific withdrawal.

5-3. IVault (yVault / Super Vault)

The Super Vault wraps CooldownVault shares. For EarnUSDT integration you typically only need:
interface IVault {
    // Underlying token of the vault (here: CooldownVault)
    function token() external view returns (address);

    // (Plus standard vault view functions, if you need them)
    // e.g. totalAssets(), pricePerShare(), etc.
}
In most cases you use IVault(SUPER_VAULT).token() just to resolve the CooldownVault address.

Summary

For both integration styles:
  • Writes (deposit / redeem) → call SuperEarnRouter:
    • deposit / depositWithPermit for USDT → EarnUSDT
    • previewRedeem + redeem for EarnUSDT → USDT (cooldown‑based)
  • Reads (positions / status):
    • Use the subgraph / GraphQL layer for portfolio, PnL, and charts.
    • Optionally use direct contract reads for real‑time balances or custom monitoring.
  • Monitoring:
    • Subscribe to Deposited / Redeemed on the router and RedeemRequested / Claimed on CooldownVault.
The only thing that changes between integration styles is which accountAddress you query:
  • Contract integration → your wrapper contract address.
  • Frontend‑only → the user’s wallet address.