# Volo Liquid Staking API Reference

## Volo Staking API Reference

**Source Code**: <https://github.com/Sui-Volo/volo-smart-contracts/tree/main/liquid_staking>

**Contract Address**: `0x68d22cf8bdbcd11ecba1e094922873e4080d4d11133e2443fddda0bfd11dae20`

> **Note:** In this documentation, `CERT` refers to **vSUI** (Volo Staked SUI). They are the same token - `CERT` is the internal module name used in the smart contract.

***

### Table of Contents

1. Data Structures
2. User Functions
3. View Functions
4. Admin Functions
5. Operator Functions
6. Events
7. Constants & Error Codes

***

### 1. Data Structures

#### StakePool

```move
public struct StakePool has key, store {
    id: UID,
    fee_config: FeeConfig,
    fees: Balance<SUI>,
    boosted_balance: Balance<SUI>,
    boosted_reward_amount: u64,
    accrued_reward_fees: u64,
    validator_pool: ValidatorPool,
    manage: Manage,
    extra_fields: Bag
}
```

| Field                   | Type            | Description                                      |
| ----------------------- | --------------- | ------------------------------------------------ |
| `fee_config`            | `FeeConfig`     | Fee configuration                                |
| `fees`                  | `Balance<SUI>`  | Accumulated protocol fees (stake + unstake fees) |
| `boosted_balance`       | `Balance<SUI>`  | Deposited boosted reward balance                 |
| `boosted_reward_amount` | `u64`           | Amount to distribute per epoch                   |
| `accrued_reward_fees`   | `u64`           | Accumulated reward fees (not yet collected)      |
| `validator_pool`        | `ValidatorPool` | Validator management                             |
| `manage`                | `Manage`        | Version and pause state                          |

***

#### ValidatorPool

```move
public struct ValidatorPool has store {
    sui_pool: Balance<SUI>,
    validator_infos: vector<ValidatorInfo>,
    total_sui_supply: u64,
    last_refresh_epoch: u64,
    total_weight: u64,
    manage: Manage,
    extra_fields: Bag
}
```

| Field                | Type                    | Description                                |
| -------------------- | ----------------------- | ------------------------------------------ |
| `sui_pool`           | `Balance<SUI>`          | Buffer for stake/unstake operations        |
| `validator_infos`    | `vector<ValidatorInfo>` | All validator information                  |
| `total_sui_supply`   | `u64`                   | Total SUI managed (sui\_pool + all stakes) |
| `last_refresh_epoch` | `u64`                   | Last epoch when pool was refreshed         |
| `total_weight`       | `u64`                   | Sum of all validator weights               |

***

#### ValidatorInfo

```move
public struct ValidatorInfo has store {
    staking_pool_id: ID,
    validator_address: address,
    active_stake: Option<FungibleStakedSui>,
    inactive_stake: Option<StakedSui>,
    exchange_rate: PoolTokenExchangeRate,
    total_sui_amount: u64,
    assigned_weight: u64,
    last_refresh_epoch: u64,
    extra_fields: Bag
}
```

| Field               | Type                        | Description                        |
| ------------------- | --------------------------- | ---------------------------------- |
| `staking_pool_id`   | `ID`                        | Validator's staking pool ID        |
| `validator_address` | `address`                   | Validator address                  |
| `active_stake`      | `Option<FungibleStakedSui>` | Active stake (earning rewards)     |
| `inactive_stake`    | `Option<StakedSui>`         | Inactive stake (first epoch)       |
| `exchange_rate`     | `PoolTokenExchangeRate`     | Current exchange rate              |
| `total_sui_amount`  | `u64`                       | Total SUI staked to this validator |
| `assigned_weight`   | `u64`                       | Weight for allocation              |

***

#### FeeConfig

```move
public struct FeeConfig has store {
    stake_fee_bps: u64,
    unstake_fee_bps: u64,
    reward_fee_bps: u64,
    unstake_fee_redistribution_bps: u64,
    extra_fields: Bag
}
```

| Field                            | Type  | Max Value    | Description                             |
| -------------------------------- | ----- | ------------ | --------------------------------------- |
| `stake_fee_bps`                  | `u64` | 500 (5%)     | Fee on staking                          |
| `unstake_fee_bps`                | `u64` | 500 (5%)     | Fee on unstaking                        |
| `reward_fee_bps`                 | `u64` | 10000 (100%) | Performance fee on rewards              |
| `unstake_fee_redistribution_bps` | `u64` | 10000 (100%) | Portion of unstake fee returned to pool |

***

#### Metadata\<CERT>

```move
public struct Metadata<phantom T> has key, store {
    id: UID,
    version: u64,
    total_supply: Supply<T>,
}
```

Shared object storing the vSUI minting authority.

***

#### Capability Structs

```move
public struct AdminCap has key, store { id: UID }
public struct OperatorCap has key, store { id: UID }
```

***

### 2. User Functions

#### stake\_entry

Stake SUI and receive vSUI.

```move
public entry fun stake_entry(
    self: &mut StakePool,
    metadata: &mut Metadata<CERT>,
    system_state: &mut SuiSystemState,
    sui: Coin<SUI>,
    ctx: &mut TxContext
)
```

**Parameters:**

| Name           | Type                  | Description                   |
| -------------- | --------------------- | ----------------------------- |
| `self`         | `&mut StakePool`      | The stake pool object         |
| `metadata`     | `&mut Metadata<CERT>` | vSUI metadata (shared object) |
| `system_state` | `&mut SuiSystemState` | Sui system state (`0x5`)      |
| `sui`          | `Coin<SUI>`           | SUI coin to stake             |

**Requirements:**

* Pool must not be paused
* SUI amount >= 0.1 SUI (100,000,000 MIST)

**Emits:** `StakeEventExt`

**TypeScript Example:**

```typescript
import { Transaction } from '@mysten/sui/transactions';

const PACKAGE_ID = '0x68d22cf8bdbcd11ecba1e094922873e4080d4d11133e2443fddda0bfd11dae20';
const STAKE_POOL = '<STAKE_POOL_OBJECT_ID>';
const CERT_METADATA = '<CERT_METADATA_OBJECT_ID>';
const SUI_SYSTEM_STATE = '0x5';

async function stake(suiAmount: bigint) {
    const tx = new Transaction();
    const [coin] = tx.splitCoins(tx.gas, [suiAmount]);

    tx.moveCall({
        target: `${PACKAGE_ID}::stake_pool::stake_entry`,
        arguments: [
            tx.object(STAKE_POOL),
            tx.object(CERT_METADATA),
            tx.object(SUI_SYSTEM_STATE),
            coin,
        ],
    });

    return tx;
}
```

***

#### stake

Non-entry version that returns the minted vSUI.

```move
public fun stake(
    self: &mut StakePool,
    metadata: &mut Metadata<CERT>,
    system_state: &mut SuiSystemState,
    sui: Coin<SUI>,
    ctx: &mut TxContext
): Coin<CERT>
```

**Returns:** `Coin<CERT>` - The minted vSUI coin

***

#### unstake\_entry

Redeem vSUI for SUI.

```move
public entry fun unstake_entry(
    self: &mut StakePool,
    metadata: &mut Metadata<CERT>,
    system_state: &mut SuiSystemState,
    cert: Coin<CERT>,
    ctx: &mut TxContext
)
```

**Parameters:**

| Name   | Type         | Description         |
| ------ | ------------ | ------------------- |
| `cert` | `Coin<CERT>` | vSUI coin to redeem |

**Requirements:**

* Pool must not be paused
* vSUI amount >= 0.1 SUI equivalent

**Emits:** `UnstakeEventExt`

**TypeScript Example:**

```typescript
async function unstake(vsuiObjectId: string) {
    const tx = new Transaction();

    tx.moveCall({
        target: `${PACKAGE_ID}::stake_pool::unstake_entry`,
        arguments: [
            tx.object(STAKE_POOL),
            tx.object(CERT_METADATA),
            tx.object(SUI_SYSTEM_STATE),
            tx.object(vsuiObjectId),
        ],
    });

    return tx;
}
```

***

#### unstake

Non-entry version that returns the redeemed SUI.

```move
public fun unstake(
    self: &mut StakePool,
    metadata: &mut Metadata<CERT>,
    system_state: &mut SuiSystemState,
    lst: Coin<CERT>,
    ctx: &mut TxContext
): Coin<SUI>
```

**Returns:** `Coin<SUI>` - The redeemed SUI coin

***

#### delegate\_stake\_entry

Stake SUI with a preferred validator.

```move
public entry fun delegate_stake_entry(
    self: &mut StakePool,
    metadata: &mut Metadata<CERT>,
    system_state: &mut SuiSystemState,
    sui: Coin<SUI>,
    v_address: address,
    ctx: &mut TxContext
)
```

**Parameters:**

| Name        | Type      | Description                 |
| ----------- | --------- | --------------------------- |
| `v_address` | `address` | Preferred validator address |

**Emits:** `DelegateStakeEvent`

**Note:** This function behaves identically to `stake_entry` but additionally emits a `DelegateStakeEvent` with the validator address. The backend uses this event to adjust validator weights.

***

#### rebalance

Rebalance stakes across validators according to weights. This is a permissionless function.

```move
public fun rebalance(
    self: &mut StakePool,
    metadata: &mut Metadata<CERT>,
    system_state: &mut SuiSystemState,
    ctx: &mut TxContext
)
```

**Emits:** `RebalanceEvent`

**TypeScript Example:**

```typescript
async function rebalance() {
    const tx = new Transaction();

    tx.moveCall({
        target: `${PACKAGE_ID}::stake_pool::rebalance`,
        arguments: [
            tx.object(STAKE_POOL),
            tx.object(CERT_METADATA),
            tx.object(SUI_SYSTEM_STATE),
        ],
    });

    return tx;
}
```

***

#### refresh

Update pool state if epoch has changed. Called automatically by stake/unstake.

```move
public fun refresh(
    self: &mut StakePool,
    metadata: &Metadata<CERT>,
    system_state: &mut SuiSystemState,
    ctx: &mut TxContext
): bool
```

**Returns:** `bool` - `true` if epoch rolled over and state was updated

**Emits:** `EpochChangedEvent` (if epoch changed)

***

### 3. View Functions

#### get\_ratio

Returns how many vSUI you get per 1 SUI (in 1e9 precision).

```move
public fun get_ratio(self: &StakePool, metadata: &Metadata<CERT>): u64
```

**Returns:** Ratio in 1e9 precision

**Example:** If returns `950_000_000`, then 1 SUI = 0.95 vSUI

***

#### get\_ratio\_reverse

Returns how many SUI you get per 1 vSUI (in 1e9 precision).

```move
public fun get_ratio_reverse(self: &StakePool, metadata: &Metadata<CERT>): u64
```

**Returns:** Ratio in 1e9 precision

**Example:** If returns `1_052_631_579`, then 1 vSUI ≈ 1.053 SUI

***

#### publish\_ratio

Emit ratio in V1 format (1e18 precision) for compatibility.

```move
public fun publish_ratio(self: &StakePool, metadata: &Metadata<CERT>)
```

***

#### get\_amount\_out

Calculate output amount after fees.

```move
public fun get_amount_out(
    self: &StakePool,
    metadata: &Metadata<CERT>,
    amount_in: u64,
    sui2lst: bool
): u64
```

**Parameters:**

| Name        | Type   | Description                               |
| ----------- | ------ | ----------------------------------------- |
| `amount_in` | `u64`  | Input amount in MIST                      |
| `sui2lst`   | `bool` | `true` for SUI→vSUI, `false` for vSUI→SUI |

**Returns:** Output amount after fees

***

#### sui\_amount\_to\_lst\_amount

Convert SUI amount to vSUI amount (no fees).

```move
public fun sui_amount_to_lst_amount(
    self: &StakePool,
    metadata: &Metadata<CERT>,
    sui_amount: u64
): u64
```

***

#### lst\_amount\_to\_sui\_amount

Convert vSUI amount to SUI amount (no fees).

```move
public fun lst_amount_to_sui_amount(
    self: &StakePool,
    metadata: &Metadata<CERT>,
    lst_amount: u64
): u64
```

***

#### total\_sui\_supply

Returns total SUI managed by the pool.

```move
public fun total_sui_supply(self: &StakePool): u64
```

***

#### total\_lst\_supply

Returns total vSUI supply.

```move
public fun total_lst_supply(metadata: &Metadata<CERT>): u64
```

***

#### validator\_pool

Get reference to validator pool.

```move
public fun validator_pool(self: &StakePool): &ValidatorPool
```

***

#### fee\_config

Get reference to fee configuration.

```move
public fun fee_config(self: &StakePool): &FeeConfig
```

***

#### total\_fees

Returns total accumulated fees (stake fees + unstake fees + reward fees).

```move
public fun total_fees(self: &StakePool): u64
```

***

#### accrued\_reward\_fees

Returns accumulated reward fees not yet collected.

```move
public fun accrued_reward_fees(self: &StakePool): u64
```

***

#### boosted\_balance

Returns remaining boosted reward balance.

```move
public fun boosted_balance(self: &StakePool): u64
```

***

#### ValidatorPool View Functions

```move
public fun sui_pool(self: &ValidatorPool): &Balance<SUI>
public fun validators(self: &ValidatorPool): &vector<ValidatorInfo>
public fun total_weight(self: &ValidatorPool): u64
public fun validator_weights(self: &ValidatorPool): VecMap<address, u64>
public fun validator_stake_amounts(self: &ValidatorPool): (VecMap<address, u64>, VecMap<address, u64>)
public fun total_sui_supply(self: &ValidatorPool): u64
public fun last_refresh_epoch(self: &ValidatorPool): u64
public fun find_validator_index_by_address(self: &ValidatorPool, addr: address): Option<u64>
```

***

#### FeeConfig View Functions

```move
public fun stake_fee_bps(fees: &FeeConfig): u64
public fun unstake_fee_bps(fees: &FeeConfig): u64
public fun reward_fee_bps(fees: &FeeConfig): u64
public fun unstake_fee_redistribution_bps(fees: &FeeConfig): u64
```

***

### 4. Admin Functions

All admin functions require `AdminCap`.

#### set\_paused

Pause or unpause the pool.

```move
public fun set_paused(self: &mut StakePool, _: &AdminCap, paused: bool)
```

**Emits:** `SetPausedEvent`

***

#### migrate\_version

Migrate pool to current version.

```move
public fun migrate_version(self: &mut StakePool, _: &AdminCap)
```

***

#### update\_stake\_fee

Update stake fee.

```move
public fun update_stake_fee(self: &mut StakePool, _: &AdminCap, fee: u64)
```

**Parameters:**

* `fee`: Fee in BPS (max 500)

**Emits:** `FeeUpdateEvent`

***

#### update\_unstake\_fee

Update unstake fee.

```move
public fun update_unstake_fee(self: &mut StakePool, _: &AdminCap, fee: u64)
```

**Parameters:**

* `fee`: Fee in BPS (max 500)

**Emits:** `FeeUpdateEvent`

***

#### update\_reward\_fee

Update reward fee (performance fee).

```move
public fun update_reward_fee(self: &mut StakePool, _: &AdminCap, fee: u64)
```

**Parameters:**

* `fee`: Fee in BPS (max 10000)

**Emits:** `FeeUpdateEvent`

***

#### update\_unstake\_fee\_redistribution

Update unstake fee redistribution percentage.

```move
public fun update_unstake_fee_redistribution(self: &mut StakePool, _: &AdminCap, fee: u64)
```

**Parameters:**

* `fee`: Percentage in BPS (max 10000)

**Emits:** `FeeUpdateEvent`

***

#### update\_boosted\_reward\_amount

Set the amount of boosted rewards to distribute per epoch.

```move
public fun update_boosted_reward_amount(self: &mut StakePool, _: &AdminCap, amount: u64)
```

**Emits:** `BoostedRewardAmountUpdateEvent`

***

#### collect\_fees

Collect accumulated protocol fees.

```move
public fun collect_fees(
    self: &mut StakePool,
    metadata: &mut Metadata<CERT>,
    system_state: &mut SuiSystemState,
    _: &AdminCap,
    ctx: &mut TxContext
): Coin<SUI>
```

**Returns:** `Coin<SUI>` - All accumulated fees

**Emits:** `CollectFeesEvent`

***

#### mint\_operator\_cap

Mint a new OperatorCap.

```move
public fun mint_operator_cap(
    self: &mut StakePool,
    _: &AdminCap,
    recipient: address,
    ctx: &mut TxContext
)
```

**Emits:** `MintOperatorCapEvent`

***

### 5. Operator Functions

All operator functions require `OperatorCap`.

#### set\_validator\_weights

Set validator weight allocation.

```move
public fun set_validator_weights(
    self: &mut StakePool,
    metadata: &mut Metadata<CERT>,
    system_state: &mut SuiSystemState,
    _: &OperatorCap,
    validator_weights: VecMap<address, u64>,
    ctx: &mut TxContext
)
```

**Parameters:**

| Name                | Type                   | Description                        |
| ------------------- | ---------------------- | ---------------------------------- |
| `validator_weights` | `VecMap<address, u64>` | Map of validator address to weight |

**Constraints:**

* Maximum 50 validators
* Total weight <= 10,000

**Emits:** `ValidatorWeightsUpdateEvent`

**TypeScript Example:**

```typescript
import { bcs } from '@mysten/sui/bcs';

async function setValidatorWeights(weights: [string, number][]) {
    const tx = new Transaction();

    // Serialize VecMap as vector of (address, u64) tuples
    const serialized = bcs.vector(
        bcs.tuple([bcs.Address, bcs.u64()])
    ).serialize(weights);

    tx.moveCall({
        target: `${PACKAGE_ID}::stake_pool::set_validator_weights`,
        arguments: [
            tx.object(STAKE_POOL),
            tx.object(CERT_METADATA),
            tx.object(SUI_SYSTEM_STATE),
            tx.object(OPERATOR_CAP),
            tx.pure(serialized),
        ],
    });

    return tx;
}

// Usage
const weights: [string, number][] = [
    ['0xvalidator1...', 5000],
    ['0xvalidator2...', 3000],
    ['0xvalidator3...', 2000],
];
await setValidatorWeights(weights);
```

***

#### deposit\_boosted\_balance

Deposit SUI as boosted rewards.

```move
public fun deposit_boosted_balance(
    self: &mut StakePool,
    _: &OperatorCap,
    coin: &mut Coin<SUI>,
    amount: u64,
    ctx: &mut TxContext
)
```

**Parameters:**

| Name     | Type             | Description              |
| -------- | ---------------- | ------------------------ |
| `coin`   | `&mut Coin<SUI>` | SUI coin to deposit from |
| `amount` | `u64`            | Amount to deposit        |

**Emits:** `DepositBoostedBalanceEvent`

***

### 6. Events

#### StakeEventExt

```move
public struct StakeEventExt has copy, drop {
    sui_amount_in: u64,      // SUI staked
    lst_amount_out: u64,     // vSUI received
    fee_amount: u64          // Stake fee charged
}
```

#### UnstakeEventExt

```move
public struct UnstakeEventExt has copy, drop {
    lst_amount_in: u64,          // vSUI burned
    sui_amount_out: u64,         // SUI received
    fee_amount: u64,             // Unstake fee (to protocol)
    redistribution_amount: u64   // Unstake fee (returned to pool)
}
```

#### EpochChangedEvent

```move
public struct EpochChangedEvent has copy, drop {
    old_sui_supply: u64,         // Previous total SUI
    new_sui_supply: u64,         // New total SUI (after rewards)
    boosted_reward_amount: u64,  // Boosted rewards distributed
    lst_supply: u64,             // Current vSUI supply
    reward_fee: u64              // Reward fee charged
}
```

#### DelegateStakeEvent

```move
public struct DelegateStakeEvent has copy, drop {
    v_address: address,      // Preferred validator
    sui_amount_in: u64,      // SUI staked
    lst_amount_out: u64      // vSUI received
}
```

#### ValidatorWeightsUpdateEvent

```move
public struct ValidatorWeightsUpdateEvent has copy, drop {
    validator_weights: VecMap<address, u64>
}
```

#### RebalanceEvent

```move
public struct RebalanceEvent has copy, drop {
    is_epoch_rolled_over: bool,  // Whether epoch changed
    sender: address              // Who triggered rebalance
}
```

#### FeeUpdateEvent

```move
public struct FeeUpdateEvent has copy, drop {
    field: String,       // Fee field name
    old_value: u64,      // Previous value
    new_value: u64       // New value
}
```

#### CollectFeesEvent

```move
public struct CollectFeesEvent has copy, drop {
    amount: u64          // Total fees collected
}
```

#### SetPausedEvent

```move
public struct SetPausedEvent has copy, drop {
    paused: bool
}
```

#### MintOperatorCapEvent

```move
public struct MintOperatorCapEvent has copy, drop {
    recipient: address
}
```

#### DepositBoostedBalanceEvent

```move
public struct DepositBoostedBalanceEvent has copy, drop {
    before_balance: u64,
    after_balance: u64
}
```

#### BoostedRewardAmountUpdateEvent

```move
public struct BoostedRewardAmountUpdateEvent has copy, drop {
    old_value: u64,
    new_value: u64
}
```

***

### 7. Constants & Error Codes

#### Constants

| Constant                | Value         | Description                         |
| ----------------------- | ------------- | ----------------------------------- |
| `SUI_MIST`              | 1,000,000,000 | 1 SUI in MIST                       |
| `MIN_STAKE_AMOUNT`      | 100,000,000   | Minimum stake/unstake (0.1 SUI)     |
| `MIN_STAKE_THRESHOLD`   | 1,000,000,000 | Minimum stake per validator (1 SUI) |
| `MAX_VALIDATORS`        | 50            | Maximum validators                  |
| `MAX_TOTAL_WEIGHT`      | 10,000        | Maximum sum of weights              |
| `DEFAULT_WEIGHT`        | 100           | Default weight for new validators   |
| `MAX_STAKE_FEE_BPS`     | 500           | Maximum stake fee (5%)              |
| `MAX_UNSTAKE_FEE_BPS`   | 500           | Maximum unstake fee (5%)            |
| `MAX_BPS`               | 10,000        | Maximum reward fee (100%)           |
| `BPS_MULTIPLIER`        | 10,000        | Basis points multiplier             |
| `VERSION`               | 2             | Current contract version            |
| `ACCEPTABLE_MIST_ERROR` | 10            | Allowed rounding error              |

#### Error Codes

| Module           | Code  | Name                         | Description                 |
| ---------------- | ----- | ---------------------------- | --------------------------- |
| `fee_config`     | 20001 | `EInvalidFee`                | Fee exceeds maximum         |
| `stake_pool`     | 30000 | `EZeroMintAmount`            | Cannot mint zero vSUI       |
| `stake_pool`     | 30001 | `ERatio`                     | Ratio invariant violated    |
| `stake_pool`     | 30002 | `EZeroSupply`                | Zero supply error           |
| `stake_pool`     | 30003 | `EUnderMinAmount`            | Below minimum amount        |
| `validator_pool` | 40000 | `ENotEnoughSuiInSuiPool`     | Insufficient SUI            |
| `validator_pool` | 40001 | `ENotActiveValidator`        | Validator not active        |
| `validator_pool` | 40002 | `ETooManyValidators`         | Exceeds max validators      |
| `validator_pool` | 40003 | `EValidatorAlreadyExists`    | Validator exists            |
| `validator_pool` | 40004 | `EValidatorNotFound`         | Validator not found         |
| `validator_pool` | 40005 | `EInvalidValidatorWeight`    | Invalid weight              |
| `validator_pool` | 40006 | `EInvalidValidatorWeightSum` | Weight sum mismatch         |
| `validator_pool` | 40007 | `EInvalidValidatorSize`      | Invalid validator size      |
| `validator_pool` | 40008 | `EMaxTotalWeight`            | Total weight exceeds max    |
| `validator_pool` | 40009 | `ETotalSuiSupplyChanged`     | Supply changed unexpectedly |
| `manage`         | 50001 | `EIncompatibleVersion`       | Version mismatch            |
| `manage`         | 50002 | `EIncompatiblePaused`        | Pool is paused              |
| `cert`           | 1     | `E_INCOMPATIBLE_VERSION`     | Metadata version mismatch   |
