Skip to main content

Stake Liquidity Tokens with Code

Overview

ZenVault allows you to stake ZenSwap liquidity pool (LP) tokens to participate in ZenChain's native staking system. When you deposit LP tokens into a ZenVault, the vault stakes those assets on your behalf, allowing you to earn rewards in the native currency (ZTC) while maintaining liquidity exposure.

This guide explains how to interact with ZenVault smart contracts to stake, unstake, and withdraw your LP tokens, as well as how rewards and potential slashing work.

Key Concepts

  • LP Token Staking: A ZenVault accepts and manages deposits of a specific ZenSwap-compatible LP token, identified by the pool() address.
  • Native Staking Participation: The vault stakes the underlying assets represented by your deposited LP tokens into ZenChain's native staking mechanism.
  • Rewards: The vault earns native staking rewards (in ZTC) on behalf of its stakers. These rewards are periodically processed and distributed proportionally to stakers.
  • Unstaking Period: When you unstake, your tokens enter an "unlocking" period determined by ZenChain's native staking bondingDuration. You can only withdraw after this period has passed.
  • Slashing: If validators nominated by the vault misbehave, the vault may be subject to slashing penalties, which are distributed proportionally among all stakers.
  • Account State Synchronization: The contract tracks your staked balance, unlocking tokens, and earned rewards. To ensure fairness, your pending rewards and any applicable slashes are calculated and applied to your balance whenever you call one of the core functions (stake, unstake, withdrawUnlocked, updateUserState).

Core User Actions

These are the primary functions you will use to manage your stake in a ZenVault contract.

1. Staking LP Tokens

Use the stake(uint256 amount) function to deposit your LP tokens into the vault.

  • Purpose: Adds your LP tokens to the vault's staking pool.
  • Parameters:
    • amount: The quantity of LP tokens you wish to stake.
  • Requirements:
    • You must have approved the ZenVault contract to spend your LP tokens beforehand.
  • Effect: Your LP tokens are transferred to the vault, your staked balance increases, and you begin earning rewards. Any pending ZTC rewards are claimed and any pending slashes are applied before the new amount is added.

2. Initiating Unstaking

Use the unstake(uint256 amount) function to start the process of withdrawing your LP tokens.

  • Purpose: Moves the specified amount of your staked tokens into the unlocking queue.
  • Parameters:
    • amount: The quantity of LP tokens you wish to unstake.
  • Requirements:
    • You must have sufficient staked balance.
    • The number of separate unlocking entries you have must be less than the maximum allowed (maxUnlockChunks()).
  • Effect: Your staked balance decreases by amount, and a new entry is added to your unlocking list, which includes the era when the funds will be available to withdraw. Any pending ZTC rewards are claimed and any pending slashes are applied before the amount is moved to unlocking.

3. Withdrawing Unlocked Tokens

Use the withdrawUnlocked() function to claim LP tokens that have completed their unlocking period.

  • Purpose: Transfers LP tokens from your unlocking queue back to your wallet.
  • Requirements:
    • You must have at least one unlocking chunk where the era is less than the current ZenChain era.
  • Effect: Tokens that have completed their unlocking period are transferred to your wallet, and the corresponding entries are removed from your unlocking list. Any pending ZTC rewards are claimed and any pending slashes are applied before the withdrawal occurs.

4. Manually Updating Your State

Use updateUserState(address user) to synchronize a user's account.

  • Purpose: Manually trigger the application of any pending rewards and slashes to a user's balance without performing any other action.
  • Effect: Any pending ZTC rewards are claimed and sent to the user's wallet. Any pending slashes are applied to the user's staked and unlocking balances. This is useful for realizing rewards without changing your stake.

Querying Information (View Functions)

You can call these functions at any time without sending a transaction to check the state of the vault and your account.

Vault Information

  • pool(): Returns the address of the LP token this vault accepts.
  • totalStake(): Returns the total amount of LP tokens currently staked in the vault by all users.
  • totalSlashableStake(): Returns the total amount of stake potentially subject to slashing (includes all staked and unlocking tokens in the vault).
  • maxUnlockChunks(): Returns the maximum number of separate unlocking entries a user can have concurrently.

Your Account

  • stakedBalances(address user): Returns the amount of LP tokens currently staked by you.
  • getUnlockingChunks(address user): Retrieves all of your unlocking chunks. Each chunk shows an amount and the era it becomes withdrawable.
  • getSlashableStake(address user): Returns the total amount of your stake that is eligible for slashing (includes both your actively staked balance and any tokens you have in the unlocking queue).
  • getPendingRewards(address user): Returns the amount of claimable ZTC rewards currently accrued for you.
  • getPendingSlash(address user): Returns the amount of your LP tokens that would be slashed if a state update were to occur now.

Understanding Rewards and Slashing

How Rewards Work

  1. Earning: The ZenVault, acting as a nominator in ZenChain's native staking system, earns ZTC rewards.
  2. Accumulation: These rewards are sent to a dedicated Reward Account managed by the Vault Staking system.
  3. Distribution: Any user can call the distributeRewards function on the system-level VaultStaking precompile. This triggers the transfer of accumulated ZTC from the Reward Account into the ZenVault smart contract for accounting.
  4. Claiming: The ZTC rewards are sent directly to your wallet whenever you interact with the vault (stake, unstake, withdrawUnlocked, or updateUserState). The amount of ZTC you receive is proportional to your share of the vault's total stake.

How Slashing Works

  1. Risk: Participating in staking carries the risk of slashing if validators chosen by the vault misbehave.
  2. Application: If a slash occurs, the penalty is proportionally distributed among all stakers. The actual reduction of your staked LP tokens happens when you next interact with the contract (stake, unstake, etc.), where your share of the slash is calculated and deducted from your balance.
  3. Pending Slash (Exception): In rare cases, if the system fails to apply a slash immediately, a Pending Slash is created at the system level. The vault is then temporarily chilled (stops nominating and earning new rewards) to prevent further risk. An administrator must manually resolve the situation to resume vault operations.

How the ZenVault System Works

The ZenVault ecosystem consists of two primary components:

  1. The ZenVault Smart Contract (ZenVault.sol): This is the user-facing EVM contract you interact with to stake, unstake, and manage your LP tokens.
  2. The VaultStaking Precompile: This is a system-level contract that administrators use to manage the lifecycle of all ZenVaults (registering, deregistering, etc.). It also exposes functions that anyone can call to trigger system-wide actions.

Publicly-callable functions on the precompile include:

  • distributeRewards(address vaultAddress): Triggers the reward distribution process for a specific vault.
  • updateVault(address vaultAddress): Forces an update of a vault's virtualBond—its total staked value as recognized by the native staking system.

Events

The contract emits events for significant actions, allowing you to track vault activity:

  • Staked(address indexed user, uint256 amount): Emitted when a user successfully stakes LP tokens.
  • Unstaked(address indexed user, uint256 amount): Emitted when a user initiates the unstaking process, moving tokens to the unlocking queue.
  • Withdrawal(address indexed user, uint256 amount): Emitted when a user withdraws LP tokens that have completed their unlocking period.
  • VaultRewardsAdded(uint256 rewardAmount, uint256 cumulativeRewardPerShare, uint256 rewardRatio): Emitted when the system adds new rewards to the vault's accounting via distributeRewards.
  • VaultSlashed(uint256 slashAmount, uint256 cumulativeSlashPerShare, uint256 slashRatio): Emitted when the system applies a slash penalty to the vault via doSlash.
  • RewardClaimed(address indexed user, uint256 amount): Emitted when a user's pending rewards are calculated and sent to their account during an interaction like stake, unstake, or withdrawUnlocked.
  • RewardSlashed(address indexed user, uint256 pendingReward, uint256 slashAmount, uint256 remainingReward): Emitted when a user's reward is reduced due to a slash penalty on their stake.
  • UserSlashApplied(address indexed user, uint256 pendingSlash, uint256 slashedFromStake, uint256 slashedFromUnlocking): Emitted when a pending slash amount for a user is calculated and applied to their staked and/or unlocking balances.
  • RewardAccountSet(address account): Emitted when the administrator updates the vault's reward account.
  • TwapUpdated(uint256 lastLpToZtcRate, uint256 price0Cumulative, uint256 price1Cumulative, uint112 reserve0Last, uint112 reserve1Last, uint256 lpTotalSupplyLast, uint32 blockTimestamp): Emitted when the TWAP oracle is updated.

Important Considerations

  • Unlocking Delay: Remember there is a time delay between unstaking and being able to withdraw your LP tokens.
  • Risks: Understand the risks involved, including smart contract vulnerabilities and the possibility of slashing.
  • ZTC Rewards: Rewards are paid in the native currency (ZTC). They are not automatically re-staked or compounded.