vTokens are standard fungible tokens on Soroban that represent a proportional share of a Vanna lending pool’s total assets. Three vToken contracts exist — one per pool.
| Token | Pool | Address |
|---|
vXLM | XLM Lending Pool | CCQAAPNBYF6I7PRM2NZ4NRDYZUVJJANMX3RZ4ZLMQH6Z5WAUL2MHU2RZ |
vUSDC | USDC Lending Pool | CDAJHQEJ26EBBGV2UYSR5S5LLA6F3A7KQISLPY5JMOL77RUDBEWG3T6Y |
vAQUSDC | Aquarius USDC Pool | CATGJWK22YGAQX4DH6BXUMQYRZRGOQ6MJNJVZP67WZEE4PHSROU763KN |
vSOUSDC | Soroswap USDC Pool | CDW5YJOBAR5KUPYQ5LQICSLKWRUMDEGSZVS5A4QNBMNKEZEARR6EJFYT |
All vToken contracts share the same interface.
Exchange Rate
vTokens appreciate against the underlying asset as interest accrues. The exchange rate is:
exchange_rate = total_assets / vtoken_supply
Where total_assets = pool_balance + outstanding_borrows. As borrowers repay interest, total_assets grows, and each vToken is redeemable for more of the underlying.
Holding vTokens is how LPs earn yield. No staking, claiming, or compounding required — yield accrues in the exchange rate.
Functions
initialize
fn initialize(
env: Env,
admin: Address,
decimals: u32,
name: String,
symbol: String,
) -> ()
One-time initialization. Sets the token metadata and admin. Can only be called once.
mint
fn mint(env: Env, to: Address, amount: i128) -> ()
Increases to’s vToken balance by amount. Only callable by the admin (the LendingPool contract).
Called by the LendingPool on every deposit.
burn
fn burn(env: Env, from: Address, amount: i128) -> ()
Decreases from’s vToken balance by amount. Only callable by the admin (the LendingPool contract).
Called by the LendingPool on every vToken redemption.
transfer
fn transfer(env: Env, from: Address, to: Address, amount: i128) -> ()
Standard token transfer. Requires from.require_auth().
vTokens are freely transferable — they can be held in any wallet or used in other DeFi protocols.
transfer_from
fn transfer_from(
env: Env,
spender: Address,
from: Address,
to: Address,
amount: i128,
) -> ()
Allowance-based transfer. Requires spender.require_auth() and sufficient allowance from from to spender.
approve
fn approve(
env: Env,
from: Address,
spender: Address,
amount: i128,
expiration_ledger: u32,
) -> ()
Grants spender permission to transfer up to amount of from’s vTokens. Standard Soroban token approval with an expiration ledger.
balance
fn balance(env: Env, id: Address) -> i128
Returns the vToken balance of id.
total_supply
fn total_supply(env: Env) -> i128
Returns the total number of vTokens currently in circulation.
allowance
fn allowance(env: Env, from: Address, spender: Address) -> i128
Returns the remaining allowance that spender can transfer from from.
decimals / name / symbol
fn decimals(env: Env) -> u32
fn name(env: Env) -> String
fn symbol(env: Env) -> String
Standard token metadata.
admin
fn admin(env: Env) -> Address
Returns the admin address (the LendingPool contract that controls mint/burn).
set_admin
fn set_admin(env: Env, new_admin: Address) -> ()
Changes the admin. Requires current admin auth. Used for protocol upgrades.
Events
MintEvent {
admin: Address,
to: Address,
amount: i128
}
BurnEvent {
admin: Address,
from: Address,
amount: i128
}
TransferEvent {
from: Address,
to: Address,
amount: i128
}
ApprovalEvent {
from: Address,
spender: Address,
amount: i128,
expiration_ledger: u32
}
Storage Layout
| Key | Type | Description |
|---|
TokenInfo | {decimals, name, symbol} | Token metadata |
Admin | Address | Admin (LendingPool) |
TotalSupply | i128 | Total vTokens in circulation |
Balance(Address) | i128 | Per-user balance |
Allowance({from, spender}) | {amount, expiration_ledger} | Transfer allowances |
Frozen(Address) | bool | Whether account is frozen (admin action) |
Integration Notes
vTokens follow the Stellar Asset Interface (SEP-41) and can be held, transferred, and used as collateral in other protocols on Stellar.
To compute the current redemption value of a vToken balance:
const vtokenBalance = await vtokenContract.balance(userAddress);
const totalSupply = await vtokenContract.total_supply();
const totalAssets = await lendingPool.total_assets(); // liquidity + borrows in WAD
const underlyingWad = (vtokenBalance * totalAssets) / totalSupply;