import FlowToken from 0x1654653399040a61import FungibleToken from 0xf233dcee88fe0abeimport SwapFactory from 0xb063c16cac85dbd1// Deploy a SwapPair given token{0|1}'s TokenName and contract address.//`stableMode` specifies whether the pair uses Uniswap-V2 algorithm (stableMode:false) or Solidly-Stableswap algorithm (stableMode:true).transaction(Token0Name: String, Token0Addr: Address, Token1Name: String, Token1Addr: Address, stableMode: Bool) {prepare(userAccount: AuthAccount) {let flowVaultRef =userAccount.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)! assert(flowVaultRef.balance >=0.002, message:"Insufficient balance to create pair, minimum balance requirement: 0.002 flow")let accountCreationFeeVault <- flowVaultRef.withdraw(amount:0.001)let token0Vault <- getAccount(Token0Addr).contracts.borrow<&FungibleToken>(name:Token0Name)!.createEmptyVault()let token1Vault <- getAccount(Token1Addr).contracts.borrow<&FungibleToken>(name:Token1Name)!.createEmptyVault()SwapFactory.createPair(token0Vault: <-token0Vault, token1Vault: <-token1Vault, accountCreationFee: <-accountCreationFeeVault, stableMode: stableMode) }}
Get Pair & LpToken Info
Get Pairs' Addresses
/// SwapFactory.cdc && StableSwapFactory.cdcpub fun getPairAddress(token0Key: String, token1Key: String): Address?pub fun getAllPairsLength(): Intpub fun getAllStableSwapPairsLength(): Intpub fun getSlicedPairs(from: UInt64, to: UInt64): [Address]
Param
Type
Comments
from
UInt64
Start Index
to
UInt64
End Index
Return Value
[Address]
An array of deployed trading pair addresses
Get number of all deployed trading pairs and an array of deployed trading pair addresses.
Example script get_all_pair_addresses.cdc:
import SwapFactory from 0xb063c16cac85dbd1pub fun main(): [Address] {let len =SwapFactory.getAllPairsLength()if (len ==0) {return [] } else {returnSwapFactory.getSlicedPairs(from: 0, to: UInt64.max) }}
getPairInfo
/// SwapFactory.cdc && StableSwapFactory.cdcpub fun getPairInfo(token0Key: String, token1Key: String): AnyStruct?pub fun getSlicedPairInfos(from: UInt64, to: UInt64): [AnyStruct]
/// SwapPair.cdcpub resource PairPublic:SwapInterfaces.PairPublic { pub fun getPairInfo(): [AnyStruct] {}}
Example script to get PairInfo given the pair address:
import SwapInterfaces from 0xb78ef7afa52ff906 import SwapConfig from 0xb78ef7afa52ff906 pub fun main(pairAddr: Address): [AnyStruct] { let pairPublicRef =getAccount(pairAddr) .getCapability<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath) .borrow() ??panic("cannot borrow reference to PairPublic resource") returnpairPublicRef.getPairInfo() }
Example script `get_all_pair_infos.cdc`:
import SwapFactory from 0xb063c16cac85dbd1pub fun main(): [AnyStruct] {let len =SwapFactory.getAllPairsLength()if (len ==0) {return [] } else {returnSwapFactory.getSlicedPairInfos(from: 0, to: UInt64.max) }}
LpTokenCollection
Liquidity Provider (LP) of a trading pair will receive corresponding FT (i.e. LpToken) representing LP's pro rata share of that pair. LP may provide liquidity to different trading pairs and receive multiple different LpTokens. All these LpTokens are grouped within the LpTokenCollection resource, with methods exposing LpToken details:
/// SwapFactory.cdcpub resource LpTokenCollection:SwapInterfaces.LpTokenCollectionPublic { pub fun getAllLPTokens(): [Address] {} pub fun getLpTokenBalance(pairAddr: Address): UFix64 {} pub fun deposit(pairAddr: Address, lpTokenVault: @FungibleToken.Vault) {} pub fun withdraw(pairAddr: Address, amount: UFix64): @FungibleToken.Vault {}}
Example script to check all the LPed pairs and liquidity shares of a given account:
A straightforward way to contruct a Swap transaction is to use the SwapRouter, which provides a set of useful methods below.
Calculate output / input amount
getAmountsOut
/// SwapRouter.cdcpub fun getAmountsOut( amountIn: UFix64, tokenKeyPath: [String]): [UFix64]
Param
Type
Comments
amountIn
UFix64
Input token amount for the FT tokenKeyPath[0], e.g. 50.0
tokenKeyPath
[String]
An array of FT identifiers denoting the chained-swap path, e.g.: [A.3c5959b568896393.FUSD, A.1654653399040a61.FlowToken, A.b19436aae4d94622.FiatToken] => denoting the swap path of [FUSD -> Flow -> USDC].
tokenKeyPath.length must be >= 2, pools for each consecutive pair of FTs must exist and have liquidity.
Return Vaule
[UFix64]
Calculated maximum output FT amounts following the given swap path, e.g. [50.0, 10.0, 48.0]
Given the input amount of a FT, with an array of FT identifiers denoting the chained-swap path, calculates all subsequent maximum output token amounts.
/// SwapRouter.cdcpub fun getAmountsIn( amountOut: UFix64, tokenKeyPath: [String]): [UFix64]
Param
Type
Comments
amountOut
UFix64
Expected output amount of the FT to receive, e.g. 48.0
tokenKeyPath
[String]
An array of FT identifiers denoting the chained-swap path, e.g.: [A.3c5959b568896393.FUSD, A.1654653399040a61.FlowToken, A.b19436aae4d94622.FiatToken] => denoting the swap path of [FUSD -> Flow -> USDC].
tokenKeyPath.length must be >= 2, pools for each consecutive pair of FTs must exist and have liquidity.
Return Value
[UFix64]
Calculated minimum input FT amounts following the given swap path, e.g. [50.0, 10.0, 48.0]
Given the expected output amount of a FT, with an array of FT identifiers denoting the chained-swap path, calculates the minimum input FT amounts required to buy the given amountOut.
The minimum amount of output token that must be received, otherwise the tx will revert.
tokenKeyPath
[String]
An array of FT identifiers denoting the chained-swap path, e.g.: [A.3c5959b568896393.FUSD, A.1654653399040a61.FlowToken, A.b19436aae4d94622.FiatToken] => denoting the swap path of [FUSD -> Flow -> USDC].
tokenKeyPath.length must be >= 2, pools for each consecutive pair of FTs must exist and have liquidity.
deadline
UFix64
Unix timestamp after which the tx will revert.
Return Value
@FT.Vault
Output FT resource
To receive as many output FT as possible for swapping the exact amount of input FT, by following the given swap path.
Example transaction swap_exact_flow_to_usdc.cdc:
import FungibleToken from 0xf233dcee88fe0abeimport SwapRouter from 0xa6850776a94e6551transaction( exactAmountIn: UFix64, amountOutMin: UFix64, path: [String], to: Address, deadline: UFix64) {prepare(userAccount: AuthAccount) {let tokenInVaultPath =/storage/flowTokenVaultlet tokenOutReceiverPath =/public/USDCVaultReceiverlet inVaultRef =userAccount.borrow<&FungibleToken.Vault>(from:tokenInVaultPath) ?? panic("Could not borrow reference to the owner's in FT.Vault")/// Note: Receiver (to) should already have out FT.Vault initialized, otherwise tx reverts.let outReceiverRef =getAccount(to).getCapability(tokenOutReceiverPath) .borrow<&{FungibleToken.Receiver}>()??panic("Could not borrow receiver reference to the recipient's out FT.Vault")let exactVaultIn <- inVaultRef.withdraw(amount:exactAmountIn)let vaultOut <- SwapRouter.swapExactTokensForTokens( exactVaultIn:<-exactVaultIn, amountOutMin: amountOutMin, tokenKeyPath: path, deadline: deadline )outReceiverRef.deposit(from: <-vaultOut) }}
Input FT to sell, whose balance is the maximum amount can be used before the tx reverts. Any remaining input token will be put in returnValue[1].
exactAmountOut
UFix64
The exact amount of output FT expected to receive.
tokenKeyPath
[String]
An array of FT identifiers denoting the chained-swap path, e.g.: [A.3c5959b568896393.FUSD, A.1654653399040a61.FlowToken, A.b19436aae4d94622.FiatToken] => denoting the swap path of [FUSD -> Flow -> USDC].
tokenKeyPath.length must be >= 2, pools for each consecutive pair of FTs must exist and have liquidity.
deadline
UFix64
Unix timestamp after which the tx will revert.
Return Value
@[FT.Vault]
2-element array of FT resources.
* returnValue[0]: output token resource, whose balance will be exactAmountOut.
* returnValue[1]: Any remaining input token resource.
To receive the exact amount of output FT for swapping as few input FT as possible, by following the given swap path.
Example transaction swap_flow_to_exact_usdc.cdc:
import FungibleToken from 0xf233dcee88fe0abeimport SwapRouter from 0xa6850776a94e6551transaction( amountInMax: UFix64, exactAmountOut: UFix64, path: [String], to: Address, deadline: UFix64) {prepare(userAccount: AuthAccount) {let tokenInVaultPath =/storage/flowTokenVaultlet tokenOutReceiverPath =/public/USDCVaultReceiverlet inVaultRef =userAccount.borrow<&FungibleToken.Vault>(from:tokenInVaultPath) ?? panic("Could not borrow reference to the owner's in FT.Vault")/// Note: Receiver (to) should already have out FT.Vault initialized, otherwise tx reverts.let outReceiverRef =getAccount(to).getCapability(tokenOutReceiverPath) .borrow<&{FungibleToken.Receiver}>()??panic("Could not borrow receiver reference to the recipient's out FT.Vault")let vaultInMax <- inVaultRef.withdraw(amount:amountInMax)let swapResVault <- SwapRouter.swapTokensForExactTokens( vaultInMax:<-vaultInMax, exactAmountOut: exactAmountOut, tokenKeyPath: path, deadline: deadline )let vaultOut <- swapResVault.removeFirst()let vaultInLeft <- swapResVault.removeLast() destroy swapResVaultoutReceiverRef.deposit(from: <-vaultOut)/// Deposit any remaining input FT backinVaultRef.deposit(from: <-vaultInLeft) }}
Note: Using flow-clitool in the commandline environment, or fcl-jslibrary as long with tx.cdc / script.cdc to interact with smart contracts directly.
*SwapRouter provides an easy way to perform chained swaps among non-stableswap pairs, but it won't work acrossing stableswap pairs: as for any two given tokens, there could be two SwapPair for them, the existing SwapRouter interfaces cannot easily support it. To perform chained swap among non-stableswap pairs and stableswap pairs, one needs to take an address array of pairs and understand SwapPair's raw apis (read below).
Helper functions and Raw apis
Helper functions in SwapConfig
/// SwapConfig.cdc// For a non-stableswap pair (under the standard constant product formula x * y = k)// Given pair reserves, swapFeeRateBps(0 ~ 10000), and the exact input amount of an asset, returns the maximum output amount of the other asset.pub fun getAmountOutVolatile(amountIn: UFix64, reserveIn: UFix64, reserveOut: UFix64, swapFeeRateBps: UInt64): UFix64// For a non-stableswap pair, given pair reserves, swapFeeRateBps, and the exact output amount of an asset wanted, returns the required (minimum) input amount of the other assetpub fun getAmountInVolatile(amountOut: UFix64, reserveIn: UFix64, reserveOut: UFix64, swapFeeRateBps: UInt64): UFix64// For a stableswap pair (under the solidly-stableswap formula: x^3 * y + x * y^3 = k)// Given pair reserves, swapFeeRateBps, stableswap p-value(usually 1.0), and the exact input amount of an asset, returns the maximum output amount of the other assetpub fun getAmountOutStable(amountIn: UFix64, reserveIn: UFix64, reserveOut: UFix64, p: UFix64, swapFeeRateBps: UInt64): UFix64// For a stableswap pair, given pair reserves, swapFeeRateBps, stableswap p-value, and the exact input amount of an asset, returns the required (minimum) input amount of the other assetpub fun getAmountInStable(amountOut: UFix64, reserveIn: UFix64, reserveOut: UFix64, p: UFix64, swapFeeRateBps: UInt64): UFix64// Can be used to compute the spot price of a non-stableswap pair.pub fun quote(amountA: UFix64, reserveA: UFix64, reserveB: UFix64): UFix64// Can be used to compute the spot price of a stableswap pair.pub fun quoteStable(amountA: UFix64, reserveA: UFix64, reserveB: UFix64, p: UFix64): UFix64// Calculates the latest cumulative price of the given SwapPair, using the last cumulative record and current spot price.// Usually a helper function in building dex-based TWAP oracle.pub fun getCurrentCumulativePrices(pairAddr: Address): [UInt256; 3]
Raw apis in SwapPair
/// Each SwapPair.cdc// The default rate is: 30 bps (0.3%) for non-stableswap pairs, and 4 bps (0.04%) for stableswap pairs.pub fun getSwapFeeBps(): UInt64pub fun isStableSwap(): Bool// 1.0 for all non-stableswap pairs and most stableswap pairs. For now the only stableswap pair with a non-1.0 p-value is the `stFlow<>Flow` pair.pub fun getStableCurveP(): UFix64pub fun getAmountIn(amountOut: UFix64, tokenOutKey: String): UFix64pub fun getAmountOut(amountIn: UFix64, tokenInKey: String): UFix64// DEX-based TWAP oracle related helpful data, see twap oracle related section below for more examples.pub fun getPrice0CumulativeLastScaled(): UInt256pub fun getPrice1CumulativeLastScaled(): UInt256pub fun getBlockTimestampLast(): UFix64// Raw swap function, one can swap with pair contract directly and build any of the chained routes.// If `exactAmountOut` is not-nil then a safety check ensures swapped output will not be smaller than the given value.pub fun swap(vaultIn: @FungibleToken.Vault, exactAmountOut: UFix64?): @FungibleToken.Vault// Request a flashloan from the current SwapPair, the default interest rate is 5 bps (0.05%). // See flashloan related section below for explanations and examples.pub fun flashloan(executorCap: Capability<&{SwapInterfaces.FlashLoanExecutor}>, requestedTokenVaultType: Type, requestedAmount: UFix64, params: {String: AnyStruct})
Add & Remove Liquidity
AddLiquidity
/// SwapPair.cdcpub fun addLiquidity( tokenAVault: @FungibleToken.Vault, tokenBVault: @FungibleToken.Vault): @FungibleToken.Vault
It's not recommended to use the low-level addLiquidity method directly, unless you're the 1st LP to set the initial price.
Use below example transaction to do slippage check and add liquidity at the ideal ratio:
DEX-based TWAP (time-weighted-average-price) oracles can be built using the last cumulative prices recorded in each SwapPair.
However, to correctly use & integrate the twap-oracle into your projects, you must ensure the sampling of the cumulative price data are kept up to date. As long as your oracle is up to date, you can depend on it to produce average prices.
Check below 2 examples for the sampling (update()) and twap-data consuming (twap()).
Fixed-window TWAP oracle example:
import StableSwapFactory from 0xStableSwapFactoryAddrimport SwapFactory from 0xSwapFactoryAddrimport SwapInterfaces from 0xSwapInterfacesAddrimport SwapConfig from 0xSwapConfigAddr/// Fixed window oracle////// Calculate the average price for the entire period based on the on-chain dex swap pair./// note that the price average is only guaranteed to be over at least 1 period, but may be over a longer period///pub contract FixedWindowOracleExample {/// Window period of the average in seconds pub letPERIOD:UInt64/// A.contractAddr.contractName: A.11111111.FlowToken, A.2222222.FUSD pub let token0Key:String pub let token1Key:String pub let isStableswap:Bool/// pair address in dex pub let pairAddr:Address/// Average price for each PERIOD, updated once per PERIOD (updating interval could be longer than 1 PERIOD) pub var price0Average:UFix64 pub var price1Average:UFix64/// Cumulative price/timestamp for the last update pub var price0CumulativeLastScaled:UInt256 pub var price1CumulativeLastScaled:UInt256 pub var blockTimestampLast:UFix64/// Sampling: update the accumulated price if it exceeds the period. pub fun update() {let now =getCurrentBlock().timestamplet timeElapsed = now -self.blockTimestampLastassert(timeElapsed >=UFix64(self.PERIOD), message: "PERIOD_NOT_ELAPSED ".concat(timeElapsed.toString().concat("s")))let res =SwapConfig.getCurrentCumulativePrices(pairAddr: self.pairAddr)let currentPrice0CumulativeScaled = res[0]let currentPrice1CumulativeScaled = res[1]let timeElapsedScaled =SwapConfig.UFix64ToScaledUInt256(timeElapsed)let price0AverageScaled =SwapConfig.underflowSubtractUInt256(currentPrice0CumulativeScaled,self.price0CumulativeLastScaled) *SwapConfig.scaleFactor / timeElapsedScaledlet price1AverageScaled =SwapConfig.underflowSubtractUInt256(currentPrice1CumulativeScaled,self.price1CumulativeLastScaled) *SwapConfig.scaleFactor / timeElapsedScaledself.price0Average =SwapConfig.ScaledUInt256ToUFix64(price0AverageScaled)self.price1Average =SwapConfig.ScaledUInt256ToUFix64(price1AverageScaled)self.price0CumulativeLastScaled = currentPrice0CumulativeScaledself.price1CumulativeLastScaled = currentPrice1CumulativeScaledself.blockTimestampLast = now }/// Queries twap price data/// Returns 0.0 for data n/a or invalid input token pub fun twap(tokenKey: String): UFix64 {if (tokenKey ==self.token0Key) {returnself.price0Average } elseif (tokenKey ==self.token1Key) {returnself.price1Average } else {return0.0 } }/// @Param - token{A|B}Key: e.g. A.f8d6e0586b0a20c7.FUSD/// @Param - isStableswap: whether the twap is for stableswap pair or not/// @Param - period: average period (in seconds)init(tokenAKey: String, tokenBKey: String, isStableswap: Bool, period: UInt64) {self.PERIOD= periodself.isStableswap = isStableswapself.pairAddr = isStableswap ?StableSwapFactory.getPairAddress(token0Key: tokenAKey, token1Key: tokenBKey) ??panic("non-existent stableswap-pair") :SwapFactory.getPairAddress(token0Key: tokenAKey, token1Key: tokenBKey) ??panic("non-existent pair")let pairPublicRef =getAccount(self.pairAddr).getCapability<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath).borrow()??panic("cannot borrow reference to PairPublic")let pairInfo =pairPublicRef.getPairInfo()self.token0Key = pairInfo[0] as! Stringself.token1Key = pairInfo[1] as! Stringlet reserve0 = pairInfo[2] as! UFix64let reserve1 = pairInfo[3] as! UFix64assert(reserve0 * reserve1 !=0.0, message: "There's no liquidity in the pair")self.price0CumulativeLastScaled =pairPublicRef.getPrice0CumulativeLastScaled()self.price1CumulativeLastScaled =pairPublicRef.getPrice1CumulativeLastScaled()self.blockTimestampLast =pairPublicRef.getBlockTimestampLast()self.price0Average =0.0self.price1Average =0.0 }}
Sliding-window TWAP oracle example
import StableSwapFactory from 0xStableSwapFactoryAddrimport SwapFactory from 0xSwapFactoryAddrimport SwapInterfaces from 0xSwapInterfacesAddrimport SwapConfig from 0xSwapConfigAddr/// Sliding window oracle///pub contract SlidingWindowOracleExample {/// The amount of time (in seconds) the moving average should be computed, e.g. 24 hours pub let windowSize:UInt64/// The number of observation data stored for windowSize./// As granularity increases from 2, more frequent updates are needed, but moving averages become more precise./// twap data is computed over intervals with sizes in the range: [windowSize - (windowSize / granularity) * 2, windowSize] pub let granularity:UInt64/// The amount of time once an update() is needed, periodSize * granularity == windowSize. pub let periodSize:UInt64/// A.contractAddr.contractName: A.11111111.FlowToken, A.2222222.FUSD pub let token0Key:String pub let token1Key:String pub let isStableswap:Bool/// pair address in dex pub let pairAddr:Address/// An array of price observation data of the pairaccess(self) let pairObservations: [Observation] pub struct Observation { pub let timestamp:UFix64 pub let price0CumulativeScaled:UInt256 pub let price1CumulativeScaled:UInt256init(t: UFix64, p0Scaled: UInt256, p1Scaled: UInt256) {self.timestamp = tself.price0CumulativeScaled = p0Scaledself.price1CumulativeScaled = p1Scaled } }/// Returns the index of the observation corresponding to the given timestamp pub fun observationIndexOf(timestamp: UFix64): UInt64 {returnUInt64(timestamp) /self.periodSize %self.granularity }/// Returns the index of the earliest observation of a windowSize (relative to the given timestamp) pub fun firstObservationIndexInWindow(timestamp: UFix64): UInt64 {let idx =self.observationIndexOf(timestamp: timestamp)return (idx +1) %self.granularity }/// Sampling: update the cumulative price for the observation at the current timestamp./// Each observation is updated at most once per periodSize. pub fun update() {let now =getCurrentBlock().timestamplet idx =self.observationIndexOf(timestamp: now)let ob =self.pairObservations[idx]let timeElapsed = now -ob.timestampif (timeElapsed >UFix64(self.periodSize)) {let timeElapsedScaled =SwapConfig.UFix64ToScaledUInt256(timeElapsed)let res =SwapConfig.getCurrentCumulativePrices(pairAddr: self.pairAddr)let currentPrice0CumulativeScaled = res[0]let currentPrice1CumulativeScaled = res[1]self.pairObservations[idx] =Observation(t: now, p0Scaled: currentPrice0CumulativeScaled, p1Scaled: currentPrice1CumulativeScaled) } }/// Queries twap price data of the time range [now - [windowSize, windowSize - 2 * periodSize], now]/// Returns 0.0 for data n/a or invalid input token pub fun twap(tokenKey: String): UFix64 {let now =getCurrentBlock().timestamplet first_ob_idx =self.firstObservationIndexInWindow(timestamp: now)let first_ob =self.pairObservations[first_ob_idx]let timeElapsed = now -first_ob.timestampassert(UInt64(timeElapsed) <=self.windowSize, message: "missing historical observations, more update() needed")assert(UInt64(timeElapsed) >=self.windowSize -self.periodSize *2, message: "should never happen")let res =SwapConfig.getCurrentCumulativePrices(pairAddr: self.pairAddr)let currentPrice0CumulativeScaled = res[0]let currentPrice1CumulativeScaled = res[1]let timeElapsedScaled =SwapConfig.UFix64ToScaledUInt256(timeElapsed)if (tokenKey ==self.token0Key) {let price0AverageScaled =SwapConfig.underflowSubtractUInt256(currentPrice0CumulativeScaled,first_ob.price0CumulativeScaled) *SwapConfig.scaleFactor / timeElapsedScaledreturnSwapConfig.ScaledUInt256ToUFix64(price0AverageScaled) } elseif (tokenKey ==self.token1Key) {let price1AverageScaled =SwapConfig.underflowSubtractUInt256(currentPrice1CumulativeScaled,first_ob.price1CumulativeScaled) *SwapConfig.scaleFactor / timeElapsedScaledreturnSwapConfig.ScaledUInt256ToUFix64(price1AverageScaled) } else {return0.0 } }/// @Param - token{A|B}Key: e.g. A.f8d6e0586b0a20c7.FUSD/// @Param - isStableswap: whether the twap is for stableswap pair or not/// @Param - windowSize: The amount of time (in seconds) the moving average should be computed, e.g.: 24 hours (86400)/// @Param - granularity: The number of observation data stored for windowSize, e.g.: 24. The more granularity, the more precise the moving average, but with the cost of more frequent updates are needed.init(tokenAKey: String, tokenBKey: String, isStableswap: Bool, windowSize: UInt64, granularity: UInt64) { pre { granularity >1&& granularity <= windowSize:"invalid granularity" windowSize / granularity * granularity == windowSize:"windowSize not-divisible by granularity" } post {UInt64(self.pairObservations.length) == granularity:"pairObservations array not initialized" }self.windowSize = windowSizeself.granularity = granularityself.periodSize = windowSize / granularityself.isStableswap = isStableswapself.pairAddr = isStableswap ?StableSwapFactory.getPairAddress(token0Key: tokenAKey, token1Key: tokenBKey) ??panic("non-existent stableswap-pair") :SwapFactory.getPairAddress(token0Key: tokenAKey, token1Key: tokenBKey) ??panic("non-existent pair")let pairPublicRef =getAccount(self.pairAddr).getCapability<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath).borrow()??panic("cannot borrow reference to PairPublic")let pairInfo =pairPublicRef.getPairInfo()self.token0Key = pairInfo[0] as! Stringself.token1Key = pairInfo[1] as! Stringlet reserve0 = pairInfo[2] as! UFix64let reserve1 = pairInfo[3] as! UFix64assert(reserve0 * reserve1 !=0.0, message: "There's no liquidity in the pair")self.pairObservations = []var i:UInt64=0while (i < granularity) {self.pairObservations.append(Observation(t: 0.0, p0Scaled: 0, p1Scaled: 0)) i = i +1 } }}
Flashloan
Flashloan Interfaces
/// interface in SwapInterfaces.cdc/// Before using the flashloan you need to first implement this interface and plugin customized logic (see example below)pub resource interfaceFlashLoanExecutor {/// @params: User-definited extra data passed to executor for further auth/check/decode pub fun executeAndRepay(loanedToken: @FungibleToken.Vault, params: {String:AnyStruct}): @FungibleToken.Vault}
Example usage
// Example_FlashloanLiquidation.cdcimport FungibleToken from 0xFungibleTokenAddrimport SwapConfig from 0xSwapConfigAddrimport SwapFactory from 0xSwapFactoryAddrimport SwapInterfaces from 0xSwapInterfacesAddrpub contract Example_FlashloanLiquidation {// Specific address to receive flashloan-liquidation profits, used as auth purposes pub let profitReceiver:Address/// Implement the flashloan interface pub resource FlashloanExecutor:SwapInterfaces.FlashLoanExecutor { pub fun executeAndRepay(loanedToken: @FungibleToken.Vault, params: {String: AnyStruct}): @FungibleToken.Vault { pre {params.containsKey("profitReceiver") && ((params["profitReceiver"]! as! Address) ==Example_FlashloanLiquidation.profitReceiver): "not-authorized caller" }/* Do magic - custom logic goes here. E.g.: - 0. Flashloan request $USDC from FUSD/USDC pool (in `do_flashloan.transaction.cdc`) - 1. Liquidate underwater borrower by repaying borrowed $USDC and grab borrower's collateralized $Flow - 2. Swap $Flow -> $USDC through IncrementSwap (cannot use flashloan-ed pool then) or BloctoSwap - 3. Repay {flashloan-ed $USDC + fees} back to FUSD/USDC pool and keep remaining $USDC as profit */// TODO: plugin detailed example herelet amountIn =loanedToken.balance// TODO: plugin detailed example here/// amountOut = amountIn x (1 + fee%)let amountOut = amountIn * (1.0+UFix64(SwapFactory.getFlashloanRateBps()) /10000.0) +SwapConfig.ufix64NonZeroMin// TODO: modify thisreturn<-loanedToken } }init(profitReceiver: Address) {self.profitReceiver = profitReceiver// Set up FlashLoanExecutor resourcelet pathStr ="swap_flashloan_executor_path"let executorPrivatePath =PrivatePath(identifier: pathStr)!let executorStoragePath =StoragePath(identifier: pathStr)! destroy <-self.account.load<@AnyResource>(from:executorStoragePath) self.account.save(<- create FlashloanExecutor(), to: executorStoragePath) self.account.link<&{SwapInterfaces.FlashLoanExecutor}>(executorPrivatePath, target:executorStoragePath) }}
/// do_flashloan.transaction.cdc - tx that triggers flashloan from a SwapPairimport FungibleToken from 0xFungibleTokenAddrimport SwapConfig from 0xSwapConfigAddrimport SwapFactory from 0xSwapFactoryAddrimport SwapInterfaces from 0xSwapInterfacesAddr/* E.g.: Flashloan request only $USDC from FUSD/USDC pool*/transaction(pairAddr: Address, requestedVaultType: Type, requestedAmount: UFix64) {prepare(signer: AuthAccount) {let pairRef =getAccount(pairAddr).getCapability<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath).borrow()??panic("cannot borrow reference to PairPublic")// TODO: add additional args? and generalize this transactionlet args: {String:AnyStruct} = {"profitReceiver":signer.address }let executorCap =signer.getCapability<&{SwapInterfaces.FlashLoanExecutor}>(/private/swap_flashloan_executor_path)pairRef.flashloan(executorCap: executorCap, requestedTokenVaultType: requestedVaultType, requestedAmount: requestedAmount, params: args) }}