From 86594f9cd1de5baaaf1d26252d53965b89e9fb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emanuel=20Tesa=C5=99?= Date: Thu, 4 Jul 2024 11:45:58 +0200 Subject: [PATCH 1/2] Minor fixes and refactorings --- .env.example | 9 +++-- README.md | 58 +++++++++++++++++++++---------- src/accounts-to-watch.ts | 5 +-- src/cli-utils.ts | 74 +++++++++++++++++++++------------------- src/commons.ts | 2 +- src/constants.ts | 6 ++-- src/oev-bot.ts | 22 ++++++------ 7 files changed, 101 insertions(+), 75 deletions(-) diff --git a/.env.example b/.env.example index 0776b62..4db54a6 100644 --- a/.env.example +++ b/.env.example @@ -3,8 +3,7 @@ LOG_COLORIZE=true LOG_LEVEL=debug LOG_FORMAT=pretty -# Bot variables -MNEMONIC="test test test test test test test test test test test junk" - -# Optional: PERSIST_ACCOUNTS_TO_WATCH=true -# Optional: ETHER_LIQUIDATOR_ADDRESS=0xAb6702A6Fd7f0F2596f70c273376036B44a10709 +# The mnemonic for the bot that will be placing transaction on the OEV and Blast network +MNEMONIC=test test test test test test test test test test test junk +# The address of the OrbitLiquidator contract (obtained by running a deployment script) +ORBIT_LIQUIDATOR_ADDRESS= diff --git a/README.md b/README.md index 57661f0..0bd1b02 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OEV Orbit Bot Example -This repository contains an example OEV Searcher bot implementation targeting [Orbit Lending](https://orbitlending.io/). +This repository contains an example OEV searcher bot implementation targeting [Orbit Lending](https://orbitlending.io/). To understand how OEV works, visit [the OEV documentation](https://oev-docs--pr12-new-oev-docs-0y2wddya.web.app/reference/oev-network/overview/oev-network.html). @@ -8,11 +8,11 @@ Before running this application, be sure to read and understand the code. ## Process Overview -The OEV Bot follows this flow to extract OEV from Orbit Lending: +The OEV bot follows this flow to extract OEV from Orbit Lending: 1. **Initialisation** -- Get log events from the target chain and build a list of accounts to watch for possible liquidation opportunities +- Get log events from the Blast chain and build a list of accounts to watch for possible liquidation opportunities - Get log events from the OEV Network to determine awarded/live/lost bids 2. **Main Loop** @@ -28,7 +28,7 @@ Given a list of accounts to watch, the app does the following: (Refer to `findOe - Simulate liquidation potential by [transmuting](#transmutation) the oracle's value for a feed - Refer to the [Transmutation section of this README](#transmutation) for more information. - - Find Orbit's Price Oracle: `orbitSpaceStation.oracle` + - Find Orbit's Price Oracle: `orbitSpaceStation.oracle()` - Read the current value of the oracle for the target feed: `priceOracle.getUnderlyingPrice(oEtherV2)` - Apply a transmutation value to the read price: `getPercentageValue(currentEthUsdPrice, 100.2)` - Create a set of calls that can be used to transmute the value of the oracle temporarily @@ -58,17 +58,18 @@ Given a list of accounts to watch, the app does the following: (Refer to `findOe - Store the active bid's parameters -### Attempt to Exploit the OEV Liquidation Opportunity +### Attempt to Capture the OEV Liquidation Opportunity Refer to `attemptLiquidation()` - [Listen for the award, expiry or loss of the active bid](https://oev-docs--pr12-new-oev-docs-0y2wddya.web.app/reference/oev-network/searchers/submit-bids.html#checking-bid-status-and-listening-for-awarded-bids) -- If the bid is awarded, encode a multicall call set containing - - Call #1: Call to the API3 Server with the awarded bid details as call data +- If the bid is awarded, encode a multicall transaction containing + - Call #1: Call to the API3 Server with the awarded bid details as call data with value corresponding to the bid + amount - Call #2: Call the Orbit Ether Liquidator contract with the liquidation parameters - Simulate the liquidation multicall and determine the profitability - bail if the profit is below the minimum - [Execute the liquidation transaction](https://oev-docs--pr12-new-oev-docs-0y2wddya.web.app/reference/oev-network/searchers/submit-bids.html#performing-the-oracle-update-using-the-awarded-bid) -- Report the fulfilment on the OEV Network #TODO there's no page for this in the OEV docs +- Report the fulfillment on the OEV Network #TODO there's no page for this in the OEV docs https://github.com/api3dao/oev-docs/pull/12#issuecomment-2186092191 ### Transmutation @@ -77,11 +78,11 @@ In order to bid on an OEV update an application will need to determine [the bid's parameters](https://oev-docs--pr12-new-oev-docs-0y2wddya.web.app/reference/oev-network/searchers/submit-bids.html#arguments-for-placebid), and in particular: -- The value of the bid (what will be paid for the bid in the target chain's native token) +- The value of the bid (what will be paid for the bid in the Blast chain's native token) - The conditions under which the bid will be considered (less-than or greater-than a specific dAPI value) -Determining these values would generally require re-implementing the mathematical logic of the dApp being targeted, -something which is often very onerous. To make integrating into a target dApp easier, API3 has built a contract that +Determining these values would generally require re-implementing the business logic of the dApp being targeted, +something which is often very onerous. To make target dApp integration easier, API3 has built a contract that facilitates the "transmutation" of a dAPI (from the concept of transmuting silver to gold). **Purpose of Transmutation:** @@ -147,19 +148,26 @@ Copy `.env.example` to `.env` and populate it cp .env.example .env ``` -3. **Ensure Wallet is Funded:** +3. **Generate Wallet for the bot:** -Make sure the account on Blast associated with your `MNEMONIC` has sufficient funds on the OEV Network and Blast. A new -mnemonic can be generated using the +A new mnemonic can be generated using the [API3 Airnode CLI](https://docs.api3.org/reference/airnode/latest/packages/admin-cli.html#generate-mnemonic) if required ```sh pnpm dlx @api3/airnode-admin generate-mnemonic ``` -3. **Deploy and Fund the OrbitLiquidator Contract (First-time setup):** +4. **Fund the wallet with Blast ETH** + +The wallet will be capturing liquidations on the Blast network, so it needs to have some small ETH balance. The +liquidations happen through a helper contract, which also requires some ETH deposit. + +5. **Deploy and Fund the OrbitLiquidator Contract (First-time setup):** ```sh +# Install the dependencies +pnpm i + # Build the project and contract pnpm build @@ -170,11 +178,25 @@ pnpm orbit-bot:cli-utils deploy 4. **Fund the OrbitLiquidator contract** ```sh -# REQUIRED: Update `.env` with the `ETHER_LIQUIDATOR_ADDRESS` with the output of the deploy command -pnpm orbit-bot:cli-utils deposit 1 # for 1 ETH +pnpm orbit-bot:cli-utils deposit 0.01 # for 0.01 ETH ``` -5. **Run the Bot:** +5. **Bridge funds to the OEV network** + +The wallet will be interacting with the +[OEV network](https://oev-docs--pr12-new-oev-docs-0y2wddya.web.app/reference/oev-network/overview/oev-network.html) for +which it needs to have ETH balance. You can use the official +[OEV bridge](https://oev-docs--pr12-new-oev-docs-0y2wddya.web.app/reference/oev-network/bridge.html) to bridge funds +from the Ethereum network to the OEV network. + +6. **Deposit funds to the OevAuctionHouse** + +You can use the +[OEV network explorer](https://oev.explorer.api3.org/address/0x34f13A5C0AD750d212267bcBc230c87AEFD35CC5?tab=write_contract) +to call `deposit` with your wallet. Be sure to leave some ETH in the wallet as well to cover gas costs for OEV network +transactions. + +7. **Run the Bot:** ```sh pnpm orbit-bot diff --git a/src/accounts-to-watch.ts b/src/accounts-to-watch.ts index b97c5f9..d676f00 100644 --- a/src/accounts-to-watch.ts +++ b/src/accounts-to-watch.ts @@ -88,7 +88,7 @@ export const getBorrowersFromLogs = async (startBlockNumber?: number | null) => export const getAccountDetails = async (borrowerBatch: string[]) => { console.info('Fetching accounts with borrowed ETH', { count: borrowerBatch.length }); const getAccountDetailsCalls = borrowerBatch.map((borrower) => ({ - target: contractAddresses.OrbitLiquidator, + target: contractAddresses.orbitLiquidator, callData: OrbitLiquidator.interface.encodeFunctionData('getAccountDetails', [borrower]), })); const [_blockNumber1, getAccountDetailsEncoded] = await multicall3.aggregate!.staticCall(getAccountDetailsCalls); @@ -122,7 +122,7 @@ export const checkLiquidationPotentialOfAccounts = async ( // Now we determine the profitability per borrower for (let i = 0; i < accountDetails.length; i++) { - const [oTokens, borrowBalances, tokenBalances, [shortfallValue, liquidityValue]] = accountDetails[i]!; + const [oTokens, borrowBalances, tokenBalances, [liquidityValue, shortfallValue]] = accountDetails[i]!; const borrower = borrowers[i]!; let usdBorrowBalance = 0n; @@ -154,6 +154,7 @@ export const checkLiquidationPotentialOfAccounts = async ( borrower, usdBorrowBalance: formatEther(usdBorrowBalance), liquidity: formatEther(liquidityValue), + shortfall: formatEther(shortfallValue), }); accountsToWatch.push(borrower); await sleep(MIN_RPC_DELAY_MS); diff --git a/src/cli-utils.ts b/src/cli-utils.ts index 90f1679..8454827 100644 --- a/src/cli-utils.ts +++ b/src/cli-utils.ts @@ -1,35 +1,17 @@ -import { Contract, ContractFactory, formatEther, parseEther } from 'ethers'; +import { Contract, ContractFactory, ethers, formatEther, parseEther } from 'ethers'; -import { blastProvider, oEtherV2, oUsdb, wallet } from './commons'; +import { blastProvider, oEtherV2, oUsdb, oevAuctionHouse, oevNetworkProvider, wallet } from './commons'; import { getOrbitLiquidatorArtifact, OrbitLiquidatorInterface } from './interfaces'; import { contractAddresses } from './constants'; -const OrbitLiquidatorAddress = contractAddresses.OrbitLiquidator; - const main = async () => { - // Print the wallet and the liquidator contract balances. - console.info(`Wallet ETH balance (${wallet.address}) `, { - eth: formatEther(await blastProvider.getBalance(wallet.address)), - oEth: formatEther(await oEtherV2.balanceOf!(wallet.address)), - ethInOEth: formatEther(await oEtherV2.balanceOfUnderlying!.staticCall(wallet.address)), - }); - console.info('Wallet USDB balance', { - oUsdb: formatEther(await oUsdb.balanceOf(wallet.address)), - usdbInOUsdb: formatEther(await oUsdb.balanceOfUnderlying.staticCall(wallet.address)), - }); - console.info('OrbitLiquidator ETH balance', { - eth: formatEther(await blastProvider.getBalance(contractAddresses.OrbitLiquidator)), - oEth: formatEther(await oEtherV2.balanceOf!(contractAddresses.OrbitLiquidator)), - ethInOEth: formatEther(await oEtherV2.balanceOfUnderlying!.staticCall(contractAddresses.OrbitLiquidator)), - }); - console.info('OrbitLiquidator USDB balance', { - oUsdb: formatEther(await oUsdb.balanceOf(contractAddresses.OrbitLiquidator)), - usdbInOUsdb: formatEther(await oUsdb.balanceOfUnderlying.staticCall(contractAddresses.OrbitLiquidator)), - }); - const { bytecode } = getOrbitLiquidatorArtifact(); - const OrbitLiquidator = new Contract(OrbitLiquidatorAddress, OrbitLiquidatorInterface, wallet.connect(blastProvider)); + const orbitLiquidator = new Contract( + contractAddresses.orbitLiquidator, + OrbitLiquidatorInterface, + wallet.connect(blastProvider) + ); // Expected usage is to call this script with the type of command to perform. const command = process.argv[2]; @@ -49,7 +31,7 @@ const main = async () => { txHash: deployTx.deploymentTransaction()!.hash, address, }); - process.stdout.write([`Add the following to your .env:`, `ETHER_LIQUIDATOR_ADDRESS=${address} `, ``].join('\n')); + process.stdout.write([`Add the following to your .env:`, `ORBIT_LIQUIDATOR_ADDRESS=${address} `, ``].join('\n')); return; } @@ -57,13 +39,13 @@ const main = async () => { const ethToSend = process.argv[3]!; if (!ethToSend) throw new Error('ETH amount to deposit is required (e.g. 0.05)'); console.info('Depositing ETH to OrbitLiquidator contract', { - address: await OrbitLiquidator.getAddress(), - ethToSend: parseEther(ethToSend), + address: await orbitLiquidator.getAddress(), + ethToSend, }); const depositTx = await wallet.connect(blastProvider).sendTransaction({ value: parseEther(ethToSend), - to: await OrbitLiquidator.getAddress(), + to: await orbitLiquidator.getAddress(), }); await depositTx.wait(1); console.info('Deposited', { txHash: depositTx.hash }); @@ -72,27 +54,49 @@ const main = async () => { } case 'withdraw-all-eth': { console.info('Withdrawing all ETH from OrbitLiquidator contract', { - address: await OrbitLiquidator.getAddress(), + address: await orbitLiquidator.getAddress(), }); - const withdrawalTx = await OrbitLiquidator.withdrawAllEth!(); + const withdrawalTx = await orbitLiquidator.withdrawAllEth!(); await withdrawalTx.wait(1); console.info('Withdrew', { txHash: withdrawalTx.hash }); return; } - case 'withdraw-all-tokens': - case 'withdraw-all-token': { + case 'withdraw-all-tokens': { const tokenAddress = process.argv[3]!; if (!tokenAddress) throw new Error('Token address to withdraw is required'); console.info('Withdrawing all tokens from OrbitLiquidator contract', { - address: await OrbitLiquidator.getAddress(), + address: await orbitLiquidator.getAddress(), }); - const withdrawalTx = await OrbitLiquidator.withdrawAllToken!(tokenAddress); + const withdrawalTx = await orbitLiquidator.withdrawAllTokens!(tokenAddress); await withdrawalTx.wait(1); console.info('Withdrew', { txHash: withdrawalTx.hash }); return; } + case 'wallet-balances': { + // Print the wallet and the liquidator contract balances. + console.info(`Wallet balance`, { + address: wallet.address, + eth: formatEther(await blastProvider.getBalance(wallet.address)), + oEth: formatEther(await oEtherV2.balanceOf!(wallet.address)), + ethInOEth: formatEther(await oEtherV2.balanceOfUnderlying!.staticCall(wallet.address)), + oUsdb: formatEther(await oUsdb.balanceOf(wallet.address)), + usdbInOUsdb: formatEther(await oUsdb.balanceOfUnderlying.staticCall(wallet.address)), + oevNetworkEth: formatEther(await oevNetworkProvider.getBalance(wallet.address)), + oevAuctionHouseEth: formatEther(await oevAuctionHouse.bidderToBalance(wallet.address)), + }); + if (contractAddresses.orbitLiquidator !== ethers.ZeroAddress) { + console.info('OrbitLiquidator balance', { + eth: formatEther(await blastProvider.getBalance(contractAddresses.orbitLiquidator)), + oEth: formatEther(await oEtherV2.balanceOf!(contractAddresses.orbitLiquidator)), + ethInOEth: formatEther(await oEtherV2.balanceOfUnderlying!.staticCall(contractAddresses.orbitLiquidator)), + oUsdb: formatEther(await oUsdb.balanceOf(contractAddresses.orbitLiquidator)), + usdbInOUsdb: formatEther(await oUsdb.balanceOfUnderlying.staticCall(contractAddresses.orbitLiquidator)), + }); + } + return; + } default: { console.error('Unknown action', { command }); return; diff --git a/src/commons.ts b/src/commons.ts index 74db51e..4065b66 100644 --- a/src/commons.ts +++ b/src/commons.ts @@ -164,7 +164,7 @@ export const externalMulticallSimulator = new Contract( externalMulticallSimulatorInterface, blastProvider ); -export const OrbitLiquidator = new Contract(contractAddresses.OrbitLiquidator, OrbitLiquidatorInterface, blastProvider); +export const OrbitLiquidator = new Contract(contractAddresses.orbitLiquidator, OrbitLiquidatorInterface, blastProvider); export const api3ServerV1 = Api3ServerV1Factory.connect(contractAddresses.api3ServerV1, blastProvider); export const getPercentageValue = (value: bigint, percent: number) => { diff --git a/src/constants.ts b/src/constants.ts index 41d94ab..8a1b7bd 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,8 +1,8 @@ -import { parseEther } from 'ethers'; +import { ethers, parseEther } from 'ethers'; // The percentage to use when simulating a transmutation. // For example, 0.1 here would test if liquidations are profitable above 100.1% of the current data feed value -export const SIMULATION_PERCENTAGE = 0.5; +export const SIMULATION_PERCENTAGE = 0.2; export const contractAddresses = { // Blast network @@ -10,7 +10,7 @@ export const contractAddresses = { api3ServerV1: '0x709944a48cAf83535e43471680fDA4905FB3920a', externalMulticallSimulator: '0xb45fe2838F47DCCEe00F635785EAF0c723F742E5', multicall3: '0xcA11bde05977b3631167028862bE2a173976CA11', - OrbitLiquidator: process.env.ETHER_LIQUIDATOR_ADDRESS!, + orbitLiquidator: process.env.ORBIT_LIQUIDATOR_ADDRESS || ethers.ZeroAddress, orbitSpaceStation: '0x1E18C3cb491D908241D0db14b081B51be7B6e652', // OEV network diff --git a/src/oev-bot.ts b/src/oev-bot.ts index 6532c9e..ea0411b 100644 --- a/src/oev-bot.ts +++ b/src/oev-bot.ts @@ -56,7 +56,7 @@ import { * The bot's main coordinator function. * * The function starts with initialisation calls: - * - Initialise the target chain related data (acquires accounts to watch) + * - Initialise the Blast chain related data (acquires accounts to watch) * - Refer to getAccountsToWatch() and getAccountsFromFile() * - Initialise the OEV Network chain data * - Fetches OEV Network logs - this allows the app to determine won/lost bids @@ -365,7 +365,7 @@ const attemptLiquidation = async () => { callData: awardDetails, }, { - target: contractAddresses.OrbitLiquidator, + target: contractAddresses.orbitLiquidator, allowFailure: false, value: 0, callData: OrbitLiquidator.interface.encodeFunctionData('liquidate', [ @@ -434,13 +434,13 @@ const findOevLiquidation = async () => { usdbInOUsdb: formatEther(await oUsdb.balanceOfUnderlying.staticCall(wallet.address)), }); console.info('OrbitLiquidator ETH balance', { - eth: formatEther(await blastProvider.getBalance(contractAddresses.OrbitLiquidator)), - oEth: formatEther(await oEtherV2.balanceOf!(contractAddresses.OrbitLiquidator)), - ethInOEth: formatEther(await oEtherV2.balanceOfUnderlying!.staticCall(contractAddresses.OrbitLiquidator)), + eth: formatEther(await blastProvider.getBalance(contractAddresses.orbitLiquidator)), + oEth: formatEther(await oEtherV2.balanceOf!(contractAddresses.orbitLiquidator)), + ethInOEth: formatEther(await oEtherV2.balanceOfUnderlying!.staticCall(contractAddresses.orbitLiquidator)), }); console.info('OrbitLiquidator USDB balance', { - oUsdb: formatEther(await oUsdb.balanceOf(contractAddresses.OrbitLiquidator)), - usdbInOUsdb: formatEther(await oUsdb.balanceOfUnderlying.staticCall(contractAddresses.OrbitLiquidator)), + oUsdb: formatEther(await oUsdb.balanceOf(contractAddresses.orbitLiquidator)), + usdbInOUsdb: formatEther(await oUsdb.balanceOfUnderlying.staticCall(contractAddresses.orbitLiquidator)), }); // Print out the close factor. Currently, the value is set to 0.5, so we can only liquidate 50% of the borrowed asset. @@ -465,7 +465,7 @@ const findOevLiquidation = async () => { ); const { targetChainData } = storage; - if (!targetChainData) throw new Error('Target chain data not initialized.'); + if (!targetChainData) throw new Error('Blast chain data not initialized.'); const { borrowers } = targetChainData; const getAccountLiquidityCalls = borrowers.map((borrower) => { return { @@ -518,7 +518,7 @@ const findOevLiquidation = async () => { const transmutationCalls = [ ...dapiTransmutationCalls, { - target: contractAddresses.OrbitLiquidator, + target: contractAddresses.orbitLiquidator, data: OrbitLiquidator.interface.encodeFunctionData('getAccountDetails', [borrower]), }, ]; @@ -541,7 +541,7 @@ const findOevLiquidation = async () => { acc.tokenBalance > curr.tokenBalance ? acc : curr ); - const orbitLiquidatorBalance = await blastProvider.getBalance(contractAddresses.OrbitLiquidator); + const orbitLiquidatorBalance = await blastProvider.getBalance(contractAddresses.orbitLiquidator); const maxBorrowRepay = min( (((ethBorrowAsset.borrowBalance * 10n ** 18n) / transmutationValue) * closeFactor) / 10n ** 18n, orbitLiquidatorBalance, @@ -563,7 +563,7 @@ const findOevLiquidation = async () => { const liquidateBorrowCalls = [ ...dapiTransmutationCalls, { - target: contractAddresses.OrbitLiquidator, + target: contractAddresses.orbitLiquidator, data: OrbitLiquidator.interface.encodeFunctionData('liquidate', [ ethBorrowAsset.oToken, borrower, From f4b9874e30be17867464e92203d37b229064f5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emanuel=20Tesa=C5=99?= Date: Mon, 8 Jul 2024 08:50:26 +0200 Subject: [PATCH 2/2] PR suggestions --- .env.example | 4 ++-- README.md | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 4db54a6..c0d125c 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,7 @@ LOG_COLORIZE=true LOG_LEVEL=debug LOG_FORMAT=pretty -# The mnemonic for the bot that will be placing transaction on the OEV and Blast network +# The mnemonic for the bot that will be placing transactions on the OEV and Blast network MNEMONIC=test test test test test test test test test test test junk -# The address of the OrbitLiquidator contract (obtained by running a deployment script) +# The address of the OrbitLiquidator contract (obtained by running the deployment script) ORBIT_LIQUIDATOR_ADDRESS= diff --git a/README.md b/README.md index 0bd1b02..f681ba8 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Given a list of accounts to watch, the app does the following: (Refer to `findOe Refer to `attemptLiquidation()` - [Listen for the award, expiry or loss of the active bid](https://oev-docs--pr12-new-oev-docs-0y2wddya.web.app/reference/oev-network/searchers/submit-bids.html#checking-bid-status-and-listening-for-awarded-bids) -- If the bid is awarded, encode a multicall transaction containing +- If the bid is awarded, encode a multicall transaction containing: - Call #1: Call to the API3 Server with the awarded bid details as call data with value corresponding to the bid amount - Call #2: Call the Orbit Ether Liquidator contract with the liquidation parameters @@ -148,7 +148,7 @@ Copy `.env.example` to `.env` and populate it cp .env.example .env ``` -3. **Generate Wallet for the bot:** +3. **Generate Wallet for the bot** A new mnemonic can be generated using the [API3 Airnode CLI](https://docs.api3.org/reference/airnode/latest/packages/admin-cli.html#generate-mnemonic) if required @@ -162,7 +162,7 @@ pnpm dlx @api3/airnode-admin generate-mnemonic The wallet will be capturing liquidations on the Blast network, so it needs to have some small ETH balance. The liquidations happen through a helper contract, which also requires some ETH deposit. -5. **Deploy and Fund the OrbitLiquidator Contract (First-time setup):** +5. **Deploy and Fund the OrbitLiquidator Contract (First-time setup)** ```sh # Install the dependencies @@ -185,7 +185,7 @@ pnpm orbit-bot:cli-utils deposit 0.01 # for 0.01 ETH The wallet will be interacting with the [OEV network](https://oev-docs--pr12-new-oev-docs-0y2wddya.web.app/reference/oev-network/overview/oev-network.html) for -which it needs to have ETH balance. You can use the official +which it needs to have an ETH balance. You can use the official [OEV bridge](https://oev-docs--pr12-new-oev-docs-0y2wddya.web.app/reference/oev-network/bridge.html) to bridge funds from the Ethereum network to the OEV network. @@ -193,10 +193,10 @@ from the Ethereum network to the OEV network. You can use the [OEV network explorer](https://oev.explorer.api3.org/address/0x34f13A5C0AD750d212267bcBc230c87AEFD35CC5?tab=write_contract) -to call `deposit` with your wallet. Be sure to leave some ETH in the wallet as well to cover gas costs for OEV network -transactions. +to call `deposit` with your wallet. Be sure to leave some ETH in the wallet as well to cover gas costs for the OEV +network transactions. -7. **Run the Bot:** +7. **Run the Bot** ```sh pnpm orbit-bot