From 399af69e3a2b8d10bb9e9c80cbfbab7c07d616b4 Mon Sep 17 00:00:00 2001 From: escottalexander Date: Sat, 17 Feb 2024 11:30:09 -0500 Subject: [PATCH] Estimate txs in bundles to support airdrop claims (#21) * estimate bundles for airdrop claims * only use bundle simulation on production * update goerli relay to match new flow --- .../components/CustomPortal/CustomPortal.tsx | 2 +- .../CustomPortal/customPortal.module.css | 1 + .../RecoveryProcess/RecoveryProcess.tsx | 92 ++++++++------- .../useGasEstimation.ts | 84 +++++++++----- .../useRecoveryProcess.ts | 96 ++++++++-------- packages/nextjs/pages/api/relay-goerli.js | 44 ------- packages/nextjs/pages/api/relay-goerli.ts | 85 ++++++++++++++ packages/nextjs/pages/api/relay.ts | 107 +++++++++++------- packages/nextjs/pages/index.tsx | 4 +- 9 files changed, 309 insertions(+), 206 deletions(-) delete mode 100644 packages/nextjs/pages/api/relay-goerli.js create mode 100644 packages/nextjs/pages/api/relay-goerli.ts diff --git a/packages/nextjs/components/CustomPortal/CustomPortal.tsx b/packages/nextjs/components/CustomPortal/CustomPortal.tsx index 36da0a9..2c13763 100644 --- a/packages/nextjs/components/CustomPortal/CustomPortal.tsx +++ b/packages/nextjs/components/CustomPortal/CustomPortal.tsx @@ -56,7 +56,7 @@ export const CustomPortal = ({ indicator, title, image, children, video, descrip

{title}

{!!image ? {""} : <>} - {!!indicator ?
{indicator > 0 ? indicator : 11} BLOCKS
: <>} + {!!indicator ?
Attempting in block #{indicator}
: <>}
{!!video ? {""} : <>} diff --git a/packages/nextjs/components/CustomPortal/customPortal.module.css b/packages/nextjs/components/CustomPortal/customPortal.module.css index 65dd04c..e1b5cbb 100644 --- a/packages/nextjs/components/CustomPortal/customPortal.module.css +++ b/packages/nextjs/components/CustomPortal/customPortal.module.css @@ -52,6 +52,7 @@ .image { height: 190px; width: 190px; + margin: auto; } .indicator { text-align: center; diff --git a/packages/nextjs/components/Processes/RecoveryProcess/RecoveryProcess.tsx b/packages/nextjs/components/Processes/RecoveryProcess/RecoveryProcess.tsx index 48009a6..aa961bc 100644 --- a/packages/nextjs/components/Processes/RecoveryProcess/RecoveryProcess.tsx +++ b/packages/nextjs/components/Processes/RecoveryProcess/RecoveryProcess.tsx @@ -47,7 +47,7 @@ interface IProps { hackedAddress: string; donationValue: string; setDonationValue: (amt: string) => void; - blockCountdown: number; + attemptedBlock: number; isDonationLoading: boolean; totalGasEstimate: BigNumber; rpcParams: IRPCParams | undefined; @@ -60,7 +60,7 @@ export const RecoveryProcess = ({ signTransactionsStep, finishProcess, showTipsModal, - blockCountdown, + attemptedBlock, donationValue, setDonationValue, connectedAddress, @@ -215,7 +215,7 @@ export const RecoveryProcess = ({ @@ -227,28 +227,30 @@ export const RecoveryProcess = ({ ); } if (recoveryStatus == RecoveryProcessStatus.SUCCESS) { - let actions:any[] =[ { - text: "Finish", - disabled: false, - isSecondary: false, - action: () => finishProcess(), - }] - if(showDonationsButton){ - actions.unshift( { + let actions: any[] = [ + { + text: "Finish", + disabled: false, + isSecondary: false, + action: () => finishProcess(), + }, + ]; + if (showDonationsButton) { + actions.unshift({ text: "Donate", isSecondary: true, disabled: false, action: () => showTipsModal(), - }) + }); } return ( finishProcess(), - }, - ]} - image={TipsSvg} - > - - <> -
- -
- setDonationValue(val.replace(",", "."))} /> - ETH -
-

Please change the network first to {networkName}

- - - ) + title={"Support Our Mission"} + description={ + "Your contribution will help us continue to offer this service free of charge. Thank you for your support!" + } + buttons={[ + { + text: isDonationLoading ? "Sending..." : "Donate", + disabled: isDonationLoading || !hasEnoughEth || !address, + action: () => finishProcess(), + }, + ]} + image={TipsSvg} + > + <> +
+ +
+ setDonationValue(val.replace(",", "."))} + /> + ETH +
+

+ Please change the network first to {networkName} +

+ + + ); } return <>; diff --git a/packages/nextjs/hooks/flashbotRecoveryBundle/useGasEstimation.ts b/packages/nextjs/hooks/flashbotRecoveryBundle/useGasEstimation.ts index 39b7ec1..967042c 100644 --- a/packages/nextjs/hooks/flashbotRecoveryBundle/useGasEstimation.ts +++ b/packages/nextjs/hooks/flashbotRecoveryBundle/useGasEstimation.ts @@ -1,15 +1,24 @@ +import { useState } from "react"; import { useShowError } from "./useShowError"; import { FlashbotsBundleProvider } from "@flashbots/ethers-provider-bundle"; +import { Alchemy, DebugTransaction, Network } from "alchemy-sdk"; import { BigNumber } from "ethers"; import { parseEther } from "viem"; import { usePublicClient } from "wagmi"; import { CoreTxToSign, RecoveryTx } from "~~/types/business"; -import { BLOCKS_IN_THE_FUTURE } from "~~/utils/constants"; import { getTargetNetwork } from "~~/utils/scaffold-eth"; +const alchemyApiKey = process.env.NEXT_PUBLIC_ALCHEMY_API_KEY; + export const useGasEstimation = () => { const targetNetwork = getTargetNetwork(); const publicClient = usePublicClient({ chainId: targetNetwork.id }); + const [alchemy] = useState( + new Alchemy({ + apiKey: alchemyApiKey, + network: targetNetwork.network == "goerli" ? Network.ETH_GOERLI : Network.ETH_MAINNET, + }), + ); const { showError } = useShowError(); const estimateTotalGasPrice = async ( txs: RecoveryTx[], @@ -17,34 +26,55 @@ export const useGasEstimation = () => { modifyTransactions: (txs: RecoveryTx[]) => void, ) => { try { - const estimates = await Promise.all( - txs - .filter(a => a) - .map(async (tx, txId) => { - const { to, from, data, value = "0" } = tx.toEstimate; - const estimate = await publicClient - .estimateGas({ account: from, to, data, value: parseEther(value) }) - .catch(e => { - console.warn( - `Following tx will fail when bundle is submitted, so it's removed from the bundle right now. The contract might be a hacky one, and you can try further manipulation via crafting a custom call.`, - ); - console.warn(tx); - console.warn(e); - deleteTransaction(txId); - return BigNumber.from("0"); - }); - return BigNumber.from(estimate.toString()); - }), - ); + let estimates: BigNumber[] = []; + if (txs.length <= 3 && targetNetwork.network == "mainnet") { + // Try to estimate the gas for the entire bundle + const bundle = [...txs.map(tx => tx.toEstimate)]; + // TODO: Add catching so that if the bundle hasn't changed we don't need to call Alchemy again + const simulation = await alchemy.transact.simulateExecutionBundle(bundle as DebugTransaction[]); + estimates = simulation.map((result, index) => { + if (result.calls[0].error) { + console.warn( + `Following tx will fail when bundle is submitted, so it's removed from the bundle right now. The contract might be a hacky one, and you can try further manipulation via crafting a custom call.`, + ); + console.warn(index, result); + deleteTransaction(index); + return BigNumber.from("0"); + } + return BigNumber.from(result.calls[0].gasUsed); + }); + } else { + // Estimate each transaction individually + estimates = await Promise.all( + txs + .filter(a => a) + .map(async (tx, txId) => { + const { to, from, data, value = "0" } = tx.toEstimate; + const estimate = await publicClient + .estimateGas({ account: from, to, data, value: parseEther(value) }) + .catch(e => { + console.warn( + `Following tx will fail when bundle is submitted, so it's removed from the bundle right now. The contract might be a hacky one, and you can try further manipulation via crafting a custom call.`, + ); + console.warn(tx); + console.warn(e); + deleteTransaction(txId); + return BigNumber.from("0"); + }); + return BigNumber.from(estimate.toString()); + }), + ); + } const maxBaseFeeInFuture = await maxBaseFeeInFutureBlock(); - const priorityFee = BigNumber.from(10).pow(10); + // Priority fee is 3 gwei + const priorityFee = BigNumber.from(3).mul(1e9); const txsWithGas: RecoveryTx[] = estimates.map((gas, index) => { const mainTx = txs[index] as RecoveryTx; // If the tx doesn't exist, skip adding gas properties if (!mainTx) return mainTx; const tx = Object.assign({}, mainTx.toEstimate) as CoreTxToSign; - // Buffer the gas limit by 5% - tx.gas = gas.mul(105).div(100).toString(); + // Buffer the gas limit by 15% + tx.gas = gas.mul(115).div(100).toString(); // Set type tx.type = "eip1559"; // Set suggested gas properties @@ -59,7 +89,7 @@ export const useGasEstimation = () => { } const totalGasCost = estimates .reduce((acc: BigNumber, val: BigNumber) => acc.add(val), BigNumber.from("0")) - .mul(105) + .mul(115) .div(100); const gasPrice = maxBaseFeeInFuture.add(priorityFee); return totalGasCost.mul(gasPrice); @@ -75,10 +105,8 @@ export const useGasEstimation = () => { const maxBaseFeeInFutureBlock = async () => { const blockNumberNow = await publicClient.getBlockNumber(); const block = await publicClient.getBlock({ blockNumber: blockNumberNow }); - return FlashbotsBundleProvider.getMaxBaseFeeInFutureBlock( - BigNumber.from(block.baseFeePerGas), - BLOCKS_IN_THE_FUTURE[targetNetwork.id], - ); + // Get the max base fee in 3 blocks to reduce the amount of eth spent on the transaction (possible to get priced out if blocks are full but unlikely) + return FlashbotsBundleProvider.getMaxBaseFeeInFutureBlock(BigNumber.from(block.baseFeePerGas), 3); }; return { diff --git a/packages/nextjs/hooks/flashbotRecoveryBundle/useRecoveryProcess.ts b/packages/nextjs/hooks/flashbotRecoveryBundle/useRecoveryProcess.ts index 3044974..1f3bca4 100644 --- a/packages/nextjs/hooks/flashbotRecoveryBundle/useRecoveryProcess.ts +++ b/packages/nextjs/hooks/flashbotRecoveryBundle/useRecoveryProcess.ts @@ -38,7 +38,7 @@ export const useRecoveryProcess = () => { const [gasCovered, setGasCovered] = useState(false); const [sentTxHash, setSentTxHash] = useLocalStorage("sentTxHash", ""); const [sentBlock, setSentBlock] = useLocalStorage("sentBlock", 0); - const [blockCountdown, setBlockCountdown] = useLocalStorage("blockCountdown", 0); + const [attemptedBlock, setAttemptedBlock] = useLocalStorage("attemptedBlock", 0); const { showError } = useShowError(); const [stepActive, setStepActive] = useState(RecoveryProcessStatus.INITIAL); @@ -64,36 +64,6 @@ export const useRecoveryProcess = () => { })(); }, [targetNetwork.id]); - useInterval(async () => { - const isNotAbleToListenBundle = stepActive != RecoveryProcessStatus.LISTEN_BUNDLE || !sentTxHash || sentBlock == 0; - try { - if (isNotAbleToListenBundle) return; - const finalTargetBlock = sentBlock + BLOCKS_IN_THE_FUTURE[targetNetwork.id]; - const currentBlock = parseInt((await publicClient.getBlockNumber()).toString()); - const blockDelta = finalTargetBlock - currentBlock; - setBlockCountdown(blockDelta); - - if (blockDelta < 0) { - showError( - `The recovery has failed. To solve this issue, remove all "Hacked Wallet Recovery RPC" and clear activity data. Check this video`, - true, - ); - setSentBlock(0); - setSentTxHash(""); - resetStatus(); - return; - } - const txReceipt = await publicClient.getTransactionReceipt({ - hash: sentTxHash as `0x${string}`, - }); - if (txReceipt && txReceipt.blockNumber) { - setStepActive(RecoveryProcessStatus.SUCCESS); - } - // return; - console.log("TXs not yet mined"); - } catch (e) {} - }, 5000); - const resetStatus = () => { setStepActive(RecoveryProcessStatus.INITIAL); }; @@ -130,10 +100,12 @@ export const useRecoveryProcess = () => { if (block) { const maxBaseFeeInFutureBlock = FlashbotsBundleProvider.getMaxBaseFeeInFutureBlock( block.baseFeePerGas as BigNumber, - 10, + 3, ).toString(); - const priorityFee = BigNumber.from(10).pow(10).toString(); // 10 Gwei - return { maxBaseFeeInFutureBlock, priorityFee }; + const priorityFee = BigNumber.from(3).mul(1e9).toString(); // 3 Gwei + // Buffer the max base fee by 15% + const adjustedMaxBaseFeeInFutureBlock = BigNumber.from(maxBaseFeeInFutureBlock).mul(120).div(100).toString(); + return { maxBaseFeeInFutureBlock: adjustedMaxBaseFeeInFutureBlock, priorityFee }; } return { maxBaseFeeInFutureBlock: "0", priorityFee: "0" }; }; @@ -155,7 +127,7 @@ export const useRecoveryProcess = () => { hackedAddress: string, transactions: RecoveryTx[], currentBundleId: string, - surpass: boolean = false, + surpass = false, ) => { if (!surpass && !gasCovered) { showError("How did you come here without covering the gas fee first??"); @@ -206,8 +178,12 @@ export const useRecoveryProcess = () => { const finalBundle = await ( await fetch( `https://rpc${targetNetwork.network == "goerli" ? "-goerli" : ""}.flashbots.net/bundle?id=${currentBundleId}`, + { + cache: "no-store", + }, ) ).json(); + if (!finalBundle || !finalBundle.rawTxs) { showError("Couldn't fetch latest bundle"); resetStatus(); @@ -217,22 +193,48 @@ export const useRecoveryProcess = () => { const txs = finalBundle.rawTxs.reverse(); try { + setStepActive(RecoveryProcessStatus.LISTEN_BUNDLE); setSentTxHash(ethers.utils.keccak256(txs[0])); setSentBlock(parseInt((await publicClient.getBlockNumber()).toString())); const currentUrl = window.location.href.replace("?", ""); - const response = await fetch(currentUrl + `api/relay${targetNetwork.network == "goerli" ? "-goerli" : ""}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - txs, - }), - }); + while (true) { + const currentBlock = parseInt((await publicClient.getBlockNumber()).toString()); + setAttemptedBlock(currentBlock + 2); + const response = await fetch(currentUrl + `api/relay${targetNetwork.network == "goerli" ? "-goerli" : ""}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + txs, + }), + cache: "no-store", + }); - await response.json(); - setStepActive(RecoveryProcessStatus.LISTEN_BUNDLE); + const parsedResponse = await response.json(); + console.log("DEBUG", parsedResponse); + if (parsedResponse.error) { + throw new Error(parsedResponse.error); + } + // Success + if (parsedResponse.success) { + setStepActive(RecoveryProcessStatus.SUCCESS); + break; + } + // Error + if (parsedResponse.SimulationResponse && (parsedResponse.SimulationResponse as any).error) { + showError( + `The recovery has failed. To solve this issue, remove all "Hacked Wallet Recovery RPC" and clear activity data. Check this video`, + true, + ); + setSentTxHash(""); + setSentBlock(0); + resetStatus(); + break; + } + // BlockPassedWithoutInclusion - try again + } } catch (e) { console.error(e); setSentTxHash(""); @@ -405,7 +407,7 @@ export const useRecoveryProcess = () => { data: stepActive, sentBlock, sentTxHash, - blockCountdown, + attemptedBlock, changeFlashbotNetwork, startRecoveryProcess, signTransactionsStep, diff --git a/packages/nextjs/pages/api/relay-goerli.js b/packages/nextjs/pages/api/relay-goerli.js deleted file mode 100644 index 2ce7427..0000000 --- a/packages/nextjs/pages/api/relay-goerli.js +++ /dev/null @@ -1,44 +0,0 @@ -import { FlashbotsBundleProvider } from "@flashbots/ethers-provider-bundle"; -import { ethers } from "ethers"; - -const SEND_ITER = 10; - -const goerliProvider = new ethers.providers.InfuraProvider("goerli", "416f5398fa3d4bb389f18fd3fa5fb58c"); -const goerliFlashbotProvider = await FlashbotsBundleProvider.create( - goerliProvider, - ethers.Wallet.createRandom(), - "https://relay-goerli.flashbots.net/", - "goerli", -); - -export default async function handler(req, res) { - const body = req.body; - if (!body || !body.txs || body.txs.length == 0) { - res.status(400).json({ reason: "Bad bundle" }); - } - - const reformattedBundle = body.txs.map(signedTx => { - return { signedTransaction: signedTx }; - }); - const signedBundle = await goerliFlashbotProvider.signBundle(reformattedBundle); - const currentBlockNumber = await goerliProvider.getBlockNumber(); - - // const simulationResult = await goerliFlashbotProvider.simulate(signedBundle, currentBlockNumber + 2); - // console.log(simulationResult); - - const submissionPromises = []; - for (var i = 1; i <= SEND_ITER; i++) - submissionPromises.push(goerliFlashbotProvider.sendRawBundle(signedBundle, currentBlockNumber + i)); - const results = await Promise.all(submissionPromises); - - // console.log("-------------------------------- SUBMITTED"); - // Promise.all( - // results.map(r => - // r.wait().then(waited => { - // console.log("ONE SUBMISSION WAITED. RESULT: " + waited); - // }), - // ), - // ); - - res.status(203).json({ response: `Bundle submitted` }); -} diff --git a/packages/nextjs/pages/api/relay-goerli.ts b/packages/nextjs/pages/api/relay-goerli.ts new file mode 100644 index 0000000..8ee89eb --- /dev/null +++ b/packages/nextjs/pages/api/relay-goerli.ts @@ -0,0 +1,85 @@ +import { + FlashbotsBundleProvider, + FlashbotsTransactionResponse, + RelayResponseError, +} from "@flashbots/ethers-provider-bundle"; +import { ethers } from "ethers"; + +const goerliProvider = new ethers.providers.InfuraProvider("goerli", "416f5398fa3d4bb389f18fd3fa5fb58c"); +const goerliFlashbotProvider = await FlashbotsBundleProvider.create( + goerliProvider, + ethers.Wallet.createRandom(), + "https://relay-goerli.flashbots.net/", + "goerli", +); + +export default async function handler(req: any, res: any) { + const body = req.body; + if (!body || !body.txs || body.txs.length == 0) { + res.status(400).json({ reason: "Bad bundle" }); + } + + const reformattedBundle: string[] = body.txs.map((signedTx: any) => { + return signedTx; + }); + + const targetBlockNumber = (await goerliProvider.getBlockNumber()) + 1; + const flashbotsTransactionResponse = await goerliFlashbotProvider.sendRawBundle(reformattedBundle, targetBlockNumber); + + console.log(`@@@@@@ Bundle submitted targetting block#${targetBlockNumber}`); + + if (Object.hasOwn(flashbotsTransactionResponse, "error")) { + const errorResponse = flashbotsTransactionResponse as RelayResponseError; + res.status(203).json({ + response: `Bundle reverted with error: ${errorResponse.error.message}`, + success: false, + }); + console.log(`@@@@@@ Bundle reverted with error: ${errorResponse.error}`); + return; + } + + const submissionResponse = flashbotsTransactionResponse as FlashbotsTransactionResponse; + console.log(`@@@@@@ Waiting for resolution....`); + const bundleResolution = await submissionResponse.wait(); + + // BundleIncluded + if (bundleResolution == 0) { + res.status(203).json({ + response: `Bundle successfully included in block number ${targetBlockNumber}!!`, + success: true, + simulationResult: null, + }); + console.log(`@@@@@@ Bundle successfully included in block number ${targetBlockNumber}!!`); + return; + } + + // BlockPassedWithoutInclusion + if (bundleResolution == 1) { + res.status(203).json({ + response: `BlockPassedWithoutInclusion.`, + success: false, + simulationResult: null, + }); + console.log(`@@@@@@ BlockPassedWithoutInclusion.`); + return; + } + + // AccountNonceTooHigh + if (bundleResolution == 2) { + res.status(203).json({ + response: `Bundle submitted but reverted because account nonce is too high. Clear activity data and start all over again.`, + success: false, + simulationResult: null, + }); + console.log( + `@@@@@@ Bundle submitted but reverted because account nonce is too high. Clear activity data and start all over again.`, + ); + return; + } + + res.status(203).json({ + response: `Unexpected state`, + success: false, + simulationResult: null, + }); +} diff --git a/packages/nextjs/pages/api/relay.ts b/packages/nextjs/pages/api/relay.ts index 2f0579c..53231d7 100644 --- a/packages/nextjs/pages/api/relay.ts +++ b/packages/nextjs/pages/api/relay.ts @@ -1,11 +1,12 @@ import { FlashbotsBundleProvider, - FlashbotsBundleResolution, FlashbotsTransactionResponse, RelayResponseError, + SimulationResponse, } from "@flashbots/ethers-provider-bundle"; import { ethers } from "ethers"; -export const maxDuration = 240 + +export const maxDuration = 240; const mainnetProvider = new ethers.providers.InfuraProvider(1, "416f5398fa3d4bb389f18fd3fa5fb58c"); const flashbotProvider = await FlashbotsBundleProvider.create( mainnetProvider, @@ -19,57 +20,79 @@ export default async function handler(req: any, res: any) { res.status(400).json({ reason: "Bad bundle" }); } - const reformattedBundle = body.txs.map((signedTx: any) => { - return { signedTransaction: signedTx }; + const reformattedBundle: string[] = body.txs.map((signedTx: any) => { + return signedTx; }); - let retries = 5; - while (retries > 0) { - // console.log(`@@@@@@ TRIAL #${3 - retries} OUT OF ${retries} @@@@@@`); - const targetBlockNumber = (await mainnetProvider.getBlockNumber()) + 1; - const flashbotsTransactionResponse = await flashbotProvider.sendBundle(reformattedBundle, targetBlockNumber); + const targetBlockNumber = (await mainnetProvider.getBlockNumber()) + 1; + const flashbotsTransactionResponse = await flashbotProvider.sendRawBundle(reformattedBundle, targetBlockNumber); + + // console.log(`@@@@@@ Bundle submitted targetting block#${targetBlockNumber}`); - // console.log(`@@@@@@ Bundle submitted targetting block#${targetBlockNumber}`); + if (Object.hasOwn(flashbotsTransactionResponse, "error")) { + const errorResponse = flashbotsTransactionResponse as RelayResponseError; + res.status(203).json({ + response: `Bundle reverted with error: ${errorResponse.error.message}`, + success: false, + }); + // console.log(`@@@@@@ Bundle reverted with error: ${errorResponse.error}`); + return; + } - if (Object.hasOwn(flashbotsTransactionResponse, "error")) { - const errorResponse = flashbotsTransactionResponse as RelayResponseError; - res.status(203).json({ - response: `Bundle reverted with error: ${errorResponse.error}`, - }); - // console.log(`@@@@@@ Bundle reverted with error: ${errorResponse.error}`); - return; - } + const simulationResult: SimulationResponse = await flashbotProvider.simulate(reformattedBundle, targetBlockNumber); - const submissionResponse = flashbotsTransactionResponse as FlashbotsTransactionResponse; - // console.log(`@@@@@@ Waiting for resolution....`); - const bundleResolution = await submissionResponse.wait(); + if (simulationResult && (simulationResult as RelayResponseError).error) { + res.status(203).json({ + response: `Bundle reverted with error: ${(simulationResult as RelayResponseError).error.message}`, + success: false, + }); + // console.log(`@@@@@@ Bundle simulation reverted with error: ${(simulationResult as RelayResponseError).error}`); + return; + } - if (bundleResolution == FlashbotsBundleResolution.AccountNonceTooHigh) { - res.status(203).json({ - response: `Bundle submitted but reverted because account nonce is too high. Clear activity data and start all over again.`, - }); - // console.log( - // `@@@@@@ Bundle submitted but reverted because account nonce is too high. Clear activity data and start all over again.`, - // ); - return; - } + const submissionResponse = flashbotsTransactionResponse as FlashbotsTransactionResponse; + // console.log(`@@@@@@ Waiting for resolution....`); + const bundleResolution = await submissionResponse.wait(); - if (bundleResolution == FlashbotsBundleResolution.BundleIncluded) { - res.status(203).json({ - response: `Bundle successfully included in block number ${targetBlockNumber}!!`, - }); - // console.log(`@@@@@@ Bundle successfully included in block number ${targetBlockNumber}!!`); - return; - } + // BundleIncluded + if (bundleResolution == 0) { + res.status(203).json({ + response: `Bundle successfully included in block number ${targetBlockNumber}!!`, + success: true, + simulationResult, + }); + // console.log(`@@@@@@ Bundle successfully included in block number ${targetBlockNumber}!!`); + return; + } + + const exactSimulationResult: SimulationResponse = await submissionResponse.simulate(); + // BlockPassedWithoutInclusion + if (bundleResolution == 1) { + res.status(203).json({ + response: `BlockPassedWithoutInclusion.`, + success: false, + simulationResult: exactSimulationResult, + }); + // console.log(`@@@@@@ BlockPassedWithoutInclusion.`); + return; + } - if (bundleResolution == FlashbotsBundleResolution.BlockPassedWithoutInclusion) { - retries--; - // console.log(`@@@@@@ BlockPassedWithoutInclusion. Retrying submission`); - continue; - } + // AccountNonceTooHigh + if (bundleResolution == 2) { + res.status(203).json({ + response: `Bundle submitted but reverted because account nonce is too high. Clear activity data and start all over again.`, + success: false, + simulationResult: exactSimulationResult, + }); + // console.log( + // `@@@@@@ Bundle submitted but reverted because account nonce is too high. Clear activity data and start all over again.`, + // ); + return; } res.status(203).json({ response: `Unexpected state`, + success: false, + simulationResult: exactSimulationResult, }); } diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index 33d65c9..cbad7c4 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -44,7 +44,7 @@ const Home: NextPage = () => { startRecoveryProcess, signTransactionsStep, signRecoveryTransactions, - blockCountdown, + attemptedBlock, showTipsModal, unsignedTxs, generateCorrectTransactions, @@ -179,7 +179,7 @@ const Home: NextPage = () => { totalGasEstimate={totalGasEstimate} showTipsModal={showTipsModal} startProcess={add => startRecovery(add)} - blockCountdown={blockCountdown} + attemptedBlock={attemptedBlock} connectedAddress={connectedAddress} safeAddress={safeAddress} hackedAddress={hackedAddress}