Liquid Finance
  • What is Liquid Finance?
  • Protocol Features
    • sARCH
    • Staking
    • Unstaking
    • Providing Liquidity
  • Economics
    • Fees + Incentives
  • User Guide
    • Staking your ARCH tokens
    • Instant unstaking your sARCH tokens
    • Traditionally unstake your sARCH tokens
    • Claiming your unstaked ARCH
    • Providing liquidity to the Instant Unstaking Queue
    • Withdrawing your Instant Unstaking Queue Liquidity
    • Claiming your LP rewards
  • Technical
    • Liquid Finance Technical Specification
  • Security
    • Multisig
    • Risks
  • Community Resources
    • Official Links
    • Archway Mainnet
    • Archway Constantine
    • Terms of Service
    • Wallets
  • FAQ
Powered by GitBook
On this page
  • Architecture overview
  • Implementation detail
  • A. Liquid Staking
  • B. Liquid Swap
  • C. Liquid Treasury
  • E. Liquid Looping
  • Workflows
  • Staking ARCH
  • Delayed Unstaking sARCH
  • Provide liquidity for the Instant Unstaking Pool
  • Instant Unstaking sARCH
  • Remove liquidity from the Instant Unstaking Pool
  • Liquidity providers claim ARCH incentives and swapped sARCH
  • Deposit ARCH liquidity to the Liquid Looping contract
  • Withdraw ARCH liquidity from the Liquid Looping contract
  • Security
  • A. Validator multisig
  • B. Team wallet
  1. Technical

Liquid Finance Technical Specification

PreviousTechnicalNextSecurity

Last updated 1 year ago

Introduction

This document describes the implementation of the Liquid Finance Smart Contract Layer.

Architecture overview

The figure below is the core flows of contracts interaction:

With the current design, we have 8 contracts in total:

  • Liquid Staking: for staking, unstaking and claiming unstaked tokens.

  • Liquid Swap: acts as the swap contract for the liquid staking token to the native token.

  • Liquid Token: is cw20-base contract.

  • Liquid Treasury: store incentives for many different uses.

  • Multisig: the admin of all the contracts in the platform.

  • Group: contain the whitelist of members who can sign the multisig contract action.

  • Liquid Looping: make sure depositors’ ARCHs keep staying in the liquidity pool to receive LP rewards.

Implementation detail

A. Liquid Staking

This contract is a staking pool for users to deposit native tokens in and receive liquid staking tokens in return. The contract bonds users tokens to a whitelist of validators and auto-compounds rewards (bond earned staking rewards) to increase the users interest rate.

New deposits are used to process users unstaking requests, so unstaking here can be faster than the typical unbonding duration.

Data Source

  • admin (Addr): should be multi-sig, is able to update the config.

  • pause_admin (Addr): should be single-sig, is able to pause the contract.

  • bond_denom (String): the denomination of the native token that can be staked.

  • liquid_token_addr (Addr): Liquid Token contract address.

  • swap_contract_addr (Addr): Liquid Swap contract address.

  • treasury_contract_addr (Addr): Liquid Treasury contract address.

  • team_wallet_addr (Addr): team wallet address.

  • commission_percentage (u16): percentage of commission taken off of staking rewards.

  • team_percentage (u16): percentage of rewards for the team.

  • liquidity_percentage (u16): percentage of rewards for the liquidity providers.

  • delegations (Vec<DelegationPercentage>): a whitelist of validators, each validator has a delegation percentage.

  • contract_state (Uint128): the state of contract (true is active, false is paused).

  • native (Uint128): total supply of native token pool.

  • unstakings (Uint128): total amount of native tokens in the unstaking queue.

  • claims (Uint128): total amount of native token reserved paying back those who unstaked.

  • maximum_processed (Uint128): max processing amount allowed in the queue in a processed_time period.

  • current_processed (Uint128): total processed amount in the queue for the current period.

  • processed_time (u64): the minimum number of blocks until the current_processed reset to Zero.

  • processed_period (Expiration): unstaking queue can only process after this period.

  • unstaking_time (u64): the minimum number of blocks between each contract unbond request.

  • unstaking_period (Expiration): contract only unbond from delegators after this period.

  • mint_cap (Uint128): max amount of liquid token can be minted.

  • CLAIMABLE (Map<&Addr, Uint128>): store the claimable native token amounts of each address.

  • UNDER_UNSTAKING (Map<&Addr, Uint128>): store the total native token amounts waiting in the unstaking queue of each address.

  • staking_linked_list: store unstaking queue data.

Messages

  • Stake { to: Option<String> } - Moves native tokens sent along the message to the staking pool, and gives back liquid staking tokens to the sender (or to the address sender put into the optional to param).

  • Claim {} - Gives sender their claimable unstaked native token, should be called after the sender's unstaking request is processed.

  • SetAdmin { admin: Option<String>, pause_admin: Option<String> } - Updates admin’s addresses, only the current admin can call this.

  • SetContractState { state: bool } - Updates contract state (active or paused), only the current admin and pause admin can call this.

  • SetLiquidToken { address: String } - Set the liquid token address, only the admin can call this. This can only be called once after deploying the liquid token contract.

  • SetRewardsConfig { swap_contract_addr: Option<String>, treasury_contract_addr: Option<String>, team_wallet_addr: Option<String>, commission_percentage: Option<u16>, team_percentage: Option<u16>, liquidity_percentage: Option<u16> } - Update the configs relate to the incentives handling of staking rewards.

  • SetLimitConfig { maximum_processed: Option<Uint128>, processed_time: Option<Uint128>, unstaking_time: Option<Uint128>, mint_cap: Option<Uint128> } - Update the limit configs.

  • SetDelegations { delegations: Vec<DelegationPercentage> } - Update the validators whitelist, only the admin can call this.

  • BondUnbondValidators {} - Bond available coin or unbond to process unstaking requests.

  • ProcessUnstakingQueue {} - Use available coin to process unstaking requests.

  • DistributeRewards {} - Distribute staking rewards.

  • BotRoutine {} Bot or users call this daily to multicall the following: DistributeRewards, ProcessUnstakingQueue, BondUnbondValidators.

  • Receive(Cw20ReceiveMsg) - handle Send messages from cw20 token contract, only the liquid token contract can call this. This is for the unstaking process.

Queries

  • ClaimableOf { address: String } - Returns the amount of native tokens the address can claim. Returns "0" if the address is unknown to the contract. Return type is:

    pub struct BalanceResponse {
        pub balance: Uint128,
    }
  • ConfigInfo {} - Returns the configuration of the contract. Return type is:

    pub struct ConfigResponse {
        /// Should be multi-sig, is able to update the config
        pub admin: String,
        /// Should be single-sig, is able to pause the contract
        pub pause_admin: String,
        /// This is the denomination we can stake (and only one we accept for payments)
        pub bond_denom: String,
        /// Liquid token address
        pub liquid_token_addr: String,
        /// Swap contract address
        pub swap_contract_addr: String,
        /// Liquid Treasury contract address
        pub treasury_contract_addr: String,
        /// Team wallet address
        pub team_wallet_addr: String,
        /// percentage of commission taken off of staking rewards
        pub commission_percentage: u16,
        /// percentage of rewards for the team
        pub team_percentage: u16,
        /// percentage of rewards for the liquidity providers
        pub liquidity_percentage: u16,
        /// Delegations preferences for a whitelist of validators, each validator has a delegation percentage
        pub delegations: Vec<DelegationPercentage>,
        /// contract state (active/paused)
        pub contract_state: bool,
    }
  • StatusInfo {} - Returns the staking status of the contract. Return type is:

    pub struct StatusResponse {
        /// issued is how many derivative tokens this contract has issued
        pub issued: Uint128,
        /// native is how many total supply of native tokens liquid token holders can withdraw
        pub native: Coin,
        /// unstakings is how many total native tokens in unstaking queue
        pub unstakings: Uint128,
        /// claims is how many tokens need to be reserved paying back those who unbonded
        pub claims: Uint128,
        /// bonded is how many native tokens exist bonded to the validator
        pub bonded: Uint128,
        /// available native token balance of this contract
        pub balance: Uint128,
        /// ratio of native / issued (or how many native tokens that one derivative token is nominally worth)
        pub ratio: Decimal,
    }
  • UnstakingQueue {} - Returns the first 50 nodes in the unstaking queue of the contract. Return type is:

    pub struct UnstakingQueueResponse {
        pub state: LinkedList,
        pub queue: Vec<NodeWithId>,
    }
  • UnderUnstakingOf { address: String } - Returns the total amount of native token the address has in the unstaking queue. Return type is:

    pub struct BalanceResponse {
        pub balance: Uint128,
    }
  • LimitInfo {} - Returns the limit status of the contract. Return type is:

    pub struct LimitResponse {
        /// current max unstake processing amount allowed in the period
        pub maximum_processed: Uint128,
        // total processed amount for the current period
        pub current_processed: Uint128,
        // the minimum number of blocks between each unstake processing limit period
        pub processed_time: u64,
        // current_processed reset to 0 after this period
        pub processed_period: Expiration,
        /// the minimum number of blocks between each contract unstaking request
        pub unstaking_time: u64,
        /// contract can only call unbound after this period
        pub unstaking_period: Expiration,
        /// current max amount of liquid token can be minted
        pub mint_cap: Uint128,
    }
  • UnclaimedRewards { address: String } - Returns the unclaimed staking rewards. Return type is:

    pub struct UnclaimedRewardsResponse {
        /// the unclaimed rewards for the team
        pub team_portion: Uint128,
        /// the unclaimed rewards for liquidity providers
        pub liquidity_portion: Uint128,
        /// the unclaimed rewards for treasury
        pub treasury_portion: Uint128,
        // the unclaimed rewards for liquid token holders
        pub staking_pool: Uint128,
    }

B. Liquid Swap

This contract is the instant unstaking swap pool for those who want to convert liquid staking tokens to native tokens without waiting for the delayed unstaking period. Liquidity providers for this pool receive some percentage of staking rewards from the Liquid Staking contract. Rewards contract receives the swap fee.

Data Source

  • admin (Addr): should be multi-sig, is able to update the config.

  • pause_admin (Addr): should be single-sig, is able to pause the contract.

  • bond_denom (String): the denomination of the native token that can be staked.

  • liquid_token_addr (Addr): Liquid Token contract address.

  • staking_manager_addr (Addr): Liquid Staking contract address.

  • treasury_contract_addr (Addr): Liquid Treasury contract address.

  • team_wallet_addr (Addr): team wallet address.

  • swap_fee_percentage (u16): percentage of liquid token taken as fee from instant unstakers.

  • team_percentage (u16): percentage of swap fee for the team.

  • liquidity_min (Uint128): minimal amount of new native token being added for liquidity.

  • contract_state (Uint128): the state of contract (true is active, false is paused).

  • liquidity (Uint128): the total amount of native token supplied to the liquidity pool.

  • issued (Uint128): total supply of rewards shares this contract has issued.

  • claims (Uint128): the total amount of swapped liquid token that liquidity providers can claim.

  • total_unclaimed (Uint128): the total amount of native token rewards that liquidity providers can claim.

  • CLAIMABLE (Map<&Addr, Uint128>): store the claimable staked token amount of each address.

  • UNCLAIMED (Map<&Addr, Uint128>): store the claimable native token amount of each address.

  • QUEUE_ID (Map<&Addr, Uint128>): store the linked list queue id of each address.

  • swap_linked_list: store liquidity queue data.

Messages

  • Add {} - Moves native tokens sent along the message to the swap pool, and updates sender account in the liquidity queue.

  • Remove {} - Liquidity providers call this to withdraw all their native tokens from the liquidity pool.

  • Claim {} - Gives sender his claimable swapped liquid token, and native token rewards for being liquidity provider.

  • SetAdmin { admin: Option<String>, pause_admin: Option<String> } - Updates admin’s addresses, only the current admin can call this.

  • SetContractState { state: bool } - Updates contract state (active or paused), only the current admin and pause admin can call this.

  • SetRewardsConfig { team_wallet_addr: Option<String>, swap_fee_percentage: Option<u16>, team_percentage: Option<u16> } - Update the configs relate to the incentives handling of swap fee.

  • SetLiquidityMin { liquidity_min: Uint128 } - Updates the minimum amount of native token being added as liquidity, only the admin can call this.

  • Receive(Cw20ReceiveMsg) - Handle Send messages from cw20 token contract. This is for swapping cw20 liquid token sender to native token.

Queries

  • ClaimableOf { address: String } - Returns the claimable swapped liquid token and native token rewards the address can claim. Return type is:

    pub struct RewardsResponse {
        pub staked_token: Uint128,
        pub native_token: Uint128,
    }
  • ConfigInfo {} - Returns the configuration of the contract. Return type is:

    pub struct ConfigResponse {
        /// should be multi-sig, is able to update the config
        pub admin: String,
        /// should be single-sig, is able to pause the contract
        pub pause_admin: String,
        /// This is the denomination we can stake (and only one we accept for payments)
        pub bond_denom: String,
        /// Liquid token address
        pub liquid_token_addr: String,
        /// Staking manager contract address
        pub staking_manager_addr: String,
        /// Liquid Treasury contract address
        pub treasury_contract_addr: String,
        /// This is the team wallet address
        pub team_wallet_addr: String,
        /// Swap fee
        pub swap_fee_percentage: u16,
        /// team percentage of swap fee
        pub team_percentage: u16,
        /// Minimal amount of new native token being added for liquidity
        pub liquidity_min: Uint128,
        /// contract state (active/paused)
        pub contract_state: bool,
    }
  • StatusInfo {} - Returns the staking status of the contract. Return type is:

    pub struct StatusResponse {
        /// issued is how many rewards shares this contract has issued
        pub issued: Uint128,
        /// claims is how many tokens need to be reserved paying back those who unbonded
        pub claims: Uint128,
        /// available native token balance of this contract
        pub balance: Uint128,
        /// available native token balance to claim
        pub unclaimed_balance: Uint128,
        /// ratio of balance / issued (or how many native tokens that one rewards share is nominally worth)
        pub ratio: Decimal,
        /// total liquidity supply of this contract
        pub liquidity: Uint128,
    }
  • OrderBook {} - Returns the first 50 orders in the liquidity queue. Return type is:

    pub struct OrderBookResponse {
        pub state: LinkedList,
        pub queue: Vec<NodeWithId>,
    }
  • OrderInfoOf { address: String } - Returns the order info of the address in the liquidity queue. Return type is:

    pub struct OrderInfoOfResponse {
        /// issued is how many rewards shares in the pool this address has
        pub issued: Uint128,
        /// native is how many native tokens in the pool this address has
        pub native: Uint128,
        /// the block height shows when this address added native token to the pool
        pub height: u64,
        /// node_id is the id of adddress order in the linked-list
        pub node_id: u64,
        /// last deposit
        pub last_deposit: Uint128,
    }
  • Simulation { offer_asset: Asset } - Return the amount of native token swapper should get. Return type is:

    pub struct SimulationResponse {
        pub return_amount: Uint128,
        pub spread_amount: Uint128,
        pub commission_amount: Uint128,
    }
  • ReverseSimulation { ask_asset: Asset } - Return the amount of liquid token swapper should put in to get an ask_asset. Return type is:

    pub struct ReverseSimulationResponse {
        pub offer_amount: Uint128,
        pub spread_amount: Uint128,
        pub commission_amount: Uint128,
    }

C. Liquid Treasury

The Liquid Treasury contract can be used to store incentives to be distributed towards different protocols, supplement the instant unstaking pool incentives, provide liquidity and/or be used as an insurance fund for slashing risk on the Liquid Staking ARCH pool.

Data Source

  • admin (Addr): able to update the config.

  • archway_rewards (Vec<ArchwayRewardsPercentage>): a whitelist of archway rewards receiver with percentage.

Messages

  • Distribute { asset_info: AssetInfo, to: Vec<SendingInfo> } - Sends a specific token, be native or cw20 token, to a list of receivers. Only the admin can call this.

  • SetAdmin { address: String } - Updates admin’s address, only the current admin can call this.

  • SetRewardsConfig{ archway_rewards: Vec } - Updates Archway rewards config, only the current admin can call this.

  • DistributeArchwayRewards {} - Claim and distribute archway rewards to whitelist receivers.

Queries

  • ConfigInfo {} - Returns the configuration of the contract. Return type is:

    pub struct ConfigResponse {
        pub admin: String,
        pub archway_rewards: Vec<ArchwayRewardsPercentage>,
    }
  • OutstandingRewards {} - Returns the claimable rewards of the contract. Return type is:

    pub struct OutstandingRewardsResponse {
        pub rewards_balance: Coins,
        pub total_records: u64,
    }
  • UnclaimedRewardsDistribution {} - Returns the detail of distribution for unclaimed archway rewards. Return type is:

    pub struct UnclaimedRewardsDistributionResponse {
        pub asset_info: AssetInfo,
        pub to: Vec<SendingInfo>
    }

E. Liquid Looping

This is the contract for liquidity providers to deposit ARCH tokens in order to automatically earn LP rewards without having to manually claim rewards, unstake swapped sARCH then re-add liquidity.

The figure below is the token flows of loop liquidity:

Data Source

  • admin (Addr): should be multi-sig, is able to update the config.

  • pause_admin (Addr): should be single-sig, is able to pause the contract.

  • bond_denom (String): the denomination of the native token that can be staked.

  • liquid_token_addr (Addr): Liquid Token contract address.

  • staking_manager_addr (Addr): Liquid Staking contract address.

  • swap_contract_addr (Addr): Liquid Swap contract address.

  • contract_state (Uint128): the state of contract (true is active, false is paused).

  • issued (Uint128): total supply of loop shares this contract has issued.

  • withdrawings (Uint128): total amount of native token in the withdrawing queue.

  • claims (Uint128): total amount of native tokens reserved for paying back those who withdrew their shares.

  • LOOP_SHARE (Map<&Addr, Uint128>): store the liquidity shares of each address.

  • CLAIMABLE (Map<&Addr, Uint128>): store the claimable native token amount of each address.

  • UNDER_WITHDRAWING (Map<&Addr, Uint128>): store the total native token amount waiting in the withdrawing queue of each address.

  • linked_list (staking_linked_list): store withdrawing queue data.

Messages

  • Deposit {} - Moves native tokens sent along the message to the instant unstaking liquidity pool, and gives back loop shares to the sender to represent his liquidity.

  • Withdraw { amount: Uint128 } - Withdraw liquidity from the instant unstaking liquidity pool by putting the representive loop shares amount in the param.

  • Claim {} - Gives sender his claimable withdrawn native token, should be called after sender’s withdrawing request is processed.

  • SetAdmin { admin: Option<String>, pause_admin: Option<String> } - Updates admin’s addresses, only the current admin can call this.

  • SetContractState { state: bool } - Updates contract state (active or paused), only the current admin and pause admin can call this.

Queries

  • Balance { address: String } - Returns the loop shares the address has that represents his liquidity. Returns "0" if the address is unknown to the contract. Return type is:

    pub struct BalanceResponse {
        pub balance: Uint128,
    }
  • ClaimableOf { address: String } - Returns the amount of native token the address can claim. Returns "0" if the address is unknown to the contract. Return type is:

    pub struct BalanceResponse {
        pub balance: Uint128,
    }
  • ConfigInfo {} - Returns the configuration of the contract. Return type is:

    pub struct ConfigResponse {
        /// should be multi-sig, is able to update the config
        pub admin: String,
        /// should be single-sig, is able to pause the contract
        pub pause_admin: String,
        /// This is the denomination we can stake (and only one we accept for payments)
        pub bond_denom: String,
        /// Liquid token address
        pub liquid_token_addr: String,
        /// Staking manager contract address
        pub staking_manager_addr: String,
        /// Swap contract address
        pub swap_contract_addr: String,
        /// contract state (active/paused)
        pub contract_state: bool,
    }
  • StatusInfo {} - Returns the status of the contract. Return type is:

    pub struct StatusResponse {
        /// issued is how many loop shares this contract has issued
        pub issued: Uint128,
        /// total supply of native token deposit pool
        pub native: Coin,
        /// claims is how many tokens need to be reserved paying back those who withdrawed
        pub claims: Uint128,
        /// total amount of native token in the withdraw queue
        pub withdrawings: Uint128,
        /// total amount of native token in the unstake queue and liquidity pool
        pub locked_native: Uint128,
        /// total amount of unclaimed native unstaked token and rewards
        pub unclaimed_native: Uint128,
        /// total amount of unclaimed swapped staked token and stake token balance (in native value)
        pub staked_to_native: Uint128,
        /// available native balance, should always be Zero
        pub available_balance: Uint128,
        /// ratio of native / issued (or how many native tokens that one loop share is nominally worth)
        pub ratio: Decimal,
    }
  • WithdrawingQueue {} - Returns the first 50 nodes in the withdrawing queue of the contract. Return type is:

    pub struct WithdrawingQueueResponse {
        pub state: LinkedList,
        pub queue: Vec<NodeWithId>,
    }
  • UnderWithdrawingOf { address: String } - Returns the total amount of native token the address has in the withdrawing queue. Return type is:

    pub struct BalanceResponse {
        pub balance: Uint128,
    }

Workflows

Staking ARCH

Delayed Unstaking sARCH

Provide liquidity for the Instant Unstaking Pool

Instant Unstaking sARCH

Remove liquidity from the Instant Unstaking Pool

Liquidity providers claim ARCH incentives and swapped sARCH

Deposit ARCH liquidity to the Liquid Looping contract

Withdraw ARCH liquidity from the Liquid Looping contract

Security

A. Validator multisig

Validator multisig owns the Liquid Staking, Liquid Swap, Liquid Token, Multisig, Group and Liquid Looping smart contracts. Multisig signers are in charge of governing the upgradeability of these smart contracts, the addition/removal of multisig signers, the addition/removal of participants within the Validator set and changing the protocol’s configuration parameters.

Functions controlled by Validator multisig:

  • Liquid Staking

    • SetAdmin { admin: Option<String>, pause_admin: Option<String> }

    • SetContractState { state: bool }

    • SetRewardsConfig { swap_contract_addr: Option<String>, treasury_contract_addr: Option<String>, team_wallet_addr: Option<String>, commission_percentage: Option<u16>, team_percentage: Option<u16>, liquidity_percentage: Option<u16> }

    • SetLimitConfig { maximum_processed: Option<Uint128>, processed_time: Option<Uint128>, unstaking_time: Option<Uint128>, mint_cap: Option<Uint128> }

    • SetDelegations { delegations: Vec<DelegationPercentage> }

  • Liquid Swap

    • SetAdmin { admin: Option<String>, pause_admin: Option<String> }

    • SetContractState { state: bool }

    • SetRewardsConfig { team_wallet_addr: Option<String>, swap_fee_percentage: Option<u16>, team_percentage: Option<u16> }

    • SetLiquidityMin { liquidity_min: Uint128 }

  • Liquid Token

    • None

  • Multisig

    • Propose{title, description, msgs, earliest, latest}

      • Proposals should be limited to multisig participants only (This is not in the spec but left open to the implementation)

    • Vote{proposal_id, vote}

      • Voters should only be able to vote yes or no

    • Execute{proposal_id}

    • Close{proposal_id}

  • Group

    • Members are defined by an address and a weight.

      • Each member gets a weight of 1. If our multisig has 9 participants we will require a weight of 5 yes votes for the proposal to pass

    • UpdateAdmin{admin}

    • AddHook{addr}

    • RemoveHook{addr}

  • Liquid Looping

    • SetAdmin { admin: Option<String>, pause_admin: Option<String> }

    • SetContractState { state: bool }

B. Team wallet

Lydia Labs team wallet owns the Treasury smart contract. This will allow the LL team to distribute incentives across different strategies in an efficient manner.

Functions controlled by LL team wallet:

  • Treasury

    • Distribute { asset_info: AssetInfo, to: Vec<SendingInfo> }

    • SetAdmin { address: String }

    • SetRewardsConfig{ archway_rewards: Vec }

Document and source
Document and source
Document and source
CW3-flex-multisig
CW4-group