Ink! contracts for a collateralized TUSDT system with vault borrowing, a lending/borrowing pool, liquidation auctions, and on-chain governance plus a treasury that books protocol fees.
The contracts split into three layers:
- Protocol —
tusdt-erc20(token, multi-minter),tusdt-vault-alpha(CDP borrowing/liquidation backed by subnet alpha),tusdt-lending-pool(TAO/TUSDT lending with alpha collateral, lToken receipt tokens, utilization-based interest rates),tusdt-auction(ascending-bid liquidation auctions for the vault),tusdt-oracle(collateral pricing). The vault owns the token/auction/oracle instances it creates; the lending pool spawns its own lToken children. - Governance & treasury —
tusdt-governance(token-holder proposals plus a maintainer/council authority that steers the protocol contracts) andtusdt-treasury(per-fund accounting for fees, released only by governance).
- Rust 1.89 (pinned via
rust-toolchain.toml— rustup picks it up automatically) wasm32-unknown-unknowntargetcargo-contract5.x- Node.js + Yarn (for the isolated contract tooling under
tools/) - A Contracts-enabled Substrate node (local or remote)
rustup target add wasm32-unknown-unknown
cargo install --locked cargo-contractOne-command gates mirror CI (requires just):
| Command | What it does |
|---|---|
just fmt |
cargo fmt --all |
just check |
cargo check --workspace |
just test |
cargo test --workspace |
just clippy |
cargo clippy --workspace --all-targets -- -D warnings |
just lint |
fmt → clippy-fix → clippy |
just build <name> |
Build one contract's artifacts (e.g. just build tusdt-lending-pool) |
just build-all |
Build all 8 contracts' artifacts |
Build all crates:
cargo checkBuild contract artifacts (.contract, .wasm, metadata):
cargo contract build --manifest-path contracts/tusdt-erc20/Cargo.toml --release
cargo contract build --manifest-path contracts/tusdt-auction/Cargo.toml --release
cargo contract build --manifest-path contracts/tusdt-oracle/Cargo.toml --release
cargo contract build --manifest-path contracts/tusdt-vault-alpha/Cargo.toml --release
cargo contract build --manifest-path contracts/tusdt-lending-pool/Cargo.toml --release
cargo contract build --manifest-path contracts/tusdt-treasury/Cargo.toml --release
cargo contract build --manifest-path contracts/tusdt-governance/Cargo.toml --release
cargo contract build --manifest-path contracts/tusdt-election/Cargo.toml --releaseArtifacts are produced in target/ink/.
# Format all Rust source files
cargo fmt -- --check
# Lint with clippy (deny warnings)
cargo clippy --all-targets --all-features -- -D warnings
# Auto-fix formatting
cargo fmt
# Run all tests across the workspace
cargo test --workspace
# Run a single contract's tests
cargo test -p tusdt-vault-alpha
# Run a single named test with output
cargo test -p tusdt-vault-alpha <test_name> -- --nocaptureContracts are panic-free by construction — the clippy lints that are denied workspace-wide in the root Cargo.toml:
arithmetic-side-effects = "deny"
unwrap-used = "deny"
expect-used = "deny"
indexing-slicing = "deny"
Contract code must use .ok_or(...)/checked_*/saturating_*; a panic can never revert a
message or brick a query. Rare provably-unreachable exceptions carry a narrowly-scoped #[allow]
with a justification comment. Test modules opt out via the #![allow(...)] header at the top of
each tests.rs (test ergonomics only — production code stays strict).
CI gate: cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace
⚠️ cargo contract buildisolates each contract into a temp dir, so contracts define their[lints.clippy]deny block inline in their ownCargo.toml—lints.workspace = trueinheritance is used only by the non-contract crates (env,primitives,voting,test-support).
The off-chain chain-extension mock (MockExtension — function ids 0/15/36/1/2/5|6/25 plus
register_mock*/set_caller helpers) used to be copy-pasted into every contract's tests.rs.
It now lives once in the test-support workspace crate and is wired as a dev-dependency of all 8
contracts. When the chain extension gains a function id, update the mock once.
Every Error variant of all 8 contracts is documented in docs/errors/ — one page per contract
with the enum's doc comment, the messages that return each variant, and client guidance (input
error? authorization? retry?). docs/errors/index.md links them all. Add/rename a variant →
update its page in the same commit.
Shared deployment scripts and on-chain tests live in an isolated TypeScript subproject under tools/.
The current iteration exposes upload support for erc20, auction, oracle, and vault-alpha, plus a single vault-alpha deployment entrypoint. The treasury, governance, and election upload scripts are also available.
Setup:
cd tools
yarn install
cp .env.example .envDefault .env values target a local dev node:
WS_URL=ws://127.0.0.1:9944
Scripts and tests use the standard local dev accounts (//Alice, //Bob, //Charlie, //Dave, //Eve, //Ferdie) from the shared dev-account helper.
When needed, you can override the selected account via SURI environment variables such as CONTRACT_UPLOADER=//Alice, CONTRACT_DEPLOYER=//Alice.
Useful commands:
cd tools
yarn build:erc20-artifacts
yarn build:auction-artifacts
yarn build:oracle-artifacts
yarn build:vault-artifacts
yarn erc20:upload
yarn auction:upload
yarn oracle:upload
yarn vault:upload
yarn vault:deploy --token-code-hash <TOKEN_CODE_HASH> --auction-code-hash <AUCTION_CODE_HASH> --oracle-code-hash <ORACLE_CODE_HASH> --treasury-address <SS58> --oracle-netuid <NETUID> --hotkey <SS58>
yarn test:oracletusdt-vault-alpha::new takes a treasury address, code hashes for token/auction/oracle,
oracle_netuid (subnet for oracle reporters), and hotkey (staking hotkey for alpha collateral).
The vault instantiates the token, auction, and oracle internally. Because the vault creates the
TUSDT token, the token address is not known until the vault exists — deploy the treasury after the
vault and wire it in via update_treasury.
-
Upload ERC20 code (
tusdt-erc20) and capture code hash. -
Upload Auction code (
tusdt-auction) and capture code hash. -
Upload Oracle code (
tusdt-oracle) and capture code hash. -
Instantiate Alpha Vault (
tusdt-vault-alpha::new) with:treasury— a placeholder address for now (e.g. the deployer); reassigned in step 7.token_code_hashauction_code_hashoracle_code_hashoracle_netuid— subnet whose registered neurons may submit oracle priceshotkey— staking hotkey for alpha collateral deposits
The deployer becomes the initial governance of the vault, auction, and oracle.
-
Read the token / auction / oracle addresses from the vault (
get_token_address,get_auction_address,get_oracle_address). -
Instantiate Treasury (
tusdt-treasury::new) with the vault's TUSDT token address. -
Wire the vault's fee recipient:
tusdt-vault-alpha::update_treasury(treasury). -
Add vault as minter on ERC20:
tusdt-erc20::add_minter(vault_address). -
Upload Election code (
tusdt-election) and capture code hash. -
Instantiate Governance (
tusdt-governance::new) with thetreasury,vault,auction,oracleaddresses, initialmaintainer, andelection_code_hash. -
Hand control to the governance contract:
tusdt-treasury::set_governance(governance)tusdt-vault-alpha::update_governance(governance)— propagates to auction and oracle too.
-
Seat the council:
tusdt-governance::set_council([c1..c5]). -
Approve target subnets:
tusdt-governance::vault_set_approved_netuid(N, true)for each subnet. -
Configure per-netuid params:
tusdt-governance::vault_set_contract_params(N, params)for each subnet (24h timelock, then anyone callstusdt-vault-alpha::execute_contract_params_update(N)). -
(Optional) Adjust global params:
tusdt-governance::vault_set_global_params(config)(24h timelock, then anyone callstusdt-vault-alpha::execute_global_params_update()).
After step 11 the protocol contracts are steered exclusively by the governance contract, and within governance the maintainer/council split (see Governance & Treasury) applies.
Example CLI for the protocol layer (adjust URL/account):
cargo contract upload \
--manifest-path contracts/tusdt-erc20/Cargo.toml \
--suri //Alice --url ws://127.0.0.1:9944
cargo contract upload \
--manifest-path contracts/tusdt-auction/Cargo.toml \
--suri //Alice --url ws://127.0.0.1:9944
cargo contract upload \
--manifest-path contracts/tusdt-oracle/Cargo.toml \
--suri //Alice --url ws://127.0.0.1:9944
cargo contract instantiate \
--manifest-path contracts/tusdt-vault-alpha/Cargo.toml \
--constructor new \
--args <TREASURY_OR_PLACEHOLDER> <ERC20_CODE_HASH> <AUCTION_CODE_HASH> <ORACLE_CODE_HASH> <ORACLE_NETUID> <HOTKEY> \
--suri //Alice --url ws://127.0.0.1:9944Then deploy the governance layer and wire the roles:
cargo contract instantiate \
--manifest-path contracts/tusdt-treasury/Cargo.toml \
--constructor new \
--args <TUSDT_TOKEN_ADDRESS> \
--suri //Alice --url ws://127.0.0.1:9944
cargo contract upload \
--manifest-path contracts/tusdt-election/Cargo.toml \
--suri //Alice --url ws://127.0.0.1:9944
cargo contract instantiate \
--manifest-path contracts/tusdt-governance/Cargo.toml \
--constructor new \
--args <TREASURY_ADDRESS> <VAULT_ADDRESS> <AUCTION_ADDRESS> <ORACLE_ADDRESS> <MAINTAINER> \
<ELECTION_CODE_HASH> \
--suri //Alice --url ws://127.0.0.1:9944Prefer the tools/ workflow above instead of using cargo contract for upload/deploy operations
where a TS script already exists. The e2e suite covers the oracle (yarn test:oracle) and lending
pool liquidation (yarn test:liquidation); upload scripts exist for erc20, auction, oracle, vault,
treasury, governance, and election (yarn <name>:upload, paired with build:<name>-artifacts),
while vault:deploy is the only full deployment entrypoint — deploy and wire the rest with
cargo contract as shown.
The lending pool (tusdt-lending-pool) is a standalone protocol contract that enables:
- Supply TAO/TUSDT to earn variable yield (receive lTAO/lTUSDT receipt tokens)
- Supply Alpha collateral (one market per approved subnet) to gain borrowing power
- Borrow TAO/TUSDT against Alpha collateral with health factor checks
- Direct full-seizure liquidation when health factor drops below 1.0 — repays the full debt on both markets, seizes all alpha collateral (platform liquidation fee, default 5%), and writes off any residual bad debt as a market deficit
Source layout: the contract module is split into lib.rs (storage, events, messages, queries) plus
params.rs (interest/alpha/global param structs + validation), rates.rs (interest accrual,
borrow-rate curve, cash accounting), and risk.rs (oracle pricing, health factor, borrow capacity,
liquidation math).
- Upload the lending pool code and capture the code hash:
cargo contract upload \ --manifest-path contracts/tusdt-lending-pool/Cargo.toml \ --suri //Alice --url ws://127.0.0.1:9944
- Instantiate the pool with the existing treasury, TUSDT token, oracle, lToken code hash (reuses
tusdt-erc20code), and a staking hotkey:The pool spawns two child lToken instances (lTAO for market 0, lTUSDT for market 1).cargo contract instantiate \ --manifest-path contracts/tusdt-lending-pool/Cargo.toml \ --constructor new \ --args <TREASURY_ADDRESS> <TUSDT_TOKEN_ADDRESS> <ORACLE_ADDRESS> \ <LTOKEN_CODE_HASH> <POOL_HOTKEY> \ --suri //Alice --url ws://127.0.0.1:9944
- Read the spawned lToken addresses:
pool.get_ltoken_address(0)→ lTAOpool.get_ltoken_address(1)→ lTUSDT
- Approve alpha markets:
pool.set_approved_netuid(netuid, true)(maintainer) for each target subnet; per-subnet collateral params are configured separately via the timelockedset_alpha_params. - (Optional) Wire governance:
pool.update_governance(governance_address), then callgovernance.update_pool_address(pool_address)to record the pool in the governance contract. - (Optional) Adjust interest rate params, alpha params, or global params via timelocked updates.
- Supply: Users deposit TAO (payable) or TUSDT (
transfer_from) → receive lTAO/lTUSDT at the current exchange rate. Exchange rate starts at 1.0 and grows as interest accrues. - Supply Alpha collateral: Users call
deposit_alpha(netuid, amount)→ atomically pulls the caller's alpha stake into the pool's coldkey via chain extension func 25 (same mechanism as the vault). One market per approved subnet. - Borrow: Users with alpha collateral can borrow TAO or TUSDT. Borrowing power is:
collateral_value × collateral_factor − existing_debt. Health factor must stay ≥ 1.0. One hour of interest is charged up front — the position's debt exceeds the amount from the very first block (hour-beginning charging), and the next increase arrives with the market's next hourly accrual. Repaying inside that hour does not refund it. - Repay: Repay TAO (payable) or TUSDT (
transfer_from). Repaid assets stay as pool liquidity — no burn. Interest accrues hourly while borrowed. - Withdraw collateral: Only allowed when the account remains healthy after withdrawal. Uses
chain extension func 6 (
transfer_stake) to return stake to the user's coldkey. - Liquidate: Permissionless. When
health_factor < 1.0, anyone can callliquidate(borrower): the liquidator repays the borrower's full debt on both markets (interest accrued first) and receives the borrower's entire alpha collateral, minus the platform's liquidation fee (default 5%, capped at the surplus share of the seized collateral). If the collateral cannot cover the debt, the residual is written off as a frozen market deficit (DeficitReported) that the maintainer can fund from the reserve viacover_deficit.
- Utilization-based 2-zone curve:
U = total_debt / (total_debt + cash).- Zone 1 (U ≤ optimal):
base_rate + slope1 × U / optimal - Zone 2 (U > optimal):
base_rate + slope1 + slope2 × (U − optimal) / (1 − optimal)
- Zone 1 (U ≤ optimal):
- Hourly compounding via
checked_powwith the first hour prepaid at borrow time (charge_prepaid_hour) — interest starts accruing from the moment of the borrow instead of after the first whole hour; the prepaid premium is split reserve/suppliers exactly like index-driven interest. - Reserve factor (default 20%): share of borrower interest sent to the protocol treasury.
supplier_rate = borrow_rate × U × (1 − reserve_factor). - lToken exchange rate:
underlying = ltoken_balance × exchange_rate. Exchange rate starts at 1.0 and grows monotonically as supplier interest accrues. Non-rebasing (Compound-style).
Alpha collateral continues earning native staking yield while supplied. A permissionless
claim_alpha_excess(netuid) function unstakes the full excess (available stake per the chain
extension − booked collateral) and transfers the proceeds to the treasury as native TAO. There is
no yield index and no 25/75 split — the entire excess goes to the treasury.
| Asset | base_rate | slope1 | slope2 | optimal_util | reserve_factor |
|---|---|---|---|---|---|
| TAO | 0% | 4% | 96% | 80% | 20% |
| TUSDT | 0% | 3% | 97% | 80% | 20% |
| Alpha param | Default |
|---|---|
| Collateral factor | 50% |
| Liquidation threshold | 60% |
| Liquidation fee (platform cut) | 5% |
| Alpha supply cap | 0 (unlimited) |
| Global param | Default |
|---|---|
| Max oracle age | 30 min |
| Supply caps (TAO/TUSDT) | 0 (unlimited) |
| Borrow caps (TAO/TUSDT) | 0 (unlimited) |
get_market_state(market_id)→MarketState(total_supplied, total_scaled_debt, total_debt, borrow_index, exchange_rate, reserve_accrued, last_update)get_position(market_id, user)→Position(ltoken_balance, scaled_debt, alpha_principal)get_exchange_rate(market_id),get_borrow_index(market_id),get_utilization(market_id)get_borrow_rate(market_id),get_supply_rate(market_id)— current annualized ratesget_underlying_balance(market_id, user)→ underlying value of lToken positionget_user_debt(market_id, user)→ current debt in underlying unitsget_user_debt_details(market_id, user)→(debt, principal)— debt includes accrued interest;interest = debt − principal. Principal is tracked in a dedicated mapping updated on borrow/repay/liquidate; positions created before principal tracking fall back to an estimate.get_alpha_markets()→Vec<(netuid, AlphaMarketParams)>— list all approved alpha marketsget_user_alpha_position(user, netuid)→ alpha principal for a specific subnetget_netuid_total_collateral(netuid),get_market_deficit(market_id)paused(),governance(),treasury(),platform(),get_pool_hotkey(),get_oracle_address()- Paginated:
get_positions(user, page),get_all_positions(page)(10 per page)
Health factor, borrow capacity, and collateral/debt value are computed by internal helpers (
get_health_factor,get_available_borrow_tusdt,get_collateral_value_tusdt,get_debt_value_tusdt) — they are not#[ink(message)]queries, so they cannot be called on-chain; clients derive them from the queryable market/position data.
After wiring (pool.update_governance(governance)), the governance contract can steer the pool:
| Forwarder | Gated by | Purpose |
|---|---|---|
pool_set_approved_netuid(netuid, approved) |
maintainer | Add/remove alpha collateral markets |
pool_set_market_params(market, config) / cancel |
maintainer | Schedule timelocked interest rate changes |
pool_set_alpha_params(netuid, config) / cancel |
maintainer | Schedule timelocked alpha param changes |
pool_set_global_params(config) / cancel |
maintainer | Schedule timelocked global param changes |
pool_unpause |
maintainer | Resume operations |
pool_update_pool_hotkey(new_hotkey, netuids) |
maintainer | Migrate alpha stake to new hotkey |
All param changes follow the 24h timelock: schedule (governance) → execute (permissionless, time-gated)
→ cancel (governance). Params can be read at any time via get_pending_*_params_update.
- User stakes alpha under the vault's hotkey (if not already staked there) — the pull keeps the hotkey.
- User creates vault:
create_alpha_vault(amount, netuid)— the contract atomically pullsamountof the caller's alpha into its own coldkey via the caller-forwardedcaller_transfer_stakechain extension (function 25) and opens the CDP in the same message. Deposits are always attributed to the caller; no separate intent ortransfer_stakeextrinsic is needed. Requires the subnet'sTransferToggleto be on and the amount to exceed the chain's minimum stake (0.002 TAO equivalent); failures revert cleanly withStakeTransferFailed. - User borrows token:
borrow_token(vault_id, amount). - User repays token:
repay_token(vault_id, amount). - User adds more alpha collateral:
add_alpha_collateral(vault_id, amount)— pulls exactlyamountfrom the caller, same mechanism as vault creation. - User releases alpha collateral:
release_alpha_collateral(vault_id, amount, dest_coldkey)— returns stake via chain extension.
Deposit messages are EOA-facing: a contract calling the vault would pull its own stake, since the chain extension forwards the immediate caller's origin.
The vault charges no interest: the borrowed amount equals the debt at all times and
repayments are 1:1. Hourly-compounding interest exists only in the lending pool —
the vault's borrow_token mints exactly amount and repay_token burns exactly amount.
- Anyone can call
trigger_liquidation_auction(owner, vault_id)when vault exceeds liquidation threshold. - Auction contract creates an auction tied to that vault.
- Bidders approve token allowance to auction contract, then call
place_bid. - After end time, call
finalize_auctionon auction contract. - Vault settlement:
settle_liquidation_auction(owner, vault_id).
Privileged protocol actions are gated by each contract's governance role. Before hand-off this is
the deployer; after wiring (deployment steps 7 & 9) it is the governance contract, and you drive
them through governance's forwarders rather than calling the protocol contracts directly. See
Governance & Treasury for who may invoke what.
Risk params split into two scopes, both applied behind a 24h timelock:
- Per-netuid — governance schedules
set_contract_params(netuid, params)per subnet (after hand-off via thevault_set_contract_paramsforwarder). Params:collateral_ratio,liquidation_ratio,liquidation_fee(all basis points). Falls back to defaults for unconfigured netuids. - Global (all netuids) — governance schedules
set_global_params(config)(after hand-off via thevault_set_global_paramsforwarder). Params:transaction_fee(basis points),auction_duration_ms,max_oracle_age_ms,vault_creation_fee(native TAO in rao).
Governance also controls which subnets are accepted via set_approved_netuid(netuid, approved)
(exposed after hand-off through the vault_set_approved_netuid forwarder).
Oracle reporting is permissionless: any registered neuron of the oracle's subnet (stake above the
minimum) may submit_price for the open round. The validator and the max price deviation are
governance-set. The active round is committed by the validator via commit_round; governance can
also commit an emergency override price (see below).
Default per-netuid params:
- Collateral ratio:
150% - Liquidation ratio:
120% - Liquidation fee:
11%
Default global params:
- Transaction fee:
0.3%(30 bps) - Auction duration:
3_600_000milliseconds (1 hour) - Max oracle age:
1_800_000milliseconds (30 minutes) - Vault creation fee:
5_000_000rao (0.005 TAO)
tusdt-governance carries two roles in addition to token-holder voting:
- Maintainer — the top authority (the elected subnet owner). Set initially at construction and thereafter replaced only by the election contract via
elect_maintainer. The maintainer seats the council (set_council), updates governance parameters (update_params), and drives the protocol config forwarders below. The election contract — instantiated by governance's constructor — runs the election and installs the winner as maintainer. - Council — a fixed committee of exactly 5 members set by the maintainer. The council performs
operational duties: committing voting snapshots (
submit_snapshot) and the emergency vault halt (vault_pause). Any single council member can act on these.
Funding and signal proposals are decided by token-weighted voting against a committed Merkle
snapshot (voting power is sqrt(snapshot balance) × time-staked multiplier, so flash-staking can't
inflate it). Flow: submit_proposal → vote → finalize → execute. Submission is gated on the
proposer's subnet alpha stake and a monthly submission window; passing requires both quorum (a
fraction of circulating supply) and an approval threshold. A passed Funding proposal calls
treasury.release(...) on execution; NonFunding proposals are signal-only.
After the role hand-off, the governance contract holds the governance role on the vault, auction,
and oracle. It exposes thin forwarders that perform the cross-contract call; the protocol contracts
see the governance contract as their governance caller. Authorization is decided inside
governance:
| Forwarder | Gated by | Target |
|---|---|---|
vault_set_contract_params(netuid, params), vault_cancel_contract_params_update(netuid) |
maintainer | vault-alpha (per-netuid timelocked params) |
vault_set_global_params(config), vault_cancel_global_params_update() |
maintainer | vault-alpha (global timelocked params: fee, auction duration, oracle age) |
vault_set_approved_netuid(netuid, approved) |
maintainer | vault-alpha (accepted collateral subnets) |
vault_update_treasury, vault_update_platform, vault_unpause |
maintainer | vault-alpha |
vault_pause |
council (fast emergency halt) | vault-alpha |
oracle_set_validator, oracle_set_max_price_deviation |
maintainer | oracle |
oracle_commit_round |
maintainer (emergency price — drives liquidations) | oracle |
auction_set_admin |
maintainer | auction |
update_governance on the protocol contracts is intentionally not forwarded — the vault's role
is fixed after wiring (and it propagates role changes to the auction/oracle itself).
tusdt-treasury books protocol fees into named funds — Emergency, Operation, Insurance,
Dividend, Buyback, Voting — in both Tusdt and Native denominations. distribute() splits
incoming balance across the funds; release(fund, token_kind, amount, recipient) pays out and is
callable only by governance (i.e. via an executed Funding proposal). The deployer is the initial
governance until set_governance hands control to the governance contract.
- Vault:
get_vault,get_total_debt,get_contract_params(netuid),get_global_params(),is_approved_netuid(netuid),get_oracle_address,get_vaults,get_all_vaults - Oracle:
get_latest_price,get_current_round_summary,get_round_price,get_price_history,get_price_history_count,get_round_submissions - Chain extension:
get_alpha_price(netuid)— on-chain subnet alpha/TAO price (RAO-scaled by 1e9) - Auction:
get_auction,get_active_vault_auction,get_bid,get_all_auctions,get_active_auctions - Token:
balance_of,allowance,total_supply - Governance:
maintainer,election,netuid,council,is_council,params,current_epoch,get_snapshot,quorum,proposal_count,get_proposal,has_voted - Treasury:
governance,token,fund_balance_tusdt,fund_balance_native
tusdt-vault-alphaowns the token and auction instances it creates;tusdt-lending-poolspawns its own lToken children (lTAO, lTUSDT) reusing thetusdt-erc20code hash.- Alpha collateral is verified via chain extension (
get_stake_info). Both the vault and lending pool act as coldkeys for staked alpha, using the caller-forwardedcaller_transfer_stake(func 25) for atomic pull deposits. - Pricing:
TUSDT_per_alpha = oracle_TUSDT_per_TAO * (get_alpha_price(netuid) / 1_000_000_000). - Borrowing from the vault mints TUSDT to borrower; the lending pool transfers existing TUSDT (no mint on borrow — pool liquidity comes from suppliers).
- Vault repayment and settlement burn TUSDT; lending pool repayment keeps TUSDT as pool cash.
- Protocol fees accrue to
tusdt-treasury; onlytusdt-governancecan release them. - After wiring, the vault/auction/oracle/pool are governed by
tusdt-governance; the maintainer and council act through its forwarders rather than calling those contracts directly. - The lending pool uses direct full-seizure liquidation with a platform fee (not auctions). The
existing
tusdt-auctioncontract serves the vault's liquidation path only.