Skip to content
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

NFT offers/mints gas estimation improvements #5448

Merged
merged 7 commits into from
Mar 21, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/components/gas/GasSpeedButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ const GasSpeedButton = ({
validateGasParams,
flashbotTransaction = false,
crossChainServiceTime,
loading = false,
}) => {
const { colors } = useTheme();
const { navigate, goBack } = useNavigation();
Expand Down Expand Up @@ -178,7 +179,7 @@ const GasSpeedButton = ({

const formatGasPrice = useCallback(
animatedValue => {
if (animatedValue === null || isNaN(animatedValue)) {
if (animatedValue === null || loading || isNaN(animatedValue)) {
return 0;
}
!gasPriceReady && setGasPriceReady(true);
Expand All @@ -199,7 +200,7 @@ const GasSpeedButton = ({
}`;
}
},
[gasPriceReady, isLegacyGasNetwork, nativeCurrencySymbol, nativeCurrency]
[loading, gasPriceReady, isLegacyGasNetwork, nativeCurrencySymbol, nativeCurrency]
);

const openCustomOptionsRef = useRef();
Expand Down Expand Up @@ -232,7 +233,7 @@ const GasSpeedButton = ({

const renderGasPriceText = useCallback(
animatedNumber => {
const priceText = animatedNumber === 0 ? lang.t('swap.loading') : animatedNumber;
const priceText = animatedNumber === 0 || loading ? lang.t('swap.loading') : animatedNumber;
return (
<Text
color={theme === 'dark' ? colors.whiteLabel : colors.alpha(colors.blueGreyDark, 0.8)}
Expand All @@ -244,7 +245,7 @@ const GasSpeedButton = ({
</Text>
);
},
[theme, colors]
[loading, theme, colors]
);

// I'M SHITTY CODE BUT GOT THINGS DONE REFACTOR ME ASAP
Expand Down
11 changes: 9 additions & 2 deletions src/graphql/queries/metadata.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,22 @@ fragment simulationError on TransactionError {
type
}

query simulateTransactions($chainId: Int!, $transactions: [Transaction!], $domain: String!) {
simulateTransactions(chainID: $chainId, transactions: $transactions, domain: $domain) {
query simulateTransactions($chainId: Int!, $transactions: [Transaction!], $domain: String, $currency: String) {
simulateTransactions(chainID: $chainId, transactions: $transactions, domain: $domain, currency: $currency) {
error {
...simulationError
}
scanning {
result
description
}
gas {
used
estimate
}
report {
url
}
simulation {
in {
...change
Expand Down
110 changes: 0 additions & 110 deletions src/handlers/nftOffers.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/references/ethereum-units.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@
"mether": 1000000000000000000000000,
"gether": 1000000000000000000000000000,
"tether": 1000000000000000000000000000000,
"mainnet_nft_offer_gas_fee_fallback": 600000,
"mainnet_nft_offer_gas_fee_fallback": 300000,
"l2_nft_offer_gas_fee_fallback": 2000000
}
118 changes: 68 additions & 50 deletions src/screens/NFTSingleOfferSheet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import { privateKeyToAccount } from 'viem/accounts';
import { createWalletClient, http } from 'viem';

import { RainbowError, logger } from '@/logger';
import { estimateNFTOfferGas } from '@/handlers/nftOffers';
import { useTheme } from '@/theme';
import { Network } from '@/helpers';
import { getNetworkObj } from '@/networks';
Expand All @@ -51,6 +50,9 @@ import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon';
import { addNewTransaction } from '@/state/pendingTransactions';
import { getUniqueId } from '@/utils/ethereumUtils';
import { getNextNonce } from '@/state/nonces';
import { metadataPOSTClient } from '@/graphql';
import { ethUnits } from '@/references';
import { Transaction } from '@/graphql/__generated__/metadataPOST';

const NFT_IMAGE_HEIGHT = 160;
const TWO_HOURS_MS = 2 * 60 * 60 * 1000;
Expand Down Expand Up @@ -99,6 +101,7 @@ export function NFTSingleOfferSheet() {
currency: nativeCurrency,
});

const [isGasReady, setIsGasReady] = useState<boolean>(false);
const [height, setHeight] = useState(0);
const [isAccepting, setIsAccepting] = useState(false);
const txsRef = useRef<string[]>([]);
Expand Down Expand Up @@ -176,68 +179,82 @@ export function NFTSingleOfferSheet() {
}, [offer.validUntil]);

const estimateGas = useCallback(() => {
const networkObj = getNetworkObj(network);
const signer = createWalletClient({
// @ts-ignore
account: accountAddress,
chain: networkObj,
transport: http(networkObj.rpc),
});
getClient()?.actions.acceptOffer({
items: [
{
token: `${offer.nft.contractAddress}:${offer.nft.tokenId}`,
quantity: 1,
},
],
options: feeParam
? {
feesOnTop: [feeParam],
}
: undefined,
chainId: networkObj.id,
precheck: true,
wallet: signer,
onProgress: async (steps: Execute['steps']) => {
let sale;
let approval;
steps.forEach(step =>
step.items?.forEach(async item => {
if (item.data?.data && item.data?.to && item.data?.from) {
if (step.id === 'sale') {
sale = {
to: item.data.to,
from: item.data.from,
data: item.data.data,
};
} else if (step.id === 'nft-approval') {
approval = {
try {
const networkObj = getNetworkObj(network);
const signer = createWalletClient({
// @ts-ignore
account: accountAddress,
chain: networkObj,
transport: http(networkObj.rpc),
});
getClient()?.actions.acceptOffer({
items: [
{
token: `${offer.nft.contractAddress}:${offer.nft.tokenId}`,
quantity: 1,
},
],
options: feeParam
? {
feesOnTop: [feeParam],
}
: undefined,
chainId: networkObj.id,
precheck: true,
wallet: signer,
onProgress: async (steps: Execute['steps']) => {
let reservoirEstimate = 0;
const txs: Transaction[] = [];
const fallbackEstimate =
offer.network === Network.mainnet ? ethUnits.mainnet_nft_offer_gas_fee_fallback : ethUnits.l2_nft_offer_gas_fee_fallback;
steps.forEach(step =>
step.items?.forEach(item => {
if (item?.data?.to && item?.data?.from && item?.data?.data) {
txs.push({
to: item.data.to,
from: item.data.from,
data: item.data.data,
};
value: item.data.value ?? '0x0',
});
}
}
})
);
const gas = await estimateNFTOfferGas(offer, approval, sale);
if (gas) {
updateTxFee(gas, null);
startPollingGasFees(network);
}
},
});
}, [accountAddress, feeParam, network, offer, startPollingGasFees, updateTxFee]);
// @ts-ignore missing from reservoir type
const txEstimate = item.gasEstimate;
if (typeof txEstimate === 'number') {
reservoirEstimate += txEstimate;
}
})
);
const txSimEstimate = parseInt(
(
await metadataPOSTClient.simulateTransactions({
chainId: networkObj.id,
transactions: txs,
})
)?.simulateTransactions?.[0]?.gas?.estimate ?? '0x0',
16
);
const estimate = txSimEstimate || reservoirEstimate || fallbackEstimate;
if (estimate) {
updateTxFee(estimate, null);
setIsGasReady(true);
}
},
});
} catch {
logger.error(new RainbowError('NFT Offer: Failed to estimate gas'));
}
}, [accountAddress, feeParam, network, offer, updateTxFee]);

// estimate gas
useEffect(() => {
if (!isReadOnlyWallet && !isExpired) {
startPollingGasFees(network);
estimateGas();
}
return () => {
stopPollingGasFees();
};
}, [estimateGas, isExpired, isReadOnlyWallet, stopPollingGasFees]);
}, [estimateGas, isExpired, isReadOnlyWallet, network, startPollingGasFees, stopPollingGasFees, updateTxFee]);

const acceptOffer = useCallback(async () => {
logger.info(`Initiating sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`);
Expand Down Expand Up @@ -706,6 +723,7 @@ export function NFTSingleOfferSheet() {
asset={{
color: offer.nft.predominantColor || buttonColorFallback,
}}
loading={!isGasReady}
horizontalPadding={0}
currentNetwork={offer.network}
theme={theme.isDarkMode ? 'dark' : 'light'}
Expand Down