Skip to content

1inch fusion #316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"lint": "next lint"
},
"dependencies": {
"@1inch/fusion-sdk": "2.1.7",
"@chakra-ui/icons": "^2.0.12",
"@chakra-ui/react": "2.8.2",
"@defillama/sdk": "^3.0.25",
Expand All @@ -23,9 +24,11 @@
"@tanstack/react-query": "5.55.4",
"@tanstack/react-virtual": "^3.0.0-beta.36",
"ariakit": "^2.0.0-next.41",
"axios": "^1.7.9",
"bignumber.js": "^9.0.2",
"echarts": "^5.4.1",
"framer-motion": "^6",
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"next": "12.3.1",
"react": "^18.2.0",
Expand Down
122 changes: 0 additions & 122 deletions src/components/Aggregator/adapters/1inch.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { approvalAddress, chainToId } from './1inch';
import { approvalAddress, chainToId } from './';
import fetch from 'node-fetch';

export async function testApprovalAddresses() {
Expand Down
58 changes: 58 additions & 0 deletions src/components/Aggregator/adapters/1inch/classic-swap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { AUTH_HEADER, CHAIN_TO_ID, SPENDERS } from '~/components/Aggregator/adapters/1inch/constants';
import { altReferralAddress } from '~/components/Aggregator/constants';
import { applyArbitrumFees } from '~/components/Aggregator/utils/arbitrumFees';
import { sendTx } from '~/components/Aggregator/utils/sendTx';
import { zeroAddress } from 'viem';
import { estimateGas } from 'wagmi/actions';
import { config } from '../../../WalletProvider';

const CLASSIC_ENDPOINT = 'http://localhost:8888/swap/v6.0/';

export async function getClassicQuote(chain: string, tokenFrom: string, tokenTo: string, amount: string, extra) {
const quoteUrl = `${CLASSIC_ENDPOINT}${CHAIN_TO_ID[chain]}/quote?src=${tokenFrom}&dst=${tokenTo}&amount=${amount}&includeGas=true`;
const swapUrl = extra.userAddress !== zeroAddress
? `${CLASSIC_ENDPOINT}${CHAIN_TO_ID[chain]}/swap?src=${tokenFrom}&dst=${tokenTo}&amount=${amount}&from=${extra.userAddress}&slippage=${extra.slippage}&referrer=${altReferralAddress}&disableEstimate=true`
: null;

const quotePromise = fetch(quoteUrl, { headers: AUTH_HEADER as any }).then((r) => r.json());
const swapPromise = swapUrl
? fetch(swapUrl, { headers: AUTH_HEADER as any }).then((r) => r.json())
: null;

return await Promise.all([quotePromise, swapPromise]);
}

export async function parseClassicQuote(chain: string, quote) {
const [data, swapData] = quote;
const tokenApprovalAddress = SPENDERS[chain];
let gas = data.gas || 0;

if (chain === 'arbitrum')
gas = swapData === null ? null : await applyArbitrumFees(swapData.tx.to, swapData.tx.data, gas);

return {
amountReturned: swapData?.dstAmount ?? data.dstAmount,
estimatedGas: gas,
tokenApprovalAddress,
rawQuote: swapData,
logo: 'https://icons.llamao.fi/icons/protocols/1inch-network?w=48&q=75'
};
}

export async function classicSwap(rawQuote) {
const txObject = {
from: rawQuote.tx.from,
to: rawQuote.tx.to,
data: rawQuote.tx.data,
value: rawQuote.tx.value
};

const gasPrediction = await estimateGas(config, txObject).catch(() => null);

const tx = await sendTx({
...txObject,
// Increase gas +20% + 2 erc20 txs
...(gasPrediction && { gas: (gasPrediction * 12n) / 10n + 86000n })
});
return tx;
}
46 changes: 46 additions & 0 deletions src/components/Aggregator/adapters/1inch/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export const AUTH_HEADER = process.env.INCH_API_KEY ? { 'auth-key': process.env.INCH_API_KEY } : {};

export const NATIVE_TOKEN = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';

export const CHAIN_TO_ID = {
ethereum: 1,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about using enum for chains here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everywhere in the project, chain ids are listed as objects. I suggest we leave them as they are for consistency.

bsc: 56,
polygon: 137,
optimism: 10,
arbitrum: 42161,
gnosis: 100,
avax: 43114,
fantom: 250,
klaytn: 8217,
aurora: 1313161554,
zksync: 324,
base: 8453
};

export const SPENDERS = {
ethereum: '0x111111125421ca6dc452d289314280a0f8842a65',
bsc: '0x111111125421ca6dc452d289314280a0f8842a65',
polygon: '0x111111125421ca6dc452d289314280a0f8842a65',
optimism: '0x111111125421ca6dc452d289314280a0f8842a65',
arbitrum: '0x111111125421ca6dc452d289314280a0f8842a65',
gnosis: '0x111111125421ca6dc452d289314280a0f8842a65',
avax: '0x111111125421ca6dc452d289314280a0f8842a65',
fantom: '0x111111125421ca6dc452d289314280a0f8842a65',
klaytn: '0x111111125421ca6dc452d289314280a0f8842a65',
aurora: '0x111111125421ca6dc452d289314280a0f8842a65',
zksync: '0x6fd4383cb451173d5f9304f041c7bcbf27d561ff',
base: '0x111111125421ca6dc452d289314280a0f8842a65'
};

export const AVAILABLE_CHAINS_FOR_FUSION = new Set<number>([
CHAIN_TO_ID.ethereum,
CHAIN_TO_ID.bsc,
CHAIN_TO_ID.polygon,
CHAIN_TO_ID.optimism,
CHAIN_TO_ID.arbitrum,
CHAIN_TO_ID.gnosis,
CHAIN_TO_ID.avax,
CHAIN_TO_ID.fantom,
CHAIN_TO_ID.zksync,
CHAIN_TO_ID.base,
]);
123 changes: 123 additions & 0 deletions src/components/Aggregator/adapters/1inch/fusion-swap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { AVAILABLE_CHAINS_FOR_FUSION, CHAIN_TO_ID, SPENDERS } from './constants';
import { FusionSDK, OrderStatus } from '@1inch/fusion-sdk';
import { formatUnits, parseUnits } from 'ethers/lib/utils';
import { call, signTypedData } from 'wagmi/actions';
import { config } from '~/components/WalletProvider';
import { Hex } from 'viem/types/misc';
import { OrderInfo } from '@1inch/fusion-sdk/dist/types/src/sdk/types';

// const FUSION_QUOTE_ENDPOINT = 'https://api-defillama.1inch.io/v2.0/fusion';
const FUSION_SDK_ENDPOINT = 'http://localhost:8888/fusion';
const SOURCE = 'f012a792';

export function isFusionSupportedByChain(chainId: number): boolean {
return AVAILABLE_CHAINS_FOR_FUSION.has(chainId);
}

export async function getFusionQuoteResponse(params: {
chain: string,
tokenFrom: string,
tokenTo: string,
amount: string,
address: string,
}) {
const { chain, tokenFrom, tokenTo, amount, address } = params;
const sdk = new FusionSDK({
url: FUSION_SDK_ENDPOINT,
network: CHAIN_TO_ID[chain]
});

return await sdk.getQuote({
fromTokenAddress: tokenFrom,
toTokenAddress: tokenTo,
amount,
walletAddress: address,
source: SOURCE,
});
}

export async function fusionSwap(chain, quote, fromAddress): Promise<OrderInfo> {
const sdk = new FusionSDK({
url: FUSION_SDK_ENDPOINT,
network: CHAIN_TO_ID[chain],
blockchainProvider: {
signTypedData: (_, typedData) => signTypedData(config, {
domain: typedData.domain,
types: typedData.types,
primaryType: typedData.primaryType,
message: typedData.message
}),
ethCall: (contractAddress: Hex, callData: Hex) =>
call(
config,
{
account: fromAddress,
data: callData,
to: contractAddress,
},
).then((result) => result.data as string),
}
});

return await sdk.placeOrder({
fromTokenAddress: quote.params.fromTokenAddress.val,
toTokenAddress: quote.params.toTokenAddress.val,
walletAddress: quote.params.walletAddress.val,
amount: quote.params.amount,
source: SOURCE,
});
}

export const getOrderStatus = ({ chain, hash }) => async (onSuccess, maxRetries = 300, delay = 1000) => {
const sdk = new FusionSDK({
url: FUSION_SDK_ENDPOINT,
network: CHAIN_TO_ID[chain],
});

let attempt = 0;

while (attempt < maxRetries) {
try {
const data = await sdk.getOrderStatus(hash);

if (data.status === OrderStatus.Filled) {
onSuccess();
return;
}

if (data.status !== OrderStatus.Pending) {
return;
}
} catch (error) {
console.error('Error fetching fusion order status:', error);
}

attempt++;
if (attempt >= maxRetries) {
throw new Error('Max retries reached. Unable to fetch order status.');
}

await new Promise((resolve) => setTimeout(resolve, delay));
}
};

export function parseFusionQuote(chain: string, quote, extra) {
const { presets, recommendedPreset, toTokenAmount } = quote;
const { auctionStartAmount, auctionEndAmount } = presets[recommendedPreset];
const dstTokenDecimals = extra.toToken.decimals;

const start = formatUnits(auctionStartAmount, dstTokenDecimals);
const end = formatUnits(auctionEndAmount, dstTokenDecimals);
const amount = formatUnits(toTokenAmount, dstTokenDecimals);

const receivedAmount = amount < start ? amount : start;
const returnedAmount = end > receivedAmount ? end : receivedAmount;

return {
amountReturned: parseUnits(returnedAmount, dstTokenDecimals).toString(),
estimatedGas: 0,
tokenApprovalAddress: SPENDERS[chain],
rawQuote: quote,
logo: 'https://icons.llamao.fi/icons/protocols/1inch-network?w=48&q=75'
};
}
Loading