Skip to main content

Dual Investment

Fetching Dyson Pairs information

After fetching our Factory data, we will obtain information about our Dyson pairs, including the contract address and two token addresses for token0 and token1.
In this SDK, we can fetching full Dyson pair data by getDysonPairInfos.

It will retrieve user data for boosting and farming, enabling the app to calculate $DYSN rewards Refer to this document to gain a comprehensive understanding of gauge and yield boosting.

note

For partial version deployed chain, we could leave farmAddress as undefined.
If user didn't connected wallet, we could keep account undefined.

Example:

tip

We got swapPoolConfigList from Factory

import { getDysonPairInfos } from '@dyson-finance/dyson-interface-sdk/reads'
import { DysonPair } from '@dyson-finance/dyson-interface-sdk/dist/entities'
import { zeroAddress } from 'viem'
import { getPublicClient } from 'wagmi/actions'

//...some async function

const swapPoolConfigList = Object.values(swapConfigMap)
// We get this data before

const client = getPublicClient({
chainId,
})

const { dysonPairInfoList } = await getDysonPairInfos(client, {
farmAddress,
account: account || zeroAddress,
pairConfigs: swapPoolConfigList,
})

//...

The type for dysonPairInfoList is an array of DysonPair objects. Let's take a look at its definition below.

// ...
export interface DysonPair {
pairAddress: string
basis: bigint
fee0: bigint
fee1: bigint
timeStamp0: number
timeStamp1: number
halfLife: bigint
token0Address: string
token1Address: string
token0Amount: bigint
token1Amount: bigint
noteCount: number | undefined
farmpairInfo: PoolStruct
}

export interface PoolStruct {
weight: bigint
rewardRate: bigint
lastUpdateTime: bigint
lastReserve: bigint
gauge: string
isPool?: boolean
}
// ...

Calculate dual investment info

When a user intends to invest in a dual investment, it necessitates the calculation of both the pool and the forthcoming user position.
This process aids in facilitating investment decisions and dynamically computing the requisite data for the contract.

For those unfamiliar with the concept of dual investment of Dyson Finance, please consult the documentation.

Current Fee

Due to potential discrepancies in timing between the app and data retrieval from the contract, it is essential to adjust for these time differences to ensure precise calculation of trading fees.
The SDK offers necessary functions to establish a hook for dynamically calculating current trading fees.

Example:

import { calcFeeWrapped } from '@dyson-finance/dyson-interface-sdk/calculations'

function useFeeValue(pairInfo: DysonPair | undefined) {
const [dateNow, setDateNow] = useState(~~(Date.now() / 1000))
const calcFee0 = useMemo(() => {
if (!pairInfo) {
return 0n
}
return calcFeeWrapped(
pairInfo.fee0,
BigInt(dateNow - pairInfo.timeStamp0),
pairInfo.halfLife,
)
}, [pairInfo?.fee0, pairInfo?.halfLife, pairInfo?.timeStamp0, dateNow])

const calcFee1 = useMemo(() => {
if (!pairInfo) {
return 0n
}
return calcFeeWrapped(
pairInfo.fee1,
BigInt(dateNow - pairInfo.timeStamp1),
pairInfo.halfLife,
)
}, [pairInfo?.fee1, pairInfo?.halfLife, pairInfo?.timeStamp1, dateNow])

useEffect(() => {
let id: any
if (pairInfo?.timeStamp0) {
const added = pairInfo?.timeStamp0 % 12
id = setInterval(() => setDateNow(~~(Date.now() / (1000 * 12)) * 12 + added), 12000)
}

return () => {
if (id) clearInterval(id)
}
}, [pairInfo?.timeStamp0 !== undefined])

return [calcFee0, calcFee1]
}

export default useFeeValue

Virtual Swap Output

Here, we assume that inputTokenType represents either token0 or token1 as the input token.

import { calcSwappedAmount } from '@dyson-finance/dyson-interface-sdk/calculations'
import { calcMinOutput } from '@dyson-finance/dyson-interface-sdk/calculations'
// ...

const dysonPair: DysonPair // one dyson pair in dysonPairInfoList
const [calcFee0, calcFee1] = useFeeValue(dysonPair)
const swapOutput =
inputTokenType === '0'
? calcSwappedAmount(
inputBigNumber,
dysonPair.token0Amount,
dysonPair.token1Amount,
calcFee0,
)
: calcSwappedAmount(
inputBigNumber,
selectPoolInfo.token1Amount,
selectPoolInfo.token0Amount,
calcFee1,
)
const minOutput = calcMinOutput(swapOutput, slippage, outputTokenData?.decimals || 18)
// example for slippage = '0.001' which means 0.1%
// ...

Fair Price for a Dual Investment pair

Here, we assume that quoteToken: '0'|'1' represents either token0 or token1 as the quote token.
The choice of quote token for every pair is left to the discretion of each individual application.

For a detailed explanation of the formula implemented here, please refer to our documentation on Fair Price.

import { calcStrikePriceByAmount } from '@dyson-finance/dyson-interface-sdk/calculations'
import { formatUnits } from 'viem'
// ...
const [calcFee0, calcFee1] = useFeeValue(poolInfo)
const fairPrice =
quoteToken === '1'
? calcFairPriceBigInt(
BigInt(calcFee1),
BigInt(calcFee0),
poolInfo.token1Amount,
poolInfo.token0Amount,
token1Data?.decimals || 18,
token0Data?.decimals || 18,
)
: calcFairPriceBigInt(
BigInt(calcFee0),
BigInt(calcFee1),
poolInfo.token0Amount,
poolInfo.token1Amount,
token0Data?.decimals || 18,
token1Data?.decimals || 18,
)
// ...

Strike Price for a Dual Investment

We assume that quoteToken represents either token0 or token1 as the quote token.
The choice of quote token for every pair is left to the discretion of each individual application.

import { calcStrikePriceByAmount } from '@dyson-finance/dyson-interface-sdk/calculations'
import { formatUnits } from 'viem'
// ...
const inputAmountFloat = parseFloat(
formatUnits(
inputBigNumber,
inputToken.decimals || 18,
),
)
const swapOutputFloat = parseFloat(
formatUnits(
swapOutput,
(inputTokenType === '0' ? token1Data?.decimals : token0Data?.decimals) || 18,
),
)
const strikePriceQuoteToken =
inputTokenType === quoteToken
? calcStrikePriceByAmount(swapOutputFloat, inputAmountFloat)
: calcStrikePriceByAmount(inputAmountFloat, swapOutputFloat)
// ...

Points, $DYSN Rewards calculation

Our points calculation document elucidates the computational procedures undertaken from dual investment to the acquisition of $DYSN rewards.

  1. Please refer to the Gauge Document to obtain the currentBoost for calculation.
  2. We have calculated the swapOutput at this stage.
  3. duration: This should be set to 1, 3, 7, or 30 days in seconds. We recommend using an enum for this purpose. (ex: 1 day: 86400)
    const dysonPair: DysonPair // one dyson pair in dysonPairInfoList
const premium = calcPremium(parseFloat(formatUnits(dysonPair.basis, 18)), duration) ?? 0

const points = localAPToGlobalAP(
calcLocalAP(inputBigNumber, swapOutput, premium, currentBoost),
getCurrentReserve(
parseFloat(formatUnits(dysonPair.farmPoolInfo.rewardRate, 18)),
parseFloat(formatUnits(dysonPair.farmPoolInfo.lastReserve, 18)),
Number(dysonPair.farmPoolInfo.lastUpdateTime),
),
parseFloat(formatUnits(dysonPair.farmPoolInfo.weight, 18)),
)

The rewarding section entails the calculation procedures for Points to $DYSN Rewards.

We make another function to calculate the $DYSN rewards directly by deposit on dual investment.

    const dysonPair: DysonPair // one dyson pair in dysonPairInfoList
const premium = calcPremium(parseFloat(formatUnits(dysonPair.basis, 18)), duration) ?? 0
const gaugePoolReserve = getCurrentReserve(
formatToFloat(dysonPair.farmPoolInfo.rewardRate, 18),
formatToFloat(dysonPair.farmPoolInfo.lastReserve, 18),
Number(dysonPair.farmPoolInfo.lastUpdateTime),
)

const globalReserve = getCurrentReserve(
farmInfo.rewardRate,
farmInfo.lastReserve,
farmInfo.lastUpdateTime,
)

const dysnAmount = calcDepositToGov(
inputBigNumber,
swapOutput,
premium,
currentBoost,
gaugePoolReserve,
formatToFloat(dysonPair.farmPoolInfo.weight, 18),
globalReserve,
farmInfo.w,
)

APR calculation

Please refer to the APR calculation document for instructions on calculating the APR for a dual investment.

We implement the function through calcTotalApr.

    const inputTokenValue: number = inputTokenAmount * inputTokenPrice  // without decimals
const dysnTokenValue: number = dysnAmount * dysnPrice // quote price of DYSN/USDC
const apr = calcTotalApr(premium, inputTokenValue, dysnTokenValue, duration) * 100

Performing a dual investment

In this section, we implement a code snippet to execute dual investments.
We create an interface for the deposit action parameters; please refer to IDepositParams.

Let's highlight a few key points for further clarification:

  1. addressTo: Users can create dual investment positions for others. If you wish to restrict this function, simply utilize the user's address.
  2. wrappedNativeToken: We will employ a different contract function for wrapped native tokens like WETH on Ethereum to handle pairs such as (WETH/USDC) and allow users to deposit ETH directly.
  3. minOutput: Utilize pre-calculated swapOutput and adjust based on the user's settled slippage.
  4. routerAddress: We use Router serves as an entry point for swapping, depositing, and withdrawing.
  5. duration: This should be set to 1, 3, 7, or 30 days in seconds. We recommend using an enum for this purpose.
export enum DurationDays {
DURATION_1DAY = 86400,
DURATION_3DAYS = 259200,
DURATION_7DAYS = 604800,
DURATION_30DAYS = 2592000,
}

Deposit dual investment example:

import { prepareInvestmentDeposit } from '@dyson-finance/dyson-interface-sdk/actions'
import {
prepareWriteContract,
writeContract,
waitForTransaction,
} from 'wagmi/actions'

// ... in async function
const client = await getWalletClient({ chainId })
const prepareResult = prepareInvestmentDeposit(client, {
tokenIn,
tokenOut,
addressTo: addressTo,
wrappedNativeToken: WRAPPED_NATIVE_TOKEN[chainId] as Address,
inputBigNumber,
minOutput,
duration,
})

const { request } = await prepareWriteContract({
...prepareResult,
address: routerAddress,
})

// Send transaction
const tx = await writeContract(request)

// Once the transaction is completed, developers can utilize the receipt to update the UI status.
const receipt = await waitForTransaction(tx)
// ...