Skip to content
Merged
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: 2 additions & 1 deletion packages/veraswap-app/src/atoms/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,9 @@ export const resetTransactionStateAtom = atom(null, (_, set) => {

export const slippageAtom = atom<"auto" | number>("auto");

/** Slippage tolerance in percentage. If slippageAtom is "auto", defaults to 2.5% */
export const slippageToleranceAtom = atom((get) => {
const slippage = get(slippageAtom);
if (slippage === "auto") return 0.5;
if (slippage === "auto") return 2.5; // default to 2.5%
return slippage;
});
4 changes: 2 additions & 2 deletions packages/veraswap-sdk/src/chains/arbitrum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export const arbitrum = {
...arbitrumChain,
rpcUrls: {
default: {
http: ["https://lb.drpc.org/ogrpc?network=arbitrum&dkey=AhYfrLlxSE3QsswFtgfKNqu1Ait49nQR75sVnqSgS7QB"],
webSocket: ["wss://lb.drpc.org/ogws?network=arbitrum&dkey=AhYfrLlxSE3QsswFtgfKNqu1Ait49nQR75sVnqSgS7QB"],
http: ["https://arbitrum-mainnet.core.chainstack.com/db36c07f913d8dbb8cdf6a8831c1bd23"],
webSocket: ["wss://arbitrum-mainnet.core.chainstack.com/db36c07f913d8dbb8cdf6a8831c1bd23"],
},
},
custom: {
Expand Down
7 changes: 5 additions & 2 deletions packages/veraswap-sdk/src/chains/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ export const base = {
...baseChain,
rpcUrls: {
default: {
http: ["https://lb.drpc.org/ogrpc?network=base&dkey=AhYfrLlxSE3QsswFtgfKNqu1Ait49nQR75sVnqSgS7QB"],
webSocket: ["wss://lb.drpc.org/ogws?network=base&dkey=AhYfrLlxSE3QsswFtgfKNqu1Ait49nQR75sVnqSgS7QB"],
// TODO:investigate issue with Chainstack causing a revert error when fetching quote
// http: ["https://base-mainnet.core.chainstack.com/a3cbc100238b9972f7d4f44c446a2c16"],
// webSocket: ["wss://base-mainnet.core.chainstack.com/a3cbc100238b9972f7d4f44c446a2c16"],
http: ["https://base-mainnet.g.alchemy.com/v2/VFO0zUhBMB8WDuge_ro4KzKRkvwyrYas"],
webSocket: ["wss://base-mainnet.g.alchemy.com/v2/VFO0zUhBMB8WDuge_ro4KzKRkvwyrYas"],
},
},
custom: {
Expand Down
2 changes: 0 additions & 2 deletions packages/veraswap-sdk/src/constants/uniswap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ export function getUniswapContracts(params?: { owner?: Address }) {
/** Uniswap contracts for local deployment via CREATE2 & salt = bytes32(0) */
export const LOCAL_UNISWAP_CONTRACTS = getUniswapContracts();

//TODO: Add metaquoter address (compute using poolManager address)
export interface UniswapContracts {
weth9: Address;
v2Factory?: Address;
Expand All @@ -176,7 +175,6 @@ export interface UniswapContracts {
universalRouter: Address;
}

// TODO: FIXME fix all weth9 addresses
/** Uniswap contracts by chain */
export const UNISWAP_CONTRACTS: Record<number, UniswapContracts | undefined> = {
[opChainL1.id]: LOCAL_UNISWAP_CONTRACTS,
Expand Down
522 changes: 12 additions & 510 deletions packages/veraswap-sdk/src/uniswap/quote/getUniswapRoute.test.ts

Large diffs are not rendered by default.

291 changes: 291 additions & 0 deletions packages/veraswap-sdk/src/uniswap/quote/getUniswapRouteV3.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
import { QueryClient } from "@tanstack/react-query";
import { Address, parseAbi, parseUnits, zeroAddress } from "viem";
import { describe, expect, test } from "vitest";

import { IERC20 } from "../../artifacts/IERC20.js";
import { IUniversalRouter } from "../../artifacts/IUniversalRouter.js";
import { opChainL1, opChainL1Client } from "../../chains/supersim.js";
import { LOCAL_CURRENCIES } from "../../constants/tokens.js";
import { LOCAL_UNISWAP_CONTRACTS } from "../../constants/uniswap.js";
import { getUniswapV4Address } from "../../currency/currency.js";
import { anvilClientL1, wagmiConfig } from "../../test/constants.js";
import { addCommandsToRoutePlanner } from "../addCommandsToRoutePlanner.js";
import { RoutePlanner } from "../routerCommands.js";

import { getRouterCommandsForQuote, getUniswapRouteExactIn } from "./getUniswapRoute.js";

//TODO: Add deeper tests with ETH wrap/unwrap
describe("uniswap/quote/getUniswapRoute.test.ts", function () {
const queryClient = new QueryClient();
// Uniswap Error Abi
const UniswapErrorAbi = parseAbi([
"error DeltaNotNegative(address)",
"error DeltaNotPositive(address)",
"error CurrencyNotSettled()",
"error PoolNotInitialized()",
"error V3TooLittleReceived()",
"error SwapAmountCannotBeZero()",
]);

const amountIn = parseUnits("0.01", 18);

// A/ETH, B/ETH Pools Exist
// A/B Pool Does Not Exist
const tokenA = getUniswapV4Address(LOCAL_CURRENCIES[0]); // 2 A tokens after this (Hyperlane)
const tokenB = getUniswapV4Address(LOCAL_CURRENCIES[3]); // 2 B tokens after this (Hyperlane)
const tokenL3 = getUniswapV4Address(LOCAL_CURRENCIES[7]);
const tokenL4 = getUniswapV4Address(LOCAL_CURRENCIES[8]);

const getBalanceForAddress = function (token: Address, address: Address) {
if (token === zeroAddress) return opChainL1Client.getBalance({ address });

return opChainL1Client.readContract({
address: token,
abi: IERC20.abi,
functionName: "balanceOf",
args: [address],
});
};

// Helper function to get balance of a token
const getBalance = (token: Address) => getBalanceForAddress(token, anvilClientL1.account.address);

test("A -> L3", async () => {
const currencyIn = tokenA;
const currencyOut = tokenL3;
const currencyHops: Address[] = [];

const contracts = {
weth9: LOCAL_UNISWAP_CONTRACTS.weth9,
metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter,
};

// Route
const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, {
chainId: opChainL1.id,
currencyIn,
currencyOut,
currencyHops,
amountIn,
contracts: {
weth9: LOCAL_UNISWAP_CONTRACTS.weth9,
metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter,
},
});
expect(route).toBeDefined();
const { quote, amountOut, value } = route!;

const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote });

const routePlanner = new RoutePlanner();
addCommandsToRoutePlanner(routePlanner, commands);

//Execute
const currencyInBalanceBeforeSwap = await getBalance(currencyIn);
const currencyOutBalanceBeforeSwap = await getBalance(currencyOut);
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
const hash = await anvilClientL1.writeContract({
abi: [...IUniversalRouter.abi, ...UniswapErrorAbi],
address: LOCAL_UNISWAP_CONTRACTS.universalRouter,
value,
functionName: "execute",
args: [routePlanner.commands, routePlanner.inputs, deadline],
});
await opChainL1Client.waitForTransactionReceipt({ hash });
const currencyInBalanceAfterSwap = await getBalance(currencyIn);
const currencyOutBalanceAfterSwap = await getBalance(currencyOut);
expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount
expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount
});

test("L4 -> ETH", async () => {
const currencyIn = tokenL4;
const currencyOut = zeroAddress; // ETH
const currencyHops: Address[] = [];

const contracts = {
weth9: LOCAL_UNISWAP_CONTRACTS.weth9,
metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter,
};

// Route
const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, {
chainId: opChainL1.id,
currencyIn,
currencyOut,
currencyHops,
amountIn,
contracts,
});
expect(route).toBeDefined();
const { quote, amountOut, value } = route!;

const commands = getRouterCommandsForQuote({
currencyIn,
currencyOut,
amountIn,
contracts,
...quote,
});

const routePlanner = new RoutePlanner();
addCommandsToRoutePlanner(routePlanner, commands);

//Execute
const currencyInBalanceBeforeSwap = await getBalance(currencyIn);
const currencyOutBalanceBeforeSwap = await getBalance(currencyOut);

const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
const hash = await anvilClientL1.writeContract({
abi: [...IUniversalRouter.abi, ...UniswapErrorAbi],
address: LOCAL_UNISWAP_CONTRACTS.universalRouter,
value,
functionName: "execute",
args: [routePlanner.commands, routePlanner.inputs, deadline],
});
const receipt = await opChainL1Client.waitForTransactionReceipt({ hash });
// console.log(
// receipt.logs.map((l) => ({
// address: l.address,
// ...decodeEventLog({
// abi: events,
// data: l.data,
// topics: l.topics,
// }),
// })),
// );

const currencyInBalanceAfterSwap = await getBalance(currencyIn);
const currencyOutBalanceAfterSwap = await getBalance(currencyOut);

const gasUsed = receipt.gasUsed;
const effectiveGasPrice = receipt.effectiveGasPrice;
const gasCost = gasUsed * effectiveGasPrice;

expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount
expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost
// expect(currencyOutBalanceAfterSwap).toBe(amountOut); // Output balance increased by variable amount minus gas cost
});

test("L4 -> ETH with Veraswap and referral fee", async () => {
const currencyIn = tokenL4;
const currencyOut = zeroAddress; // ETH
const currencyHops: Address[] = [];

const contracts = {
weth9: LOCAL_UNISWAP_CONTRACTS.weth9,
metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter,
};

const feeBips = 25n; // 0.25% referral fee
const bipsDenominator = 10000n; // 100% = 10000 bips
const feeAmount = (amountIn * feeBips) / bipsDenominator;
const amountInWithoutFee = amountIn - feeAmount * 2n;

// Route
const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, {
chainId: opChainL1.id,
currencyIn,
currencyOut,
currencyHops,
amountIn: amountInWithoutFee,
contracts,
});
expect(route).toBeDefined();
const { quote, amountOut, value } = route!;

const veraswapAddress = "0x000000000000000000000000000000000000beef" as Address;
const veraswapFeeRecipient = { address: veraswapAddress, bips: feeBips };

const referralAddress = "0x00000000000000000000000000000000000beef2" as Address;
const referralFeeRecipient = { address: referralAddress, bips: feeBips };

const commands = getRouterCommandsForQuote({
currencyIn,
currencyOut,
amountIn,
contracts,
veraswapFeeRecipient,
referralFeeRecipient,
...quote,
});

const routePlanner = new RoutePlanner();
addCommandsToRoutePlanner(routePlanner, commands);

//Execute
const currencyInBalanceBeforeSwap = await getBalance(currencyIn);
const currencyOutBalanceBeforeSwap = await getBalance(currencyOut);
const veraswapBalanceBeforeSwap = await getBalanceForAddress(currencyIn, veraswapAddress);
const referralBalanceBeforeSwap = await getBalanceForAddress(currencyIn, referralAddress);

const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
const hash = await anvilClientL1.writeContract({
abi: [...IUniversalRouter.abi, ...UniswapErrorAbi],
address: LOCAL_UNISWAP_CONTRACTS.universalRouter,
value,
functionName: "execute",
args: [routePlanner.commands, routePlanner.inputs, deadline],
});
const receipt = await opChainL1Client.waitForTransactionReceipt({ hash });

const currencyInBalanceAfterSwap = await getBalance(currencyIn);
const currencyOutBalanceAfterSwap = await getBalance(currencyOut);
const veraswapBalanceAfterSwap = await getBalanceForAddress(currencyIn, veraswapAddress);
const referralBalanceAfterSwap = await getBalanceForAddress(currencyIn, referralAddress);

const gasUsed = receipt.gasUsed;
const effectiveGasPrice = receipt.effectiveGasPrice;
const gasCost = gasUsed * effectiveGasPrice;

expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount
expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut - gasCost); // Output balance increased by variable amount minus gas cost
expect(veraswapBalanceAfterSwap).toBe(veraswapBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount
expect(referralBalanceAfterSwap).toBe(referralBalanceBeforeSwap + feeAmount); // Input balance increased by exact amount
});

test("A -> L3 -> B", async () => {
const currencyIn = tokenA;
const currencyOut = tokenB;
const currencyHops = [tokenL3];

const contracts = {
weth9: LOCAL_UNISWAP_CONTRACTS.weth9,
metaQuoter: LOCAL_UNISWAP_CONTRACTS.metaQuoter,
};

// Route
const route = await getUniswapRouteExactIn(queryClient, wagmiConfig, {
chainId: opChainL1.id,
currencyIn,
currencyOut,
currencyHops,
amountIn,
contracts,
});
expect(route).toBeDefined();
const { quote, amountOut, value } = route!;

const commands = getRouterCommandsForQuote({ currencyIn, currencyOut, amountIn, contracts, ...quote });

const routePlanner = new RoutePlanner();
addCommandsToRoutePlanner(routePlanner, commands);

//Execute
const currencyInBalanceBeforeSwap = await getBalance(currencyIn);
const currencyOutBalanceBeforeSwap = await getBalance(currencyOut);
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
const hash = await anvilClientL1.writeContract({
abi: [...IUniversalRouter.abi, ...UniswapErrorAbi],
address: LOCAL_UNISWAP_CONTRACTS.universalRouter,
value,
functionName: "execute",
args: [routePlanner.commands, routePlanner.inputs, deadline],
});
await opChainL1Client.waitForTransactionReceipt({ hash });
const currencyInBalanceAfterSwap = await getBalance(currencyIn);
const currencyOutBalanceAfterSwap = await getBalance(currencyOut);
expect(currencyInBalanceAfterSwap).toBe(currencyInBalanceBeforeSwap - amountIn); // Input balance decreased by exact amount
expect(currencyOutBalanceAfterSwap).toBe(currencyOutBalanceBeforeSwap + amountOut); // Output balance increased by variable amount
});
});
Loading
Loading