spToken (AToken contract)
What it is
When a user calls Pool.supply(asset, amount, …), the corresponding AToken is minted to the recipient. Each reserve has its own AToken proxy, deployed by PoolConfigurator.initReserves. On SparkLend, the AToken for each reserve is deployed with a Spark-branded symbol like spUSDS, spDAI, spWETH. The contract type is AToken; "spToken" is shorthand for "the AToken deployed for a SparkLend reserve". Holders of an spToken hold a 1:1 claim on the underlying, plus all accrued supply-side interest.
ATokens are standard ERC-20 tokens with two key extensions:
- Rebasing balance.
balanceOf(user)returnsscaledBalanceOf(user) * liquidityIndex / 1e27, whereliquidityIndexis the cumulative supply rate for the reserve. As the reserve accrues interest, balances rise without explicit minting. - Pool-coupled transfers. Every transfer calls back into
Pool.finalizeTransfer, which revalidates the sender's health factor and updates the collateral-enabled bitmap on both sides.
Lifecycle (Pool-only)
| Method | Caller | Effect |
|---|---|---|
mint(caller, onBehalfOf, amount, index) | Pool (via SupplyLogic.executeSupply) | Mints scaled balance to onBehalfOf. Returns true if this is the recipient's first supply. |
burn(from, receiverOfUnderlying, amount, index) | Pool (via SupplyLogic.executeWithdraw, BorrowLogic.executeRepay with useATokens=true, LiquidationLogic._burnCollateralATokens) | Burns scaled balance and transfers underlying to receiverOfUnderlying (unless receiverOfUnderlying == address(this), used for repay-with-ATokens). |
mintToTreasury(amount, index) | Pool.mintToTreasury | Mints accrued reserve-factor income to the treasury address. |
transferOnLiquidation(from, to, value) | Pool (via LiquidationLogic._liquidateATokens) | Transfer that skips the finalizeTransfer health-factor revalidation. |
transferUnderlyingTo(target, amount) | Pool (via BorrowLogic.executeBorrow and FlashLoanLogic) | Sends underlying out of the AToken to fund a borrow or flash loan. |
handleRepayment(user, onBehalfOf, amount) | Pool (after repay) | No-op by default; provides an override point for AToken subclasses (e.g. yield strategies). |
EIP-2612 permit
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) externalATokens implement permit using the typehash Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline). The domain separator is bound to the token's name() and the chain id.
DOMAIN_SEPARATOR() and nonces(address) are exposed on the token.
View methods specific to AToken
| Method | Returns |
|---|---|
POOL() | The IPool this AToken is bound to. |
UNDERLYING_ASSET_ADDRESS() | The underlying ERC-20 backing the AToken. |
RESERVE_TREASURY_ADDRESS() | Treasury address that receives the reserve-factor portion of interest. |
scaledBalanceOf(user) | The user's scaled balance (balance / liquidityIndex). |
scaledTotalSupply() | Sum of all scaled balances. |
getScaledUserBalanceAndSupply(user) | Returns (scaledBalanceOf, scaledTotalSupply) in one call. |
getPreviousIndex(user) | Last liquidity index at which the user's balance was updated. |
getIncentivesController() | Address of the IAaveIncentivesController used for rewards bookkeeping. |
DOMAIN_SEPARATOR() | EIP-712 domain separator for permit. |
nonces(address) | Permit nonce for an account. |
Admin methods
function setIncentivesController(IAaveIncentivesController newController) external onlyPoolAdminReplaces the incentives controller used by this AToken. Restricted to holders of POOL_ADMIN_ROLE on ACLManager.
function rescueTokens(address token, address to, uint256 amount) external onlyPoolAdminRecovers stray ERC-20s sent directly to the AToken contract. Cannot be used on the AToken's own underlying asset — that would break the AToken's 1:1 backing.
FAQ
How does an spToken (AToken) accrue yield?
Every state-changing call into the reserve (supply, withdraw, borrow, repay, liquidationCall, flashLoanSimple) updates the reserve's liquidityIndex. AToken.balanceOf returns the user's scaled balance multiplied by the current index, so balances increase passively over time.
Can I transfer spTokens?
Yes — they are standard ERC-20 with one caveat: transferring out reduces your collateral. The transfer is followed by a finalizeTransfer callback that reverts if your health factor would drop below 1e18.
Do unclaimed rewards transfer with the spToken?
No. Rewards accrued before the transfer remain with the sender's address inside the incentives controller. The recipient starts accruing rewards from the transfer onward.