This document describes the token management features implemented in Ethos-Protocol, including token conversion, staking, yield distribution, lending, collateral, hedging, and rebalancing.
Ethos-Protocol now supports advanced token management capabilities that allow vault owners to:
- Convert tokens before release (Issue #581)
- Validate token whitelisting in batch operations (Issue #582)
- Stake tokens for yield while locked (Issue #583)
- Distribute or reinvest yield (Issue #584)
- Lend vault tokens for interest income (Issue #585)
- Use tokens as collateral for loans (Issue #586)
- Hedge token price risk using derivatives (Issue #587)
- Rebalance multi-token portfolios based on target weights (Issue #588)
This section explains how various fees are calculated in Ethos-Protocol token management operations, enabling users to predict transaction costs and understand fee distribution.
Swap fees are charged when converting one token to another.
Formula:
swap_fee = (amount * swap_fee_bps) / 10000
amount_after_fee = amount - swap_fee
Parameters:
amount: Total input token amountswap_fee_bps: Basis points charged by the protocol (default: 30 bps = 0.3%)amount_after_fee: Actual amount used for conversion
Example:
Input amount: 1,000 USDC
Swap fee rate: 30 bps (0.3%)
Swap fee = (1,000 * 30) / 10000 = 3 USDC
Amount converted = 1,000 - 3 = 997 USDC
Fee Distribution:
- 70% goes to protocol treasury (2.1 USDC)
- 30% goes to the vault as a rebate (0.9 USDC)
Conversion fees apply when transferring between different token types during withdrawal or release.
Formula:
conversion_fee = (amount * conversion_fee_bps) / 10000
amount_received = amount - conversion_fee
Parameters:
amount: Total token amount to convertconversion_fee_bps: Basis points charged (default: 15 bps = 0.15%)from_token: Source tokento_token: Destination token
Example:
Original vault amount: 500 XLM
Converting to USDC
Conversion fee rate: 15 bps (0.15%)
Conversion fee = (500 * 15) / 10000 = 0.75 XLM
Amount after fee = 500 - 0.75 = 499.25 XLM (converted)
Fee Distribution:
- 60% goes to protocol treasury (0.45 XLM)
- 40% goes to the originating token pool (0.30 XLM)
Staking fees are charged when enabling or managing staked positions.
Formula:
entry_fee = (amount * staking_entry_fee_bps) / 10000
management_fee = (staked_amount * annual_management_fee_bps / 365) / 10000
Parameters:
amount: Amount being stakedstaking_entry_fee_bps: Entry fee in basis points (default: 25 bps = 0.25%)annual_management_fee_bps: Annual management fee (default: 50 bps = 0.5% per year)
Example:
Staking 10,000 XLM
Entry fee = (10,000 * 25) / 10000 = 2.5 XLM
Amount staked = 10,000 - 2.5 = 9,997.5 XLM
Daily management fee = (9,997.5 * 50 / 365) / 10000 = 0.137 XLM/day
Annual management fee = 50 XLM (approximately)
Fee Distribution:
- Entry fee: 100% to protocol treasury
- Management fee: 80% to protocol, 20% to vault operator
When yield is distributed or reinvested, fees may apply based on the distribution method.
Formula:
yield_accrued = (staked_amount * annual_yield_bps * days_elapsed) / (10000 * 365)
distribution_fee = (yield_accrued * yield_fee_bps) / 10000
yield_after_fee = yield_accrued - distribution_fee
Parameters:
annual_yield_bps: Annual yield rate (varies by staking pool, e.g., 500 bps = 5% APY)days_elapsed: Number of days since staking or last distributionyield_fee_bps: Fee on distributed yield (default: 10 bps = 0.1%)
Example:
Staked amount: 10,000 XLM at 5% APY
Days elapsed: 365 (1 year)
Yield accrued = (10,000 * 500 * 365) / (10000 * 365) = 500 XLM
Yield fee = (500 * 10) / 10000 = 0.5 XLM
Yield distributed = 500 - 0.5 = 499.5 XLM
Options:
1. Distribute to beneficiary: 499.5 XLM sent
2. Reinvest: 499.5 XLM added to staked amount
3. Split (70/30): 349.65 XLM to beneficiary, 149.85 XLM reinvested
Fee Distribution:
- 100% of yield fees go to protocol treasury
Lending fees include entry fees and origination fees charged by the protocol.
Formula:
entry_fee = (loan_amount * lending_entry_fee_bps) / 10000
origination_fee = (loan_amount * origination_fee_bps) / 10000
total_fees = entry_fee + origination_fee
net_loan_amount = loan_amount - total_fees
Parameters:
loan_amount: Amount being lent outlending_entry_fee_bps: Entry fee (default: 50 bps = 0.5%)origination_fee_bps: Origination fee (default: 25 bps = 0.25%)interest_rate_bps: Annual interest rate (e.g., 500 bps = 5%)
Example:
Lending 5,000 XLM at 5% annual interest for 180 days
Entry fee = (5,000 * 50) / 10000 = 2.5 XLM
Origination fee = (5,000 * 25) / 10000 = 1.25 XLM
Total fees = 3.75 XLM
Net loan to borrower = 5,000 - 3.75 = 4,996.25 XLM
Interest accrued = (5,000 * 500 * 180) / (10000 * 365) = 123.29 XLM
Total received after loan term = 5,000 + 123.29 = 5,123.29 XLM
Fee Distribution:
- Entry fee: 100% to protocol treasury
- Origination fee: 100% to protocol treasury
- Interest: 100% to vault owner
Collateral fees apply when using vault tokens as collateral for external loans.
Formula:
collateral_fee = (collateral_amount * collateral_fee_bps) / 10000
Parameters:
collateral_amount: Amount used as collateralcollateral_fee_bps: Annual collateral management fee (default: 30 bps = 0.3%)
Example:
Using 2,000 XLM as collateral for a loan
Annual collateral fee = (2,000 * 30) / 10000 = 0.6 XLM per year
Monthly fee = 0.6 / 12 = 0.05 XLM per month
Fee Distribution:
- 100% to protocol treasury
Hedging fees cover the cost of maintaining derivative positions.
Formula:
hedge_fee = (notional_amount * annual_hedge_fee_bps * days_active) / (10000 * 365)
Parameters:
notional_amount: Notional value of the hedgeannual_hedge_fee_bps: Annual fee rate (default: 100 bps = 1%)days_active: Number of days the hedge has been active
Example:
Hedging 1,000 XLM (notional: $200 at $0.20/XLM) for 90 days
Annual hedge fee rate: 1% (100 bps)
Hedge fee = (1,000 * 100 * 90) / (10000 * 365) = 2.47 XLM
Fee Distribution:
- 80% to protocol treasury (1.98 XLM)
- 20% to hedge provider/liquidity pool (0.49 XLM)
Rebalancing fees apply when the portfolio is rebalanced to target weights.
Formula:
rebalance_fee = (total_portfolio_value * rebalance_fee_bps) / 10000
Parameters:
total_portfolio_value: Sum of all token values in the vaultrebalance_fee_bps: Fee per rebalance event (default: 40 bps = 0.4%)
Example:
Portfolio value:
- 1,000 XLM ($200)
- 1,000 USDC ($1,000)
- Total: $1,200
Rebalancing fee = ($1,200 * 40) / 10000 = $4.80 (or ~24 XLM)
Fee Distribution:
- 100% to protocol treasury
| Operation | Typical Fee | Distribution | Notes |
|---|---|---|---|
| Swap | 30 bps | 70% treasury, 30% vault | Applied on conversion amount |
| Conversion | 15 bps | 60% treasury, 40% pool | Applied on output amount |
| Staking Entry | 25 bps | 100% treasury | One-time at staking start |
| Staking Management | 50 bps/year | 80% treasury, 20% operator | Daily deduction |
| Yield Distribution | 10 bps | 100% treasury | Applied to yield only |
| Lending Entry | 50 bps | 100% treasury | One-time upfront |
| Lending Origination | 25 bps | 100% treasury | One-time upfront |
| Collateral | 30 bps/year | 100% treasury | Monthly deduction |
| Hedging | 100 bps/year | 80% treasury, 20% provider | Daily deduction |
| Rebalancing | 40 bps | 100% treasury | Per rebalance event |
Smart contract operations incur Stellar network fees in addition to Ethos-Protocol fees.
Typical Gas Costs (XLM):
- Simple transfer: 0.00001 XLM (1 stroops)
- Token contract interaction: 0.0001 - 0.001 XLM (10-100 stroops)
- Complex multi-step operation: 0.001 - 0.01 XLM (100-1000 stroops)
Total Cost Example:
Staking 10,000 XLM:
1. Staking entry fee: 2.5 XLM
2. Network gas: ~0.0005 XLM
3. Total: ~2.5005 XLM
Effective cost: 0.025005% (minimal additional impact)
Protocol fees may be adjusted by governance. Upcoming changes include:
- Dynamic fees based on network congestion
- Tiered fees for high-volume users
- Loyalty discounts for long-term vault holders
- Fee reductions during low-activity periods
Monitoring Fee Changes: Subscribe to Ethos-Protocol notifications for protocol updates affecting fees.
Allow vault owners to convert their vault tokens to different tokens before the vault is released to the beneficiary. This is useful for:
- Converting to stablecoins before release
- Exchanging to preferred tokens
- Hedging against price volatility
pub fn enable_token_conversion(
env: Env,
vault_id: u64,
from_token: Address,
to_token: Address,
conversion_rate: i128,
)Parameters:
vault_id: The vault IDfrom_token: Source token address (must be whitelisted)to_token: Target token address (must be whitelisted)conversion_rate: Conversion rate in basis points (10000 = 1:1)
Requirements:
- Caller must be the vault owner
- Both tokens must be whitelisted
- Conversion rate must be positive
Events:
TOKEN_CONVERSION_TOPIC: Emitted when conversion is enabled
pub fn get_token_conversion(env: Env, vault_id: u64) -> Option<TokenConversion>Returns the token conversion configuration for a vault, or None if not configured.
// Enable conversion from XLM to USDC at 1:1 rate
client.enable_token_conversion(
&vault_id,
&xlm_token,
&usdc_token,
&10000i128, // 1:1 rate
);
// Retrieve conversion config
if let Some(conversion) = client.get_token_conversion(&vault_id) {
println!("Converting {} to {} at rate {}",
conversion.from_token,
conversion.to_token,
conversion.conversion_rate);
}Allow vault owners to use wrapped tokens that represent a canonical token from another chain or bridge. Wrapped tokens are accepted whenever their registered canonical token is whitelisted.
pub fn register_wrapped_token(
env: Env,
wrapped_token_address: Address,
canonical_token_address: Address,
)client.register_wrapped_token(&wrapped_token, &xlm_token);Wrapped token registration makes it possible to create vaults and deposit with the wrapped asset while still enforcing the canonical token whitelist.
Ensure that only whitelisted tokens can be deposited into vaults through batch operations. This prevents accidental or malicious use of non-approved tokens.
The batch_deposit function now validates that each vault's token is whitelisted before processing deposits:
pub fn batch_deposit(env: Env, from: Address, deposits: Vec<(u64, i128)>) {
// ... validation ...
// Issue #582: Validate token whitelist
Self::assert_token_whitelisted(&env, &vault.token_address);
// ... process deposit ...
// Emit token whitelist validation event
env.events().publish(
(TOKEN_WHITELIST_VALIDATED_TOPIC, vault_id),
(&vault.token_address, amount),
);
}- Default XLM token is always whitelisted
- Custom tokens must be explicitly whitelisted by admin
- Validation occurs for each vault in the batch before any transfers
- If any vault uses a non-whitelisted token, the entire batch is rejected
TOKEN_WHITELIST_VALIDATED_TOPIC: Emitted for each successfully validated deposit
Allow vault owners to stake their vault tokens in external staking pools to earn yield while the vault is locked. This enables passive income generation during the vault's active period.
pub fn enable_token_staking(
env: Env,
vault_id: u64,
staking_pool: Address,
annual_yield_bps: u32,
)Parameters:
vault_id: The vault IDstaking_pool: Address of the staking pool contractannual_yield_bps: Annual yield in basis points (e.g., 500 = 5%)
Requirements:
- Caller must be the vault owner
- Annual yield must be between 0 and 10000 basis points
Events:
TOKEN_STAKING_TOPIC: Emitted when staking is enabled
pub fn disable_token_staking(env: Env, vault_id: u64)Disables staking for a vault. The vault owner can call this to stop earning yield.
Events:
TOKEN_UNSTAKING_TOPIC: Emitted when staking is disabled
pub fn get_token_staking(env: Env, vault_id: u64) -> Option<TokenStaking>Returns the staking configuration for a vault.
// Enable staking with 5% annual yield
client.enable_token_staking(
&vault_id,
&staking_pool_address,
&500u32, // 5% APY
);
// Check staking status
if let Some(staking) = client.get_token_staking(&vault_id) {
println!("Staking {} tokens at {}% APY",
staking.staked_amount,
staking.annual_yield_bps as f64 / 100.0);
}
// Disable staking
client.disable_token_staking(&vault_id);Configure how staking yield is distributed or reinvested. Vault owners can choose to:
- Distribute to Beneficiary: Send all yield to the beneficiary
- Reinvest: Automatically reinvest yield back into the vault
- Split: Distribute a percentage to beneficiary and reinvest the rest
pub fn set_yield_distribution(
env: Env,
vault_id: u64,
mode: YieldDistributionMode,
)Parameters:
vault_id: The vault IDmode: The distribution mode (see below)
Yield Distribution Modes:
pub enum YieldDistributionMode {
/// Distribute all yield to beneficiary
DistributeToBeneficiary,
/// Reinvest all yield back into vault
Reinvest,
/// Split yield: beneficiary_bps to beneficiary, rest reinvested
Split(u32), // basis points for beneficiary
}Requirements:
- Caller must be the vault owner
- Vault must have staking enabled
Events:
YIELD_DISTRIBUTED_TOPIC: Emitted when yield is distributed
pub fn get_yield_distribution(env: Env, vault_id: u64) -> Option<YieldDistributionConfig>Returns the yield distribution configuration for a vault.
pub fn distribute_yield(env: Env, vault_id: u64)Calculates accumulated yield and distributes it according to the configured mode.
Yield Calculation:
yield = (staked_amount × annual_yield_bps × time_elapsed) / (10000 × 365 × 86400)
Events:
YIELD_DISTRIBUTED_TOPIC: Emitted when yield is sent to beneficiaryYIELD_REINVESTED_TOPIC: Emitted when yield is reinvested
// Distribute all yield to beneficiary
client.set_yield_distribution(
&vault_id,
&YieldDistributionMode::DistributeToBeneficiary,
);
// Or reinvest all yield
client.set_yield_distribution(
&vault_id,
&YieldDistributionMode::Reinvest,
);
// Or split 70% to beneficiary, 30% reinvest
client.set_yield_distribution(
&vault_id,
&YieldDistributionMode::Split(7000u32),
);
// Distribute accumulated yield
client.distribute_yield(&vault_id);
// Check distribution stats
if let Some(config) = client.get_yield_distribution(&vault_id) {
println!("Total distributed: {}", config.total_distributed);
println!("Total reinvested: {}", config.total_reinvested);
}Here's a complete example showing how to use all token management features together:
// 1. Create a vault
let vault_id = client.create_vault(&owner, &beneficiary, &86400u64, &None);
// 2. Deposit funds
client.deposit(&vault_id, &owner, &1_000_000i128);
// 3. Enable staking with 5% APY
client.enable_token_staking(&vault_id, &staking_pool, &500u32);
// 4. Set yield distribution (70% to beneficiary, 30% reinvest)
client.set_yield_distribution(
&vault_id,
&YieldDistributionMode::Split(7000u32),
);
// 5. Enable token conversion (optional)
client.enable_token_conversion(
&vault_id,
&xlm_token,
&usdc_token,
&10000i128,
);
// 6. After some time, distribute yield
client.distribute_yield(&vault_id);
// 7. Check final state
let config = client.get_yield_distribution(&vault_id).unwrap();
println!("Distributed to beneficiary: {}", config.total_distributed);
println!("Reinvested: {}", config.total_reinvested);Allow vault owners to lend vault tokens to a borrower and earn interest income.
pub fn enable_token_lending(
env: Env,
vault_id: u64,
caller: Address,
borrower: Address,
amount: i128,
interest_rate_bps: u32,
duration_seconds: u64,
) -> Result<(), ContractError>Parameters:
vault_id: The vault IDcaller: Must be the vault ownerborrower: Address of the borroweramount: Amount to lend (must be ≤ vault balance)interest_rate_bps: Annual interest rate in basis points (e.g., 500 = 5%)duration_seconds: Loan duration in seconds
Events:
TOKEN_LENDING_TOPIC: Emitted when lending is enabled
pub fn repay_token_loan(env: Env, vault_id: u64, caller: Address) -> Result<i128, ContractError>Returns the accrued interest earned.
Events:
TOKEN_LEND_REPAY_TOPIC: Emitted on repayment
pub fn get_token_lending(env: Env, vault_id: u64) -> Option<TokenLending>Allow vault owners to use vault tokens as collateral for an external loan.
pub fn set_token_collateral(
env: Env,
vault_id: u64,
caller: Address,
collateral_amount: i128,
loan_amount: i128,
collateral_ratio_bps: u32,
) -> Result<(), ContractError>Parameters:
collateral_ratio_bps: Required collateral ratio ≥ 10000 (100%)
Events:
TOKEN_COLLATERAL_TOPIC: Emitted when collateral is set
pub fn release_token_collateral(env: Env, vault_id: u64, caller: Address) -> Result<(), ContractError>Events:
TOKEN_COLLAT_RLSD_TOPIC: Emitted when collateral is released
pub fn get_token_collateral(env: Env, vault_id: u64) -> Option<TokenCollateral>Allow vault owners to hedge token price risk using a derivative position.
pub fn enable_token_hedge(
env: Env,
vault_id: u64,
caller: Address,
hedge_token: Address,
notional_amount: i128,
strike_price_bps: u32,
expiry: u64,
) -> Result<(), ContractError>Events:
TOKEN_HEDGE_TOPIC: Emitted when hedge is enabled
pub fn close_token_hedge(env: Env, vault_id: u64, caller: Address) -> Result<(), ContractError>Events:
TOKEN_HEDGE_CLOSE_TOPIC: Emitted when hedge is closed
pub fn get_token_hedge(env: Env, vault_id: u64) -> Option<TokenHedge>Automatically rebalance a multi-token vault portfolio based on configured target weights.
pub fn set_token_rebalance(
env: Env,
vault_id: u64,
caller: Address,
target_weights: Vec<TokenWeight>,
rebalance_threshold_bps: u32,
) -> Result<(), ContractError>Parameters:
target_weights: Per-token allocations;target_bpsvalues must sum to 10000rebalance_threshold_bps: Drift tolerance before triggering a rebalance (e.g., 500 = 5%)
Events:
TOKEN_REBALANCE_TOPIC: Emitted when rebalance config is set
pub fn trigger_rebalance(env: Env, vault_id: u64) -> Result<(), ContractError>Events:
TOKEN_REBALANCED_TOPIC: Emitted on each rebalance
pub fn get_token_rebalance(env: Env, vault_id: u64) -> Option<TokenRebalanceConfig>- Token Whitelisting: Only whitelisted tokens can be used in vaults
- Owner Authorization: Only vault owners can configure staking, lending, collateral, hedging, and rebalancing
- Yield Calculation: Yield is calculated based on time elapsed and annual rate
- Atomic Operations: Batch deposits validate all items before any transfers
- Event Tracking: All operations emit events for on-chain audit trails
- Collateral Ratio: Collateral ratio must be ≥ 100% to prevent under-collateralised loans
- Balance Checks: Lending and collateral operations verify sufficient vault balance