Skip to content

Commit

Permalink
Merge pull request #167 from peanutprotocol/develop
Browse files Browse the repository at this point in the history
Release smart slippage calculation
  • Loading branch information
Hugo0 authored Nov 19, 2024
2 parents b9a9e5c + 15ff0e4 commit e7ee437
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 38 deletions.
1 change: 1 addition & 0 deletions src/consts/interfaces.consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export interface IPrepareXchainRequestFulfillmentTransactionResponse {
unsignedTxs: IPeanutUnsignedTransaction[]
feeEstimation: string
estimatedFromAmount: string
slippagePercentage: number
}

//signAndSubmitTx
Expand Down
55 changes: 27 additions & 28 deletions src/request.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ethers, getDefaultProvider, utils } from 'ethersv5'
import { ethers, getDefaultProvider } from 'ethersv5'
import { EPeanutLinkType, IPeanutUnsignedTransaction } from './consts/interfaces.consts'
import { ERC20_ABI, LATEST_STABLE_BATCHER_VERSION } from './data'
import { config, getSquidRoute, interfaces, prepareApproveERC20Tx, resolveFromEnsName } from '.'
import { prepareXchainFromAmountCalculation, normalizePath } from './util'
import { config, interfaces, prepareApproveERC20Tx, resolveFromEnsName } from '.'
import { normalizePath, routeForTargetAmount } from './util'

// INTERFACES
export interface ICreateRequestLinkProps {
Expand Down Expand Up @@ -50,6 +50,7 @@ export type IPrepareXchainRequestFulfillmentTransactionProps = {
squidRouterUrl: string
provider: ethers.providers.Provider
tokenType: EPeanutLinkType
slippagePercentage?: number
} & (
| {
link: string
Expand Down Expand Up @@ -186,7 +187,16 @@ export async function getRequestLinkDetails(
export async function prepareXchainRequestFulfillmentTransaction(
props: IPrepareXchainRequestFulfillmentTransactionProps
): Promise<interfaces.IPrepareXchainRequestFulfillmentTransactionResponse> {
let { senderAddress, fromToken, fromTokenDecimals, fromChainId, squidRouterUrl, provider, tokenType } = props
let {
senderAddress,
fromToken,
fromTokenDecimals,
fromChainId,
squidRouterUrl,
provider,
tokenType,
slippagePercentage,
} = props
let linkDetails: Pick<
IGetRequestLinkDetailsResponse,
'chainId' | 'recipientAddress' | 'tokenAmount' | 'tokenDecimals' | 'tokenAddress'
Expand Down Expand Up @@ -246,32 +256,15 @@ export async function prepareXchainRequestFulfillmentTransaction(
chainId: destinationChainId,
decimals: destinationTokenDecimals,
}
const estimatedFromAmount = await prepareXchainFromAmountCalculation({

const { estimatedFromAmount, weiFromAmount, routeResult, finalSlippage } = await routeForTargetAmount({
slippagePercentage,
fromToken: fromTokenData,
toAmount: destinationTokenAmount,
toToken: toTokenData,
slippagePercentage: 0.3, // this can be low because squid will add slippage
})

console.log('estimatedFromAmount', estimatedFromAmount)
if (!estimatedFromAmount) {
throw new Error('Failed to estimate from amount')
}
// get wei of amount being withdrawn and send as string (e.g. "10000000000000000")
const tokenAmount = utils.parseUnits(estimatedFromAmount, fromTokenDecimals)
config.verbose && console.log('Getting squid info..')
const unsignedTxs: interfaces.IPeanutUnsignedTransaction[] = []

const routeResult = await getSquidRoute({
targetAmount: destinationTokenAmount,
squidRouterUrl,
fromChain: fromChainId,
fromToken: fromToken,
fromAmount: tokenAmount.toString(),
toChain: destinationChainId,
toToken: destinationToken,
fromAddress: senderAddress,
toAddress: recipientAddress,
enableBoost: true,
})

// Transaction estimation from Squid API allows us to know the transaction fees (gas and fee), then we can iterate over them and add the values ​​that are in dollars
Expand All @@ -294,6 +287,8 @@ export async function prepareXchainRequestFulfillmentTransaction(
})
}

const unsignedTxs: interfaces.IPeanutUnsignedTransaction[] = []

if (tokenType == EPeanutLinkType.native) {
txOptions = {
...txOptions,
Expand All @@ -305,7 +300,7 @@ export async function prepareXchainRequestFulfillmentTransaction(
senderAddress,
destinationChainId,
fromToken,
tokenAmount,
weiFromAmount,
fromTokenDecimals,
true,
LATEST_STABLE_BATCHER_VERSION,
Expand Down Expand Up @@ -334,7 +329,12 @@ export async function prepareXchainRequestFulfillmentTransaction(
value: BigInt(routeResult.value.toString()),
})

return { unsignedTxs, feeEstimation: feeEstimation.toString(), estimatedFromAmount }
return {
unsignedTxs,
feeEstimation: feeEstimation.toString(),
estimatedFromAmount,
slippagePercentage: finalSlippage,
}
}

export function prepareRequestLinkFulfillmentTransaction({
Expand Down Expand Up @@ -378,7 +378,6 @@ export async function submitRequestLinkFulfillment({
chainId,
hash,
payerAddress,
signedTx,
apiUrl = 'https://api.peanut.to/',
link,
amountUsd,
Expand Down
168 changes: 158 additions & 10 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { config } from './config.ts'
import * as interfaces from './consts/interfaces.consts.ts'
import { ANYONE_WITHDRAWAL_MODE, PEANUT_SALT, RECIPIENT_WITHDRAWAL_MODE } from './consts/misc.ts'
import { TransactionRequest } from '@ethersproject/abstract-provider'
import { getSquidRoute } from '.'

export function assert(condition: any, message: string) {
if (!condition) {
Expand Down Expand Up @@ -685,7 +686,13 @@ export function compareVersions(version1: string, version2: string, lead: string
return true
}

async function getTokenPrice({ tokenAddress, chainId }: { tokenAddress: string; chainId: string | number }) {
export async function getTokenPrice({
tokenAddress,
chainId,
}: {
tokenAddress: string
chainId: string | number
}): Promise<number> {
const response = await fetch(
'https://api.0xsquid.com/v1/token-price?' + new URLSearchParams({ tokenAddress, chainId: chainId.toString() })
)
Expand All @@ -704,11 +711,15 @@ export async function prepareXchainFromAmountCalculation({
toAmount,
toToken,
slippagePercentage = 0.3, // 0.3%
fromTokenPrice,
toTokenPrice,
}: {
fromToken: TokenData
toToken: TokenData
toAmount: string
slippagePercentage?: number
fromTokenPrice?: number
toTokenPrice?: number
}): Promise<string | null> {
if (slippagePercentage < 0) {
console.error('Invalid slippagePercentage: Cannot be negative.')
Expand All @@ -717,15 +728,19 @@ export async function prepareXchainFromAmountCalculation({

try {
// Get usd prices for both tokens
const [fromTokenPrice, toTokenPrice] = await Promise.all([
getTokenPrice({
chainId: fromToken.chainId,
tokenAddress: fromToken.address,
}),
getTokenPrice({
chainId: toToken.chainId,
tokenAddress: toToken.address,
}),
;[fromTokenPrice, toTokenPrice] = await Promise.all([
fromTokenPrice
? Promise.resolve(fromTokenPrice)
: getTokenPrice({
chainId: fromToken.chainId,
tokenAddress: fromToken.address,
}),
toTokenPrice
? Promise.resolve(toTokenPrice)
: getTokenPrice({
chainId: toToken.chainId,
tokenAddress: toToken.address,
}),
])

// Normalize prices to account for different decimal counts between tokens.
Expand All @@ -750,6 +765,139 @@ export async function prepareXchainFromAmountCalculation({
}
}

async function estimateRouteWithMinSlippage({
slippagePercentage,
fromToken,
toToken,
targetAmount,
squidRouterUrl,
fromAddress,
toAddress,
fromTokenPrice,
toTokenPrice,
}: {
slippagePercentage: number
fromToken: TokenData
toToken: TokenData
targetAmount: string
squidRouterUrl: string
fromAddress: string
toAddress: string
fromTokenPrice: number
toTokenPrice: number
}): Promise<{
estimatedFromAmount: string
weiFromAmount: ethers.BigNumber
routeResult: interfaces.ISquidRoute
}> {
const estimatedFromAmount = await prepareXchainFromAmountCalculation({
fromToken,
toAmount: targetAmount,
toToken,
slippagePercentage,
fromTokenPrice,
toTokenPrice,
})
console.log('estimatedFromAmount', estimatedFromAmount)
if (!estimatedFromAmount) {
throw new Error('Failed to estimate from amount')
}
const weiFromAmount = ethers.utils.parseUnits(estimatedFromAmount, fromToken.decimals)
const routeResult = await getSquidRoute({
squidRouterUrl,
fromChain: fromToken.chainId,
fromToken: fromToken.address,
fromAmount: weiFromAmount.toString(),
toChain: toToken.chainId,
toToken: toToken.address,
fromAddress,
toAddress,
enableBoost: true,
})
return { estimatedFromAmount, weiFromAmount, routeResult }
}

/**
* For a token pair and target amount calculates the minium from amount
* needed to get the target amount, and the squid route to get there
*/
export async function routeForTargetAmount({
slippagePercentage,
fromToken,
toToken,
targetAmount,
squidRouterUrl,
fromAddress,
toAddress,
}: {
slippagePercentage?: number
fromToken: TokenData
toToken: TokenData
targetAmount: string
squidRouterUrl: string
fromAddress: string
toAddress: string
}): Promise<{
estimatedFromAmount: string
weiFromAmount: ethers.BigNumber
routeResult: interfaces.ISquidRoute
finalSlippage: number
}> {
const [fromTokenPrice, toTokenPrice] = await Promise.all([
getTokenPrice({
chainId: fromToken.chainId,
tokenAddress: fromToken.address,
}),
getTokenPrice({
chainId: toToken.chainId,
tokenAddress: toToken.address,
}),
])

if (slippagePercentage) {
return {
...(await estimateRouteWithMinSlippage({
slippagePercentage,
fromToken,
toToken,
targetAmount,
squidRouterUrl,
fromAddress,
toAddress,
fromTokenPrice,
toTokenPrice,
})),
finalSlippage: slippagePercentage,
}
}

let result: { estimatedFromAmount: string; weiFromAmount: ethers.BigNumber; routeResult: interfaces.ISquidRoute }

const weiToAmount = ethers.utils.parseUnits(targetAmount, toToken.decimals)
let minToAmount: ethers.BigNumber = ethers.BigNumber.from(0)
slippagePercentage = 0
while (minToAmount.lt(weiToAmount)) {
result = await estimateRouteWithMinSlippage({
slippagePercentage,
fromToken,
toToken,
targetAmount,
squidRouterUrl,
fromAddress,
toAddress,
fromTokenPrice,
toTokenPrice,
})
minToAmount = ethers.BigNumber.from(result.routeResult.txEstimation.toAmountMin)
slippagePercentage += 0.1
if (5.0 < slippagePercentage) {
// we dont want to go over 5% slippage
throw new Error('Slippage percentage exceeded maximum allowed value')
}
}
return { ...result, finalSlippage: slippagePercentage }
}

export function normalizePath(url: string): string {
try {
const urlObject = new URL(url)
Expand Down

0 comments on commit e7ee437

Please sign in to comment.