Skip to content

Instantly share code, notes, and snippets.

@sonicflare
Created October 17, 2021 00:49
Show Gist options
  • Save sonicflare/f8b60536d849e40f45fbc21e2c15bfe9 to your computer and use it in GitHub Desktop.
Save sonicflare/f8b60536d849e40f45fbc21e2c15bfe9 to your computer and use it in GitHub Desktop.
import { BigNumber, providers, Wallet, Contract } from "ethers";
import { FlashbotsBundleProvider, FlashbotsBundleResolution, FlashbotsBundleTransaction, SimulationResponseSuccess } from "@flashbots/ethers-provider-bundle";
import { Provider } from "@ethersproject/abstract-provider";
import { Console } from "console";
import { send } from "process";
import * as fs from "fs"
import { connect } from "http2";
const MWEI = 10n ** 6n
const GWEI = 10n ** 9n
const ETHER = 10n ** 18n
const LEASH_ABI = '[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"name","type":"string"},{"name":"symbol","type":"string"},{"name":"decimals","type":"uint8"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"paused","type":"bool"}],"name":"setRebasePaused","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"paused","type":"bool"}],"name":"setTokenPaused","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"rebasePaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"epoch","type":"uint256"},{"name":"supplyDelta","type":"int256"}],"name":"rebase","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"tokenPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"monetaryPolicy_","type":"address"}],"name":"setMonetaryPolicy","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"monetaryPolicy","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner_","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"epoch","type":"uint256"},{"indexed":false,"name":"totalSupply","type":"uint256"}],"name":"LogRebase","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"paused","type":"bool"}],"name":"LogRebasePaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"paused","type":"bool"}],"name":"LogTokenPaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"monetaryPolicy","type":"address"}],"name":"LogMonetaryPolicyUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"}],"name":"OwnershipRenounced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}]';
//const LEASH_CONTRACT = "0x27C70Cd1946795B66be9d954418546998b546634";
const LEASH_CONTRACT = "0x27C70Cd1946795B66be9d954418546998b546634";
const NFT_CONTRACT = "0x11450058d796b02eb53e65374be59cff65d3fe7f";
const NFT_MINT_DATA = "0xc04b78fe000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000026c9022ebb87baf00000000000000000000000000000000000000000000000000000000000000000";
const bank = "YOUR PRIVATE KEY";
const MAX_GWEI = 2000;
const numberOfBots = 1;
type TransactionObject
= {
ethTransfer: FlashbotsBundleTransaction,
leashTransfer: FlashbotsBundleTransaction,
approve: FlashbotsBundleTransaction,
mint: FlashbotsBundleTransaction
};
//const CHAIN_ID = 5; //gorli
const CHAIN_ID = 1; //mainnet
const provider = new providers.InfuraProvider(CHAIN_ID)
//const FLASHBOTS_ENDPOINT = "https://relay-goerli.flashbots.net";
const FLASHBOTS_ENDPOINT = "https://relay.flashbots.net";
const bankWallet = new Wallet(bank, provider);
const contract = new Contract(LEASH_CONTRACT, LEASH_ABI, provider);
async function mintFromWallet() {
var promiseResolve, promiseReject;
var promise = new Promise(function (resolve, reject) {
promiseResolve = resolve;
promiseReject = reject;
});
//var botWallets = [];
//const wallet = new Wallet(wallets[4], provider)
const wallet = Wallet.createRandom();
console.info("Wallet:" + wallet.address);
console.info("Key:" + wallet.privateKey);
console.info("Mnemonics:" + wallet.mnemonic);
fs.appendFile("keys.txt",
`${Date.toString()}
Wallet: ${wallet.address}
Key: ${wallet.privateKey}
Mnemonics: ${wallet.mnemonic}
`, function (err) {
if (err) return console.log(err);
//console.log('Hello World > helloworld.txt')
});
fs.appendFile("keys2.txt",
`${Date.toString()}
Wallet: ${wallet.address}
Key: ${wallet.privateKey}
Mnemonics: ${wallet.mnemonic}
`, function (err) {
if (err) return console.log(err);
//console.log('Hello World > helloworld.txt')
});
fs.appendFile("keys3.txt",
`${Date.toString()}
Wallet: ${wallet.address}
Key: ${wallet.privateKey}
Mnemonics: ${wallet.mnemonic}
`, function (err) {
if (err) return console.log(err);
//console.log('Hello World > helloworld.txt')
});
//var currentBalance = await contract.balanceOf(wallet.address);
var sendLeash = BigNumber.from(0);
var sendEtherium = BigNumber.from(0);
const maxLeashSend = BigNumber.from(3).mul(ETHER);
const maxETHSend = BigNumber.from(3).mul(ETHER);
// var currentETHBalance = await bankWallet.getBalance();
var approveTransactionData = await contract.populateTransaction.approve(NFT_CONTRACT, ETHER * 100000000000000n);
var transferLeashTransactionData = await contract.populateTransaction.transfer(wallet.address, ETHER * 3n);
var balanceOfTransactionData = await contract.populateTransaction.balanceOf(wallet.address);
// var allowedSpending = await contract.allowance(wallet.address, NFT_CONTRACT);
// console.log(allowedSpending);
const flashbotsProvider = await FlashbotsBundleProvider.create(provider, Wallet.createRandom(), FLASHBOTS_ENDPOINT)
// transfer ETH funds to bot
var transaferETHTransaction =
{
transaction: {
chainId: CHAIN_ID,
type: 2,
value: ETHER * 10n,
to: wallet.address,
//gasLimit: 2100000n
},
signer: bankWallet
};
//transfer LEASH to bot
var transferLeashTransaction = {
transaction: {
chainId: CHAIN_ID,
type: 2,
value: 0,
data: transferLeashTransactionData.data,
// maxFeePerGas: GWEI * 4n,
// maxPriorityFeePerGas: GWEI * 4n,
to: LEASH_CONTRACT,
//gasLimit: 2100000n
},
signer: bankWallet
};
// approve use of Leash by NFT contract
var approveLeashTransaction = {
transaction: {
chainId: CHAIN_ID,
type: 2,
value: 0,
data: approveTransactionData.data,
// maxFeePerGas: GWEI * 7n,
// maxPriorityFeePerGas: GWEI * 7n,
to: LEASH_CONTRACT,
//gasLimit: 2100000n
},
signer: wallet
};
var approveLeashTransactionGas = await provider.estimateGas({ ...approveLeashTransaction.transaction });
approveLeashTransaction.transaction.gasLimit = approveLeashTransactionGas.toBigInt();
var mintNFTTransaction = {
transaction: {
chainId: CHAIN_ID,
type: 2,
value: 0,
data: NFT_MINT_DATA,
// maxFeePerGas: GWEI * 7n,
// maxPriorityFeePerGas: GWEI * 7n,
to: NFT_CONTRACT,
gasLimit: 3100000n
},
signer: wallet
};
// transfer ETH funds to bot
var balanceOfTransaction =
{
transaction: {
chainId: CHAIN_ID,
type: 2,
value: 0,
to: LEASH_CONTRACT,
data: balanceOfTransactionData.data
//gasLimit: 2100000n
},
signer: bankWallet
};
var transactionFn = async (blockNumber) => {
console.log(` Block: ${blockNumber}`);
var transactionObject = {
ethTransfer: transaferETHTransaction,
leashTransfer: transferLeashTransaction,
approve: approveLeashTransaction,
mint: mintNFTTransaction
};
var simulationTransactions = [
transactionObject.ethTransfer,
transactionObject.leashTransfer,
transactionObject.approve,
transactionObject.mint,
balanceOfTransaction
];
var transactions = [
transactionObject.ethTransfer,
transactionObject.leashTransfer,
transactionObject.approve,
transactionObject.mint,
];
var currentBlock = await provider.getBlock(blockNumber);
var lastBlockGwei = currentBlock.baseFeePerGas;
var lastBlockGweiHuman = lastBlockGwei.mul(100).div(GWEI).toNumber() / 100;
var proposedGwei = lastBlockGwei.add(BigNumber.from(GWEI).mul(31));
var proposedGweiHuman = proposedGwei.mul(100).div(GWEI).toNumber() / 100;
//console.log(lastBlockGwei);
console.log("Base fee: " + lastBlockGweiHuman + " GWEI " + `(${lastBlockGwei})`);
console.log("Proposed GWEI: " + proposedGweiHuman + ` (${proposedGwei})`);
if (lastBlockGweiHuman > MAX_GWEI) {
console.warn("Price " + lastBlockGweiHuman + " higher than MAX_GWEI of " + MAX_GWEI);
return;
}
if (!await symulateAndAdjustPrices(flashbotsProvider, simulationTransactions, transactions, blockNumber, transactionObject, proposedGwei, wallet)) {
console.warn("Simulation failed");
provider.off('block', transactionFn);
freeze(11000000);
return;
}
const bundleSubmitResponse = await flashbotsProvider.sendBundle(transactions, blockNumber + 1);
// By exiting this function (via return) when the type is detected as a "RelayResponseError", TypeScript recognizes bundleSubmitResponse must be a success type object (FlashbotsTransactionResponse) after the if block.
if ('error' in bundleSubmitResponse) {
console.warn(bundleSubmitResponse.error.message);
provider.off('block', transactionFn);
return;
}
console.log("...Submitted");
var result = await bundleSubmitResponse.wait();
if (result == FlashbotsBundleResolution.BundleIncluded) {
console.log(` Block: ${blockNumber}`);
console.log(` SUCCESS!`);
console.log(wallet.address);
console.log(transactions[0].transaction.value.toString());
console.log(transactions);
provider.off('block', transactionFn);
promiseResolve();
return;
} else {
console.error(` Missed`);
}
};
provider.on('block', transactionFn);
return promise;
}
function freeze(time) {
const stop = new Date().getTime() + time;
while (new Date().getTime() < stop);
}
async function symulateAndAdjustPrices(flashbotsProvider: FlashbotsBundleProvider, transactions: Array<FlashbotsBundleTransaction>, finalTransactions: Array<FlashbotsBundleTransaction>, blockNumber, transactionObject: TransactionObject, proposedGwei: BigNumber, wallet: Wallet): boolean {
const priorityFee = BigNumber.from(GWEI).mul(2n);
for (var transaction of transactions) {
transaction.transaction.maxFeePerGas = proposedGwei;
transaction.transaction.maxPriorityFeePerGas = BigNumber.from(GWEI).mul(2n);
}
var currentLeashBalance = await contract.balanceOf(bankWallet.address);
var currentETHBalance = await bankWallet.getBalance();
const maxLeashSend = BigNumber.from(3n).mul(ETHER);
const maxETHSend = BigNumber.from(3n).mul(ETHER);
var sendLeash = maxLeashSend;
var sendETH = maxETHSend;
if (sendLeash.gt(currentLeashBalance)) {
sendLeash = currentLeashBalance;
}
if (sendETH.gt(currentETHBalance)) {
sendETH = currentETHBalance;
}
transactionObject.ethTransfer.transaction.value = sendETH;
await updateSendLeash(transactionObject, wallet, sendLeash);
var bundleSubmitResponse = await simulateAndVerifyBundle(flashbotsProvider, transactions, blockNumber);
if (!bundleSubmitResponse) {
return false;
}
let result = (bundleSubmitResponse as SimulationResponseSuccess);
for (const [index, transaction] of result.results.entries()) {
transactions[index].transaction.gasLimit = transaction.gasUsed;
}
console.log(`Adjusting transaction to cost: ${result.totalGasUsed}`)
var botGasUsed = result.results[2].gasUsed + result.results[3].gasUsed;
var totalGas = BigNumber.from(result.totalGasUsed).mul(GWEI);
transactionObject.ethTransfer.transaction.value = BigNumber.from(botGasUsed).mul(proposedGwei).add(priorityFee);
console.log(`Adjusting ETH send value to ${transactionObject.ethTransfer.transaction.value}`);
var remainingLeashBalance = contract.interface.decodeFunctionResult("balanceOf", result.results.at(-1).value)[0];
console.log(`Remaining LEASH balance: ${remainingLeashBalance}`)
var leashRequired = sendLeash.sub(remainingLeashBalance);
console.log(`Sending LEASH balance: ${leashRequired}`)
await updateSendLeash(transactionObject, wallet, leashRequired);
bundleSubmitResponse = await simulateAndVerifyBundle(flashbotsProvider, finalTransactions, blockNumber);
if (!bundleSubmitResponse) {
return false;
}
console.log(`Total gas usage: ${bundleSubmitResponse.totalGasUsed}`);
return true;
}
async function updateSendLeash(transactionObject: TransactionObject, wallet: Wallet, sendLeash: BigNumber) {
var transferLeashTransactionData = await contract.populateTransaction.transfer(wallet.address, sendLeash);
transactionObject.leashTransfer.transaction.data = transferLeashTransactionData.data;
}
async function simulateAndVerifyBundle(flashbotsProvider: FlashbotsBundleProvider, transactions: Array<FlashbotsBundleTransaction>, blockNumber) {
console.log("Simulate:");
const signedTransactions = await flashbotsProvider.signBundle(transactions)
const simulateResult = await flashbotsProvider.simulate(signedTransactions, blockNumber + 1);
//const bundleSubmitResponse = await flashbotsProvider.sendBundle(transactions, blockNumber + 1);
// By exiting this function (via return) when the type is detected as a "RelayResponseError", TypeScript recognizes bundleSubmitResponse must be a success type object (FlashbotsTransactionResponse) after the if block.
if ('error' in simulateResult) {
console.warn(simulateResult.error.message);
//provider.off('block', transactionFn);
return false;
}
console.log(simulateResult);
if ('error' in simulateResult) {
console.warn(simulateResult.error.message);
//provider.off('block', transactionFn);
return false;
}
if ('firstRevert' in simulateResult) {
if (simulateResult.firstRevert) {
console.warn(simulateResult.firstRevert.error);
//provider.off('block', transactionFn);
return false;
}
}
if ('results' in simulateResult) {
for (const result of simulateResult.results) {
if ('error' in result) {
console.warn(result.error);
//provider.off('block', transactionFn);
return false;
}
}
}
return simulateResult;
}
async function run() {
while (true) {
var promises = [];
for (let count = 0; count < numberOfBots; count++) {
var promise = mintFromWallet();
promises.push(promise);
}
console.log('Waiting for the mint');
await Promise.all(promises);
console.log('Done, executing more wallets');
}
}
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment