Skip to content

Commit

Permalink
feat: estimate unlock and swap using metadata (#1395)
Browse files Browse the repository at this point in the history
  • Loading branch information
estebanmino authored Mar 27, 2024
1 parent fdf7a2a commit 776abe5
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 5 deletions.
3 changes: 3 additions & 0 deletions src/core/graphql/queries/metadata.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ query simulateTransactions(
message
type
}
gas {
estimate
}
simulation {
in {
...change
Expand Down
79 changes: 79 additions & 0 deletions src/core/raps/actions/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Signer } from '@ethersproject/abstract-signer';
import { StaticJsonRpcProvider } from '@ethersproject/providers';
import { Transaction } from '@ethersproject/transactions';
import {
CrosschainQuote,
ETH_ADDRESS as ETH_ADDRESS_AGGREGATORS,
Quote,
RAINBOW_ROUTER_CONTRACT_ADDRESS,
ChainId as SwapChainId,
WRAPPED_ASSET,
fillQuote,
Expand All @@ -14,11 +16,14 @@ import {
} from '@rainbow-me/swaps';
import { Address, getProvider } from '@wagmi/core';

import { metadataPostClient } from '~/core/graphql';
import { ChainId } from '~/core/types/chains';
import { NewTransaction, TxHash } from '~/core/types/transactions';
import { add } from '~/core/utils/numbers';
import { isLowerCaseMatch } from '~/core/utils/strings';
import { isUnwrapEth, isWrapEth } from '~/core/utils/swaps';
import { addNewTransaction } from '~/core/utils/transactions';
import { TransactionSimulationResponse } from '~/entries/popup/pages/messages/useSimulateTransaction';
import { RainbowError, logger } from '~/logger';

import { REFERRER, gasUnits } from '../../references';
Expand All @@ -36,8 +41,11 @@ import {
estimateSwapGasLimitWithFakeApproval,
getDefaultGasLimitForTrade,
overrideWithFastSpeedIfNeeded,
populateSwap,
} from '../utils';

import { populateApprove } from './unlock';

const WRAP_GAS_PADDING = 1.002;

export const estimateSwapGasLimit = async ({
Expand Down Expand Up @@ -131,6 +139,77 @@ export const estimateSwapGasLimit = async ({
}
};

export const estimateUnlockAndSwapFromMetadata = async ({
swapAssetNeedsUnlocking,
chainId,
accountAddress,
sellTokenAddress,
quote,
}: {
swapAssetNeedsUnlocking: boolean;
chainId: ChainId;
accountAddress: Address;
sellTokenAddress: Address;
quote: Quote | CrosschainQuote;
}) => {
try {
const approveTransaction = await populateApprove({
owner: accountAddress,
tokenAddress: sellTokenAddress,
spender: RAINBOW_ROUTER_CONTRACT_ADDRESS,
chainId,
});
const swapTransaction = await populateSwap({
provider: getProvider({ chainId }),
quote,
});
if (
approveTransaction?.to &&
approveTransaction?.data &&
approveTransaction?.from &&
swapTransaction?.to &&
swapTransaction?.data &&
swapTransaction?.from
) {
const transactions = swapAssetNeedsUnlocking
? [
{
to: approveTransaction?.to,
data: approveTransaction?.data || '0x0',
from: approveTransaction?.from,
value: approveTransaction?.value?.toString() || '0x0',
},
{
to: swapTransaction?.to,
data: swapTransaction?.data || '0x0',
from: swapTransaction?.from,
value: swapTransaction?.value?.toString() || '0x0',
},
]
: [
{
to: swapTransaction?.to,
data: swapTransaction?.data || '0x0',
from: swapTransaction?.from,
value: swapTransaction?.value?.toString() || '0x0',
},
];

const response = (await metadataPostClient.simulateTransactions({
chainId,
transactions,
})) as TransactionSimulationResponse;
const gasLimit = response.simulateTransactions
.map((res) => res.gas.estimate)
.reduce((acc, limit) => add(acc, limit), '0');
return gasLimit;
}
} catch (e) {
return null;
}
return null;
};

export const executeSwap = async ({
chainId,
gasLimit,
Expand Down
30 changes: 30 additions & 0 deletions src/core/raps/actions/unlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,36 @@ export const estimateApprove = async ({
}
};

export const populateApprove = async ({
owner,
tokenAddress,
spender,
chainId,
}: {
owner: Address;
tokenAddress: Address;
spender: Address;
chainId: ChainId;
}): Promise<PopulatedTransaction | null> => {
try {
const provider = getProvider({ chainId });
const tokenContract = new Contract(tokenAddress, erc20ABI, provider);
const approveTransaction = await tokenContract.populateTransaction.approve(
spender,
MaxUint256,
{
from: owner,
},
);
return approveTransaction;
} catch (error) {
logger.error(new RainbowError(' error populateApprove'), {
message: (error as Error)?.message,
});
return null;
}
};

export const estimateERC721Approval = async ({
owner,
tokenAddress,
Expand Down
16 changes: 15 additions & 1 deletion src/core/raps/unlockAndSwap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
ALLOWS_PERMIT,
ChainId,
ETH_ADDRESS as ETH_ADDRESS_AGGREGATOR,
PermitSupportedTokenList,
RAINBOW_ROUTER_CONTRACT_ADDRESS,
Expand All @@ -9,6 +8,7 @@ import {
import { Address } from 'wagmi';

import { ETH_ADDRESS } from '../references';
import { ChainId } from '../types/chains';
import { isNativeAsset } from '../utils/chains';
import { add } from '../utils/numbers';
import { isLowerCaseMatch } from '../utils/strings';
Expand All @@ -19,6 +19,7 @@ import {
estimateApprove,
estimateSwapGasLimit,
} from './actions';
import { estimateUnlockAndSwapFromMetadata } from './actions/swap';
import { createNewAction, createNewRap } from './common';
import {
RapAction,
Expand Down Expand Up @@ -63,6 +64,19 @@ export const estimateUnlockAndSwap = async (
});
}

if (swapAssetNeedsUnlocking) {
const gasLimitFromMetadata = await estimateUnlockAndSwapFromMetadata({
swapAssetNeedsUnlocking,
chainId,
accountAddress,
sellTokenAddress,
quote,
});
if (gasLimitFromMetadata) {
return gasLimitFromMetadata;
}
}

let unlockGasLimit;

if (swapAssetNeedsUnlocking) {
Expand Down
25 changes: 24 additions & 1 deletion src/core/raps/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Block, Provider } from '@ethersproject/abstract-provider';
import { MaxUint256 } from '@ethersproject/constants';
import { Contract } from '@ethersproject/contracts';
import { Contract, PopulatedTransaction } from '@ethersproject/contracts';
import { StaticJsonRpcProvider } from '@ethersproject/providers';
import {
ALLOWS_PERMIT,
Expand Down Expand Up @@ -280,3 +280,26 @@ export const estimateSwapGasLimitWithFakeApproval = async (
}
return getDefaultGasLimitForTrade(quote, chainId);
};

export const populateSwap = async ({
provider,
quote,
}: {
provider: Provider;
quote: Quote | CrosschainQuote;
}): Promise<PopulatedTransaction | null> => {
try {
const { router, methodName, params, methodArgs } = getQuoteExecutionDetails(
quote,
{ from: quote.from },
provider as StaticJsonRpcProvider,
);
const swapTransaction = await router.populateTransaction[methodName](
...(methodArgs ?? []),
params,
);
return swapTransaction;
} catch (e) {
return null;
}
};
8 changes: 6 additions & 2 deletions src/core/utils/userChains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from 'viem/chains';
import { Chain, goerli, mainnet, sepolia } from 'wagmi';

import { ChainId, ChainNameDisplay } from '../types/chains';
import { ChainId, ChainNameDisplay, chainBlast } from '../types/chains';

import {
getSupportedChainsWithHardhat,
Expand All @@ -27,11 +27,13 @@ import {
export const chainIdMap: Record<
| ChainId.mainnet
| ChainId.optimism
| ChainId.arbitrum
| ChainId.polygon
| ChainId.base
| ChainId.bsc
| ChainId.zora
| ChainId.avalanche,
| ChainId.avalanche
| ChainId.blast,
ChainId[]
> = {
[ChainId.mainnet]: [mainnet.id, goerli.id, sepolia.id, holesky.id],
Expand All @@ -42,6 +44,7 @@ export const chainIdMap: Record<
[ChainId.bsc]: [bsc.id, bscTestnet.id],
[ChainId.zora]: [zora.id, zoraSepolia.id],
[ChainId.avalanche]: [avalanche.id, avalancheFuji.id],
[ChainId.blast]: [chainBlast.id],
};

export const chainLabelMap: Record<
Expand All @@ -66,6 +69,7 @@ export const chainLabelMap: Record<
[ChainId.bsc]: [ChainNameDisplay[bscTestnet.id]],
[ChainId.zora]: [ChainNameDisplay[zoraSepolia.id]],
[ChainId.avalanche]: [ChainNameDisplay[avalancheFuji.id]],
[ChainId.blast]: [ChainNameDisplay[chainBlast.id]],
};

export const sortNetworks = (order: ChainId[], chains: Chain[]) => {
Expand Down
8 changes: 7 additions & 1 deletion src/entries/popup/pages/messages/useSimulateTransaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ type SimulationMeta = {
transferTo: SimulationTarget;
};

type TransactionSimulationResponse = {
export type TransactionSimulationResponse = {
simulateTransactions: [
{
scanning: {
Expand All @@ -221,6 +221,9 @@ type TransactionSimulationResponse = {
message: string;
type: SimulationError;
};
gas: {
estimate: string;
};
simulation: {
in: SimulationChange[];
out: SimulationChange[];
Expand Down Expand Up @@ -248,6 +251,9 @@ type MessageSimulationResponse = {
message: string;
type: SimulationError;
};
gas: {
estimate: string;
};
simulation: {
in: SimulationChange[];
out: SimulationChange[];
Expand Down

0 comments on commit 776abe5

Please sign in to comment.