//! [Kujira's](https://orca.kujira.app/) plug-and-play liquidation queue contract //! for all CosmWASM compatible Blockchains. //! //! Designed to create a backstop of solvency against liquidations on your Protocol, a liquidation //! queue provides a community-focused and decentralized way of bidding for at-risk collateral. At the same time the //! gamification of the bidding process creates competition between bidders, allowing market forces to determine //! a discount percentage. //! //! If you're interested in deploying a liquidation queue for your Protocol and having it listed on Orca in front of //! 50,000+ bidders, reach out in our [Telegragm Group](https://t.me/team_kujira). //! //! # Features //! ### Direct Orca Integration //! Any contract initialized with this interface can be integrated into the Orca UI at . //! //! The easiest way to do this is by using one of the Code IDs from the deployed contract list below. //! //! ### Configurable Pools //! Full control over the discounts available for bidders. Set the number of pools, and the incremental amount //! of discount per-pool. //! //! ### Closable Pools //! Optionally close off specific pools from new bids. This can be useful when bootstrapping liquidity in a new //! liquidation queue, for example, where you close lower percentage pools for a fixed period of time, //! guaranteeing bidders a minimum discount percentage on liquidations in that time. //! //! ### Custom Swappers //! Allow bidders to place bids in a different asset to the one your Protocol requires repayment in. //! On Terra we use these for example to support native aUST bidding, where a market requires repayment //! in UST. The bids can be denominated in yield-bearing aUST right up until the point that the liquidation //! ocurrs and the market is repaid. //! //! ### Delegated Activation //! No more missed liquidations! When you submit a bid, you can optionally include a delegate address, //! which is permitted to activate the bid on your behalf. Kujira will have a bot that liquidation queues can //! be registered with, so that bidders can have their bids automatically activated as soon as the wait_end //! period expires. //! //! # Getting Started //! ## Deploy A Contract //! The easiest way to get up and running to to instantiate our own audited contract with the code IDs below. //! Here is a sample JSON-encoded init message, where the bid denomination is Terra's aUST, and the collateral //! that is being bid on is the Native Luna, and fees connected to Kujira's [sKUJI Protocol Revenue Pool](https://blue.kujira.app/). //! ```json //! { //! "owner": "terra123...", //! "market": "terra123...", //! "bid_denom": { //! "cw20": "terra1ajt556dpzvjwl0kl5tzku3fc3p3knkg9mkv8jl" //! }, //! "collateral_denom": { //! "native": "uluna" //! }, //! "bid_threshold": "10000000", //! "max_slot": 15, //! "premium_rate_per_slot": "0.01", //! "waiting_period": 600, //! "liquidation_fee": "0.01", //! "withdrawal_fee": "0.005", //! "fee_address": "terra1vvj874nwtmxk0u0spj83d364xyhqk2e652jrck" //! } //! ``` //! //! ## Integrating your Market //! //! 1. Add the package to Cargo.toml //! //! `kujira = "0.2.1"` //! //! 2. Extend your contract config to store the address of your new liquidation queue //! ```rust //! pub struct Config { //! liquidation_contract: Addr, //! .., //! } //! ``` //! 3. Transfer funds to the liquidation queue at the point of liquidation //! ```rust //! fn liquidate_collateral(deps: DepsMut, ..) -> StdResult { //! // .. //! let msg = CosmosMsg::Wasm(WasmMsg::Execute { //! contract: config.liquidation_contract, //! msg: to_binary(&kujira::orca::ExecuteMsg::ExecuteLiquidation { //! exchange_rate, //! repay_denom: Denom::Cw20("terra1ajt556dpzvjwl0kl5tzku3fc3p3knkg9mkv8jl"), //! }), //! funds: coins(collateral_amount, "uluna") //! }); //! //! Ok(Response::default().add_message(msg)) //! } //! ``` //! //! ### Terra //! - Mainnet: Code ID `3541` //! - Testnet: Code ID `52750` use cosmwasm_std::{Addr, Decimal256, Uint128, Uint256}; use cw20::{Cw20ReceiveMsg, Denom}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; /// Create a new liquidation queue. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InstantiateMsg { /// Contract owner. Allowed to call [ExecuteMsg::UpdateConfig] . pub owner: Addr, /// Market the holds collateral to be liquidated. Only the market can /// call [ExecuteMsg::ExecuteLiquidation] and [Cw20HookMsg::ExecuteLiquidation] pub market: Addr, /// The denomination of the bids. This is fixed at deployment, however with custom /// swappers, the market can be repaid in a different denomination to the bid pub bid_denom: Denom, /// The collateral that can be liquidated using this liquidation queue pub collateral_denom: Denom, /// The threshold under which bids are automatically activated when placed pub bid_threshold: Uint256, /// The total number of pools in this queue pub max_slot: u8, /// The incremental discount offered per-pool pub premium_rate_per_slot: Decimal256, /// The amount of time in seconds that a bid must wait until it can be activated pub waiting_period: u64, /// The amount of the repay amount that is sent to [fee_address](InstantiateMsg::fee_address) when executing a liquidation pub liquidation_fee: Decimal256, /// The amount of the collateral that is sent to [fee_address](InstantiateMsg::fee_address) when a bid is claimed pub withdrawal_fee: Decimal256, /// The fee destination address pub fee_address: Addr, } /// Callable interfaces #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] #[allow(clippy::large_enum_variant)] pub enum ExecuteMsg { /// Hook to handle (Cw20ExecuteMsg::Send) Receive(Cw20ReceiveMsg), /// Update queue configuration UpdateConfig { /// Change the owner owner: Option, /// Change the market market: Option, /// Change the amount of time to wait before a bid can be activated waiting_period: Option, /// Change the minimum total bid amount for a bid to require manual activation bid_threshold: Option, /// Change the discount received per pool premium_rate_per_slot: Option, /// Close/open specific pools closed_slots: Option>, /// Update fee taken at liquidation liquidation_fee: Option, /// Update fee taken at withdrawal withdrawal_fee: Option, /// Update fee destination fee_address: Option, }, /// Called by an end-user to place a bid SubmitBid { /// The slot (and therefore discount) selected premium_slot: u8, /// An optional delegate, who can activate the bid on behalf of the user delegate: Option, }, /// Retract the bid and withdraw funds RetractBid { /// The bid idx to be retracted bid_idx: Uint128, /// The amount of bid to retract. IF omitted, the whole bid is retracted amount: Option, }, /// Activate bids to be used for liquidation ActivateBids { /// If provided, only the selected bids will be activated. /// If omitted, the first 30 bids for the sending address /// will be activated bids_idx: Option>, }, /// Claim liquidated collateral ClaimLiquidations { /// If provided, only the selected bids will be claimed. /// If omitted, the first 30 bids for the sending address /// will be claimed bids_idx: Option>, }, /// Swap liquidated collateral for the repay denomination. /// This is only callable by the `market` as defined in /// (InstantiateMsg) ExecuteLiquidation { /// Optional different address to send the repay amount to repay_address: Option, /// The denomination that the market requires repaying in. /// By default this will be the [bid_denom](InstantiateMsg::bid_denom), /// however if a compatible Swapper has been /// registered with [ExecuteMsg::AddSwapper] then the market /// can be repaid in a different denom to that of the bids repay_denom: Denom, /// The market must provide an exchange rate between the repay /// denom and the collateral denom in the form `repay / collateral` exchange_rate: Decimal256, }, /// Register a custom swapper to support different [repay](ExecuteMsg::ExecuteLiquidation::repay_denom) /// and [bid](InstantiateMsg::bid_denom) denoms AddSwapper { /// See [SwapperResponse::repay_denom] denom: Denom, /// See [SwapperResponse::addr] address: Addr, }, /// Remove a previously regsitered swapper RemoveSwapper { denom: Denom }, } /// Support for CW20 send messages. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Cw20HookMsg { /// Functionally identical to [ExecuteMsg::ExecuteLiquidation]. Used when [collateral_denom](InstantiateMsg::collateral_denom) is a CW20 token ExecuteLiquidation { repay_address: Option, repay_denom: Denom, exchange_rate: Decimal256, }, /// Functionally identical to [ExecuteMsg::ExecuteLiquidation]. Used when [bid_denom](InstantiateMsg::bid_denom) is a CW20 token SubmitBid { premium_slot: u8, delegate: Option, }, } /// Standard interface to query contract state #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { /// Current config. Returns [ConfigResponse] Config {}, /// Simulate a liquidation based on the current pool balances. Returns [SimulationResponse] Simulate { collateral_amount: Uint256, repay_denom: Denom, exchange_rate: Decimal256, }, /// Query a specific bid by idx. Returns [BidResponse] Bid { bid_idx: Uint128 }, /// Paginate user bids. Upper limit of 30 per page. Returns [BidsResponse] BidsByUser { bidder: Addr, start_after: Option, limit: Option, }, /// Query a specific bid pool. Returns [BidPoolResponse] BidPool { bid_slot: u8 }, /// Paginate bid pools. Upper limit of 30 per page. Returns [BidPoolsResponse] BidPools { start_after: Option, limit: Option, }, /// Paginate registered swappers. Upper limit of 30 per page. Returns [SwappersResponse] Swappers { start_after: Option, limit: Option, }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct ConfigResponse { /// See [InstantiateMsg::owner] pub owner: Addr, /// See [InstantiateMsg::market] pub market: Addr, /// See [InstantiateMsg::bid_denom] pub bid_denom: Denom, /// See [InstantiateMsg::collateral_denom] pub collateral_denom: Denom, /// See [InstantiateMsg::bid_threshold] pub bid_threshold: Uint256, /// See [InstantiateMsg::max_slot] pub max_slot: u8, /// See [InstantiateMsg::premium_rate_per_slot] pub premium_rate_per_slot: Decimal256, /// See [ExecuteMsg::UpdateConfig::closed_slots] pub closed_slots: Vec, /// See [InstantiateMsg::waiting_period] pub waiting_period: u64, /// See [InstantiateMsg::liquidation_fee] pub liquidation_fee: Decimal256, /// See [InstantiateMsg::withdrawal_fee] pub withdrawal_fee: Decimal256, /// See [InstantiateMsg::fee_address] pub fee_address: Addr, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct SimulationResponse { /// A confirmation of the amount of collateral consumed in this liquidation. /// The simulation will fail if there are insufficient bids to execute the /// liquidation pub collateral_amount: Uint256, /// The simulated amount repaid to the market pub repay_amount: Uint256, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct BidResponse { /// A unnique ID for the bid pub idx: Uint128, /// The premium slot selected in [ExecuteMsg::SubmitBid::premium_slot] pub premium_slot: u8, /// The address used to place the bid pub bidder: Addr, /// The remaining bid amount pub amount: Uint256, /// Allocated and unclaimed liquidated collateral pub pending_liquidated_collateral: Uint256, /// The epoch timestamp at which the bid can be activated. /// IF None, it's already active pub wait_end: Option, /// An optionally selected delegate address who may activate the bid /// on behalf of the bidder pub delegate: Option, pub product_snapshot: Decimal256, pub sum_snapshot: Decimal256, pub epoch_snapshot: Uint128, pub scale_snapshot: Uint128, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct BidsResponse { pub bids: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct BidPoolResponse { /// Total amount of all active bids in this pool pub total_bid_amount: Uint256, /// The discount applied to bids in this pool pub premium_rate: Decimal256, /// Whether or not this pool has been closed with [ExecuteMsg::UpdateConfig::closed_slots] pub is_closed: bool, pub sum_snapshot: Decimal256, pub product_snapshot: Decimal256, pub current_epoch: Uint128, pub current_scale: Uint128, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct BidPoolsResponse { pub bid_pools: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct SwappersResponse { pub swappers: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct SwapperResponse { /// The repay denom that the swapper swaps the bid_denom into pub repay_denom: Denom, /// The contract address that implements [SwapperQueryMsg] and [SwapperExecuteMsg] pub addr: Addr, }