SperaxOS
Dec 18, 2025
CollateralData) public collateralInfo;
collateralStrategyInfo
mapping(address => mapping(address => StrategyData)) private
collateralStrategyInfo;
collateralStrategies
mapping(address => address[]) private collateralStrategies;
Functions
constructor
Constructor to initialize the Collateral Manager
constructor(address _vault);
Parameters
Name Type Description
Address of the Vault
_vault address
contract
addCollateral
Register a collateral for mint & redeem in USDs
69function addCollateral(address _collateral, CollateralBaseData memory
_data) external onlyOwner;
Parameters
Name Type Description
_collateral address Address of the collateral
_data CollateralBaseData Collateral configuration data
updateCollateralData
Update existing collateral configuration
function updateCollateralData(address _collateral, CollateralBaseData
memory _updateData) external onlyOwner;
Parameters
Name Type Description
_collateral address Address of the collateral
Updated configuration for
_updateData CollateralBaseData
the collateral
removeCollateral
Un-list a collateral
function removeCollateral(address _collateral) external onlyOwner;
Parameters
70Name Type Description
_collateral address Address of the collateral
addCollateralStrategy
Add a new strategy to collateral
function addCollateralStrategy(address _collateral, address _strategy,
uint16 _allocationCap) external onlyOwner;
Parameters
Name Type Description
_collateral address Address of the collateral
_strategy address Address of the strategy
_allocationCap uint16 Allocation capacity
updateCollateralStrategy
Update existing strategy for collateral
function updateCollateralStrategy(address _collateral, address
_strategy, uint16 _allocationCap) external onlyOwner;
Parameters
Name Type Description
_collateral address Address of the collateral
_strategy address Address of the strategy
_allocationCap uint16 Allocation capacity
71removeCollateralStrategy
Remove an existing strategy from collateral
Ensure all the collateral is removed from the strategy before calling this Otherwise it
will create error in collateral accounting
function removeCollateralStrategy(address _collateral, address
_strategy) external onlyOwner;
Parameters
Name Type Description
_collateral address Address of the collateral
_strategy address Address of the strategy
updateCollateralDefaultStrategy
function updateCollateralDefaultStrategy(address _collateral, address
_strategy) external onlyOwner;
validateAllocation
Validate allocation for a collateral
function validateAllocation(address _collateral, address _strategy,
uint256 _amount) external view returns (bool);
Parameters
72Name Type Description
_collateral address Address of the collateral
Address of the desired
_strategy address
strategy
_amount uint256 Amount to be allocated.
Returns
Name Type Description
True for valid allocation
bool
request.
getFeeCalibrationData
Get the required data for mint
function getFeeCalibrationData(address _collateral) external view
returns (uint16, uint16, uint16, uint256);
Parameters
Name Type Description
_collateral address Address of the collateral
Returns
73Name Type Description
Base fee config for
collateral (baseMintFee,
uint16
baseRedeemFee,
composition, totalCollateral)
uint16
uint16
uint256
getMintParams
Get the required data for mint
function getMintParams(address _collateral) external view returns
(CollateralMintData memory mintData);
Parameters
Name Type Description
_collateral address Address of the collateral
Returns
Name Type Description
mintData CollateralMintData mintData
getRedeemParams
Get the required data for USDs redemption
74function getRedeemParams(address _collateral) external view returns
(CollateralRedeemData memory redeemData);
Parameters
Name Type Description
_collateral address Address of the collateral
Returns
Name Type Description
redeemData CollateralRedeemData redeemData
getAllCollaterals
Gets a list of all listed collaterals
function getAllCollaterals() external view returns (address[] memory);
Returns
Name Type Description
List of addresses
address[] representing all listed
collaterals
getCollateralStrategies
Gets a list of all strategies linked to a collateral
75function getCollateralStrategies(address _collateral) external view
returns (address[] memory);
Parameters
Name Type Description
_collateral address Address of the collateral
Returns
Name Type Description
List of addresses
address[] representing available
strategies for the collateral
isValidStrategy
Verifies if a strategy is linked to a collateral
function isValidStrategy(address _collateral, address _strategy)
external view returns (bool);
Parameters
Name Type Description
_collateral address Address of the collateral
_strategy address Address of the strategy
Returns
76Name Type Description
True if the strategy is linked
bool to the collateral, otherwise
False
getCollateralInStrategies
Get the amount of collateral in all Strategies
function getCollateralInStrategies(address _collateral) public view
returns (uint256 amountInStrategies);
Parameters
Name Type Description
_collateral address Address of the collateral
Returns
Name Type Description
amountInStrategies uint256 amountInStrategies
getCollateralInVault
Get the amount of collateral in vault
function getCollateralInVault(address _collateral) public view returns
(uint256 amountInVault);
Parameters
77Name Type Description
_collateral address Address of the collateral
Returns
Name Type Description
amountInVault uint256 amountInVault
getCollateralInAStrategy
Get the amount of collateral allocated in a strategy
function getCollateralInAStrategy(address _collateral, address
_strategy) public view returns (uint256 allocatedAmt);
Parameters
Name Type Description
_collateral address Address of the collateral
_strategy address Address of the strategy
Returns
Name Type Description
allocatedAmt uint256 Allocated amount
Events
CollateralAdded
78event CollateralAdded(address collateral, CollateralBaseData data);
CollateralRemoved
event CollateralRemoved(address collateral);
CollateralInfoUpdated
event CollateralInfoUpdated(address collateral, CollateralBaseData
data);
CollateralStrategyAdded
event CollateralStrategyAdded(address collateral, address strategy);
CollateralStrategyUpdated
event CollateralStrategyUpdated(address collateral, address strategy);
CollateralStrategyRemoved
event CollateralStrategyRemoved(address collateral, address strategy);
Errors
CollateralExists
79error CollateralExists();
CollateralDoesNotExist
error CollateralDoesNotExist();
CollateralStrategyExists
error CollateralStrategyExists();
CollateralStrategyMapped
error CollateralStrategyMapped();
CollateralStrategyNotMapped
error CollateralStrategyNotMapped();
CollateralNotSupportedByStrategy
error CollateralNotSupportedByStrategy();
CollateralAllocationPaused
error CollateralAllocationPaused();
80CollateralStrategyInUse
error CollateralStrategyInUse();
AllocationPercentageLowerThanAllocatedAmt
error AllocationPercentageLowerThanAllocatedAmt();
IsDefaultStrategy
error IsDefaultStrategy();
Structs
CollateralData
struct CollateralData {
bool mintAllowed;
bool redeemAllowed;
bool allocationAllowed;
bool exists;
address defaultStrategy;
uint16 baseMintFee;
uint16 baseRedeemFee;
uint16 downsidePeg;
uint16 desiredCollateralComposition;
uint16 collateralCapacityUsed;
uint256 conversionFactor;
}
StrategyData
81struct StrategyData {
uint16 allocationCap;
bool exists;
}
82SPA Buyback
Git Source
Inherits: Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable
Author: Sperax Foundation
This contract allows users to exchange SPA tokens for USDs tokens.
Users can provide SPA tokens and receive USDs tokens in return based on the
current exchange rate.
A percentage of the provided SPA tokens are distributed as rewards, and the rest
are burned.
State Variables
veSpaRewarder
address public veSpaRewarder;
rewardPercentage
uint256 public rewardPercentage;
oracle
address public oracle;
83Functions
constructor
constructor();
initialize
Contract initializer
function initialize(address _veSpaRewarder, uint256 _rewardPercentage)
external initializer;
Parameters
Name Type Description
_veSpaRewarder address Rewarder''s address
Percentage of SPA to be
_rewardPercentage uint256
rewarded
withdraw
Emergency withdrawal function for unexpected situations
Can only be called by the owner
function withdraw(address _token, address _receiver, uint256 _amount)
external onlyOwner;
Parameters
84Name Type Description
Address of the asset to be
_token address
withdrawn
Address of the receiver of
_receiver address
tokens
Amount of tokens to be
_amount uint256
withdrawn
updateRewardPercentage
Changes the reward percentage
Example value for _newRewardPercentage = 5000 for 50%
function updateRewardPercentage(uint256 _newRewardPercentage) external
onlyOwner;
Parameters
Name Type Description
_newRewardPercentage uint256 New Reward Percentage
updateVeSpaRewarder
Update veSpaRewarder address
function updateVeSpaRewarder(address _newVeSpaRewarder) external
onlyOwner;
Parameters
85Name Type Description
is the address of desired
_newVeSpaRewarder address
veSpaRewarder
updateOracle
Update oracle address
function updateOracle(address _newOracle) external onlyOwner;
Parameters
Name Type Description
is the address of desired
_newOracle address
oracle
buyUSDs
Function to buy USDs for SPA for frontend
function buyUSDs(uint256 _spaIn, uint256 _minUSDsOut) external;
Parameters
Name Type Description
_spaIn uint256 Amount of SPA tokens
Minimum amount out in
_minUSDsOut uint256
USDs
getSPAReqdForUSDs
86Calculates and returns SPA amount required for _usdsAmount
function getSPAReqdForUSDs(uint256 _usdsAmount) external view returns
(uint256);
Parameters
Name Type Description
USDs amount the user
_usdsAmount uint256
wants
Returns
Name Type Description
uint256 Amount of SPA required
buyUSDs
Buy USDs for SPA if you want a different receiver
function buyUSDs(address _receiver, uint256 _spaIn, uint256
_minUSDsOut) public nonReentrant;
Parameters
Name Type Description
_receiver address Receiver of USDs
_spaIn uint256 Amount of SPA tokens
Minimum amount out in
_minUSDsOut uint256
USDs
87distributeAndBurnSPA
Sends available SPA in this contract to rewarder based on rewardPercentage and
burns the rest
function distributeAndBurnSPA() public;
getUsdsOutForSpa
Returns the amount of USDS for SPA amount in
function getUsdsOutForSpa(uint256 _spaIn) public view returns
(uint256);
Parameters
Name Type Description
_spaIn uint256 Amount of SPA tokens
Returns
Name Type Description
Amount of USDs user will
uint256
get
_getUsdsOutForSpa
Returns the amount of USDS for SPA amount in
function _getUsdsOutForSpa(uint256 _spaIn) private view returns
(uint256, uint256);
88Parameters
Name Type Description
_spaIn uint256 Amount of SPA tokens
Returns
Name Type Description
Amount of USDs user will
uint256
get
uint256
_getOracleData
Retrieves price data from the oracle contract for SPA and USDS tokens.
function _getOracleData() private view returns (uint256, uint256,
uint256, uint256);
Returns
Name Type Description
The price of USDS in SPA,
the price of SPA in USDS,
uint256
and their respective
precisions.
uint256
uint256
uint256
_isValidRewardPercentage
89Checks if the provided reward percentage is valid.
The reward percentage must be a non-zero value and should not exceed the
maximum percentage value.
function _isValidRewardPercentage(uint256 _rewardPercentage) private
pure;
Parameters
Name Type Description
The reward percentage to
_rewardPercentage uint256
validate.
Events
BoughtBack
event BoughtBack(
address indexed receiverOfUSDs, address indexed senderOfSPA,
uint256 spaPrice, uint256 spaAmount, uint256 usdsAmount
);
Withdrawn
event Withdrawn(address indexed token, address indexed receiver,
uint256 amount);
SPARewarded
event SPARewarded(uint256 spaAmount);
90SPABurned
event SPABurned(uint256 spaAmount);
RewardPercentageUpdated
event RewardPercentageUpdated(uint256 newRewardPercentage);
VeSpaRewarderUpdated
event VeSpaRewarderUpdated(address newVeSpaRewarder);
OracleUpdated
event OracleUpdated(address newOracle);
Errors
CannotWithdrawSPA
error CannotWithdrawSPA();
InsufficientUSDsBalance
error InsufficientUSDsBalance(uint256 toSend, uint256 bal);
91MasterPriceOracle
Git Source
Inherits: Ownable, IOracle
Author: Sperax Foundation
Communicates with different price feeds to get the price
State Variables
tokenPriceFeed
Store price feed data for tokens.
mapping(address => PriceFeedData) public tokenPriceFeed;
Functions
updateTokenPriceFeed
Add/Update price feed for _token
Have to be extra cautious while updating the price feed.
function updateTokenPriceFeed(address _token, address _source, bytes
memory _data) external onlyOwner;
Parameters
92Name Type Description
address of the desired
_token address
token.
_source address price feed source.
call data for fetching the
_data bytes
price feed.
removeTokenPriceFeed
Remove an existing price feed for _token .
function removeTokenPriceFeed(address _token) external onlyOwner;
Parameters
Name Type Description
_token address address of the token.
getPrice
Gets the price feed for _token .
Function reverts if the price feed does not exists.
function getPrice(address _token) external view returns (PriceData
memory);
Parameters
93Name Type Description
address of the desired
_token address
token.
Returns
Name Type Description
(uint256 price, uint256
PriceData
precision).
priceFeedExists
Validates if price feed exists for a _token
Function reverts if price feed not set.
function priceFeedExists(address _token) external view returns (bool);
Parameters
Name Type Description
address of the desired
_token address
token.
Returns
Name Type Description
bool bool if price feed exists.
_getPriceFeed
94Gets the price feed for a _token given the feed data.
function _getPriceFeed(address _token, address _source, bytes memory
_msgData)
private
view
returns (PriceData memory priceData);
Parameters
Name Type Description
address of the desired
_token address
token.
_source address price feed source.
_msgData bytes call data for fetching feed.
Returns
Name Type Description
(uint256 price, uint256
priceData PriceData
precision).
Events
PriceFeedUpdated
event PriceFeedUpdated(address indexed token, address indexed source,
bytes msgData);
PriceFeedRemoved
95event PriceFeedRemoved(address indexed token);
Errors
InvalidAddress
error InvalidAddress();
UnableToFetchPriceFeed
error UnableToFetchPriceFeed(address token);
InvalidPriceFeed
error InvalidPriceFeed(address token);
PriceFeedNotFound
error PriceFeedNotFound(address token);
96Yield Reserve
Git Source
Inherits: ReentrancyGuard, Ownable
Author: Sperax Foundation
This contract allows users to swap supported stable-coins for yield earned by the
USDs protocol. It sends USDs to the Dripper contract for rebase and to the Buyback
Contract for buyback.
State Variables
vault
address public vault;
oracle
address public oracle;
buyback
address public buyback;
dripper
address public dripper;
97buybackPercentage
uint256 public buybackPercentage;
tokenData
mapping(address => TokenData) public tokenData;
Functions
constructor
Constructor of the YieldReserve contract.
constructor(address _buyback, address _vault, address _oracle, address
_dripper);
Parameters
Name Type Description
Address of the Buyback
_buyback address
contract.
_vault address Address of the Vault.
_oracle address Address of the Oracle.
Address of the Dripper
_dripper address
contract.
swap
98Swap function to be called by frontend users.
function swap(address _srcToken, address _dstToken, uint256 _amountIn,
uint256 _minAmountOut) external;
Parameters
Name Type Description
_srcToken address Source/Input token.
_dstToken address Destination/Output token.
_amountIn uint256 Input token amount.
Minimum output tokens
_minAmountOut uint256
expected.
toggleSrcTokenPermission
Allow or disallow a specific token for use as a source/input token.
function toggleSrcTokenPermission(address _token, bool _isAllowed)
external onlyOwner;
Parameters
Name Type Description
Address of the token to be
_token address
allowed or disallowed.
If set to true, the token will
be allowed as a
_isAllowed bool source/input token;
otherwise, it will be
disallowed.
99toggleDstTokenPermission
Allow or disallow a specific token for use as a destination/output token.
Reverts if caller is not owner.
function toggleDstTokenPermission(address _token, bool _isAllowed)
external onlyOwner;
Parameters
Name Type Description
Address of the token to be
_token address
allowed or disallowed.
If set to true, the token will
be allowed as a
_isAllowed bool destination/output token;
otherwise, it will be
disallowed.
withdraw
Emergency withdrawal function for unexpected situations.
function withdraw(address _token, address _receiver, uint256 _amount)
external onlyOwner;
Parameters
100Name Type Description
Address of the asset to be
_token address
withdrawn.
Address of the receiver of
_receiver address
tokens.
Amount of tokens to be
_amount uint256
withdrawn.
updateBuybackPercentage
Set the percentage of newly minted USDs to be sent to the Buyback contract.
Reverts if caller is not owner.
The remaining USDs are sent to VaultCore for rebase.
function updateBuybackPercentage(uint256 _toBuyback) public onlyOwner;
Parameters
Name Type Description
The percentage of USDs
_toBuyback uint256 sent to Buyback (e.g., 3000
for 30%).
updateBuyback
Update the address of the Buyback contract.
Reverts if caller is not owner.
function updateBuyback(address _newBuyBack) public onlyOwner;
101Parameters
Name Type Description
New address of the
_newBuyBack address
Buyback contract.
updateOracle
Update the address of the Oracle contract.
Reverts if caller is not owner.
function updateOracle(address _newOracle) public onlyOwner;
Parameters
Name Type Description
New address of the Oracle
_newOracle address
contract.
updateDripper
Update the address of the Dripper contract.
Reverts if caller is not owner.
function updateDripper(address _newDripper) public onlyOwner;
Parameters
102Name Type Description
New address of the Dripper
_newDripper address
contract.
updateVault
Update the address of the VaultCore contract.
Reverts if caller is not owner.
function updateVault(address _newVault) public onlyOwner;
Parameters
Name Type Description
New address of the
_newVault address
VaultCore contract.
swap
Swap allowed source token for allowed destination token.
function swap(address _srcToken, address _dstToken, uint256 _amountIn,
uint256 _minAmountOut, address _receiver)
public
nonReentrant;
Parameters
103Name Type Description
_srcToken address Source/Input token.
_dstToken address Destination/Output token.
_amountIn uint256 Input token amount.
Minimum output tokens
_minAmountOut uint256
expected.
_receiver address Receiver of the tokens.
mintUSDs
Mints USDs directly with the allowed collaterals for USDs.
Only collaterals configured in USDs vault are allowed to be used for minting.
function mintUSDs(address _token) public nonReentrant;
Parameters
Name Type Description
Address of token to mint
_token address
USDs with
getTokenBForTokenA
Get an estimate of the output token amount for a given input token amount.
function getTokenBForTokenA(address _srcToken, address _dstToken,
uint256 _amountIn) public view returns (uint256);
Parameters
104Name Type Description
_srcToken address Input token address.
_dstToken address Output token address.
_amountIn uint256 Input amount of _srcToken.
Returns
Name Type Description
Estimated output token
uint256
amount.
_sendUSDs
Distributes USDs to the Buyback and Dripper contracts based on
buybackPercentage.
Sends a portion of the USDs balance to the Buyback contract and the remaining to
the Dripper contract for rebase.
function _sendUSDs() private;
Events
Swapped
event Swapped(
address indexed srcToken, address indexed dstToken, address indexed
dstReceiver, uint256 amountIn, uint256 amountOut
);
105USDsMintedViaSwapper
event USDsMintedViaSwapper(address indexed collateralAddr, uint256
usdsMinted);
Withdrawn
event Withdrawn(address indexed token, address indexed receiver,
uint256 amount);
BuybackPercentageUpdated
event BuybackPercentageUpdated(uint256 toBuyback);
BuybackUpdated
event BuybackUpdated(address newBuyback);
OracleUpdated
event OracleUpdated(address newOracle);
VaultUpdated
event VaultUpdated(address newVault);
DripperUpdated
106event DripperUpdated(address newDripper);
USDsSent
event USDsSent(uint256 toBuyback, uint256 toDripper);
SrcTokenPermissionUpdated
event SrcTokenPermissionUpdated(address indexed token, bool isAllowed);
DstTokenPermissionUpdated
event DstTokenPermissionUpdated(address indexed token, bool isAllowed);
Errors
InvalidSourceToken
error InvalidSourceToken();
InvalidDestinationToken
error InvalidDestinationToken();
AlreadyInDesiredState
107error AlreadyInDesiredState();
TokenPriceFeedMissing
error TokenPriceFeedMissing();
Structs
TokenData
struct TokenData {
bool srcAllowed;
bool dstAllowed;
uint160 conversionFactor;
}
108Fee Calculator
Git Source
Inherits: IFeeCalculator
Author: Sperax Foundation
A contract that calculates fees for minting and redeeming USDs.
State Variables
LOWER_THRESHOLD
uint16 private constant LOWER_THRESHOLD = 5000;
UPPER_THRESHOLD
uint16 private constant UPPER_THRESHOLD = 15000;
DISCOUNT_FACTOR
uint16 private constant DISCOUNT_FACTOR = 2;
PENALTY_MULTIPLIER
uint16 private constant PENALTY_MULTIPLIER = 2;
109CALIBRATION_GAP
uint32 private constant CALIBRATION_GAP = 1 days;
COLLATERAL_MANAGER
ICollateralManager public immutable COLLATERAL_MANAGER;
collateralFee
mapping(address => FeeData) public collateralFee;
Functions
constructor
constructor(address _collateralManager);
calibrateFee
Calibrates fee for a particular collateral
function calibrateFee(address _collateral) external;
Parameters
110Name Type Description
Address of the desired
_collateral address
collateral
getMintFee
Calculates fee to be collected for minting
function getMintFee(address _collateral) external view returns
(uint256);
Parameters
Name Type Description
_collateral address
Returns
Name Type Description
uint256 (uint256) baseFeeIn
getRedeemFee
Calculates fee to be collected for redeeming
function getRedeemFee(address _collateral) external view returns
(uint256);
Parameters
111Name Type Description
_collateral address
Returns
Name Type Description
uint256 (uint256) baseFeeOut
calibrateFeeForAll
Calibrates fee for all the collaterals registered
function calibrateFeeForAll() public;
_calibrateFee
Helper function for calibrating fee for a collateral
function _calibrateFee(address _collateral) private;
Parameters
Name Type Description
Address of the desired
_collateral address
collateral
Events
FeeCalibrated
112event FeeCalibrated(address indexed collateral, uint16 mintFee, uint16
redeemFee);
Errors
InvalidCalibration
error InvalidCalibration();
Structs
FeeData
struct FeeData {
uint32 nextUpdate;
uint16 mintFee;
uint16 redeemFee;
}
113RebaseManager
Git Source
Inherits: IRebaseManager, Ownable
Author: Sperax Foundation
This contract handles the configuration and execution of the rebasing mechanism
for the USDs stablecoin. It ensures that rebases occur only when certain
prerequisites are fulfilled, such as the time gap between rebases and acceptable
APR (Annual Percentage Rate) ranges.
The Rebase Manager coordinates with the Vault and Dripper contracts to manage
the rebase process.
State Variables
ONE_YEAR
uint256 private constant ONE_YEAR = 365 days;
vault
address public vault;
dripper
address public dripper;
114gap
uint256 public gap;
aprCap
uint256 public aprCap;
aprBottom
uint256 public aprBottom;
lastRebaseTS
uint256 public lastRebaseTS;
Functions
onlyVault
modifier onlyVault();
constructor
Constructor to initialize the Rebase Manager
115constructor(address _vault, address _dripper, uint256 _gap, uint256
_aprCap, uint256 _aprBottom);
Parameters
Name Type Description
Address of the vault
_vault address
contract
Address of the dripper
_dripper address
contract for collecting USDs
Minimum time gap required
_gap uint256 between two consecutive
rebases
Maximum allowed APR for a
_aprCap uint256
rebase
Minimum allowed APR for a
_aprBottom uint256
rebase
fetchRebaseAmt
Get the current amount valid for rebase
Function is called by the vault while rebasing
function fetchRebaseAmt() external onlyVault returns (uint256);
Returns
Name Type Description
The available amount for
uint256
rebasing USDs
116updateVault
Updates the vault address
function updateVault(address _newVault) public onlyOwner;
Parameters
Name Type Description
Address of the new vault
_newVault address
contract
updateDripper
Updates the dripper contract for USDs vault
function updateDripper(address _dripper) public onlyOwner;
Parameters
Name Type Description
Address of the new dripper
_dripper address
contract
updateGap
Update the minimum time gap required between two rebases
function updateGap(uint256 _gap) public onlyOwner;
Parameters
117Name Type Description
_gap uint256 Updated gap time
updateAPR
Update the APR requirements for each rebase
function updateAPR(uint256 _aprBottom, uint256 _aprCap) public
onlyOwner;
Parameters
Name Type Description
New minimum APR for a
_aprBottom uint256
rebase
New maximum APR for a
_aprCap uint256
rebase
getAvailableRebaseAmt
Gets the current available rebase fund
function getAvailableRebaseAmt() public view returns (uint256);
Returns
Name Type Description
Current balance in the vault
uint256 plus collectable dripped
USDs amount
118getMinAndMaxRebaseAmt
Gets the minimum and maximum rebase USDs amount based on the APR config
function getMinAndMaxRebaseAmt() public view returns (uint256,
uint256);
Returns
Name Type Description
Minimum and maximum
uint256
rebase amounts
uint256
Events
VaultUpdated
event VaultUpdated(address vault);
DripperUpdated
event DripperUpdated(address dripper);
GapUpdated
event GapUpdated(uint256 gap);
119APRUpdated
event APRUpdated(uint256 aprBottom, uint256 aprCap);
Errors
CallerNotVault
error CallerNotVault(address caller);
InvalidAPRConfig
error InvalidAPRConfig(uint256 aprBottom, uint256 aprCap);
120Dripper
Git Source
Inherits: IDripper, Ownable
Author: Sperax Foundation
This contract releases tokens at a steady rate to the Vault contract, for rebasing the
USDs stablecoin.
The Dripper contract ensures that tokens are released gradually over time, allowing
for consistent and controlled distribution.
State Variables
vault
address public vault;
dripRate
uint256 public dripRate;
dripDuration
uint256 public dripDuration;
lastCollectTS
121uint256 public lastCollectTS;
Functions
constructor
Constructor to initialize the Dripper.
constructor(address _vault, uint256 _dripDuration);
Parameters
Name Type Description
Address of the contract that
_vault address receives the dripped
tokens.
The duration over which
_dripDuration uint256
tokens are dripped.
recoverTokens
Emergency fund recovery function.
Transfers the asset to the owner of the contract.
function recoverTokens(address _asset) external onlyOwner;
Parameters
122Name Type Description
Address of the asset to
_asset address
recover.
addUSDs
Function to be used to send USDs to dripper and update dripRate .
function addUSDs(uint256 _amount) external;
Parameters
Name Type Description
Amount of USDs to be sent
_amount uint256
form caller to this contract.
collect
Transfers the dripped tokens to the vault.
This function also updates the dripRate based on the fund state.
function collect() public returns (uint256);
Returns
Name Type Description
The amount of tokens
uint256 collected and transferred to
the vault.
123updateVault
Update the vault address.
function updateVault(address _vault) public onlyOwner;
Parameters
Name Type Description
Address of the new vault
_vault address
contract.
updateDripDuration
Updates the dripDuration.
function updateDripDuration(uint256 _dripDuration) public onlyOwner;
Parameters
Name Type Description
The desired drip duration to
_dripDuration uint256
be set.
getCollectableAmt
Gets the collectible amount of tokens at the current time.
function getCollectableAmt() public view returns (uint256);
Returns
124Name Type Description
The amount of tokens that
uint256
can be collected.
Events
Collected
event Collected(uint256 amount);
Recovered
event Recovered(address owner, uint256 amount);
VaultUpdated
event VaultUpdated(address vault);
DripDurationUpdated
event DripDurationUpdated(uint256 dripDuration);
USDsAdded
event USDsAdded(uint256 _amount);
125Errors
NothingToRecover
error NothingToRecover();
126BaseStrategy
Git Source
Inherits: Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable
Author: Sperax Foundation
Contract acts as a single interface for implementing specific yield-earning
strategies.
State Variables
vault
address public vault;
withdrawSlippage
uint16 public withdrawSlippage;
depositSlippage
uint16 public depositSlippage;
harvestIncentiveRate
uint16 public harvestIncentiveRate;
127assetsMapped
address[] internal assetsMapped;
rewardTokenAddress
address[] public rewardTokenAddress;
assetToPToken
mapping(address => address) public assetToPToken;
gap
uint256[40] private __gap__;
Functions
onlyVault
modifier onlyVault();
onlyVaultOrOwner
modifier onlyVaultOrOwner();
128constructor
constructor();
updateVault
Update the linked vault contract.
function updateVault(address _newVault) external onlyOwner;
Parameters
Name Type Description
_newVault address Address of the new Vault.
updateHarvestIncentiveRate
Updates the HarvestIncentive rate for the user.
function updateHarvestIncentiveRate(uint16 _newRate) external
onlyOwner;
Parameters
Name Type Description
_newRate uint16 new Desired rate.
recoverERC20
A function to recover any erc20 token sent to this strategy mistakenly.
129Only callable by owner.
reverts if amount > balance.
function recoverERC20(address token, address receiver, uint256 amount)
external onlyOwner;
Parameters
Name Type Description
token address Address of the token.
receiver address Receiver of the token.
amount uint256 Amount to be recovered.
deposit
Deposit an amount of asset into the platform.
function deposit(address _asset, uint256 _amount) external virtual;
Parameters
Name Type Description
_asset address Address for the asset.
_amount uint256 Units of asset to deposit.
withdraw
Withdraw an amount of asset from the platform.
130function withdraw(address _recipient, address _asset, uint256 _amount)
external
virtual
returns (uint256 amountReceived);
Parameters
Name Type Description
Address to which the asset
_recipient address
should be sent.
_asset address Address of the asset.
_amount uint256 Units of asset to withdraw.
Returns
Name Type Description
The actual amount
amountReceived uint256
received.
withdrawToVault
Withdraw an amount of asset from the platform to vault.
function withdrawToVault(address _asset, uint256 _amount) external
virtual returns (uint256 amount);
Parameters
Name Type Description
_asset address Address of the asset.
_amount uint256 Units of asset to withdraw.
131collectInterest
Withdraw the interest earned of asset from the platform.
function collectInterest(address _asset) external virtual;
Parameters
Name Type Description
_asset address Address of the asset.
collectReward
Collect accumulated reward token and send to Vault.
function collectReward() external virtual;
checkBalance
Get the amount of a specific asset held in the strategy, excluding the interest.
Curve: assuming balanced withdrawal.
function checkBalance(address _asset) external view virtual returns
(uint256);
Parameters
Name Type Description
_asset address Address of the asset.
132Returns
Name Type Description
uint256 Balance of _asset in
uint256
the strategy.
checkAvailableBalance
Get the amount of a specific asset held in the strategy, excluding the interest and
any locked liquidity that is not available for instant withdrawal.
Curve: assuming balanced withdrawal.
function checkAvailableBalance(address _asset) external view virtual
returns (uint256);
Parameters
Name Type Description
_asset address Address of the asset.
Returns
Name Type Description
uint256 Available balance
uint256 inside the strategy for
_asset.
checkInterestEarned
AAVE: Get the interest earned on a specific asset. Curve: Get the total interest
earned.
133Curve: to avoid double-counting, _asset has to be of index ''entryTokenIndex''.
function checkInterestEarned(address _asset) external view virtual
returns (uint256);
Parameters
Name Type Description
_asset address Address of the asset.
Returns
Name Type Description
uint256 Amount of interest
uint256
earned.
checkRewardEarned
Get the amount of claimable reward.
function checkRewardEarned() external view virtual returns
(RewardData[] memory);
Returns
Name Type Description
struct array of type
RewardData[] RewardData (address token,
uint256 amount).
checkLPTokenBalance
134Get the total LP token balance for a asset.
function checkLPTokenBalance(address _asset) external view virtual
returns (uint256);
Parameters
Name Type Description
_asset address Address of the asset.
supportsCollateral
Check if an asset/collateral is supported.
function supportsCollateral(address _asset) external view virtual
returns (bool);
Parameters
Name Type Description
_asset address Address of the asset.
Returns
Name Type Description
bool Whether asset is
bool
supported.
updateSlippage
Change to a new depositSlippage & withdrawSlippage.
135function updateSlippage(uint16 _depositSlippage, uint16
_withdrawSlippage) public onlyOwner;
Parameters
Name Type Description
Slippage tolerance for
_depositSlippage uint16
allocation.
Slippage tolerance for
_withdrawSlippage uint16
withdrawal.
_initialize
Initialize the base properties of the strategy.
function _initialize(address _vault, uint16 _depositSlippage, uint16
_withdrawSlippage) internal;
Parameters
Name Type Description
_vault address Address of the USDs Vault.
Allowed max slippage for
_depositSlippage uint16
Deposit.
Allowed max slippage for
_withdrawSlippage uint16
withdraw.
_setPTokenAddress
Provide support for asset by passing its pToken address. Add to internal mappings
and execute the platform specific, abstract method _abstractSetPToken .
136function _setPTokenAddress(address _asset, address _pToken) internal;
Parameters
Name Type Description
_asset address Address for the asset.
Address for the
_pToken address corresponding platform
token.
_removePTokenAddress
Remove a supported asset by passing its index. This method can only be called by
the system owner.
function _removePTokenAddress(uint256 _assetIndex) internal returns
(address asset);
Parameters
Name Type Description
Index of the asset to be
_assetIndex uint256
removed.
Returns
Name Type Description
asset address address which is removed.
_splitAndSendReward
137Splits and sends the accumulated rewards to harvestor and yield receiver.
Sends the amount to harvestor as per harvestIncentiveRate and sends the rest to
yield receiver.
function _splitAndSendReward(address _token, address _yieldReceiver,
address _harvestor, uint256 _amount)
internal
returns (uint256);
Parameters
Name Type Description
Address of the reward
_token address
token.
Address of the yield
_yieldReceiver address
receiver.
_harvestor address Address of the harvestor.
_amount uint256 to be split and sent.
Returns
Name Type Description
uint256 Harvested amount
uint256
sent to yield receiver.
_abstractSetPToken
Call the necessary approvals for the underlying strategy.
function _abstractSetPToken(address _asset, address _pToken) internal
virtual;
138Parameters
Name Type Description
_asset address Address of the asset.
Address of the
_pToken address corresponding receipt
token.
Events
VaultUpdated
event VaultUpdated(address newVaultAddr);
YieldReceiverUpdated
event YieldReceiverUpdated(address newYieldReceiver);
PTokenAdded
event PTokenAdded(address indexed asset, address pToken);
PTokenRemoved
event PTokenRemoved(address indexed asset, address pToken);
Deposit
139event Deposit(address indexed asset, uint256 amount);
Withdrawal
event Withdrawal(address indexed asset, uint256 amount);
SlippageUpdated
event SlippageUpdated(uint16 depositSlippage, uint16 withdrawSlippage);
HarvestIncentiveCollected
event HarvestIncentiveCollected(address indexed token, address indexed
harvestor, uint256 amount);
HarvestIncentiveRateUpdated
event HarvestIncentiveRateUpdated(uint16 newRate);
InterestCollected
event InterestCollected(address indexed asset, address indexed
recipient, uint256 amount);
RewardTokenCollected
event RewardTokenCollected(address indexed rwdToken, address indexed
recipient, uint256 amount);
140Errors
CallerNotVault
error CallerNotVault(address caller);
CallerNotVaultOrOwner
error CallerNotVaultOrOwner(address caller);
PTokenAlreadySet
error PTokenAlreadySet(address collateral, address pToken);
InvalidIndex
error InvalidIndex();
CollateralNotSupported
error CollateralNotSupported(address asset);
InvalidAssetLpPair
error InvalidAssetLpPair(address asset, address lpToken);
141CollateralAllocated
error CollateralAllocated(address asset);
Structs
RewardData
struct RewardData {
address token;
uint256 amount;
}
142Deployed contracts
Name Explorer link
https://arbiscan.io/address/0xD74f5255D55
USDs
7944cf7Dd0E45FF521520002D5748
https://arbiscan.io/address/0x6Bbc476Ee3
Vault
5CBA9e9c3A59fc5b10d7a0BC6f74Ca
https://arbiscan.io/address/0xdA423BFa1E1
CollateralManager
96598190deEfbAFC28aDb36FaeDF0
https://arbiscan.io/address/0xd122840Fa5b
FeeCalculator
48B2ddB723cCC5928f88dcb558AFC
https://arbiscan.io/address/0x14D99412dAB
MasterPrice Oracle
1878dC01Fe7a1664cdE85896e8E50
https://arbiscan.io/address/0xFbc0d3cA777
SPABuyback
722d234FE01dba94DeDeDb277AFe3
https://arbiscan.io/address/0xfD14C8ef099
YieldReserve
3fd9409f7820BA8BA80370529d861
https://arbiscan.io/address/0xd50193e8fFb
Dripper
00beA274bD2b11d0a7Ea08dA044c1
https://arbiscan.io/address/0x297331A0155
RebaseManager
B1e30bBFA85CF3609eC0fF037BEEC
https://arbiscan.io/address/0x974993ee8df
AaveStrategy
7f5c4f3f9aa4eb5b4534f359f3388
https://arbiscan.io/address/0xb9c9100720d
StargateStrategy
8c6e35eb8dd0f9c1abef320daa136
https://arbiscan.io/address/0xBCeb486257
CompoundStrategy
71E35420076f79Ec6921E783a82442
143Buyback Contract
Buyback contract is designed to decentralize the SPA buyback process while
adding a boosting mechanism to continuously grow USDs TVL with protocol
revenue (Yield + Fee). This contract allows users to directly purchase USDs with
SPA. The available-for-purchase USDs comes from two sources - USDs
mint/redeem fees and USDs yield. The contract distributes 70% of the yield to
USDs holders and the rest 30% goes to SPA buyback and burn.
1. Protocol Yield - The yield is collected in multiple tokens through pool fee
(USDC, etc.) and farm rewards. This yield is converted to USDs. A portion is
sent to buyback contract and remaining to auto-yield reserve.
• Swap yield tokens to eligible collateral tokens (for minting USDs) using
DEXes based on the path of lowest slippage
• Mint USDs using the collateral tokens
• Deposit 30% of the yield (USDs received in previous step) that will be used
to buyback SPA into the buyback contract and the rest of the USDs is
transferred back to the USDs vaultʼs auto-yield reserve. Yield share
percentage is set to 30% currently and can be changed in future through
governance.
2. Protocol Fee - Mint and redemption fee from the USDs protocol is collected in
the form of USDs. This component is directly deposited to the Buyback
contract.
3. Buyback SPA - Any users or external wallet can view the USDs balance in the
Buyback contract and sell their SPA tokens for USDs.
4. Burn 2.0 - 30% of the SPA received in step 3 is burnt and remaining 70% is
sent to USDs holding wallets. The burn percentage can be changed through
governance.
Example flow of the buyback contract:
Sperax protocol deposits USDC.e to Stargate strategy (Stargate-LP USDC.e) and
stakes LP tokens. Protocol earns STG token.
1441. Sell STG tokens for USDC.e
2. Mint USDs from USDC.e
3. Transfer USDs to the Buyback contract
4. Deposit USDs Mint and Redemption Fees directly into the Buyback contract as
and when they are collected
5. Users can view the amount of USDs left in the contract.
6. USDs is bought by the users using their SPA and contract receives SPA against
the USDs.
Technical Specification
Buyback Architecture
View Functions
1451. getSPAReqdForUSDs - Input the target USDs amount to receive and get an
estimate of how many SPA tokens are needed.
a. Function: getSPAReqdForUSDs(uint256 _usdsAmount) external view returns
(uint256)
b. Input : _usdsAmount is the amount of USDs to purchase
c. Output: Estimated amount of SPA required
d. Example: Suppose getSPAReqdForUSDs(1e18) returns 100*1e18. It means
the contract estimates that one will need to speed 100 SPA to purchase 1
USDs at this moment.
2. getUsdsOutForSpa - Input the amount of SPA to be sold and get an estimate of
how many USDs can be purchased.
a. Function: getUsdsOutForSpa(uint256 _spaIn) external view returns (uint256)
b. Input : _spaIn is the amount of SPA to be sold
c. Output: Estimated amount of USDs to be received
d. Example: Suppose getSPAReqdForUSDs(100*1e18) returns 1e18. It means
the contract estimates that one will receive 1 USDs after spending 100 SPA.
User End Write Functions
1461. buyUSDs - Purchase USDs with SPA
a. Function: buyUSDs(uint256 _spaIn, uint256 _minUSDsOut) external
b. Input : _spaIn is the amount of SPA to be sold _minUSDsOut is the minimum
amount of USDs to be received
c. Output : The contract will extract _spaIn amount of SPA from the wallet
executing the transaction, exchange it to USDs, and send USDs back to the
wallet
d. Example : Suppose wallet A triggers buyUSDs(100*1e18, 1e18) (after he has
already approved the Buyback contract to spend its SPA). The contract will
extract 100 SPA from A, exchange it to USDs, and send USDs back to A.
2. buyUSDs - Purchase USDs with SPA (arbitrary USDs receipt)
a. Function: buyUSDs(address _receiver, uint256 _spaIn, uint256
_minUSDsOut) external
b. Input : _receiver is the receiver of the purchased USDs _spaIn is the amount
of SPA to be sold _minUSDsOut is the minimum amount of USDs to be
received
c. Output : The contract will extract _spaIn amount of SPA from the wallet
executing the transaction, exchange it to USDs, and send USDs to the
_receiver wallet
d. Example : Suppose wallet A triggers buyUSDs(B, 100*1e18, 1e18) (after he
has already approved the Buyback contract to spend its SPA). The contract
will extract 100 SPA from A, exchange it to USDs, and send USDs to B.
147Staking Protocol
Stake SPA to earn rewards from fees and yield
SPA holders can stake SPA tokens and receive veSPA tokens which are non-
transferable. veSPA balance is proportional to the lockup period, meaning if user
locks up for higher duration they receive proportionally more veSPA tokens. veSPA
balance determines the share of staking reward and voting power
1. Staking rewards: Rewards will be distributed proportionally to the user''s veSPA
balance. Users who lock SPA for longer periods are eligible for proportionally
more rewards (accrued on a weekly base) than users who locked the same
amount of SPA for shorter periods. Rewards are accumulated through:
• As per SIP-66 , the emission of xSPA for veSPA holders has been
increased to 420,000 xSPA tokens per week from the treasury to account
for the decrement in APR due to cutting the allocation from bought-back
SPA.
• Fee Rewards - 100% of the Fee income from USDs mints and redemptions.
The yield and fee income are originally generated in USDs. It is then swapped
for SPA tokens before distribution. This makes the reward claiming process
simple for stakers and maintains a constant buying pressure on the SPA token.
2. Voting power: once governance protocol is launched, voting power will depend
on users'' veSPA balance. Users who are committed to longer lockups will own
more votes, whatever they are voting for.
veSPA balance at the moment of staking will not stay the same all the time - it will
decay/reduce linearly. User''s rewards per week and voting power will also
decrease over time, together with the veSPA balance. Stakers can increase their
veSPA balance and thereby their staking rewards and voting power by either
extending the locking period or locking more SPA or re-staking SPA rewards.
At the end of the lockup period, veSPA balance will reduce depending on the pre-
selected withdrawal option:
1481. For Auto-cooldown option - to zero.
2. For the "Staying Staked" option (manual cooldown) - to [0.01917*staked SPA
tokens]. Please note that for new stakers the "Staying Staked option" is not
available.
To stake SPA through our dApp, checkout Staking SPA
149Locking SPA
veSPA is SPA locked with the "vote escrow" mechanism (ve-). This is the
mechanism of locking tokens for relatively long pre-defined time periods. On
staking SPA, the staker receives non-tranferrable tokens veSPA.
The veSPA balance will depend on the amount of SPA tokens locked and the user''s
chosen lockup period. Stakers can lock their tokens for a maximum of 4 years or a
minimum of 7 days. A higher lockup period means a higher veSPA balance for the
same amount of SPA staked.
veSPA value = SPA tokens staked * (Lockup Period in days / 365)
Below table shows how veSPA value will be determined
Lock-up Period SPA Tokens staked veSPA value
4 year 1 4
3 year 1 3
2 year 1 2
1 year 1 1
6 months 1 0.5
veSPA balance will decay linearly with time. At the end of lockup period, veSPA
balance will reduce to 0 or (0.01917*staked SPA tokens) based on the withdrawal
method user chooses while staking the SPA. Two withdrawal methods were
available to the early stakers, they will work till the end of their lockup period. For all
the new stakers method 1 (Auto-cooldown) stays the only available method.
1501. Auto-cooldown - veSPA balance reduces to 0 at the end of lockup period and
staker is able to withdraw locked SPA balance immediately after expiry date
2. Stay Staked at Residual Value - Stakers can opt to remain staked at the end of
the unlock date at a residual veSPA balance (0.01917*staked SPA tokens). They
can initiate cooldown when 7 days are left in the staking period or anytime after.
After the cooldown period of 7 days is over, they can withdraw the staked SPA
tokens. Check outStay Staked at Residual Valuefor more details.
Example of veSPA balance decay over time
User stakes 1000 SPA for 4 years
• At Day 0 user will have 4000 veSPA
• At day 365 user will have 3000 veSPA
• At day 365*2 user will have 2000 veSPA
• At day 365*3 user will have 1000 veSPA
• At Day 365*4 user will have 0 veSPA or 19.17 veSPA depending on
withdrawal method chosen
Some important points to consider before locking your SPA tokens:
1. Staking is an irreversible process, once locked the tokens cannot be unlocked
before the unlock date. Users cannot prepone the expiry date or reduce the
amount of locked SPA.
2. Due to precision issues it may not always be possible to choose an exact unlock
date. Users can select the lockup period but the exact unlock date is rounded
down to nearest Thursday UTC. Stakers will be able to choose these eligible
unlock dates in our dapp .
3. Users rewards per week and voting power will also decrease over time,
together with the veSPA balance. The users who are committed to long-term
staking can increase their rewards per week and voting power by:
• Extending the locking period
• Locking more SPA
• Claiming SPA staking rewards and restaking them into veSPA
(compounding)
151Extension of Lockup
Stakers can extend their lockup such that the lockup expiry date is <= 4 years from
the current date. At no point, lockup period can be more than 4 years.
The lockup period cannot be extended in the following scenarios:
1. When a stakerʼs lockup period has expired and they only have residual veSPA
balance
2. When a staker has already initiated cooldown period Example: If a user has 100
veSPA tokens which expire in 3 years, the user can increase the lockup by a
maximum of 1 year.
Increasing Staked Balance
Users can also increase their veSPA balance by staking any additional amount of
SPA tokens for the same lockup as their existing veSPA balance.
Example: if a user has 100 veSPA tokens which are expiring in 34 days, they can
stake any amount of additional SPA for 34 days. The increase in veSPA balance will
be calculated based on the same lockup period as the previously staked tokens.
152Withdrawing SPA
Users who initiate staking will get the auto-cooldown feature. Users who initiated
staking in the past were able select one of two cooldown options that will stay
active till the end of the staking period:
1. Auto-cooldown: the protocol will automatically initiate a cooldown 7 days
before expiry. In this case, the assets will be available immediately after the lock
period, but they will not bring any more rewards post-expiry.
2. Stay staked at residual value (or manual cooldown): After the lock expires, the
assets remain locked with the residual value of [0.01917*staked SPA tokens],
and will continue to bring rewards to the user. If one day the user decides to
take their assets out - they need to initiate the cooldown manually. After a one-
week cooldown, the assets will become available.
Withdrawing from the protocol does not automatically claim all the SPA rewards.
The users would be required to claim the rewards separately. In case the staking
rewards are not distributed till that point of time, users can still claim the rewards
and fee earnings accrued to them after they have withdrawn their staked SPA
tokens.
Auto-Cooldown
When new users start staking, the auto-cooldown feature is applied. Auto-
cooldown means that the veSPA balance decays to zero linearly and the stakers are
able to withdraw their staked SPA balance at the end of their lockup period.
Stay Staked at Residual Value
Currently, this option is not available. If a user has chosen this option while staking
in the past, their final veSPA balance was set at the residual veSPA balance. They
would continue to earn all the staking rewards at this veSPA balance for as long as
the protocol keeps distributing rewards. The residual veSPA balance is equivalent to
the veSPA balance of a staking position that unlocks in 7 days.
153Residual veSPA balance = (7/365)*Staked SPA tokens = 0.01917*Staked SPA
tokens
Initiate Cooldown
Stakers can initiate cooldown when 7 days are left in lockup expiry or anytime after
that. When the stakerʼs veSPA balance is set to the residual veSPA balance or when
only 7 days are left in the lockup period, the staker can initiate a transaction to start
a cooldown period. When the cooldown period is initiated the lockup expiry date is
updated to a new date which would be 7 days from the date on which the cooldown
was initiated.
Stakerʼs veSPA balance decays from the residual veSPA balance to zero during the
cooldown period. At the end of the cooldown period, the staker can withdraw the
SPA they had deposited. Stakers receive rewards during the cooldown period in
proportion to their veSPA balance.
At the end of the cooldown period, the veSPA balance remains zero and the user
doesnʼt get any rewards. The users can unstake at any point in time after the
cooldown period has ended.
154Example
On Day 0, user staked 1000 SPA for 365 days. Their veSPA balance will
be 1000 veSPA
On the 358th day the userʼs veSPA balance will be ~ 19.17
(1000x(7/365)). At this point of time, the user can initiate a cooldown
period.
On the 365th day, if the user hasnʼt yet initiated a cooldown period the
userʼs veSPA balance will be ~ 19.17. At this point the lockup has expired
and the user can initiate a cooldown period.
On the 400th day, if the user hasnʼt yet initiated a cooldown period the
userʼs veSPA balance will be ~ 19.17 and they can initiate a cooldown
period. After the end of the cooldown period the user can unstake the
1000 SPA tokens.
Letʼs say on the 400th day the user initiates a cooldown, they would be
able to withdraw the tokens on the 407th day.
155Staking Rewards
veSPA holders or stakers get SPA token rewards in proportion to their veSPA
balance. Users have an option to stake their SPA rewards directly into the staking
protocol instead of claiming, thereby compounding their veSPA balances.
veSPA holders or stakers will receive 2 kinds of rewards. Both kinds of rewards are
distributed with the same mechanism and in the form of SPA tokens:
1. 100% of the USDs Fees generated by USDs protocol.
2. Incentives sponsored by Treasury to bootstrap the Staking protocol - This
emission has been set to 0 through governance.
1. USDs Fee Rewards
The USDs protocol fees collected will be distributed across all veSPA holders on a
pro rata basis. With the fees collected by the USDs protocol the Staking protocol
will purchase SPA tokens from the market and distribute the SPA tokens amongst all
veSPA holders.
2. Incentive Rewards
A separate SPA reward budget was set aside from Treasury to bootstrap staking
protocol. Incentive rewards for staking were initially set at a fixed daily distribution
number of 54.794 K SPA. This number was later changed through governance and
set to 0 to reduce SPA inflation, and xSPA rewards have been increased to 420,000
per week as per SIP-66 .
The yield and fee income are swapped for SPA tokens before distribution. The SPA
that is bought back from the open markets using 30% (SIP-66) of the auto-yield
and 100% of the fees is stored in:
0xA61a0719e9714c95345e89a2f1C83Fae6f5745ef (Arbitrum One).
Distribution of Staking Rewards
156Staking reward distribution happens on a weekly basis. New weeks start on
Thursdays at 00:00:00 UTC. Rewards are distributed at the end of each week.
‘Stake-Reward-Claim-Repeatʼ is a four-step cycle:
1. Stake SPA - In Week 1, you stake SPA and obtain veSPA. Reward accumulation
starts from Week 2.
2. Reward distribution for Week 2 occurs at the end of the Week 2
• Total staking reward available for distribution in Week 2 is based on USDs
fee income that was collected, the yield that was generated by the USDs
protocol and the daily distribution rate of the SPA staking incentives for
Week 2
• Userʼs share of staking rewards for Week 2 is calculated based on their
veSPA balance at the starting of Week 2 relative to the total veSPA supply at
the starting of week 2
3. Rewards for Week 2 can be claimed by users from Week 3 onwards. Users can
also stake their SPA rewards and increase their veSPA balance and future
rewards.
4. Step 2 and 3 repeat every week till expiry
• Reward distribution for Week X occurs at the end of the Week X
• Rewards for Week X can be claimed by users from Week X+1 onwards.
Calculation of Staking Rewards
Total SPA Rewards for the week = Staking Incentive Rewards + USDs Fees
Rewards + USDs Yield Share Rewards
where Staking Incentive Rewards = Daily Incentive Rewards*7
USDs Fees Rewards = USDs Fee earned*USDs Price / SPA price
USDs Yield Share Rewards = 50%*USDs yield earned in the week * USDs
price/ SPA price
157The above formula is an assumption, actual SPA rewards from USDs fees and yield
would differ based on the asset prices in the open market
Total SPA Rewards for the week = R
Total veSPA balance of the protocol at the start of the week = V
Userʼs veSPA balance at the start of the week = v
Userʼs SPA staked at the start of the week = s
User''s staking duration in years = d = v/s
Weekly Reward Earned by a user = r = (R/V)*v
Staking APR
= (Weekly Reward Earned by a user /SPA staked by user)*(365/7)*100%
= (r/s)*(365/7)*100%
= (R/V)*(v/s)*(365/7)*100%
= (R/V)*d *(365/7)*100%
Staking APY
= [{1+ (Weekly Reward Earned by a user /SPA staked by user)}^(365/7) -1] *
100%
= [{1+ (r/s)}^(365/7) -1] * 100%
= [{1+ (R/V)*(v/s)}^(365/7) -1] * 100%
= [{1+ (R/V)*d}^(365/7) -1] * 100%
Calculation of APY assumes that the weekly rewards are re-staked in the protocol.
158Sperax Farms Protocol
Works on Arbitrum Uniswap V2, Uniswap V3, Camelot V2, Camelot V3 and
Balancer V2.
Sperax Farms protocol is a protocol for DAOs to launch and manage decentralized
exchange liquidity - without needing to know how to code. Sperax Farms give users
the power to launch incentivized liquidity pools on Uniswap V3 and Camelot V2.
Future versions will support custom liquidity shapes on major DEXs such as
Balancer, Sushiswap or anything veSPA holders prefer. Sperax Farms is launched
on Arbitrum and will be expanded to Optimism, Polygon and Ethereum soon.
Additional blockchains will be added in future versions.
Sperax Farms automates the fundamental aspects of launching and managing
decentralized exchange liquidity for the DAOs native token:
• Engineering support to launch and manage the farm - The Audited Farm
Factory contract will generate the pool and farm contracts for the Sperax Farms
user.
• Marketing support to make the community aware of the new farm - Protocols
that launch their farm through Sperax Farms benefit from being whitelisted on
the Sperax Farms active farms dashboard. This exclusive list features all of the
farms that are actively distributing rewards that were deployed with Sperax
Farms. Farmers will regularly look to this dashboard for new projects and
become users of these protocols.
On Arbitrum, Uniswap has less liquidity than Balancer and Sushiswap. This is
because Sushiswap uses the simple Uniswap V2, x*y=k approach. This is simple
because all LP tokens are the same but penalizes the LP because they are forced to
provide liquidity from 0 to infinity. Incentivized liquidity pools on Uniswap V3 lets
DAOs benefit from concentrated liquidity - the same TVL offers less slippage
compared to diluted liquidity. This directly translates to a lower emissions budget
for other protocols and much more fees for LPs.
159To launch a Uniswap V3 or Camelot V3 farm, DAOs are currently expected to write
complicated V3 farm contracts, get the contracts audited, incentivize LP deposits
with only their native token, then promote this new pool. With Sperax Farms, DAOs
can launch these farms without knowing how to code and get marketing and
technical support from Sperax.
DAOs can also launch their farms on Camelot V2, their farms can get rewards both
in SPA and Camelot tokens (xGRAIL/GRAIL) that decrease DAO''s token spend and
decrease sale pressure on it.
Sperax Farms V2
In Sperax Farms Protocol V2, we have focused more on the protocol''s multichain
vision and to support that, we have discontinued incentives on pairing with SPA and
USDs as these tokens are not available on all the chains. Now the fee params are
configurable in the farm registry contract. More details are shared in the next
section.
160How does Sperax Farms work?
Overview
Sperax Farms is a protocol that enables DAOs to launch and manage decentralized
exchange (DEX) liquidity pools and farms on platforms like Uniswap V2, Uniswap
V3, Camelot V2, Camelot V3 and Balancer V2. The protocol simplifies the process
of incentivizing liquidity for the DAOs'' native tokens without requiring coding
expertise. Sperax Farms automates the creation, management, and reward
distribution for liquidity pools, providing support in engineering, marketing, and
financial incentives.
Launching Farm on Sperax Farms (For Farm Admins)
Steps to Launch a Farm
1611. Approve Fees Spend: Users must first approve the spending of the fee token
for the farm creation fee. The fee is 100 USDs on Arbitrum. On any chain not
supporting USDs, it can be any other stable coin supported on that chain.
2. Input Pool and Farm Parameters: Users must provide necessary parameters for
the pool and farm.
3. Execute Transaction: A transaction is executed to create the farm and pay the
farm creation fee.
• Reversion Conditions:
◦ If the pool does not exist, the transaction reverts.
◦ If the user does not have enough fee tokens to pay the fee, the
transaction reverts.
Pool Parameters
1. Token address
• Token A
• Token B
2. Fee Tier (for Uniswap V3 Pools)
3. Liquidity Parameter L - An LP token parameter which is set based on the userʼs
liquidity inside the pool. Updating this parameter will allow the farm users to add
or remove liquidity. When the LP token is updated, the LP token should start
receiving rewards based on the new L parameter. This will allow users to add or
remove liquidity without having to unstake their entire LP position.
4. Active Liquidity Time Check - (a Boolean value of true or false which
determines whether the position is in the current price range) - To check if the
position is in the current price range, as only that liquidity will be rewarded
which is in range. However, users will be allowed to remove any locked liquidity
during the period when the liquidity is not in the current price range.
Base Farm Parameters
1621. Farm admin: It is the address that will have admin control on the farm. It can be
the same as the deployerʼs address or any other desired address which will be
used to manage the farm.
2. Price range for the LP
3. Reward tokens
• Token addresses
• Token address managers: Each token will have its own token manager.
• Reward tokens have to be added at the time of farm creation and can''t be
added or removed after the farm is created. Maximum 4 reward tokens are
possible for a farm.
• For reward tokens emitting fixed APR, Token manager will be the Rewarder
contract deployed through the Rewarder factory.
4. Cooldown Period for Locked Liquidity - It is the number of days users have to
wait after initiating cooldown before they can unstake from a locked position.
Only whole numbers are allowed for this parameter.
• If Cooldown Period = 0, then the farm only allows creation of unlocked
positions. Unlocked positions can be unstaked anytime.
• If Cooldown Period > =1, the farm will allow creation of both locked and
unlocked positions. For unstaking a locked position, users have to initiate
cooldown and wait for cooldown period before unstaking.
5. Start date time stamp - Reward emission starts from this date. This date can be
changed by the farm admin using admin functions (updateFarmStartTime).
However, date change is not allowed after the farm starts.
• The farms start accepting liquidity immediately after the creation of the farm
contract. However, the reward accrual starts from the farm start date time
stamp.
6. Annual Percentage Return APR - The farm admin can set a fixed APR which
will guarantee a reward to the LPs based on the current price of the reward
tokens. The farm admin will also have to choose one or more base tokens out of
the tokens present in the farm for calculation of the daily number of reward
tokens emitted.
163• Letʼs say APR set = APR%.
• Total value of Base Tokens in USD = Σ (Number of Base tokens in farm x
USD value of Base Token)
• No. of Reward tokens per day = [(APR x Total value of Base Tokens in USD)
/ (100 x 365)] / (Price of 1 reward token based on the oracle)]
7. Maximum number of Rewards Tokens Emitted Per Day - The farm admin
would be able to add the maximum number of reward tokens per day that the
farm can distribute, to prevent any deficiency in the farmʼs reward tokens in
cases when many LPs deposit on the same day since then all the withdrawals
may also happen on the same day.
• It will override the reward tokens value calculated using the formula
mentioned above in point no 6. The rewards emitted per day will be the
minimum of the amount calculated above or the max reward rate set by the
reward manager.
Expirable Farm Parameters
This is a new feature which has all the above parameters along with the expiry date
feature:
1641. Expiry Date - Farms will have an expiry date associated with them. Users can
specify the expiry date while creating the farms. The initial launch fee of 100
USDs (or some other stable coin if USDs is not present on that chain) will add
100 days to the farm expiry. After that users have the option to extend the farm
expiry date. Post that users will have to extend the farm in the multiple of 1
USDs/day with minimum of 100 days at a time and a maximum of 300 days.
• Farm admins will not be able to update any farm parameters once the farm
has expired. However, they can remove any unclaimed reward tokens from
the contract.
• Farm users can still claim any accrued rewards from the farm or remove
liquidity from them once that farm has expired.
• Farms that have expired will be available on the Dashboard for removing
liquidity up to 30 days beyond expiry. After that the farms will only appear
on the expired farm list and admins cannot make the expired farms active
again. Users or admins will be able to apply all actions through the smart
contract.
• Expired farms shall be removed from the Sperax Gauge and would not be
eligible for SPA rewards.
• Farm does not accrue rewards after the expiration so users can call
updateFarmRewardData function on the farm to accrue rewards before
farmEndTime to avoid any loss of rewards.
Fee
• Sperax Farms will charge a flat $100 fee to launch the farm, which will add 100
days to the expiry date set while the creation of the farm.
• After that users have the option to extend the farm expiry date. They can do so
in the multiple of 1 USDs/day with minimum of 100 days at a time and a
maximum of 300 days. The fees can be paid in either USDs or in any other
stable coin provided USDs is not present in that chain.
• The fee collected belongs to the SPA stakers and can be transferred directly to
the wallet address where all Sperax protocol fees are collected. Fee amount can
be changed in future through governance.
• The Fee details can be fetched from getFeeParams function on the Farm
registry contract.
165Farm Management
No one can make changes to the farm contract once deployed. Farm admins can
do the following:
1. Transfer farm ownership to another address
2. Change start date of the farm - Farm will emit rewards from this date. The date
can be changed after farm creation. However, date change is not allowed after
the farm starts.
3. Update cooldown period of the locked positions
4. Pause or Unpause the farm
• Pause the farm - All reward distributions are paused, LPs do not earn any
rewards during this period. Withdrawals are allowed (including lockup LPs)
and users can also claim previously accrued rewards. Admin/managers can
make changes to the distribution rates and the other parameters when the
farm is paused.
• Unpause the farm - Resume the reward distribution. The reward distribution
rate remains the same as set by the reward managers.
5. Update Expiry Date of the Farm by paying Subscription Fees
• Farm admins can choose and update the expiry date of farms as mentioned
in the Farm Parameters.
6. Close the farm - The farms can be closed before the expiry date and will
automatically get closed once the expiry date is reached. Once the farm is
closed, all liquidity providers including lockup users can now unstake their
liquidity pool tokens and claim the accrued rewards from the farm.
Reward Management
Each reward token will be assigned a reward token manager. Farm admin cannot
update the reward token manager once the farms are deployed. Reward token
managers can do the following:
1661. Add reward token balance
2. Update reward distribution rate per second for each token. Only future
distribution rates can be affected through this. Reward distribution can be
paused by setting the rate to 0. These actions can be done:
a. For all liquidity providers.
b. For lockup liquidity providers (If cooldown period is greater than 0)
3. Changing the maximum number of token rewards per day - Farm admins can
increase or decrease the reward tokens limit in the fixed APR model as per their
choice.
4. Withdraw reward tokens (Any rewards already accrued to LPs cannot be
removed).
5. Transfer reward token management to another address.
Setting up a Farm Position (For Liquidity Providers/Retail Users)
Adding a Farm Position:
1. Select the farm: LPs need to choose the required farm from the whitelisted
farms which are present on the dashboard, based on their choice of tokens and
price range.
2. Enter the number of farm tokens: Post that, they can enter the number of one
of the tokens for the position, the other tokens are automatically calculated.
3. Execute Transaction: The user then needs to approve the wallet transactions
for the spending of tokens and creation of a farm position.
• Reversion Conditions:
◦ If the user does not have the required tokens in their wallet, the
transaction reverts.
4. LP Token: On successful execution of the transaction, the Liquidity Provider will
receive the LP token(s) in their wallet.
5. Depositing the LP Token: Users then need to deposit the lp tokens inside the
farm to create a position.
Updating Liquidity in the LP Tokens:
1671. Updating the Liquidity Balance - This will allow LPs to add to or remove
liquidity from the LP Token without having to unstake their entire LP position.
When the LP token is updated, the LP token should start receiving rewards
based on the new L parameter.
Rewards Emission:
1. Fixed APR Rewards - The LPs will receive fixed APR rewards (this fixed APR will
be set by the farm admin) generated from their invested farms. Each farm will
have some base reward tokens which will be the base for calculating the
number of reward tokens to be emitted per day. The LP will receive the reward
tokens (selected by the farm admin) in their wallet, which will be calculated as:
• Letʼs say APR set = APR%.
• Total value of Base Tokens in USD = Σ (Number of Base tokens in farm x
USD value of Base Token)
• No. of Reward tokens per day = [(APR x Total value of Base Tokens in USD)
/ (100 x 365)] / (Price of 1 reward token based on the oracle)]
2. Maximum number of Rewards Tokens Emitted Per Day - However, the number
of reward tokens to be emitted by the farm has been capped. The farm admin
would be able to add the maximum number of reward tokens per day that the
farm can distribute, to prevent any deficiency in the farmʼs reward tokens in
cases when many LPs deposit on the same day since then all the withdrawals
may also happen on the same day.
168Technical documents
This technical document is about the upgrade to Sperax Farms v2. It details the
changes made to enhance the protocol''s multi chain vision, transparency, security,
and scalability. The document covers new features and functionalities.
Sperax Farms protocol farm admin and user interaction diagram
FIxed APR rewarder flow
169170Smart contracts
High level documentation of smart contracts
171E721 Farms
E721 farms include all the farms built for pools in which the liquidity provider has an
NFT (ERC721) position.
172E721Farm
Git Source
Inherits: Farm, IERC721Receiver
Author: Sperax Foundation.
This contract contains the core logic for E721 farms.
State Variables
nftContract
address public nftContract;
depositToTokenId
mapping(uint256 => uint256) public depositToTokenId;
Functions
onERC721Received
Function is called when user transfers the NFT to this farm.
173function onERC721Received(address, address _from, uint256 _tokenId,
bytes calldata _data)
external
override
returns (bytes4);
Parameters
Name Type Description
address
_from address The address of the owner.
NFT Id generated by other
_tokenId uint256 protocol (e.g. Camelot or
Uniswap).
The data should be the
_data bytes
lockup flag (bool).
Returns
Name Type Description
bytes4 The
bytes4 onERC721Received
selector.
withdraw
Function to withdraw a deposit from the farm.
function withdraw(uint256 _depositId) external override nonReentrant;
Parameters
174Name Type Description
The id of the deposit to be
_depositId uint256
withdrawn.
_getLiquidity
Function to get the liquidity. Must be defined by the farm.
This function should be overridden to add the respective logic.
function _getLiquidity(uint256 _tokenId) internal view virtual returns
(uint256);
Parameters
Name Type Description
_tokenId uint256 The nft tokenId.
Returns
Name Type Description
The liquidity of the nft
uint256
position.
Errors
UnauthorisedNFTContract
error UnauthorisedNFTContract();
175NoData
error NoData();
176Camelot V3
Pools in camelot V3 are very similar to Uniswap V3. When a liquidity provider
supplies assets to the pool, the LP receives an NFT position in return.
Next, you can see the specification for Camelot V3 farm and Camelot V3 farm
deployer.
177CamelotV3FarmDeployer
Git Source
Inherits: FarmDeployer
Author: Sperax Foundation.
This contract allows anyone to calculate fees, pay fees and create farms.
State Variables
CAMELOT_V3_FACTORY
address public immutable CAMELOT_V3_FACTORY;
NFPM
address public immutable NFPM;
CAMELOT_UTILS
address public immutable CAMELOT_UTILS;
CAMELOT_NFPM_UTILS
address public immutable CAMELOT_NFPM_UTILS;
178Functions
constructor
Constructor of the contract.
constructor(
address _farmRegistry,
string memory _farmId,
address _camelotV3Factory,
address _nfpm,
address _camelotUtils,
address _nfpmUtils
) FarmDeployer(_farmRegistry, _farmId);
Parameters
Name Type Description
Address of the Farm
_farmRegistry address
Registry.
_farmId string Id of the farm.
Address of CamelotV3
_camelotV3Factory address
factory.
Address of Camelot
_nfpm address NonfungiblePositionManage
r contract.
Address of CamelotUtils
_camelotUtils address
(Camelot helper) contract.
Address of Camelot
INonfungiblePositionManag
_nfpmUtils address erUtils
(NonfungiblePositionManag
er helper) contract.
179createFarm
Deploys a new CamelotV3 farm.
The caller of this function should approve feeAmount to this contract before calling
this function.
function createFarm(FarmData memory _data) external nonReentrant
returns (address);
Parameters
Name Type Description
_data FarmData Data for deployment.
Returns
Name Type Description
Address of the deployed
address
farm.
Structs
FarmData
struct FarmData {
address farmAdmin;
uint256 farmStartTime;
uint256 cooldownPeriod;
CamelotPoolData camelotPoolData;
RewardTokenData[] rewardData;
}
180CamelotV3Farm
Git Source
Inherits: E721Farm, OperableDeposit, ClaimableFee
Author: Sperax Foundation.
This contract is the implementation of the Camelot V3 farm.
State Variables
tickLowerAllowed
int24 public tickLowerAllowed;
tickUpperAllowed
int24 public tickUpperAllowed;
camelotPool
address public camelotPool;
camelotV3Factory
address public camelotV3Factory;
181camelotUtils
address public camelotUtils;
nfpmUtils
address public nfpmUtils;
MIN_TICK
int256 internal constant MIN_TICK = -887272;
MAX_TICK
int256 internal constant MAX_TICK = 887272;
Functions
initialize
Initializer function of this farm.
function initialize(InitializeInput calldata _input) external;
Parameters
182Name Type Description
A struct having all the input
_input InitializeInput
params.
increaseDeposit
Allow user to increase liquidity for a deposit.
function increaseDeposit(uint256 _depositId, uint256[2] calldata
_amounts, uint256[2] calldata _minAmounts)
external
nonReentrant;
Parameters
Name Type Description
The id of the deposit to be
_depositId uint256
increased.
Desired amount of tokens to
_amounts uint256[2]
be increased.
Minimum amount of tokens
_minAmounts uint256[2]
to be added as liquidity.
decreaseDeposit
Withdraw liquidity partially from an existing deposit.
function decreaseDeposit(uint256 _depositId, uint128
_liquidityToWithdraw, uint256[2] calldata _minAmounts)
external
nonReentrant;
Parameters
183Name Type Description
_depositId uint256 Deposit index for the user.
_liquidityToWithdraw uint128 Amount to be withdrawn.
Minimum amount of tokens
_minAmounts uint256[2]
to be received.
getTokenAmounts
Function to be called by Rewarder to get tokens and amounts associated with the
farm''s liquidity.
function getTokenAmounts() external view override returns (address[]
memory, uint256[] memory);
Returns
Name Type Description
tokens An array of token
address[]
addresses.
amounts An array of token
uint256[]
amounts.
_claimPoolFee
Claim pool fee implementation from ClaimableFee feature.
function _claimPoolFee(uint256 _depositId)
internal
override
returns (uint256 tokenId, uint256 amt0Recv, uint256 amt1Recv);
Parameters
184Name Type Description
Deposit ID of the deposit in
_depositId uint256
the farm.
_getLiquidity
Validate the position for the pool and get Liquidity.
The position must adhere to the price ranges.
Only allow specific pool token to be staked.
function _getLiquidity(uint256 _tokenId) internal view override returns
(uint256);
Parameters
Name Type Description
_tokenId uint256 The tokenId of the position.
Returns
Name Type Description
uint256 The liquidity of the position.
_validateTickRange
Validate the ticks (upper and lower).
Get the info of the required token.
Check if the token belongs to correct pool.
185Check if the token adheres to the tick range.
The ticks must be within the max range and must be multiple of tickSpacing.
function _validateTickRange(int24 _tickLower, int24 _tickUpper) private
view;
Parameters
Name Type Description
_tickLower int24 The lower tick of the range.
_tickUpper int24 The upper tick of the range.
Errors
InvalidCamelotPoolConfig
error InvalidCamelotPoolConfig();
IncorrectPoolToken
error IncorrectPoolToken();
IncorrectTickRange
error IncorrectTickRange();
InvalidTickRange
186error InvalidTickRange();
InvalidAmount
error InvalidAmount();
187Base contracts
These contracts are base for all the other contracts and they have the common
logic for functionalities like deposit, withdraw, createFarm, etc.
188Farm
Git Source
Inherits: FarmStorage, OwnableUpgradeable, ReentrancyGuardUpgradeable,
MulticallUpgradeable
Author: Sperax Foundation.
This contract contains the core logic for the Sperax farms.
Functions
constructor
constructor();
withdraw
Function to be called to withdraw deposit.
function withdraw(uint256 _depositId) external virtual;
Parameters
Name Type Description
_depositId uint256 The id of the deposit.
claimRewards
189A function to be called by the depositor to claim rewards.
function claimRewards(uint256 _depositId) external;
Parameters
Name Type Description
_depositId uint256 The id of the deposit.
initiateCooldown
Function to be called to initiate cooldown for a staked deposit.
_depositId is corresponding to the user''s deposit.
function initiateCooldown(uint256 _depositId) external nonReentrant;
Parameters
Name Type Description
The id of the deposit to be
_depositId uint256
locked.
addRewards
Add rewards to the farm.
function addRewards(address _rwdToken, uint256 _amount) external
nonReentrant;
Parameters
190Name Type Description
The reward token''s
_rwdToken address
address.
The amount of reward
_amount uint256
tokens to add.
updateCooldownPeriod
Update the cooldown period.
function updateCooldownPeriod(uint256 _newCooldownPeriod) external
onlyOwner;
Parameters
Name Type Description
The new cooldown period
_newCooldownPeriod uint256 (in days). E.g: 7 means 7
days.
farmPauseSwitch
Pause / UnPause the farm.
function farmPauseSwitch(bool _isPaused) external onlyOwner;
Parameters
Name Type Description
Desired state of the farm
_isPaused bool
(true to pause the farm).
191closeFarm
A function to explicitly close the farm.
Recovers remaining non accrued rewards.
function closeFarm() external onlyOwner nonReentrant;
recoverERC20
Recover erc20 tokens other than the reward tokens.
function recoverERC20(address _token) external onlyOwner nonReentrant;
Parameters
Name Type Description
Address of token to be
_token address
recovered.
recoverRewardFunds
Get the remaining reward balance out of the farm.
Function recovers minOf(_amount, rewardsLeft).
function recoverRewardFunds(address _rwdToken, uint256 _amount)
external nonReentrant;
Parameters
192Name Type Description
The reward token''s
_rwdToken address
address.
The amount of the reward
_amount uint256
tokens to be withdrawn.
setRewardRate
Function to update reward params for a fund.
function setRewardRate(address _rwdToken, uint128[] memory
_newRewardRates) external;
Parameters
Name Type Description
The reward token''s
_rwdToken address
address.
The new reward rate for the
_newRewardRates uint128[] fund (includes the
precision).
updateRewardData
Transfer the tokenManagerRole to other user.
Only the existing tokenManager for a reward can call this function.
function updateRewardData(address _rwdToken, address _newTknManager)
external;
Parameters
193Name Type Description
The reward token''s
_rwdToken address
address.
Address of the new token
_newTknManager address
manager.
computeRewards
Function to compute the total accrued rewards for a deposit for each subscription.
function computeRewards(address _account, uint256 _depositId)
external
view
virtual
returns (uint256[][] memory rewards);
Parameters
Name Type Description
_account address The user''s address.
_depositId uint256 The id of the deposit.
Returns
Name Type Description
The total accrued rewards
rewards uint256[][] for the deposit for each
subscription (uint256[][]).
getRewardFunds
Get the reward fund details.
194function getRewardFunds() external view returns (RewardFund[] memory);
Returns
Name Type Description
The available reward funds''
RewardFund[] details for all the reward
funds.
getRewardData
Get the reward details for specified reward token.
function getRewardData(address _rwdToken) external view returns
(RewardData memory);
Parameters
Name Type Description
The address of the reward
_rwdToken address
token.
Returns
Name Type Description
The available reward details
RewardData for the specified reward
token.
getDepositInfo
Get deposit info for a deposit id.
195function getDepositInfo(uint256 _depositId) external view returns
(Deposit memory);
Parameters
Name Type Description
_depositId uint256 The id of the deposit.
Returns
Name Type Description
Deposit The deposit info (Deposit).
getNumSubscriptions
Get number of subscriptions for an account.
function getNumSubscriptions(uint256 _depositId) external view returns
(uint256);
Parameters
Name Type Description
_depositId uint256 The deposit id.
Returns
Name Type Description
The number of
uint256 subscriptions for the
deposit.
196getSubscriptionInfo
Get subscription stats for a deposit.
function getSubscriptionInfo(uint256 _depositId, uint256
_subscriptionId) external view returns (Subscription memory);
Parameters
Name Type Description
_depositId uint256 The deposit id.
_subscriptionId uint256 The subscription''s id.
Returns
Name Type Description
The subscription info
Subscription
(Subscription).
getRewardRates
Get reward rates for a rewardToken.
function getRewardRates(address _rwdToken) external view returns
(uint256[] memory);
Parameters
Name Type Description
The reward token''s
_rwdToken address
address.
197Returns
Name Type Description
The reward rates for the
uint256[]
reward token (uint256[]).
getRewardFundInfo
Get farm reward fund info.
function getRewardFundInfo(uint8 _fundId) external view returns
(RewardFund memory);
Parameters
Name Type Description
_fundId uint8 The fund''s id.
Returns
Name Type Description
The reward fund info
RewardFund
(RewardFund).
getRewardTokens
Function to get the reward tokens added in the farm.
function getRewardTokens() external view returns (address[] memory);
Returns
198Name Type Description
The reward tokens added in
address[]
the farm.
getTokenAmounts
Function to be called by Rewarder to get tokens and amounts associated with the
farm''s liquidity.
This function should be overridden to add the respective logic.
function getTokenAmounts() external view virtual returns (address[]
memory, uint256[] memory);
Returns
Name Type Description
Tokens associated with the
address[]
farm''s pool.
Amounts associated with
uint256[]
the farm''s liquidity.
updateFarmRewardData
Function to update the FarmRewardData for all funds.
function updateFarmRewardData() public virtual;
claimRewardsTo
Claim rewards and send it to another account.
199Only the depositor can call this function.
function claimRewardsTo(address _account, uint256 _depositId) public
nonReentrant;
Parameters
Name Type Description
_account address To receive the rewards.
_depositId uint256 The id of the deposit.
updateFarmStartTime
Update the farm start time.
Can be updated only before the farm start. New start time should be in future.
function updateFarmStartTime(uint256 _newStartTime) public virtual
onlyOwner;
Parameters
Name Type Description
_newStartTime uint256 The new farm start time.
isFarmOpen
Returns if farm is open. Farm is open if it is not closed.
This function can be overridden to add any new/additional logic.
function isFarmOpen() public view virtual returns (bool);
200Returns
Name Type Description
bool bool True if farm is open.
isFarmActive
Returns if farm is active. Farm is active if it is not paused and not closed.
This function can be overridden to add any new/additional logic.
function isFarmActive() public view virtual returns (bool);
Returns
Name Type Description
bool bool True if farm is active.
getRewardBalance
Get the reward balance for specified reward token.
This function calculates the available reward balance by considering the accrued
rewards and the token supply.
function getRewardBalance(address _rwdToken) public view returns
(uint256);
Parameters
201Name Type Description
The address of the reward
_rwdToken address
token.
Returns
Name Type Description
The available reward
uint256 balance for the specified
reward token.
_recoverERC20
function _recoverERC20(address _token) internal virtual;
_deposit
Common logic for deposit in the farm.
function _deposit(address _account, bool _lockup, uint256 _liquidity)
internal returns (uint256);
Parameters
Name Type Description
_account address Address of the depositor.
Lockup option for the
_lockup bool
deposit.
Liquidity amount to be
_liquidity uint256
added to the pool.
202Returns
Name Type Description
uint256 The deposit id.
_initiateCooldown
Common logic for initiating cooldown.
function _initiateCooldown(uint256 _depositId) internal;
Parameters
Name Type Description
_depositId uint256 User''s deposit Id.
_withdraw
Common logic for withdraw.
function _withdraw(uint256 _depositId) internal;
Parameters
Name Type Description
_depositId uint256 User''s deposit id.
_updateAndClaimFarmRewards
Claim rewards for the user.
203NOTE: any function calling this private function should be marked as non-reentrant.
function _updateAndClaimFarmRewards(uint256 _depositId) internal;
Parameters
Name Type Description
_depositId uint256 The id of the deposit.
_updateAndClaimFarmRewardsTo
Claim rewards for the user and send it to another account.
NOTE: any function calling this private function should be marked as non-reentrant.
function _updateAndClaimFarmRewardsTo(uint256 _depositId, address
_receiver) internal;
Parameters
Name Type Description
_depositId uint256 The id of the deposit.
The receiver of the rewards
_receiver address (Could be different from
depositor)
_recoverRewardFunds
Get the remaining reward balance out of the farm.
Function recovers minOf(_amount, rewardsLeft).
204In case of partial withdraw of funds, the reward rate has to be set manually again.
function _recoverRewardFunds(address _rwdToken, uint256 _amount)
internal;
Parameters
Name Type Description
The reward token''s
_rwdToken address
address.
The amount of the reward
_amount uint256
token to be withdrawn.
_setRewardRate
Function to update reward params for a fund.
function _setRewardRate(address _rwdToken, uint128[] memory
_newRewardRates) internal;
Parameters
Name Type Description
The reward token''s
_rwdToken address
address.
The new reward rate for the
_newRewardRates uint128[] fund (includes the
precision).
_setupFarm
Function to setup the reward funds and initialize the farm global params during
construction.
205function _setupFarm(
string calldata _farmId,
uint256 _farmStartTime,
uint256 _cooldownPeriod,
RewardTokenData[] memory _rwdTokenData
) internal initializer;
Parameters
Name Type Description
ID of the farm. E.g:
_farmId string
Demeter_Camelot_V2 .
_farmStartTime uint256 - Farm start time.
- Cooldown period in days
_cooldownPeriod uint256 for locked deposits. E.g: 7
means 7 days.
- Reward data for each
_rwdTokenData RewardTokenData[]
reward token.
_addRewardData
Adds new reward token to the farm.
function _addRewardData(address _token, address _tknManager) internal;
Parameters
Name Type Description
Address of the reward
_token address
token to be added.
Address of the reward
_tknManager address
token Manager.
206_updateLastRewardAccrualTime
Update the last reward accrual time.
function _updateLastRewardAccrualTime() internal virtual;
_getAccRewards
Computes the accrued reward for a given fund id and time interval.
_alreadyAccRewardBal is useful when this function called from computeRewards
function. As computeReward is a view function and it doesn''t update the
accRewardBal in the rewardData .
function _getAccRewards(uint8 _rwdId, uint8 _fundId, uint256 _time,
uint256 _alreadyAccRewardBal)
internal
view
returns (uint256);
Parameters
Name Type Description
_rwdId uint8 Id of the reward token.
_fundId uint8 Id of the reward fund.
Time interval for the reward
_time uint256
computation.
Already accrued reward
_alreadyAccRewardBal uint256
balance.
Returns
207Name Type Description
accRewards Accrued
rewards for the given
uint256
_rwdId , _fundId and
_time .
_validateDeposit
Validate the deposit for account.
function _validateDeposit(address _account, uint256 _depositId)
internal view;
Parameters
Name Type Description
Address of the caller to be
_account address
checked against depositor.
_depositId uint256 Id of the deposit.
_validateNotRecentDeposit
A function to validate deposit ts to prevent flash loan vulnerabilities
Reverts when deposit made in the same transaction.
function _validateNotRecentDeposit(uint256 _depositTs) internal view;
Parameters
208Name Type Description
depositTs of user''s deposit.
_depositTs uint256 (It represents deposit ts or
increaseDeposit ts)
_validateFarmOpen
Validate if farm is open. Revert otherwise.
This function can be overridden to add any new/additional logic.
function _validateFarmOpen() internal view;
_validateFarmActive
Validate if farm is active. Revert otherwise. Farm is active if it is not paused and not
closed.
This function can be overridden to add any new/additional logic.
function _validateFarmActive() internal view;
_validateTokenManager
Validate the caller is the token Manager. Revert otherwise.
function _validateTokenManager(address _rwdToken) internal view;
Parameters
209Name Type Description
_rwdToken address Address of reward token.
_validateRewardToken
Validate the reward token is valid.
function _validateRewardToken(address _rwdToken) internal view;
Parameters
Name Type Description
_rwdToken address Address of reward token.
_getRewardAccrualTimeElapsed
Get the time elapsed since the last reward accrual.
function _getRewardAccrualTimeElapsed() internal view virtual returns
(uint256);
Returns
Name Type Description
time The time elapsed since
uint256
the last reward accrual.
_validateCooldownPeriod
An internal function to validate cooldown period.
210function _validateCooldownPeriod(uint256 _cooldownPeriod) internal
pure;
Parameters
Name Type Description
_cooldownPeriod uint256 Period to be validated.
_validateNonZeroAddr
Validate address.
function _validateNonZeroAddr(address _addr) internal pure;
Parameters
Name Type Description
_addr address Address to be validated.
_subscribeRewardFund
Add subscription to the reward fund for a deposit.
function _subscribeRewardFund(uint8 _fundId, uint256 _depositId,
uint256 _liquidity) private;
Parameters
211Name Type Description
_fundId uint8 The reward fund id.
The unique ID of the
_depositId uint256
deposit.
_liquidity uint256 The liquidity of the deposit.
_unsubscribeRewardFund
Unsubscribe a reward fund from a deposit.
The rewards claimed from the reward fund is persisted in the event.
function _unsubscribeRewardFund(uint8 _fundId, uint256 _depositId)
private;
Parameters
Name Type Description
_fundId uint8 The reward fund id.
The deposit id
_depositId uint256
corresponding to the user.
212FarmStorage
Git Source
Inherits: IFarm
Author: Sperax Foundation.
This contract contains the base storage variables for farms.
State Variables
COMMON_FUND_ID
uint8 public constant COMMON_FUND_ID = 0;
LOCKUP_FUND_ID
uint8 public constant LOCKUP_FUND_ID = 1;
PRECISION
uint256 public constant PRECISION = 1e18;
MAX_COOLDOWN_PERIOD
uint256 public constant MAX_COOLDOWN_PERIOD = 30;
213MAX_NUM_REWARDS
uint256 public constant MAX_NUM_REWARDS = 4;
farmId
string public farmId;
isPaused
bool internal isPaused;
isClosed
bool internal isClosed;
cooldownPeriod
uint256 public cooldownPeriod;
lastFundUpdateTime
uint256 public lastFundUpdateTime;
farmStartTime
214uint256 public farmStartTime;
totalDeposits
uint256 public totalDeposits;
rewardFunds
RewardFund[] internal rewardFunds;
rewardTokens
address[] internal rewardTokens;
rewardData
mapping(address => RewardData) internal rewardData;
deposits
mapping(uint256 => Deposit) internal deposits;
subscriptions
mapping(uint256 => Subscription[]) internal subscriptions;
215FarmRegistry
Git Source
Inherits: IFarmRegistry, OwnableUpgradeable
Author: Sperax Foundation.
This contract tracks fee details, privileged users, deployed farms and farm
deployers.
State Variables
farms
address[] internal farms;
deployerList
address[] internal deployerList;
feeReceiver
address public feeReceiver;
feeToken
address public feeToken;
216feeAmount
uint256 public feeAmount;
extensionFeePerDay
uint256 public extensionFeePerDay;
farmRegistered
mapping(address => bool) public farmRegistered;
deployerRegistered
mapping(address => bool) public deployerRegistered;
isPrivilegedUser
mapping(address => bool) public isPrivilegedUser;
Functions
constructor
constructor();
217initialize
constructor
function initialize(address _feeReceiver, address _feeToken, uint256
_feeAmount, uint256 _extensionFeePerDay)
external
initializer;
Parameters
Name Type Description
_feeReceiver address Receiver of the fees.
The fee token for farm
_feeToken address
creation.
The fee amount to be paid
_feeAmount uint256
by the creator.
_extensionFeePerDay uint256 Extension fee per day.
registerFarm
Register a farm created by registered Deployer.
Only registered deployer can register a farm.
function registerFarm(address _farm, address _creator) external;
Parameters
218Name Type Description
Address of the created farm
_farm address
contract
_creator address Address of the farm creator.
registerFarmDeployer
Register a new farm deployer.
Only owner can call this function.
function registerFarmDeployer(address _deployer) external onlyOwner;
Parameters
Name Type Description
Address of deployer to be
_deployer address
registered.
removeDeployer
Remove an existing deployer from registry.
Only owner can call this function.
function removeDeployer(uint16 _id) external onlyOwner;
Parameters
219Name Type Description
ID of the deployer to be
_id uint16
removed (0 index based).
updatePrivilege
Function to add/remove privileged User.
Only callable by the owner.
function updatePrivilege(address _user, bool _privilege) external
onlyOwner;
Parameters
Name Type Description
User Address for which
_user address
privilege is to be updated.
Privilege(bool) whether true
_privilege bool
or false.
getFarmDeployerList
Get list of registered deployer.
function getFarmDeployerList() external view returns (address[]
memory);
Returns
220Name Type Description
Returns array of registered
address[]
deployer addresses.
getFarmList
Get list of farms created via registered deployer.
function getFarmList() external view returns (address[] memory);
Returns
Name Type Description
Returns array of farm
address[]
addresses.
getFeeParams
Get all the fee parameters for creating farm.
It returns fee amount and extension fee as 0 if _user is privileged.
function getFeeParams(address _user) external view returns (address,
address, uint256, uint256);
Parameters
Name Type Description
The account creating the
_user address
farm.
Returns
221Name Type Description
address Receiver of the fees.
Token in which fee is to be
address
paid.
Amount of fees to be paid
uint256
for creation of farm.
Extension fee per day in
uint256
case of extending a farm.
updateFeeParams
Update the fee params for registry.
function updateFeeParams(address _receiver, address _feeToken, uint256
_amount, uint256 _extensionFeePerDay)
public
onlyOwner;
Parameters
Name Type Description
_receiver address FeeReceiver address.
_feeToken address Token address for fee.
Amount of token to be
_amount uint256
collected.
_extensionFeePerDay uint256 Extension fee per day.
_validateNonZeroAddr
Validate address.
222function _validateNonZeroAddr(address _addr) private pure;
223FarmDeployer
Git Source
Inherits: Ownable, ReentrancyGuard
Author: Sperax Foundation.
Exposes base functionalities which will be useful in every deployer.
State Variables
FARM_REGISTRY
address public immutable FARM_REGISTRY;
farmImplementation
address public farmImplementation;
farmId
string public farmId;
Functions
constructor
224Constructor.
constructor(address _farmRegistry, string memory _farmId)
Ownable(msg.sender);
Parameters
Name Type Description
Address of the Farm
_farmRegistry address
Registry.
_farmId string Id of the farm.
updateFarmImplementation
Update farm implementation''s address.
Only callable by the owner.
Ensure that _newFarmId is correct for the new farm implementation.
function updateFarmImplementation(address _newFarmImplementation,
string calldata _newFarmId) external onlyOwner;
Parameters
Name Type Description
New farm implementation''s
_newFarmImplementation address
address.
_newFarmId string ID of the new farm.
_collectFee
225Collect fee and transfer it to feeReceiver.
Function fetches all the fee params from farmRegistry.
function _collectFee() internal virtual;
_validateNonZeroAddr
Validate address.
function _validateNonZeroAddr(address _addr) internal pure;
Events
FarmCreated
event FarmCreated(address indexed farm, address indexed creator,
address indexed admin);
FeeCollected
event FeeCollected(address indexed creator, address indexed token,
uint256 amount);
FarmImplementationUpdated
event FarmImplementationUpdated(address indexed newFarmImplementation,
string newFarmId);
226Errors
InvalidAddress
error InvalidAddress();
NewFarmImplementationSameAsOld
error NewFarmImplementationSameAsOld();
227Features
Features contracts can be considered as plugins, which are used in farms only
where they are needed.
228ClaimableFee
Git Source
Inherits: Farm
Author: Sperax Foundation.
Farms build for pairs/ pools in which fee can be claimed can extend and override
_claimPoolFee function of this contract.
Functions
claimPoolFee
A function to claim the pool fee earned by lp.
Only the deposit owner can call this function.
function claimPoolFee(uint256 _depositId) external nonReentrant;
Parameters
Name Type Description
_depositId uint256 ID of the deposit.
_claimPoolFee
Claim pool fee internal logic to be implemented by child farm contract.
Just override this function and write the logic to claim fee, validation and other
checks are handled in claimPoolFee .
229function _claimPoolFee(uint256 _depositId)
internal
virtual
returns (uint256 tokenId, uint256 amt0Recv, uint256 amt1Recv);
Parameters
Name Type Description
Deposit ID of the deposit in
_depositId uint256
the farm.
Returns
Name Type Description
Token ID of the deposit for
tokenId uint256 E721 farms, for other farms
return depositId.
amt0Recv uint256 Amount 0 received as fee.
amt1Recv uint256 Amount 1 received as fee.
Events
PoolFeeCollected
event PoolFeeCollected(address indexed recipient, uint256 tokenId,
uint256 amt0Recv, uint256 amt1Recv);
Errors
230NoFeeToClaim
error NoFeeToClaim();
231ExpirableFarm
Git Source
Inherits: Farm
Author: Sperax Foundation.
This contract helps in creating farms with expiry feature.
State Variables
MIN_EXTENSION
uint256 public constant MIN_EXTENSION = 100;
MAX_EXTENSION
uint256 public constant MAX_EXTENSION = 300;
farmEndTime
uint256 public farmEndTime;
farmRegistry
address public farmRegistry;
232Functions
extendFarmDuration
Update the farm end time.
Can be updated only before the farm expired or closed. Extension should be
incremented in multiples of 1 USDs/day with minimum of 100 days at a time and a
maximum of 300 days. Extension is possible only after farm started.
function extendFarmDuration(uint256 _extensionDays) external onlyOwner
nonReentrant;
Parameters
Name Type Description
The number of days to
_extensionDays uint256 extend the farm. Example:
150 means 150 days.
updateFarmStartTime
Update the farm start time.
Can be updated only before the farm start. New start time should be in future.
Adjusts the farm end time accordingly.
function updateFarmStartTime(uint256 _newStartTime) public virtual
override;
Parameters
233Name Type Description
_newStartTime uint256 The new farm start time.
isFarmOpen
Returns bool status if farm is open. Farm is open if it is not closed and not expired.
function isFarmOpen() public view virtual override returns (bool);
Returns
Name Type Description
bool bool True if farm is open.
_setupFarmExpiry
Setup the farm data for farm expiry.
function _setupFarmExpiry(uint256 _farmStartTime, address
_farmRegistry) internal;
Parameters
Name Type Description
_farmStartTime uint256 Start time of the farm.
Address of the farm
_farmRegistry address
registry.
_collectExtensionFee
234Collects farm extension fee and transfers it to feeReceiver.
Function fetches all the fee params from farmRegistry.
function _collectExtensionFee(uint256 _extensionDays) private;
Parameters
Name Type Description
The number of days to
_extensionDays uint256 extend the farm. Example:
150 means 150 days.
Events
FarmEndTimeUpdated
event FarmEndTimeUpdated(uint256 newEndTime);
ExtensionFeeCollected
event ExtensionFeeCollected(address indexed token, uint256
extensionFee);
Errors
InvalidExtension
235error InvalidExtension();
DurationExceeded
error DurationExceeded();
FarmNotYetStarted
error FarmNotYetStarted();
236OperableDeposit
Git Source
Inherits: Farm
Author: Sperax Foundation.
This contract helps in creating farms with increase/decrease deposit functionality.
Functions
_updateSubscriptionForIncrease
Update subscription data of a deposit for increase in liquidity.
function _updateSubscriptionForIncrease(uint256 _depositId, uint256
_amount) internal;
Parameters
Name Type Description
Unique deposit id for the
_depositId uint256
deposit.
_amount uint256 _amount to be increased.
_updateSubscriptionForDecrease
Update subscription data of a deposit after decrease in liquidity.
237function _updateSubscriptionForDecrease(uint256 _depositId, uint256
_amount) internal;
Parameters
Name Type Description
Unique deposit id for the
_depositId uint256
deposit
_amount uint256 _amount to be decreased.
_increaseDeposit
Common logic for increasing a deposit.
function _increaseDeposit(uint256 _depositId, uint256 _amount)
internal;
Parameters
Name Type Description
Unique deposit id for the
_depositId uint256
deposit
_amount uint256 _amount to be decreased.
_decreaseDeposit
Common logic for decreasing a deposit.
function _decreaseDeposit(uint256 _depositId, uint256 _amount)
internal;
Parameters
238Name Type Description
Unique deposit id for the
_depositId uint256
deposit
_amount uint256 _amount to be decreased.
Events
DepositIncreased
event DepositIncreased(uint256 indexed depositId, uint256 liquidity);
DepositDecreased
event DepositDecreased(uint256 indexed depositId, uint256 liquidity);
Errors
DecreaseDepositNotPermitted
error DecreaseDepositNotPermitted();
InsufficientLiquidity
error InsufficientLiquidity();
239Rewarder
Rewarder is a contract which can be used by farm admins when they want to emit
rewards in fixed APR instead of fixed token amounts (by setting reward rate).
240Rewarder
Git Source
Inherits: IRewarder, OwnableUpgradeable, ReentrancyGuardUpgradeable
Author: Sperax Foundation.
This contract tracks farms, their APR and other data for a specific reward token.
Farms for UniV3 pools using Rewarder contract must have a minimum
observationCardinality of 20. It can be updated by calling
increaseObservationCardinalityNext function on the pool.
State Variables
MAX_PERCENTAGE
uint256 public constant MAX_PERCENTAGE = 1e4;
APR_PRECISION
uint256 public constant APR_PRECISION = 1e8;
REWARD_PERIOD
uint256 public constant REWARD_PERIOD = 1 weeks;
DENOMINATOR
241uint256 public constant DENOMINATOR = 100;
ONE_YEAR
uint256 public constant ONE_YEAR = 365 days;
REWARD_TOKEN
address public REWARD_TOKEN;
REWARD_TOKEN_DECIMALS
uint8 public REWARD_TOKEN_DECIMALS;
totalRewardRate
uint256 public totalRewardRate;
rewarderFactory
address public rewarderFactory;
calibrationRestricted
mapping(address => bool) public calibrationRestricted;
242farmRewardConfigs
mapping(address => FarmRewardConfig) internal farmRewardConfigs;
_decimals
mapping(address => uint8) private _decimals;
Functions
constructor
constructor();
initialize
Initializer function of this contract.
function initialize(address _rwdToken, address _oracle, address _admin)
external initializer;
Parameters
243Name Type Description
Address of the reward
_rwdToken address
token.
Address of the USDs
_oracle address
Master Price Oracle.
Admin/ deployer of this
_admin address
contract.
calibrateReward
Function to calibrate rewards for this rewarder''s reward token for a farm.
Calculates based on APR, caps based on maxRewardPerSec or balance rewards,
sends and sets the rewardRate in the farm.
function calibrateReward(address _farm) external nonReentrant returns
(uint256 rewardsToSend);
Parameters
Name Type Description
Address of the farm for
_farm address which the rewards are to be
calibrated.
Returns
Name Type Description
Rewards which are sent to
rewardsToSend uint256
the farm.
updateTokenManagerOfFarm
244Function to update the token manager''s address in the farm.
function updateTokenManagerOfFarm(address _farm, address _newManager)
external onlyOwner;
Parameters
Name Type Description
Farm''s address in which the
_farm address token manager is to be
updated.
Address of the new token
_newManager address
manager.
recoverRewardFundsOfFarm
Function to recover reward funds from the farm.
function recoverRewardFundsOfFarm(address _farm, uint256 _amount)
external onlyOwner;
Parameters
Name Type Description
Farm''s address from which
_farm address reward funds is to be
recovered.
Amount which is to be
_amount uint256
recovered.
updateAPR
Function to update APR.
245function updateAPR(address _farm, uint256 _apr) external onlyOwner
nonReentrant;
Parameters
Name Type Description
_farm address Address of the farm.
_apr uint256 APR in 1e8 precision.
toggleCalibrationRestriction
Function to toggle calibration restriction.
function toggleCalibrationRestriction(address _farm) external
onlyOwner;
Parameters
Name Type Description
Address of farm for which
_farm address calibration restriction is to
be toggled.
recoverERC20
Function to recover ERC20 tokens from this contract.
function recoverERC20(address _token, uint256 _amount) external
onlyOwner;
Parameters
246Name Type Description
_token address Address of the token.
_amount uint256 Amount of the tokens.
getTokenAmounts
Function to get token amounts value of underlying pool of the farm.
function getTokenAmounts(address _farm) external view returns
(address[] memory, uint256[] memory);
Parameters
Name Type Description
_farm address Address of the farm.
Returns
Name Type Description
address[] Array of token addresses.
uint256[]
getFarmRewardConfig
Function to get reward config for a farm.
function getFarmRewardConfig(address _farm) external view returns
(FarmRewardConfig memory);
Parameters
247Name Type Description
_farm address Address of the farm.
Returns
Name Type Description
FarmRewardConfig Farm
FarmRewardConfig
reward config.
rewardsEndTime
Function to calculate the time till which rewards are there for an LP.
function rewardsEndTime(address _farm) external view returns (uint256
rewardsEndingOn);
Parameters
Name Type Description
Address of the farm for
_farm address which the end time is to be
calculated.
Returns
Name Type Description
Timestamp in seconds till
rewardsEndingOn uint256 which the rewards are there
in farm and in rewarder.
updateRewardConfig
248Function to update the REWARD_TOKEN configuration. This function calibrates
reward so token manager must be updated to address of this contract in the farm.
function updateRewardConfig(address _farm, FarmRewardConfigInput memory
_rewardConfig) public onlyOwner nonReentrant;
Parameters
Name Type Description
Address of the farm for
_farm address which the config is to be
updated.
The config which is to be
_rewardConfig FarmRewardConfigInput
set.
_initialize
Internal initialize function.
function _initialize(address _rwdToken, address _oracle, address
_admin, address _rewarderFactory) internal;
Parameters
Name Type Description
Address of the reward
_rwdToken address
token.
Address of the USDs
_oracle address
Master Price Oracle.
Admin/ deployer of this
_admin address
contract.
Address of Rewarder
_rewarderFactory address
factory contract.
249_isConfigured
Function to check if the farm''s reward is configured.
function _isConfigured(address _farm) internal view;
Parameters
Name Type Description
_farm address Address of the farm.
_getTokenAmounts
An internal function to get token amounts for the farm.
function _getTokenAmounts(address _farm) internal view virtual returns
(address[] memory, uint256[] memory);
Parameters
Name Type Description
_farm address Address of the farm.
Returns
Name Type Description
address[] Array of token addresses.
uint256[] Array of token amounts.
_hasRewardToken
250Function to check if the reward token of this contract is one of farm''s reward token.
function _hasRewardToken(address _farm) internal view virtual returns
(bool);
Parameters
Name Type Description
_farm address Address of the farm.
Returns
Name Type Description
If farm has one of the
bool reward token as reward
token of this contract.
_validateNonZeroAddr
Validate address.
function _validateNonZeroAddr(address _addr) internal pure;
Parameters
Name Type Description
_addr address Address to be validated.
_calibrateReward
251function _calibrateReward(address _farm) private returns (uint256
rewardsToSend);
_setRewardRate
Function to set reward rate in the farm.
function _setRewardRate(address _farm, uint128 _rwdRate, uint256
_nonLockupRewardPer) private;
Parameters
Name Type Description
_farm address Address of the farm.
Reward per second to be
_rwdRate uint128
emitted.
Reward percentage to be
_nonLockupRewardPer uint256
allocated to no lockup fund.
_adjustGlobalRewardRate
Function to adjust global rewards per second emitted for a reward token.
function _adjustGlobalRewardRate(uint256 _oldRewardRate, uint256
_newRewardRate) private;
Parameters
Name Type Description
_oldRewardRate uint256 Old emission rate.
_newRewardRate uint256 New emission rate.
252_isValidFarm
Function to validate farm.
It checks that the farm should implement getTokenAmounts and have
REWARD_TOKEN. as one of the reward tokens.
function _isValidFarm(address _farm, address[] memory _baseTokens)
private returns (bool);
Parameters
Name Type Description
Address of the farm to be
_farm address
validated.
_baseTokens address[] Array of base tokens.
Returns
Name Type Description
bool bool True if farm is valid.
_hasBaseTokens
Function to check whether the base tokens are a subset of farm''s assets.
It handles repeated base tokens as well and pushes indexed in farmRewardConfigs.
function _hasBaseTokens(address _farm, address[] memory _baseTokens)
private returns (bool);
Parameters
253Name Type Description
_farm address Address of the farm.
Array of base token
_baseTokens address[] addresses to be considered
for value calculation.
Returns
Name Type Description
hasBaseTokens True if
baseTokens are non
bool
redundant and are a subset
of assets.
_normalizeAmount
Function to normalize asset amounts to be of precision
REWARD_TOKEN_DECIMALS.
function _normalizeAmount(address _token, uint256 _amount) private view
returns (uint256);
Parameters
Name Type Description
_token address Address of the asset token.
_amount uint256 Amount of the token.
Returns
254Name Type Description
Normalized amount of the
uint256
token in _desiredPrecision.
_getPrice
Function to fetch and get the price of a token.
function _getPrice(address _token, address _oracle) private view
returns (IOracle.PriceData memory priceData);
Parameters
Name Type Description
Token for which the the
_token address
price is to be fetched.
Address of the oracle
_oracle address
contract.
Returns
Name Type Description
priceData IOracle.PriceData Price data of the token.
_validatePriceFeed
Function to validate price feed.
function _validatePriceFeed(address _token, address _oracle) private
view;
Parameters
255Name Type Description
_token address Token to be validated.
_oracle address Address of the oracle.
_validateRewardPer
Function to validate the no lockup fund''s reward percentage.
function _validateRewardPer(uint256 _percentage) private pure;
Parameters
Name Type Description
No lockup fund''s reward
_percentage uint256
percentage to be validated.
256RewarderFactory
Git Source
Inherits: IRewarderFactory, Ownable
Author: Sperax Foundation.
This contract deploys new rewarders and keeps track of them.
State Variables
oracle
address public oracle;
rewarderImplementation
address public rewarderImplementation;
Functions
constructor
Constructor.
constructor(address _oracle) Ownable(msg.sender);
Parameters
257Name Type Description
Address of the master price
_oracle address
oracle of USDs.
deployRewarder
Function to deploy new rewarder.
function deployRewarder(address _rwdToken) external returns (address
rewarder);
Parameters
Name Type Description
Address of the reward
_rwdToken address token for which the
rewarder is to be deployed.
Returns
Name Type Description
rewarder address Rewarder''s address.
updateRewarderImplementation
Update rewarder implementation''s address
function updateRewarderImplementation(address
_newRewarderImplementation) external onlyOwner;
Parameters
258Name Type Description
_newRewarderImplementati New Rewarder
address
on Implementation
updateOracle
Function to update the oracle''s address.
function updateOracle(address _newOracle) public onlyOwner;
Parameters
Name Type Description
_newOracle address Address of the new oracle.
_validateNonZeroAddr
Validate address.
function _validateNonZeroAddr(address _addr) private pure;
Parameters
Name Type Description
_addr address Address to be validated.
259Deployed contracts
Name Explorer link
https://arbiscan.io/address/0x45bC6B4410
FarmRegistry
7837E7aBB21E2CaCbe7612Fce222e0
https://arbiscan.io/address/0x926477bAF6
RewarderFactory
0C25857419CC9Bf52E914881E1bDD3
https://arbiscan.io/address/0x212208daF12
CamelotV3Deployer
D7612e65fb39eE9a07172b08226B8
260Getting Started on Our DApp
Visit our dApp to Mint, Redeem and Farm.
Go through this YouTube Playlist for step-by-step tutorials on how to use and
navigate the Sperax ecosystem.
261Minting & Redeeming USDs
Users can mint USDs from their collateral (USDC.e, USDC or USDT). The Mint page
will automatically reflect the amount of collateral and SPA required to mint USDs
(Currently, no SPA required). Users can redeem USDs for a collateral of their
choice.
Minting USDs
• Make sure your wallet holds eligible collateral (USDC, USDT or USDC.e).
• Go to the dApp homepage and connect your wallet.
• Select the collateral and enter the amount of collateral that you want to deposit.
• View the Latest Auto-Yield APY before proceeding.
• The app will automatically show how much USDs you can mint. Then click on
‘Mint USDsʼ and review ‘Max Slippage .̓
• Then, you have to approve 2 transactions on your wallet - Approve and Sell
USDC - after clicking ‘Confirm .̓
• You can now view your USDs balance in your wallet.
🚀 How to Mint USDs with Stablecoins (USDC, USDT & USDC.e)
262Redeeming USDs
1. Go to the dApp homepage and connect your wallet.
2. Select the Redeem tab and enter the amount of USDs you would like to redeem.
3. You can also select the maximum slippage for the transaction and then select
the token in which you want to redeem. You can view the redemption fee and
redemption amount as well.
4. Now, click on ''Redeem USDs'' to redeem.
5. Click on ''Confirm'' and unlock USDs transfer by providing required consent.
6. Then approve the transaction and your USDs will be redeemed succesfully.
🎥 How to Redeem USDs on Sperax dApp | Step-by-Step Guide
263Staking SPA
Stake | Sperax USD - 1 April 2022 0
2 min 299 views
Powered by
1.2×
1 min 49 sec⚡ 1 min 30 sec
Stake SPA
1. Visit the stake page .
2. Choose amount of SPA you want to stake, lockup period and you can check
veSPA balance corresponding to that.
3. Click on Stake to stake your SPA.
4. Once the transaction is processed, you can see your
• veSPA balance
• SPA staked
• Expiry date
• Button to extend lockup period
Extend Lockup for staked SPA
2641. Click on the ''Extend Lockup'' button.
2. Select how much you want to extend your lockup period. You can see the
updated expiry date and veSPA balance.
3. Click on Extend Lockup.
Increase Staked Balance
1. Select additional amount of SPA tokens you want to stake.
2. You can see veSPA balance corresponding the same lockup as your existing
veSPA balance.
3. Click on Stake to stake additional amount of SPA. The new veSPA balance will
also expire at the same time as the previous balance.
265Governance
veSPA holders will deliberate on protocol governance
Sperax has launched its off-chain governance process. On-chain governance
protocol will be launched next. Through governance, community can make
changes to the USDs protocol parameters, bring in new partnerships, new yield
opportunities etc.
Sperax Off-Chain Governance Process:
The Sperax DAO governance process primarily utilizes the Sperax DAO Governance
Forum . In order for a proposal to be accepted, it must go through the following
phases:
Phase 0: Casual Ideation (Discord):
If you have an idea youʼd like to share, please feel free to post it in #DAO-
discussion channel on Sperax Discord, or, if you prefer to submit a proposal, you
can use the SIP Template and submit your concept in the Proposal (Active
Discussion) channel.
Ideation Flow:
1. Start a conversation in the official governance channel in Discord.
2. Gather feedback from the community.
3. (Optional) Create a poll on discord to gauge community sentiment.
4. (Optional) If youʼd like to talk about your idea on a Sperax Community Call, feel
free to contact a team member via Discord to coordinate.
Phase 1: Governance Proposal (On Forum)
266If you are ready to submit a formal proposal, you may do so on the Proposal (Active
Discussion) channel using the SIP Template . Here youʼll begin to receive
constructive feedback from the community as well as the Sperax team. Discussion
will continue for a minimum of 48 hours.
Make sure to add the correct tag to the proposal(see below for definitions):
1. USDs parameter: Proposals related to adjusting/modifying USDs components.
2. New Assets and Yield Strategies: Proposals related to adding new forms of
collateral and yield strategy schemes/methodologies.
3. Liquidity Mining: Proposals related to the improvement of Sperax farming &
liquidity mining mechanisms.
4. Product Feature: Proposals related to the improvement, addition, or modification
of new & existing Sperax products.
5. Partnership(s): Proposals related to inquiring & establishing potential
partnerships with the Sperax Protocol.
6. Other: Miscellaneous proposals that have yet to be assigned a defined
tag/category.
How to Vote on Sperax DAO Proposals using veSPA!
Tutorial for using Sperax Forum
Phase 2: Snapshot vote:
267Once a proposal has gained traction, a snapshot poll will be created for voting. A
Moderator will proceed to create the snapshot poll, link it to the corresponding
forum post, and submit it on the Snapshot Voting channel on Forum. Votes can be
cast directly through Sperax Snapshot Space .
All snapshot polls will last 3 days upon initiation. Votes will be weighed by the
voters'' veSPA balance. A snapshot poll will include 2 vote options (Yes/ No) by
default. If proposal creator wants 3 vote options (Yes/ No/ Yes with modification)
then they can inform that to the moderators while snapshot poll is created. If ''Yes
with modification'' option gets max votes, then the proposal is not subject to
cooldown period.
Proposal Passing Criteria:
1. Acceptance Threshold: Proposal must receive more than 50% in "Yes" votes
2. Minimum Quorum: At least 200 Million veSPA should vote in the snapshot poll
Possible outcomes at this stage:
1. Both the proposal passing criteria are met: the proposal passes and escalates
to a Sperax Improvement Plan (SIP).
2. Quorum is not met: The proposal does not meet minimum quorum and is
marked “Defeated” by the moderators. The proposal undergoes a 7-day
cooldown period. At the conclusion of this period, the proposer can resubmit
the proposal and proceed with the governance process.
3. Quorum is met but does not receive more than 50% in ''Yes'' votes: The post is
marked as ‘Defeatedʼ by the moderators.
• If “No” votes > “Yes with modification” votes - The proposal must then
undergo a 7-day cooldown period. Afterward, the proposer must then
resubmit the proposal, along with any necessary modifications, and proceed
with the governance process.
• If “Yes with modification” votes >= “No” votes - The proposal can be
returned to the deliberation phase for modifications and is not subject to the
7-day cooldown period. Once modifications are made to the proposal it can
be elevated to a Snapshot vote once again.
268Phase 3: Sperax Improvement Plan (SIP):
When a governance proposal passes the snapshot vote in the previous stage, the
proposal moves to Sperax Improvement Plan. This will have the list of all accepted
proposals. The Sperax engineering team will pick up proposals from SIP for
implementation based on their priorities and bandwidth. Community can also help
write the code for implementing proposals from SIP. All codes will have to be
audited before implementation. Sperax Foundation will help in facilitating the audits.
Check the repository of all the winning proposals
Approved SIPs
Sperax DAO
Track the implementation of these proposals
Sperax Improvement Plan – Asana
Asana
269Bug Bounty Program
Introduction
The Security of Speraxʼs USDs and Demeter users is paramount. The engineering
team and our auditors have invested significant time and resources to ensure that
USDs and Demeter are secure and dependable. The USDs and Demeter smart
contracts are publicly verifiable. The details and statistics of circulating supply,
underlying collateral, collateral strategies, Farms etc are publicly available.
On 1st March 2024 we are launching our bug bounty program. Security
researchers, fulfilling the eligibility criteria as mentioned in this document, are
eligible for a bug bounty for reporting undiscovered vulnerabilities. The Program
aims to incentivize responsible disclosure and enhance the security of the USDs
protocol and Demeter.
Bug Bounty Program
Security is one of our core values. We value the input of hackers acting in good
faith to help us maintain the highest standard for the security and safety of the
Sperax ecosystem. The USDs protocol and Demeter, while it has gone through a
professional audit, depends on new technology that may contain undiscovered
vulnerabilities.
The Sperax team encourages the community to audit our contracts and security.
We also encourage the responsible disclosure of any issues. This program is
intended to recognize the value of working with the community of independent
security researchers. It sets out our definition of good faith in the context of finding
and reporting vulnerabilities, as well as, what you can expect from us in return.
Scope
270The Program includes the vulnerabilities and bugs in the USDs protocol core
repository (located in the GitHub repositories, primarily at:
https://github.com/Sperax/USDs-v2/tree/main/contracts and
https://github.com/Sperax/Demeter-Protocol-Contracts . This list may change as
new contracts are deployed or existing contracts are removed from usage.
The following are not within the scope of the Program:
1. Bugs in any third-party contract or platform that interacts with USDs protocol;
2. Vulnerabilities related to domains, DNS, or servers of websites;
3. Vulnerabilities already reported or discovered in contracts built by third parties
on USDs;
4. Any already-reported bugs or other vulnerabilities.
5. Test contracts and staging servers unless the discovered vulnerability also
affects the USDs Protocol or could otherwise be exploited in a way that risks
user funds.
Disclosure
A researcher needs to submit all bug bounty disclosures to here . The disclosure
must include clear and concise steps to reproduce the discovered vulnerability in
written or video format. The Sperax team will follow up promptly with
acknowledgment of the disclosure.
Terms and Conditions
To be eligible for a reward under this Program, you must:
271• Discover a previously unreported, non-public vulnerability within the scope of
this Program. Vulnerabilities must be distinct from the issues covered in the
previously conducted publicly available audits.
• Include sufficient detail in your disclosure to enable our engineers to quickly
reproduce, understand, and fix the vulnerability.
• Be the first to disclose the unique vulnerability to the Team by the disclosure
requirements below. If similar vulnerabilities are reported, the first submission
shall be rewarded (if determined valid and otherwise in the scope of this
Program)
• Be reporting in an individual capacity, or if employed by a company, reporting
with the companyʼs written approval to submit a disclosure to Sperax
• Not be a current or former Sperax team member, vendor, contractor, or
employee of a SperaxDAO vendor or contractor.
• Not be subject to any international, national, or state-level sanctions.
• Be at least 18 years of age or, if younger, submit your vulnerability with the
consent of your parent or guardian.
• Not exploit the vulnerability in any way, including by making it public or
obtaining a profit (other than a reward under this Program). Any publicity in any
way, whether direct or indirect, relating to any bug or vulnerability will
automatically disqualify it and you from the Program.
To encourage vulnerability research and to avoid any confusion between good-faith
hacking and malicious attacks, we require that you:
272• Play by the rules, including following the terms and conditions of this program
and any other relevant agreements. If there is any inconsistency between this
program and any other relevant agreements, the terms of this program will
prevail.
• Report any vulnerability youʼve discovered promptly.
• Make a good faith effort to avoid privacy violations, data destruction, harming
user experience, interruption, or degradation of the Sperax ecosystem and
services.
• Use only the google form to submit vulnerabilities with us.
• Keep the details of any discovered vulnerabilities confidential until they are
fixed.
• Perform testing only on in-scope systems, and respect systems and activities
which are out-of-scope.
• Not submit a separate vulnerability caused by an underlying issue that is the
same as an issue on which a reward has been paid under this Program.
• Only interact with accounts you own or with explicit permission from the
account holder.
• Not engage in any unlawful conduct when disclosing the bug, including through
threats, demands, or any other coercive tactics.
When working with us according to this program, you can expect us to:
273• Pay generous rewards for eligible discoveries based on the severity and
exploitability of the discovery, at The Sperax team''s sole discretion
• Extend Safe Harbor for your vulnerability research related to this program,
meaning we will not threaten or bring any legal action against anyone who
makes a good faith effort to comply with our bug bounty program.
• Work with you to understand and validate your report, including a timely initial
response to the submission.
• Work to remediate discovered vulnerabilities promptly.
• Recognize your contribution to improving our security if you are the first to
report a unique vulnerability, and your report triggers a code or configuration
change.
• All reward determinations, including eligibility and payment amount, are made at
Speraxʼs sole discretion. The Sperax team reserves the right to reject
submissions and alter the terms and conditions of this program.
Rewards
Sperax Treasury offers rewards for discoveries that can prevent the loss of assets,
the freezing of assets, or harm to a user, commensurate with the severity and
exploitability of the vulnerability. Sperax Treasury will pay a reward of $500 to
$15,000 for eligible discoveries according to the terms and conditions provided
below.
The Team evaluates all submissions on a case-by-case basis. Rewards are
allocated based on the severity of the issue, and other variables, including, but not
limited to a) the quality of the issue description, b) the instructions for
reproducibility, and c) the quality of the fix (if included). A detailed report of a
vulnerability increases the likelihood of a reward and may increase the reward
amount. Therefore, please provide as much information about the vulnerability as
possible.
The Program intends to follow a similar approach as the Ethereum Bug Bounty,
where the severity of the issues will be based according to the OWASP risk rating
model based on “Impact” and “Likelihood”. The evaluation of scoring is however at
the sole discretion of the Sperax Team.
274All rewards are paid in SPA and xSPA tokens with a 50-50 split (15-day TWAP) via a
transfer to the wallet address provided by the participant to the Team. As a
condition of participating in this Program, the participants give the Sperax Team
permission to share their wallet addresses and other information provided by them
to third parties to administer this Program and comply with applicable laws,
regulations, and rules.
The reward will be received in SPA token based on the following severity scheme:
• Note = Up to 100 US dollars
• Very low = Up to 500 US dollars
• Low = Up to 1,000 US dollars
• Medium = Up to 2,500 US dollars
• High = Up to 5,000 US dollars
• Very High = Up to 10,000 US dollars
• Critical = Up to 15,000 US dollars
Likelihood/ Very Low Low Moderate High Critical
Severity
Almost
$1000 $2500 $5000 $10000 $15000
certain
Likely $500 $1000 $2500 $5000 $10000
Possible $100 $500 $1000 $2500 $5000
Unlikely $100 $100 $500 $1000 $2500
Almost
$100 $100 $100 $500 $1000
Possible
Other terms
The decisions made regarding rewards are final and binding.
275By submitting your report, you grant the Company all rights, including without
limitation intellectual property rights, needed to validate, mitigate, and disclose the
vulnerability. All reward decisions, including eligibility for and amounts of the
rewards and how such rewards will be paid, are made at the Company''s sole
discretion.
Terms and conditions of the Program may be altered at any time. The company may
change or cancel this Program at any time, for any reason.
276FAQ
277SPA Tokenomics
Token distribution schedule, details of tokens held by the foundation and
community treasury
SPA Circulating Supply Calculation Logic
Circulating Supply = Total Supply of SPA on Ethereum + Total Supply of SPA
on Arbitrum + Total Supply of wSPA on Ethereum - wSPA locked on Arbitrum
bridge - SPA balance held by major wallets - SPA locked in SPA Staking
Protocol
Important contract addresses:
SPA token address (Ethereum): 0xb4a3b0faf0ab53df58001804dda5bfc6a3d59008
SPA token address (Arbitrum):
0x5575552988A3A80504bBaeB1311674fCFd40aD4B
wSPA (wrapped SPA) token address (Ethereum):
0x2a95FE4c7e64e09856989F9eA0b57B9AB5f770CB
Arbitrum bridge where wSPA is locked (Ethereum):
0xcEe284F754E854890e311e3280b767F80797180d
Major wallets with SPA tokens
278Name Address Vesting & Initial % of Initial
Purpose Allocation Supply
This vests
linearly over a
0x4a692fD139 4-year time-
259a5b94Cad lock, beginning
7753E3C9635 from the
0b7F2B9f launch date of
0xBA6ca0B9e governance
7333f5e66781 protocol. The
Treasury 6b85704c024 DAO will 1,250,000,000 25%
AB250C9D control this
fund and utilize
0x8898A38Eb for future
8E3104f7c986 partnerships,
22b55260E014 marketing
B3a0217 incentives,
liquidity mining
rewards, etc.
279Foundation
funds are
being used to
make initial
markets and
for protocol
development.
Since the
foundation
lends the
tokens to third
parties for
market making
0xD95791bcab activities, the
484C0552833 actual token
cB558d18d4D3 balance held in
F198AF9 the foundation
wallet will
0xb56e5620A
fluctuate.
79cfe59aF7c0
Foundation
FcaE95aADbE
Foundation has burnt 1,250,783,000 25.02%
A8ac32A1
375MM SPA in
2022 to
0xC6e00e0E3 further
544C93460cd decentralise
Fb53E85C452 the protocol -
8EF348265 250MM SPA in
May and
125MM SPA in
September.
0xC6e00e0E3
544C93460cd
Fb53E85C452
8EF348265
is an operator
wallet which is
sometimes
used to move
tokens from
Layer 1 to
Layer 2.
280This will be
0x8B65CE3b4 used to
Eaa89583460 provide
96C3a9303b7 rewards for
3f2012aCc liquidity mining
Bootstrap
during protocol 500,000,000 10%
Liquidity
0xAF64e027D genesis. Future
42bAc1C14277 liquidity mining
fd295De9Ae31 rewards will be
8eEF17E funded from
treasury funds
This will be
used to reward
users who
stake $SPA in
0xCD1B1ce6ce the staking
877a9315E73E protocol.
2E4Ba3137228 Stakers will
Staking 068A59 earn fees from
500,000,000 10%
Rewards 0x3702E3e2D the minting
B2b5d037c1db and redeeming
B23Ab7A51d0 of $USDs, as
Cc90BD0e well as staking
rewards from
the allocated
rewards
budget.
This vests
linearly over 4
years with a 6
month cliff.
Token unlock
schedule will
0xE10b88d70b start from
Team & 01b956782Dc9 4/1/2022 for
499,217,000 9.98%
Advisor 8d7D4f3a931F existing team
F59Fc7 members. For
new members,
vesting would
start at least
three months
from the day
they join.
281All SPA tokens
have been
0x2Fc8d8BCf4 vested under a
b2c0fc659447 strict vesting
Private Sale 750,000,000 15%
5E44c473AC3 schedule of 1
E844B6a year starting
from
9/18/2021.
Staking Protocol Related
Title Chain Contract / Wallet Address
0xbF82a3212e13b2d407D1
veSPA L1 Ethereum
0f5107b5C8404dE7F403
0x2e2071180682Ce6C247B
veSPA L2 Arbitrum
1eF93d382D509F5F6A17
0xa61DD4480BE2582283Af
RewardDistributor L1 Ethereum
a54E461A1d3643b36040
0x2c07bc934974BbF413a4
RewardDistributor L2 Arbitrum
a4CeDA98713DCb8d9e16
0x3702E3e2DB2b5d037c1d
Staking Reward Arbitrum
bB23Ab7A51d0Cc90BD0e
Bootstrap Liquidity Related
Tokens from Bootstrap Liquidity have been moved to a lot of Farm rewarder
contracts and deployer wallet addresses for operational reasons. Please find below
the list of those addresses. We will try our best to keep this list constantly updated.
282Title Chain Contract / Wallet Address
0x8B65CE3b4Eaa8958346
Bootstrap Liquidity Ethereum
096C3a9303b73f2012aCc
0xc28c6970D8A345988e8
Bootstrap liquidity deployer Ethereum and Arbitrum
335b1C229dEA3c802e0a6
USDs/USDC Farm Rewarder 0x1733c5bc884090C73D89
Arbitrum
(SPA) 303467461693c54Ba58B
SPA/USDs Farm Rewarder 1 0x136C218Ff8E87eD68f851
Arbitrum
(SPA) 433685960819F06b1fE
USDs/USDC Farm Vesting 0x638d76763dE9492b609
Arbitrum
(SPA) b0d8830D8F626C5933A4D
SPA/USDs Farm Vesting 0x03b35477cFD400dEdfAc
Arbitrum
(SPA) 06f40422491500cbc663
SPA/USDs Farm Rewarder 2 0x36033594EC23E0f0B187
Arbitrum
(SPA) f767889Eb4C539F4aE03
SPA/USDs Farm Vesting 2 0xC0F0484a216AfF20E0ea
Arbitrum
(SPA) d1a1513cE40fe0AFe0fe
0xb56e5620A79cfe59aF7c
SPA-Reserve-L2 multi-sig Arbitrum
0FcaE95aADbEA8ac32A1
0xc150cbdDC5932258fAc7
SPA Farm Arbitrum
68bEB4d2352D127039fd
0x852afF031bb282C054B2
SPA Farm rewarder Arbitrum
6628A799D7F3a896873e
0xAF64e027D42bAc1C1427
Bootstrap liquidity Arbitrum Arbitrum
7fd295De9Ae318eEF17E
SPA Buyback
283The SPA that is bought back from the open markets using 30% of the auto-yield
and 100% of the fees is stored in :
0xA61a0719e9714c95345e89a2f1C83Fae6f5745ef (Arbitrum One)
SPA circulating supply sheet which is in Beta. It can be used to view the
circulating supply breakdown for the token.
284xSPA token
xSPA is a reward token of the Sperax ecosystem. xSPA can be either staked for
veSPA with a lockup of 180 days or more, or redeemed within 15 to 180 days giving
50% to 100% SPA upon redemption.
Summary / Abstract
Earlier in 2023, SperaxDAO decided on the SPA budget for emission through Gauge
(SIP-32 ) and redirecting veSPA emissions to bribes on Gauge (SIP-33 ). Every
week, Gauge emits 2.9 M SPA and veSPA voters are bribed 383K SPA. This SPA
should be used more prominently and assertively to drive USDs growth and bring
new adoption.
Motivation
Since the start of SPA Gauge, USDs total supply has decreased from $2M to
$1.78M. In the meantime, SPA Gauge has emitted about 80M SPA amounting to
$392K. The SPA circulating supply has increased from 1.593B to 1.658B.
Emitting SPA in a predetermined manner and without assessing the market
dynamics is improper utilization of resources. Since our target is to increase USDs
adoption which will help in growing the Sperax ecosystem and its participants, we
must rethink our overall strategy.
Overview
Apart from maintaining the target to make SPA deflationary, SperaxDAO should
ensure that the emission is invested back into the ecosystem and contributes
directly towards increasing the USDs adoption and supply.
285All SPA emissions should have a looping effect such that a good portion of SPA
distributed should be invested back into the ecosystem in the form of veSPA and
increase the burning rate of SPA.
Since the emissions are not helping in increasing either USDs adoption or the
locked tokens. The recent increment in veSPA numbers is primarily driven by the
team token allocation in veSPA. The Sperax ecosystem is steadily moving towards
complete decentralization of protocols and hence should have more governance
participation.
The Sperax ecosystem should have a new reward token. A token which can be
staked as veSPA or redeemed for SPA. After drawing some inspiration from the
Camelot emission strategy, the core team proposes the launch of a new token
called xSPA. Users can either stake 1 xSPA for one veSPA or redeem 1 xSPA for 1
SPA.
Technical overview
Users can redeem 1 xSPA token for SPA by depositing their xSPA token through the
redemption contract. Users can stake 1 xSPA in veSPA to increase their staked SPA
balance. The relation between xSPA, SPA, and veSPA will be governed by the
following rules
286• 1 xSPA will be equivalent to 1 SPA.
• Users can redeem 1 xSPA for 1 SPA if they lock the xSPA in the redemption
contract for 180 days, the maximum redemption period.
• Users can redeem 1 xSPA for 0.5 SPA if they lock the xSPA in the redemption
contract of 15 days, the minimum redemption period.
• If the redemption period is ‘x ,̓ between 15 and 180 days, the amount of SPA a
user gets is governed by the following equation:
Receivable spaAmount = (_xSpaAmount * (_redeemDuration + 150 days)) /
330 days
• A redemption request cannot be modified or canceled.
• The redemption contract will instantly burn any differential SPA
◦ In case of the minimum locking period of 15 days, half the SPA tokens will be
burnt right away and users can claim their SPA tokens after 15 days.
◦ In case of a maximum locking period of 180 days, no SPA will be burnt and
users can claim their SPA token after 180 days.
◦ In case of a period between 15 and 180 days, SPA burnt is:
SpaBurnt = _xSpaAmount - Receivable spaAmount
Users can claim their SPA tokens after the locking period.
• Users can stake 1 xSPA token in the veSPA contract to increase their staked SPA
balance by 1 SPA token for the existing lockup period if the lockup period is
greater than 180 days.
◦ If the user has 0 staked balance, the system will throw an error and will ask
the user to create a staked position with a minimum staking period of 180
days
◦ If the user has staked balance but the lockup is less than 180 days, the
system will throw an error and ask the user to increase the locking period to
a minimum of 180 days
◦ Users will be able to increase their staked position if the lockup period is
above 180 days.
• xSPA token is transferrable.
• The staking and redemption criteria can be updated/modified through
governance.
287How to get, deposit and redeem xSPA token.
Getting xSPA from SPA:
1. Allow xSPA token contract to transfer SPA from your account by calling approve
function on SPA token contract by passing spender as
0x0966E72256d6055145902F72F9D3B6a194B9cCc3 xSPAʼs address and amount as
the desired amount of xSPA you would like to have. Note: To allow 1 SPA pass
1000000000000000000 i.e 1e18 as amount in the approve function.
2. Call this mint function on xSPA contract by passing the amount if you want to
receive xSPA on your account or this mint function by passing the address of
the receiver account and amount if you want to receive the xSPA on another
account.
3. Check your xSPA balance by calling balanceOf function on by passing in your
accountʼs address.
Depositing xSPA for redeeming SPA:
• The minimum redemption period is 15 days in which you will receive only 100/2
= 50 SPA after 15 days.
• The maximum redemption period is 180 days in which you will receive all your
100 SPA back for your 100 xSPA.
• If you select any period between 15 days to 180 days, the SPA amount
redeemed would be calculated on pro rata basis between 50% to 100% of SPA
for your xSPA. You can get this amount by calling getSpaforxSPA function on
the xSPA contract by passing the xSPA amount with precision and your
redeemDuration in seconds between 1296000 (15 days) to 15552000 (180
days) and it will return the SPA amount you will receive at the end of your
redemption period.
• To create a redemption request you can call createRedemptionRequest on the
xSPA contract by passing the xSPA amount with precision and the redeem
duration between the range specified above. It returns the redemption request
ID which will be used to track and claim later.
288Redeeming xSPA for SPA:
• Once you have created a redemption request in the above step, you can see
and track your redemption request by calling redemptionRequests function on
the xSPA contract by passing your redemption request ID, it returns the
requesterʼs address, unlock time in unix epoch and spa amount which will be
unlocked.
• Once the current unix epoch time is more than the unlock time of the
redemption request, you can call the redeemSPA function by passing in your
redemption request ID if you want to receive the SPA tokens on your account
otherwise you can call this function by passing an address of another account
as receiver and your request ID to send the SPA to another account.
Redeeming xSPA for veSPA:
• For this redemption you must have an existing veSPA lock for at least 180 days
or more, if you do not have, you can call createLock function on the veSPA
contract by passing the amount, lock duration (minimum 180 days) and auto
cooldown preference.
• If you have an active veSPA lock for at least 180 days or more, you can call
stakeXSpa function on the xSPA token contract and your veSPA balance
would be increased immediately.
289Smart Contract Addresses
USDs Token Address
USDs L2 (Arbitrum) address: 0xD74f5255D557944cf7Dd0E45FF521520002D5748
USDs Contract Addresses:
•
SPA Token Addresses
Arbitrum One:
SPA L2 address: 0x5575552988A3A80504bBaeB1311674fCFd40aD4B
Ethereum
SPA L1 address: 0xB4A3B0Faf0Ab53df58001804DdA5Bfc6a3D59008
wSPA L1 address: 0x2a95FE4c7e64e09856989F9eA0b57B9AB5f770CB
Binance Smart Chain
SPA BSC address: 0x1A9Fd6eC3144Da3Dd6Ea13Ec1C25C58423a379b1
xSPA Contract Address
xSPA L2 address: 0x0966E72256d6055145902F72F9D3B6a194B9cCc3
veSPA Contract Address
290Arbitrum One:
Proxy contract deployed at: 0x2e2071180682Ce6C247B1eF93d382D509F5F6A17
Implementation contract deployed at:
0xD16f5343FDDD2DcF6A8791e302A204c13069D165
Ethereum:
Proxy contract deployed at: 0xbF82a3212e13b2d407D10f5107b5C8404dE7F403
Implementation contract deployed at:
0xA3F8745548A98ee67545Abcb0Cc8ED3129b8fF8D
291How to Transfer SPA from
Ethereum to Arbitrum
Though all the Sperax and SPA functionality lives on Arbitrum now - it might happen
that some early users still have SPA on the chain Sperax started with - Ethereum.
From today''s perspective, SPA held on Ethereum can be used only for transfers
between on-chain accounts or for depositing SPA to CEXs supporting SPA deposits
on Ethereum. SPA on Ethereum can''t be used for staking, yield farming (Farms on
Sperax DApp), or for voting on Snapshot.
If you still have SPA on Ethereum Mainnet, you need to bridge it from Ethereum to
Arbitrum in order to unlock SPA potential in DeFi.
The Arbitrum bridge accepts a wrapped form of SPA (wSPA), so first of all you must
wrap your SPA on Ethereum in order to bridge it. Donʼt worry, the value of your
tokens will not change.
Below you can find instructions for transferring SPA to Arbitrum. Please make sure
you have some ETH in your wallet to manage gas fees when wrapping and bridging
SPA.
Please remember: wSPA has no other function except being an intermediary token
for bridging. Please don''t try to send wSPA to any CEX or sell it - the transaction
may fail or you can lose your tokens. Use wSPA only to bridge it to Arbitrum and get
your SPA on Arbitrum.
Additionally, if your tokens do not appear after swapping or bridging, make sure you
manually add SPA token addresses to your wallet.
Step 1: SPA (Ethereum Mainnet) → wSPA (Ethereum Mainnet)
2921. Open this link .
2. Connect to Ethereum mainnet using your wallet using the appropriate provider.
3. Import wSPA token into your wallet using this token address:
0x2a95FE4c7e64e09856989F9eA0b57B9AB5f770CB
4. Enter the amount of SPA you want to convert to wSPA. Note that 1 SPA = 1
wSPA.
5. Then click on ''Swap'', provide consent and send the transaction.
6. You can now see the wSPA tokens in your wallet.
How to Convert SPA on Ethereum to wSPA
Step 2: wSPA (Ethereum Mainnet) → SPA (Arbitrum)
2931. Navigate to Arbitrum Bridge . (Make sure that you are bridging from Ethereum
to Arbitrum One.)
2. Connect your wallet using the appropriate provider.
3. Select wSPA token by pasting its token address:
(0x2a95FE4c7e64e09856989F9eA0b57B9AB5f770CB)
4. Enter the amount of wSPA that you want to bridge. You will receive the same
amount of SPA on Arbitrum. (Note that the website may show that you will
receive wSPA, but be assured that you will receive SPA on Arbitrum. You can
verify that using SPA Arbitrum''s token address:
0x5575552988A3A80504bBaeB1311674fCFd40aD4B)
5. Click on ''Move funds to Arbitrum One'' and then send the transaction to give
permission to transfer the capped amount of wSPA.
6. Send the transaction now to bridge and wait for some time for the transcation to
be completed successfully.
7. You can track the transaction status/history on the bridge page. In some time,
you should see the SPA tokens on Arbitrum in your wallet.
How to Convert wSPA to SPA on Arbitrum
294Quick Links
295">