//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import "openzeppelin-contracts/contracts/access/Ownable.sol";
import "contracts/MevShareCaptureLogger.sol";
import "./MevShareCTF.sol";
contract MevShareCTFTriple is MevShareCTFBase {
uint256 public activeBlock;
mapping (address => mapping (uint256 => uint256)) addressBlockCount;
event Activate();
constructor(MevShareCaptureLogger _mevShareCaptureLogger) MevShareCTFBase(_mevShareCaptureLogger) payable {
}
function activateRewardTriple() external payable onlyOwner {
activeBlock = block.number;
emit Activate();
}
function claimReward() external {
require (activeBlock == block.number);
require (tx.origin == msg.sender);
uint256 claimCount = addressBlockCount[tx.origin][block.number] + 1;
if (claimCount == 3) {
mevShareCaptureLogger.registerCapture(401, tx.origin);
return;
}
addressBlockCount[tx.origin][block.number] = claimCount;
}
}
This is fairly simple, since addressBlockCount[tx.origin][block.number]
starts from 0 and gets incremented by 1 each time, we can call claimReward()
three times. So there will be 4 txs in the bundle.
import MevShareClient, {
BundleParams,
HintPreferences,
IPendingBundle,
IPendingTransaction,
TransactionOptions
} from "@flashbots/mev-share-client"
import { Contract, JsonRpcProvider, Wallet } from 'ethers'
import { MevShareCTFTriple_ABI } from './abi'
import dotenv from "dotenv"
dotenv.config()
const TX_GAS_LIMIT = 400000
const MAX_GAS_PRICE = 20n
const MAX_PRIORITY_FEE = 20n
const GWEI = 10n ** 9n
const RPC_URL = process.env.RPC_URL
const PRIVATE_KEY = process.env.PRIVATE_KEY || Wallet.createRandom().privateKey
const provider = new JsonRpcProvider(RPC_URL)
const signer = new Wallet(PRIVATE_KEY, provider)
const mevShare = MevShareClient.useEthereumGoerli(signer)
const MevShareCTFTriple_ADDRESS = "0x1eA6Fb65BAb1f405f8Bdb26D163e6984B9108478"
const mevShareCTFTriple = new Contract(MevShareCTFTriple_ADDRESS, MevShareCTFTriple_ABI, signer)
function transactionIsRelevant(pendingTx: IPendingTransaction, PAIR_ADDRESS: string) {
return ((pendingTx.logs || []).some(log => log.address === MevShareCTFTriple_ADDRESS.toLowerCase()))
}
async function getSignedBackrunTx(nonce: number) {
const backrunTx = await mevShareCTFTriple.claimReward.populateTransaction({from: signer.address})
const backrunTxFull = {
...backrunTx,
chainId: 5,
maxFeePerGas: MAX_GAS_PRICE * GWEI,
maxPriorityFeePerGas: MAX_PRIORITY_FEE * GWEI,
gasLimit: TX_GAS_LIMIT,
nonce: nonce
}
return signer.signTransaction(backrunTxFull)
}
async function backrunAttempt( currentBlockNumber: number, nonce: number, pendingTxHash: string) {
const backrunSignedTx1 = await getSignedBackrunTx(nonce)
const backrunSignedTx2 = await getSignedBackrunTx(++nonce)
const backrunSignedTx3 = await getSignedBackrunTx(++nonce)
try {
const sendBundleResult = await mevShare.sendBundle({
inclusion: { block: currentBlockNumber + 1 },
body: [
{ hash: pendingTxHash },
{ tx: backrunSignedTx1, canRevert: false },
{ tx: backrunSignedTx2, canRevert: false },
{ tx: backrunSignedTx3, canRevert: false }
]
},)
console.log('Bundle Hash: ' + sendBundleResult.bundleHash)
} catch (e) {
console.log('Error: ', e)
}
}
async function main() {
mevShare.on('transaction', async ( pendingTx: IPendingTransaction ) => {
// console.log(pendingTx)
if (transactionIsRelevant(pendingTx, MevShareCTFTriple_ADDRESS)) {
console.log(pendingTx)
const currentBlockNumber = await provider.getBlockNumber()
const nonce = await signer.getNonce('latest')
backrunAttempt(currentBlockNumber, nonce, pendingTx.hash);
}
})
}
main()