RFC: https://github.com/polkadot-fellows/RFCs/pull/155
Polkadot USD over-collateralised debt token (PUSD) is a new DOT‑collateralized stablecoin deployed on Asset Hub. It is an overcollateralized stablecoin backed purely by DOT. The implementation follows the Honzon protocol pioneered by Acala.
Total Estimation: 520 hours
Rate: $250/h
15% slippage + price-volatility margin
50% upfront: 17679 DOT using EMA7 Price 4.22807
Remainder billed based on actual hours after completion
There are a number of primary use cases of PUSD:
PUSD is implemented using the Honzon protocol stack used to power aUSD, adapted for DOT‑only collateral on Asset Hub.
DOT holders can open a vault (CDP) to lock their DOT and borrow up to a protocol‑defined percentage of its value as PUSD, subject to a required collateral ratio and per‑asset debt ceilings.
At any time, the vault owner can repay PUSD (principal plus accrued interest via the debit exchange rate) to unlock DOT, fully or partially.
When a vault’s collateral ratio falls below the liquidation ratio, it becomes unsafe and is liquidated. The system attempts to recover PUSD by selling the DOT in the vault:
Any excess collateral after repaying debt and penalties is refunded to the owner. Shortfalls become bad debt and are handled by CDP Treasury mechanisms.
The goal of PIB is to improve the stability and reduce market impact during liquidation.
PIB allows the treasury to lock DOT and mint stables to purchase liquidated DOT with a discount price.
There is a quota limit controlled by governance.
Governance may sell purchased PIB to dex or via onchain auction to de-leverage PIB position.
A Financial Fellowship will govern risk parameters and treasury actions to ensure economic safety, under the broader Polkadot on‑chain governance framework.
As a last resort, an emergency shutdown can be performed to halt minting/liquidation and allow equitable settlement: lock oracle prices, cancel auctions, and let users settle PUSD against collateral at the locked rates.
The Honzon protocol functions as a lending system where users can:
The protocol consists of several interconnected pallets that provide a complete CDP system:
+-----------+ +-------------+ +---------+
| Honzon |-----| CDP Engine |-----| Loans |
| (Frontend)| | (Core Logic)| |(Positions)
+-----------+ +-------------+ +---------+
| | |
| | |
+-----------+ +-------------+ +---------+
| Oracle |-----| CDP Treasury|-----| Auctions|
|(Prices) | | (Surplus) | |(Liquid.)|
+-----------+ +-------------+ +---------+
// From pallet-cdp-engine
pub struct Position<Balance> {
pub collateral: Balance,
pub debit: Balance,
}
// From pallet-cdp-engine
pub struct RiskManagementParams<Balance> {
/// Liquidation ratio, when the collateral ratio of
/// CDP under this collateral type is below the liquidation ratio, this
/// CDP is unsafe and can be liquidated.
pub liquidation_ratio: Option<Ratio>,
/// Required collateral ratio, if it's set, cannot adjust the position
/// of CDP so that the current collateral ratio is lower than the
/// required collateral ratio.
pub required_collateral_ratio: Option<Ratio>,
}
// From pallet-auction-manager
pub struct CollateralAuctionItem<AccountId, BlockNumber, Balance> {
/// Refund recipient for may receive refund
refund_recipient: AccountId,
/// Initial collateral amount for sale
#[codec(compact)]
initial_amount: Balance,
/// Current collateral amount for sale
#[codec(compact)]
amount: Balance,
/// Target sales amount of this auction
/// if zero, collateral auction will never be reverse stage,
/// otherwise, target amount is the actual payment amount of active
/// bidder
#[codec(compact)]
target: Balance,
/// Auction start time
start_time: BlockNumber,
}
The main entry point for users to interact with their CDPs.
/// Adjust the loans of `currency_id` by specific
/// `collateral_adjustment` and `debit_adjustment`
pub fn adjust_loan(
origin: OriginFor<T>,
currency_id: <T as pallet_loans::Config>::CurrencyId,
collateral_adjustment: Amount,
debit_adjustment: Amount,
) -> DispatchResult
/// Close caller's CDP which has debit but still in safe by use collateral to swap
/// stable token on DEX for clearing debit.
pub fn close_loan_has_debit_by_dex(
origin: OriginFor<T>,
currency_id: <T as pallet_loans::Config>::CurrencyId,
#[pallet::compact] max_collateral_amount: <T as pallet_cdp_engine::Config>::Balance,
) -> DispatchResult
/// Transfer the whole CDP of `from` under `currency_id` to caller's CDP
/// under the same `currency_id`, caller must have the authorization of
/// `from` for the specific collateral type
pub fn transfer_loan_from(
origin: OriginFor<T>,
currency_id: <T as pallet_loans::Config>::CurrencyId,
from: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult
/// Authorize `to` to manipulate the loan under `currency_id`
pub fn authorize(
origin: OriginFor<T>,
currency_id: <T as pallet_loans::Config>::CurrencyId,
to: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult
/// Cancel the authorization for `to` under `currency_id`
pub fn unauthorize(
origin: OriginFor<T>,
currency_id: <T as pallet_loans::Config>::CurrencyId,
to: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult
/// Cancel all authorization of caller
pub fn unauthorize_all(origin: OriginFor<T>) -> DispatchResult
/// Generate new debit in advance, buy collateral and deposit it into CDP.
/// Note: This function is not yet implemented.
pub fn expand_position_collateral(
origin: OriginFor<T>,
currency_id: <T as pallet_loans::Config>::CurrencyId,
increase_debit_value: <T as pallet_cdp_engine::Config>::Balance,
min_increase_collateral: <T as pallet_cdp_engine::Config>::Balance,
) -> DispatchResult
/// Sell the collateral locked in CDP to get stable coin to repay the debit.
/// Note: This function is not yet implemented.
pub fn shrink_position_debit(
origin: OriginFor<T>,
currency_id: <T as pallet_loans::Config>::CurrencyId,
decrease_collateral: <T as pallet_cdp_engine::Config>::Balance,
min_decrease_debit_value: <T as pallet_cdp_engine::Config>::Balance,
) -> DispatchResult
/// Adjust the loans of `currency_id` by specific
/// `collateral_adjustment` and `debit_value_adjustment`
/// Note: This function is not yet implemented.
pub fn adjust_loan_by_debit_value(
origin: OriginFor<T>,
currency_id: <T as pallet_loans::Config>::CurrencyId,
collateral_adjustment: Amount,
debit_value_adjustment: Amount,
) -> DispatchResult
/// Transfers debit between two CDPs
pub fn transfer_debit(
origin: OriginFor<T>,
from_currency: <T as pallet_loans::Config>::CurrencyId,
to_currency: <T as pallet_loans::Config>::CurrencyId,
debit_transfer: <T as pallet_cdp_engine::Config>::Balance,
) -> DispatchResult
The core module responsible for CDP risk management, liquidation, and interest accumulation.
/// Update parameters related to risk management of CDP
pub fn set_collateral_params(
origin: OriginFor<T>,
liquidation_ratio: Option<Ratio>,
required_collateral_ratio: Option<Ratio>,
) -> DispatchResult
/// Emergency shutdown the system
pub fn emergency_shutdown(origin: OriginFor<T>) -> DispatchResult
/// Adjust position by adding/removing collateral and debit
pub fn adjust_position(
origin: OriginFor<T>,
collateral_adjustment: T::Balance,
debit_adjustment: T::Balance,
) -> DispatchResult
Manages individual CDP positions and collateral/debit accounting. This pallet has no public extrinsics.
Manages system surplus, debit, and collateral auctions.
/// Extract surplus to treasury account
pub fn extract_surplus_to_treasury(origin: OriginFor<T>, #[pallet::compact] amount: T::Balance) -> DispatchResult
/// Auction the collateral not occupied by the auction.
pub fn auction_collateral(
origin: OriginFor<T>,
#[pallet::compact] amount: T::Balance,
#[pallet::compact] target: T::Balance,
split: bool,
) -> DispatchResultWithPostInfo
/// Update parameters related to collateral auction under specific
/// collateral type
pub fn set_expected_collateral_auction_size(
origin: OriginFor<T>,
#[pallet::compact] size: T::Balance,
) -> DispatchResult
/// Update the debit offset buffer
pub fn set_debit_offset_buffer(origin: OriginFor<T>, #[pallet::compact] amount: T::Balance) -> DispatchResult
Handles collateral auctions for liquidated CDPs. This pallet has no public extrinsics.
Provides oracle price feeds.
/// Feed the external value.
pub fn feed_values(
origin: OriginFor<T>,
values: BoundedVec<(T::OracleKey, T::OracleValue), T::MaxFeedValues>,
) -> DispatchResultWithPostInfo
A governance-controlled pallet that provides a protocol-native backstop during liquidations. Governance locks DOT into an internal CDP and configures an issuance quota and a discount factor. During liquidation, the pallet quotes a standing bid at oracle_price * discount and fills up to its remaining PUSD issuance headroom, competing with DEX and auctions. Fills transfer liquidated DOT to the buffer CDP and increase its PUSD debt accordingly, subject to collateralization limits.
Stability fee are not applied to PIB vault as the stability fee goes to treasury and PIB is part of treasury.
Design goals
- Predictable buyer-of-last-resort to smooth liquidation slippage.
- Supply-safe: only mints PUSD when filling a liquidation and accounts it as buffer CDP debt.
- Bounded risk via governance-set quota, discount, per-auction caps, and enable/disable switch.
/// Fund the buffer by locking DOT as collateral into the buffer CDP.
pub fn fund(
origin: OriginFor<T>, // Governance origin
#[pallet::compact] amount_dot: T::Balance
) -> DispatchResult
/// Unlock DOT from the buffer CDP.
pub fn defund(
origin: OriginFor<T>,
#[pallet::compact] amount_dot: T::Balance
) -> DispatchResult
/// Set the discount factor used to price bids vs oracle. For example,
/// discount = 95% means bid = 0.95 * oracle_price (a 5% discount).
pub fn set_discount(
origin: OriginFor<T>,
discount: Permill // 1_000_000 == 100%
) -> DispatchResult
/// Set the maximum additional PUSD the buffer may issue (as debt on its CDP).
pub fn set_issuance_quota(
origin: OriginFor<T>,
#[pallet::compact] quota_pdd: T::Balance
) -> DispatchResult
#[pallet::storage]
pub type Discount<T> = StorageValue<_, Permill, ValueQuery>; // default: Permill::from_percent(100)
#[pallet::storage]
pub type IssuanceQuota<T: Config> = StorageValue<_, T::Balance, ValueQuery>; // max PUSD debt
#[pallet::storage]
pub type IssuanceUsed<T: Config> = StorageValue<_, T::Balance, ValueQuery>; // current PUSD debt
Future versions of the system will allow smart contracts to register as liquidation participants, enabling: