Skip to content

Commit

Permalink
Merge branch 'main' into feature/WT-1585-InjectPassport
Browse files Browse the repository at this point in the history
  • Loading branch information
ZacharyCouchman committed Aug 10, 2023
2 parents 430d720 + fcc92db commit f409be7
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 54 deletions.
15 changes: 10 additions & 5 deletions packages/internal/dex/sdk-sample-app/src/components/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ import { ConnectAccount } from './ConnectAccount';
import { getTokenSymbol } from '../utils/getTokenSymbol';
import { AmountInput } from './AmountInput';
import { SecondaryFeeInput } from './SecondaryFeeInput';
import { FeeBreakdown } from './FeeBreakdown';

type mapping = {
[address: string]: string;
};

export function Example() {
// Instead of hard-coding these tokens, you can optionally retrieve available tokens from the user's wallet
const TEST_IMX_TOKEN = '0x0000000000000000000000000000000000001010';
const ZKCATS_TOKEN = '0x1836E16b2036088490C2CFe4d11970Fc8e5884C4';
const ZKCATS_TOKEN = '0xaC953a0d7B67Fae17c87abf79f09D0f818AC66A2';

const [ethereumAccount, setEthereumAccount] = useState<string | null>(null);
const [isFetching, setIsFetching] = useState(false);
Expand Down Expand Up @@ -44,10 +49,6 @@ export function Example() {
return <ConnectAccount setAccount={setEthereumAccount} />;
}

type mapping = {
[address: string]: string;
};

const getQuote = async () => {
setIsFetching(true);
setError(null)
Expand Down Expand Up @@ -176,9 +177,13 @@ export function Example() {
]
}`}
</h3>

<h3>Slippage: {result.quote.slippage}%</h3>
{result.approval && <h3>Approval Gas Estimate: {showGasEstimate(result.approval)}</h3>}
<h3>Swap Gas estimate: {showGasEstimate(result.swap)}</h3>

<FeeBreakdown fees={result.quote.fees} addressMap={addressToSymbolMapping} />

<>
<button
className="disabled:opacity-50 mt-2 py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Fee } from "@imtbl/dex-sdk"
import { ethers } from "ethers"

type mapping = {
[address: string]: string;
};

export const FeeBreakdown = ({fees, addressMap}: {fees: Fee[], addressMap: mapping}) => {
return (<>
<h2 className="mb-1 mt-4 text-lg font-extrabold leading-none tracking-tight text-gray-900 md:text-lg lg:text-lg dark:text-white">Fee Breakdown:</h2>
{fees.map((fee, index) => {
return (<div key={index}>
<div>Fee Recipient: {fee.feeRecipient}</div>
<div>Fee Basis Points: {fee.feeBasisPoints}</div>
<div>Fee Amount: {ethers.utils.formatEther(fee.amount.value.toString())} {addressMap[fee.amount.token.address]}</div>
</div>)
})}
</>
)
}
4 changes: 2 additions & 2 deletions packages/internal/dex/sdk-sample-app/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Environment, ImmutableConfiguration } from '@imtbl/sdk';
import { Environment, ImmutableConfiguration } from '@imtbl/config';

const chainId = 13372; // You can optionally retrieve the chain ID from the users wallet, or prompt the user to change networks
const chainId = 13472; // You can optionally retrieve the chain ID from the users wallet, or prompt the user to change networks

const immutableConfig = new ImmutableConfiguration({
environment: Environment.SANDBOX,
Expand Down
8 changes: 4 additions & 4 deletions packages/internal/dex/sdk/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ export type ExchangeContracts = {

export const CONTRACTS_FOR_CHAIN_ID: Record<number, ExchangeContracts> = {
[IMMUTABLE_TESTNET_CHAIN_ID]: {
multicall: '0xb18c44b211065E69844FbA9AE146DA362104AfBf',
coreFactory: '0x12739A8f1A8035F439092D016DAE19A2874F30d2',
quoterV2: '0xF674847fBcca5C80315e3AE37043Dce99F6CC529',
peripheryRouter: '0x0Afe6F5f4DC34461A801420634239FFaD50A2e44',
multicall: '0xD17c98b38bA28c7eA1080317EB9AB2b9663BEd92',
coreFactory: '0x8AC26EfCbf5D700b37A27aA00E6934e6904e7B8e',
quoterV2: '0x0Afe6F5f4DC34461A801420634239FFaD50A2e44',
peripheryRouter: '0x87854A7D4b9BaC3D37f4516A1Ac7F36fB5ad539f',
secondaryFee: '0x8dBE1f0900C5e92ad87A54521902a33ba1598C51',
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const IMMUTABLE_TESTNET_RPC_URL = 'https://rpc.testnet.immutable.com';
export const IMMUTABLE_TESTNET_CHAIN_ID = 13372;
export const IMMUTABLE_TESTNET_CHAIN_ID = 13472;
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
decodeMulticallExactInputSingleWithoutFees,
expectToBeDefined,
} from './test/utils';
import { Router, SecondaryFee } from './lib';
import { Router, SecondaryFee, uniswapTokenToTokenInfo } from './lib';

jest.mock('@ethersproject/providers');
jest.mock('@ethersproject/contracts');
Expand Down Expand Up @@ -241,6 +241,37 @@ describe('getUnsignedSwapTxFromAmountIn', () => {
expect(swapParams.amountIn.toString()).toBe('100000000000000000000'); // 100
expect(swapParams.amountOutMinimum.toString()).toBe('899100899100899100899'); // 899 includes slippage and fees
});

it('returns a quote', async () => {
const params = setupSwapTxTest(true);
mockRouterImplementation(params);

const secondaryFees: SecondaryFee[] = [
{ feeRecipient: TEST_FEE_RECIPIENT, feeBasisPoints: TEST_MAX_FEE_BASIS_POINTS },
];

const exchange = new Exchange({ ...TEST_DEX_CONFIGURATION, secondaryFees });

const { quote } = await exchange.getUnsignedSwapTxFromAmountIn(
params.fromAddress,
params.inputToken,
params.outputToken,
ethers.utils.parseEther('100'),
);

const tokenIn = { ...uniswapTokenToTokenInfo(IMX_TEST_TOKEN), name: undefined, symbol: undefined };

expect(quote.fees).toEqual([
{
feeRecipient: TEST_FEE_RECIPIENT,
feeBasisPoints: TEST_MAX_FEE_BASIS_POINTS,
amount: {
token: tokenIn,
value: ethers.utils.parseEther('10'),
},
},
]);
});
});

describe('Swap with single pool without fees and default slippage tolerance', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { JsonRpcProvider } from '@ethersproject/providers';
import { Contract } from '@ethersproject/contracts';
import { BigNumber } from '@ethersproject/bignumber';
import { SecondaryFee } from 'lib';
import { SecondaryFee, uniswapTokenToTokenInfo } from 'lib';
import { ethers } from 'ethers';
import { ERC20__factory } from 'contracts/types';
import { Exchange } from './exchange';
Expand All @@ -19,6 +19,8 @@ import {
decodeMulticallExactOutputWithFees,
expectToBeDefined,
decodePathForExactOutput,
makeAddr,
IMX_TEST_TOKEN,
} from './test/utils';

jest.mock('@ethersproject/providers');
Expand Down Expand Up @@ -161,6 +163,46 @@ describe('getUnsignedSwapTxFromAmountOut', () => {

expect(spenderAddress).toEqual(TEST_SECONDARY_FEE_ADDRESS);
});

it('returns valid swap quote', async () => {
const params = setupSwapTxTest();
mockRouterImplementation(params);

const secondaryFees: SecondaryFee[] = [
{ feeRecipient: makeAddr('recipienta'), feeBasisPoints: 200 }, // 2% fee
{ feeRecipient: makeAddr('recipientb'), feeBasisPoints: 400 }, // 4% fee
];

const exchange = new Exchange({ ...TEST_DEX_CONFIGURATION, secondaryFees });

const { quote } = await exchange.getUnsignedSwapTxFromAmountOut(
params.fromAddress,
params.inputToken,
params.outputToken,
ethers.utils.parseEther('1000'),
);

const tokenIn = { ...uniswapTokenToTokenInfo(IMX_TEST_TOKEN), name: undefined, symbol: undefined };

expect(quote.fees).toEqual([
{
feeRecipient: makeAddr('recipienta'),
feeBasisPoints: 200,
amount: {
token: tokenIn,
value: ethers.utils.parseEther('2'),
},
},
{
feeRecipient: makeAddr('recipientb'),
feeBasisPoints: 400,
amount: {
token: tokenIn,
value: ethers.utils.parseEther('4'),
},
},
]);
});
});

describe('Swap with single pool without fees and default slippage tolerance', () => {
Expand Down
11 changes: 7 additions & 4 deletions packages/internal/dex/sdk/src/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import {
import { fetchGasPrice } from 'lib/transactionUtils/gas';
import { getApproval, prepareApproval } from 'lib/transactionUtils/approval';
import { getOurQuoteReqAmount, prepareUserQuote } from 'lib/transactionUtils/getQuote';
import { Fees } from 'lib/fees';
import {
DEFAULT_DEADLINE, DEFAULT_MAX_HOPS, DEFAULT_SLIPPAGE, MAX_MAX_HOPS, MIN_MAX_HOPS,
} from './constants';
import { Router } from './lib/router';
import { getERC20Decimals, isValidNonZeroAddress } from './lib/utils';
import { getERC20Decimals, isValidNonZeroAddress, uniswapTokenToTokenInfo } from './lib/utils';
import {
ExchangeModuleConfiguration, SecondaryFee, TokenInfo, TransactionResponse,
} from './types';
Expand Down Expand Up @@ -112,7 +113,9 @@ export class Exchange {
otherToken = tokenIn;
}

const ourQuoteReqAmount = getOurQuoteReqAmount(amountSpecified, this.secondaryFees, tradeType);
const fees = new Fees(this.secondaryFees, uniswapTokenToTokenInfo(tokenIn));

const ourQuoteReqAmount = getOurQuoteReqAmount(amountSpecified, fees, tradeType);

const ourQuote = await this.router.findOptimalRoute(
ourQuoteReqAmount,
Expand All @@ -124,7 +127,7 @@ export class Exchange {
// get gas details
const gasPrice = await fetchGasPrice(this.provider);

const adjustedQuote = prepareSwap(ourQuote, amount, this.secondaryFees);
const adjustedQuote = prepareSwap(ourQuote, amount, fees);

const swap = getSwap(
this.nativeToken,
Expand All @@ -138,7 +141,7 @@ export class Exchange {
this.secondaryFees,
);

const userQuote = prepareUserQuote(otherToken, adjustedQuote, slippagePercent);
const userQuote = prepareUserQuote(otherToken, adjustedQuote, slippagePercent, fees);

const preparedApproval = prepareApproval(
tradeType,
Expand Down
56 changes: 49 additions & 7 deletions packages/internal/dex/sdk/src/lib/fees.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,56 @@
import { BASIS_POINT_PRECISION } from 'constants/router';
import { ethers } from 'ethers';
import { SecondaryFee } from 'lib';
import {
Fee, SecondaryFee, TokenInfo, newAmount,
} from 'lib';

export function calculateFees(amount: ethers.BigNumber, secondaryFees: SecondaryFee[]) {
let totalFees = ethers.BigNumber.from(0);
export class Fees {
private secondaryFees: SecondaryFee[];

for (let i = 0; i < secondaryFees.length; i++) {
const feeAmount = amount.mul(secondaryFees[i].feeBasisPoints).div(BASIS_POINT_PRECISION);
totalFees = totalFees.add(feeAmount);
private token: TokenInfo;

private amount: ethers.BigNumber = ethers.BigNumber.from(0);

constructor(secondaryFees: SecondaryFee[], token: TokenInfo) {
this.secondaryFees = secondaryFees;
this.token = token;
}

addAmount(amount: ethers.BigNumber): void {
this.amount = this.amount.add(amount);
}

amountWithFeesApplied(): ethers.BigNumber {
return this.amount.add(this.total());
}

amountLessFees(): ethers.BigNumber {
return this.amount.sub(this.total());
}

withAmounts(): Fee[] {
return this.secondaryFees.map((fee) => {
const feeAmount = this.amount
.mul(fee.feeBasisPoints)
.div(BASIS_POINT_PRECISION);

return {
...fee,
amount: newAmount(feeAmount, this.token),
};
});
}

return totalFees;
private total(): ethers.BigNumber {
let totalFees = ethers.BigNumber.from(0);

for (let i = 0; i < this.secondaryFees.length; i++) {
const feeAmount = this.amount
.mul(this.secondaryFees[i].feeBasisPoints)
.div(BASIS_POINT_PRECISION);
totalFees = totalFees.add(feeAmount);
}

return totalFees;
}
}
14 changes: 7 additions & 7 deletions packages/internal/dex/sdk/src/lib/transactionUtils/getQuote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
import { ethers } from 'ethers';
import { QuoteTradeInfo } from 'lib/router';
import { toBigNumber } from 'lib/utils';
import { calculateFees } from 'lib/fees';
import { Fees } from 'lib/fees';
import {
Amount, Quote, SecondaryFee, TokenInfo,
Amount, Quote, TokenInfo,
} from '../../types';
import { slippageToFraction } from './slippage';

Expand Down Expand Up @@ -40,6 +40,7 @@ export function prepareUserQuote(
otherCurrency: Currency,
tradeInfo: QuoteTradeInfo,
slippage: number,
fees: Fees,
): Quote {
const resultToken: Token = otherCurrency.wrapped;
const tokenInfo: TokenInfo = {
Expand All @@ -60,22 +61,21 @@ export function prepareUserQuote(
value: amountWithSlippage,
},
slippage,
fees: fees.withAmounts(),
};
}

export function getOurQuoteReqAmount(
amount: CurrencyAmount<Token>,
secondaryFees: SecondaryFee[],
fees: Fees,
tradeType: TradeType,
) {
if (tradeType === TradeType.EXACT_OUTPUT) {
// For an exact output swap, we do not need to subtract fees from the given amount
return amount;
}

const totalFees = calculateFees(toBigNumber(amount), secondaryFees);
const totalFeesCurrencyAmount = CurrencyAmount.fromRawAmount(amount.currency, totalFees.toString());
fees.addAmount(toBigNumber(amount));

// Subtract the fee amount from the given amount
return amount.subtract(totalFeesCurrencyAmount);
return CurrencyAmount.fromRawAmount(amount.currency, fees.amountLessFees().toString());
}
Loading

0 comments on commit f409be7

Please sign in to comment.