Created
October 17, 2021 00:49
-
-
Save sonicflare/f8b60536d849e40f45fbc21e2c15bfe9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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