Math
Math Library
The following libraries implement the math below.
https://gitlab.com/thorchain/asgardex-common/asgardex-util/-/tree/master/src/calc
https://github.com/xchainjs/xchainjs-lib/blob/master/packages/xchain-thorchain-query/src/utils/swap.ts
Example Data
All the examples below use the following snapshotted BTC and BUSD Pool data
https://thornode.ninerealms.com/thorchain/pool/BTC.BTC https://thornode.ninerealms.com/thorchain/pool/BNB.BUSD-BD1
{
"LP_units": "117582615428135",
"asset": "BNB.BUSD-BD1",
"balance_asset": "952382623537567",
"balance_rune": "508868258770825",
"pending_inbound_asset": "310872270739",
"pending_inbound_rune": "1701596418307",
"pool_units": "134664599295503",
"status": "Available",
"synth_supply": "241616351972821",
"synth_units": "17081983867368"
},
{
"LP_units": "476785169622350",
"asset": "BTC.BTC",
"balance_asset": "81439552768",
"balance_rune": "863897777396922",
"pending_inbound_asset": "386699833",
"pending_inbound_rune": "216117023429",
"pool_units": "492710913491074",
"status": "Available",
"synth_supply": "5264691415",
"synth_units": "15925743868724"
}
Prices
price = \frac{quoteBalance}{baseBalance}=\frac{USD}{RUNE} = $RUNE
Prices of all assets on THORChain are in ratios of each other, based on the depths of the pools. The quote asset is the "pricing" asset, and the base asset is the asset to be quoted. Ie, for the $ value of RUNE, the quote asset is USD and the base asset is RUNE.
Example
Let's take the BTC and BUSD Pool data
https://thornode.ninerealms.com/thorchain/pool/BTC.BTC
https://thornode.ninerealms.com/thorchain/pool/BNB.BUSD-BD1
The $BTC Price of RUNE is BTC/RUNE = 81439552768/863897777396922 = 0.000094 BTC
The $BUSD price of BTC is (BUSD/RUNE) * (RUNE/BTC) = (952382623537567/508868258770825) * (863897777396922/81439552768) = 19,854 BUSD
export const getValueOfAssetInRune = (inputAsset: BaseAmount, pool: PoolData): BaseAmount => {
// formula: ((a * R) / A) => R per A (Runeper$)
const t = inputAsset.amount()
const R = pool.runeBalance.amount()
const A = pool.assetBalance.amount()
const result = t.times(R).div(A)
return baseAmount(result)
}
export const getValueOfRuneInAsset = (inputRune: BaseAmount, pool: PoolData): BaseAmount => {
// formula: ((r * A) / R) => A per R ($perRune)
const r = inputRune.amount()
const R = pool.runeBalance.amount()
const A = pool.assetBalance.amount()
const result = r.times(A).div(R)
return baseAmount(result)
}
export const getValueOfAsset1InAsset2 = (inputAsset: BaseAmount, pool1: PoolData, pool2: PoolData): BaseAmount => {
// formula: (A2 / R) * (R / A1) => A2/A1 => A2 per A1 ($ per Asset)
const oneAsset = assetToBase(assetAmount(1))
// Note: All calculation needs to be done in `AssetAmount` (not `BaseAmount`)
const A2perR = baseToAsset(getValueOfRuneInAsset(oneAsset, pool2))
const RperA1 = baseToAsset(getValueOfAssetInRune(inputAsset, pool1))
const result = A2perR.amount().times(RperA1.amount())
// transform result back from `AssetAmount` into `BaseAmount`
return assetToBase(assetAmount(result))
}
Slippage
Slippage is simply the transaction divided by its corresponding depth.
Ie, swapping 10 BTC to RUNE = 1000000000 / (1000000000 + 81439552768) = 0.012 = 1.2%
Since THORChain has all pools in RUNE, a cross-asset (double) swap would involve two swaps in two pools, thus the slip needs to be doubled.
Here's a reference implementation of calculating slip for a double swap:
// Calculate swap output with slippage
function calcSwapOutput(inputAmount, pool, toRune) {
// formula: (inputAmount * inputBalance * outputBalance) / (inputAmount + inputBalance) ^ 2
const inputBalance = toRune ? pool.assetBalance : pool.runeBalance; // input is asset if toRune
const outputBalance = toRune ? pool.runeBalance : pool.assetBalance; // output is rune if toRune
const numerator = inputAmount * inputBalance * outputBalance;
const denominator = Math.pow(inputAmount + inputBalance, 2);
const result = numerator / denominator;
return result;
}
// Calculate swap slippage
function calcSwapSlip(inputAmount, pool, toRune) {
// formula: (inputAmount) / (inputAmount + inputBalance)
const inputBalance = toRune ? pool.assetBalance : pool.runeBalance; // input is asset if toRune
const result = inputAmount / (inputAmount + inputBalance);
return result;
}
// Calculate swap slippage for double swap
function calcDoubleSwapSlip(inputAmount, pool1, pool2) {
// formula: calcSwapSlip1(input1) + calcSwapSlip2(calcSwapOutput1 => input2)
const swapSlip1 = calcSwapSlip(inputAmount, pool1, true);
const r = calcSwapOutput(inputAmount, pool1, true);
const swapSlip2 = calcSwapSlip(r, pool2, false);
const result = swapSlip1 + swapSlip2;
return result;
}
Source: https://gitlab.com/thorchain/asgardex-common/asgardex-util/-/blob/master/src/calc/swap.ts
Swap Output
The output in a swap is the CLP formula.
Ie, output after swapping 10 BTC: (1000000000 * 81439552768 * 863897777396922)/ (1000000000 + 81439552768)^2 = 10352052898302 = 103520 RUNE
export const getSwapOutput = (inputAmount: BaseAmount, pool: PoolData, toRune: boolean): BaseAmount => {
// formula: (x * X * Y) / (x + X) ^ 2
const x = inputAmount.amount()
const X = toRune ? pool.assetBalance.amount() : pool.runeBalance.amount() // input is asset if toRune
const Y = toRune ? pool.runeBalance.amount() : pool.assetBalance.amount() // output is rune if toRune
const numerator = x.times(X).times(Y)
const denominator = x.plus(X).pow(2)
const result = numerator.div(denominator)
return baseAmount(result)
}
export const getDoubleSwapOutput = (inputAmount: BaseAmount, pool1: PoolData, pool2: PoolData): BaseAmount => {
// formula: getSwapOutput(pool1) => getSwapOutput(pool2)
const r = getSwapOutput(inputAmount, pool1, true)
const output = getSwapOutput(r, pool2, false)
return output
}
Swap Input
X = inputBalance
Y = outputBalance
, y = outputAmount
The swap formula can be reversed to specify what needs to be deposited to get a certain output.
export const getSwapInput = (toRune: boolean, pool: PoolData, outputAmount: BaseAmount): BaseAmount => {
// formula: (((X*Y)/y - 2*X) - sqrt(((X*Y)/y - 2*X)^2 - 4*X^2))/2
// (part1 - sqrt(part1 - part2))/2
const X = toRune ? pool.assetBalance.amount() : pool.runeBalance.amount() // input is asset if toRune
const Y = toRune ? pool.runeBalance.amount() : pool.assetBalance.amount() // output is rune if toRune
const y = outputAmount.amount()
const part1 = X.times(Y).div(y).minus(X.times(2))
const part2 = X.pow(2).times(4)
const result = part1.minus(part1.pow(2).minus(part2).sqrt()).div(2)
return baseAmount(result)
}
LP Units Add
- (P): Existing Pool Units
- (R): runeBalance, (A): assetBalance
- (r): runeAdded, (a): assetAdded
The units to give an LP depend on the existing units, as well as the assets they are adding, and the depths of the pool they are adding to.
LP Units Withdrawn
- (L): Liquidity units owned
- (P): Pool Units
- (X): depth of side
THORChain allows LPs to redeem a Basis Points amount of their position (out of 10000). To find out how much the user will get, multiply this by each side.
export const getPoolShare = (unitData: UnitData, pool: PoolData): StakeData => {
// formula: (rune * part) / total; (asset * part) / total
const units = unitData.stakeUnits.amount()
const total = unitData.totalUnits.amount()
const R = pool.runeBalance.amount()
const T = pool.assetBalance.amount()
const asset = T.times(units).div(total)
const rune = R.times(units).div(total)
const stakeData = {
asset: baseAmount(asset),
rune: baseAmount(rune)
}
return stakeData
}