Skip to content

Commit

Permalink
implement okx 1% (#331)
Browse files Browse the repository at this point in the history
* implement okx 1%

* relax test case

* remove getLogger due to 'Dependency with key logger not found' when used from backend.

* relax test case

* added okx approve transaction

* register loggers

* remove registering logger in sdk, should be done in backend instead

* review comments

* change logger format

* version

* make logs human readable
  • Loading branch information
tommyzhao451 authored Sep 12, 2024
1 parent 5b8eb78 commit 75986b4
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 44 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aperture_finance/uniswap-v3-automation-sdk",
"version": "3.10.0",
"version": "3.10.1",
"description": "SDK for Aperture's CLMM automation platform",
"author": "Aperture Finance <[email protected]>",
"license": "MIT",
Expand Down
9 changes: 4 additions & 5 deletions src/viem/solver/get1InchSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function apiRequestUrl(chainId: ApertureSupportedChainId, methodName: string) {
return new URL(`/swap/v5.2/${chainId}/${methodName}`, ApiBaseUrl).toString();
}

export async function get1inchApproveTarget(
export async function get1InchApproveTarget(
chainId: ApertureSupportedChainId,
): Promise<Address> {
try {
Expand Down Expand Up @@ -61,8 +61,7 @@ export const get1InchSolver = (): ISolver => {
throw new Error('Expected: Chain or AMM not support');
}

// get a quote from 1inch
const { tx, protocols } = await quote(
const { tx, protocols } = await get1InchQuote(
chainId,
zeroForOne ? token0 : token1,
zeroForOne ? token1 : token0,
Expand All @@ -72,7 +71,7 @@ export const get1InchSolver = (): ISolver => {
true,
);

const approveTarget = await get1inchApproveTarget(chainId);
const approveTarget = await get1InchApproveTarget(chainId);
return {
swapData: encodeOptimalSwapData(
chainId,
Expand Down Expand Up @@ -102,7 +101,7 @@ export const get1InchSolver = (): ISolver => {
* @param from Address of a seller, make sure that this address has approved to spend src in needed amount
* @param slippage Limit of price slippage you are willing to accept in percentage
*/
export async function quote(
export async function get1InchQuote(
chainId: ApertureSupportedChainId,
src: string,
dst: string,
Expand Down
163 changes: 163 additions & 0 deletions src/viem/solver/getOkxSolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { ApertureSupportedChainId, getAMMInfo } from '@/index';
import axios from 'axios';
import { Address, Hex } from 'viem';

import { encodeOptimalSwapData } from '../automan';
import { limiter } from './common';
import { ISolver } from './types';
import { SwapRoute } from './types';

const ApiBaseUrl = 'https://okx-api.aperture.finance';
const headers = {
Accept: 'application/json',
};

export async function buildRequest(methodName: string, params: object) {
return limiter.schedule(() =>
axios.get(apiRequestUrl(methodName), {
headers,
params,
}),
);
}

function apiRequestUrl(methodName: string) {
const rv = new URL(
`api/v5/dex/aggregator/${methodName}`,
ApiBaseUrl,
).toString();
console.log('apiRequestUrl', rv);
return rv;
}

export async function getOkxApproveTarget(
chainId: ApertureSupportedChainId,
tokenContractAddress: string,
approveAmount: string,
): Promise<Address> {
try {
return (
await buildRequest('approve-transaction', {
chainId,
tokenContractAddress,
approveAmount,
})
).data.data[0].dexContractAddress;
} catch (e) {
console.error(e);
throw e;
}
}

export const getOkxSolver = (): ISolver => {
return {
optimalMint: async (props) => {
const {
chainId,
amm,
token0,
token1,
feeOrTickSpacing,
tickLower,
tickUpper,
slippage,
poolAmountIn,
zeroForOne,
} = props;

const { optimalSwapRouter } = getAMMInfo(chainId, amm)!;
if (!optimalSwapRouter) {
throw new Error('Expected: Chain or AMM not support');
}

const { tx, protocols } = await getOkxQuote(
chainId,
zeroForOne ? token0 : token1,
zeroForOne ? token1 : token0,
poolAmountIn.toString(),
optimalSwapRouter,
slippage * 100,
);

const approveTarget = await getOkxApproveTarget(
chainId,
zeroForOne ? token0 : token1,
poolAmountIn.toString(),
);
return {
swapData: encodeOptimalSwapData(
chainId,
amm,
token0,
token1,
feeOrTickSpacing,
tickLower,
tickUpper,
zeroForOne,
approveTarget,
tx.to,
tx.data,
),
swapRoute: protocols,
};
},
};
};

/**
* Get a quote for a swap.
* @param chainId The chain ID.
* @param src Contract address of a token to sell
* @param dst Contract address of a token to buy
* @param amount Amount of a token to sell, set in minimal divisible units
* @param from Address of a seller, make sure that this address has approved to spend src in needed amount
* @param slippage Limit of price slippage you are willing to accept in percentage
*/
export async function getOkxQuote(
chainId: ApertureSupportedChainId,
src: string,
dst: string,
amount: string,
from: string,
slippage: number,
): Promise<{
toAmount: string;
tx: {
from: Address;
to: Address;
data: Hex;
value: string;
gas: string;
gasPrice: string;
};
protocols?: SwapRoute;
}> {
if (amount === '0') {
throw new Error('amount should greater than 0');
}
const swapParams = {
chainId: chainId.toString(),
fromTokenAddress: src,
toTokenAddress: dst,
amount,
slippage: slippage.toString(),
userWalletAddress: from,
};
try {
const swapData = (
await buildRequest('swap', new URLSearchParams(swapParams))
).data.data;
if (swapData.length < 1) {
throw new Error(
`Error: No swap route found with swapParams=${JSON.stringify(swapParams)}`,
);
}
return {
toAmount: swapData[0].routerResult.toTokenAmount,
tx: swapData[0].tx,
};
} catch (e) {
console.error(e);
throw e;
}
}
54 changes: 40 additions & 14 deletions src/viem/solver/increaseLiquidityOptimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import { AutomatedMarketMakerEnum } from 'aperture-lens/dist/src/viem';
import Big from 'big.js';
import { Address, Hex, PublicClient } from 'viem';

import { SwapRoute, quote } from '.';
import { SwapRoute, get1InchQuote, getIsOkx, getOkxQuote } from '.';
import { computePoolAddress } from '../../utils';
import {
IncreaseLiquidityParams,
encodeOptimalSwapData,
getAutomanContract,
simulateIncreaseLiquidityOptimal,
} from '../automan';
import { get1inchApproveTarget } from './get1InchSolver';
import { get1InchApproveTarget } from './get1InchSolver';
import { getOkxApproveTarget } from './getOkxSolver';
import { calcPriceImpact, getSwapPath } from './internal';
import { SolverResult } from './types';

Expand Down Expand Up @@ -222,9 +223,9 @@ async function getIncreaseLiquidityOptimalSwapData(
try {
const ammInfo = getAMMInfo(chainId, amm)!;
const automan = getAutomanContract(chainId, amm, publicClient);
const approveTarget = await get1inchApproveTarget(chainId);
// get swap amounts using the same pool
const isOkx = getIsOkx();

// get swap amounts using the same pool
const [poolAmountIn, , zeroForOne] = await automan.read.getOptimalSwap([
computePoolAddress(
chainId,
Expand All @@ -241,16 +242,41 @@ async function getIncreaseLiquidityOptimalSwapData(
increaseParams.amount1Desired,
]);

// get a quote from 1inch
const { tx, protocols } = await quote(
chainId,
zeroForOne ? position.pool.token0.address : position.pool.token1.address,
zeroForOne ? position.pool.token1.address : position.pool.token0.address,
poolAmountIn.toString(),
ammInfo.optimalSwapRouter!,
slippage * 100,
includeRoute,
);
const approveTarget = await (isOkx
? getOkxApproveTarget(
chainId,
zeroForOne
? position.pool.token0.address
: position.pool.token1.address,
poolAmountIn.toString(),
)
: get1InchApproveTarget(chainId));
const { tx, protocols } = await (isOkx
? getOkxQuote(
chainId,
zeroForOne
? position.pool.token0.address
: position.pool.token1.address,
zeroForOne
? position.pool.token1.address
: position.pool.token0.address,
poolAmountIn.toString(),
ammInfo.optimalSwapRouter!,
slippage * 100,
)
: get1InchQuote(
chainId,
zeroForOne
? position.pool.token0.address
: position.pool.token1.address,
zeroForOne
? position.pool.token1.address
: position.pool.token0.address,
poolAmountIn.toString(),
ammInfo.optimalSwapRouter!,
slippage * 100,
includeRoute,
));
return {
swapData: encodeOptimalSwapData(
chainId,
Expand Down
10 changes: 9 additions & 1 deletion src/viem/solver/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { get1InchSolver } from './get1InchSolver';
import { getOkxSolver } from './getOkxSolver';
import { getPropellerHeadsSolver } from './getPropellerHeadsSolver';
import { E_Solver, ISolver, SolvedSwapInfo } from './types';

export { quote } from './get1InchSolver'; // TODO: remove when complete refactor
export { getOkxQuote } from './getOkxSolver'; // TODO: remove when complete refactor
export { get1InchQuote } from './get1InchSolver';

export * from './increaseLiquidityOptimal';
export * from './increaseLiquidityOptimalV2';
Expand All @@ -19,6 +21,8 @@ export const getSolver = (solver: E_Solver): ISolver => {
switch (solver) {
case E_Solver.OneInch:
return get1InchSolver();
case E_Solver.OKX:
return getOkxSolver();
case E_Solver.PH:
return getPropellerHeadsSolver();
case E_Solver.SamePool:
Expand All @@ -29,3 +33,7 @@ export const getSolver = (solver: E_Solver): ISolver => {
throw new Error('Invalid solver');
}
};

export function getIsOkx() {
return Number(process.env.OKX_RAMPUP_PERCENT || '1') / 100 > Math.random();
}
38 changes: 28 additions & 10 deletions src/viem/solver/optimalMint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import { AutomatedMarketMakerEnum } from 'aperture-lens/dist/src/viem';
import Big from 'big.js';
import { Address, Hex, PublicClient } from 'viem';

import { SolverResult, SwapRoute, quote } from '.';
import {
SolverResult,
SwapRoute,
get1InchQuote,
getIsOkx,
getOkxQuote,
} from '.';
import {
SlipStreamMintParams,
UniV3MintParams,
Expand All @@ -14,7 +20,7 @@ import {
simulateMintOptimal,
} from '../automan';
import { getPool } from '../pool';
import { get1inchApproveTarget } from './get1InchSolver';
import { getOkxApproveTarget } from './getOkxSolver';
import {
calcPriceImpact,
getFeeOrTickSpacingFromMintParams,
Expand Down Expand Up @@ -272,19 +278,31 @@ async function getOptimalMintSwapData(
);

const ammInfo = getAMMInfo(chainId, amm)!;
// get a quote from 1inch
const { tx, protocols } = await quote(
const { tx, protocols } = await (getIsOkx()
? getOkxQuote(
chainId,
zeroForOne ? mintParams.token0 : mintParams.token1,
zeroForOne ? mintParams.token1 : mintParams.token0,
poolAmountIn.toString(),
ammInfo.optimalSwapRouter!,
slippage * 100,
)
: get1InchQuote(
chainId,
zeroForOne ? mintParams.token0 : mintParams.token1,
zeroForOne ? mintParams.token1 : mintParams.token0,
poolAmountIn.toString(),
ammInfo.optimalSwapRouter!,
slippage * 100,
includeRoute,
));

const approveTarget = await getOkxApproveTarget(
chainId,
zeroForOne ? mintParams.token0 : mintParams.token1,
zeroForOne ? mintParams.token1 : mintParams.token0,
poolAmountIn.toString(),
ammInfo.optimalSwapRouter!,
slippage * 100,
includeRoute,
);

const approveTarget = await get1inchApproveTarget(chainId);

return {
swapData: encodeOptimalSwapData(
chainId,
Expand Down
Loading

0 comments on commit 75986b4

Please sign in to comment.