forked from flashbots/web3-flashbots
-
Notifications
You must be signed in to change notification settings - Fork 1
/
simple.py
138 lines (113 loc) · 4.25 KB
/
simple.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
"""
Minimal viable example of flashbots usage with dynamic fee transactions.
Sends a bundle of two transactions which transfer some ETH into a random account.
Environment Variables:
- ETH_SENDER_KEY: Private key of account which will send the ETH.
- ETH_SIGNER_KEY: Private key of account which will sign the bundle.
- This account is only used for reputation on flashbots and should be empty.
- PROVIDER_URL: HTTP JSON-RPC Ethereum provider URL.
"""
import os
import secrets
from traceback import print_exc
from uuid import uuid4
from eth_account.account import Account
from eth_account.signers.local import LocalAccount
from web3 import HTTPProvider, Web3
from web3.exceptions import TransactionNotFound
from web3.types import TxParams
from flashbots import flashbot
# change this to `False` if you want to use mainnet
USE_GOERLI = True
CHAIN_ID = 11155111 if USE_GOERLI else 1
def env(key: str) -> str:
return os.environ.get(key)
def random_account() -> LocalAccount:
key = "0x" + secrets.token_hex(32)
return Account.from_key(key)
def main() -> None:
# account to send the transfer and sign transactions
sender: LocalAccount = Account.from_key(env("ETH_SENDER_KEY"))
# account to receive the transfer
# receiverAddress: str = random_account().address
receiverAddress: str = sender.address
# account to sign bundles & establish flashbots reputation
# NOTE: this account should not store funds
signer: LocalAccount = Account.from_key(env("ETH_SIGNER_KEY"))
w3 = Web3(HTTPProvider(env("PROVIDER_URL")))
if USE_GOERLI:
flashbot(w3, signer, "https://relay-sepolia.flashbots.net")
else:
flashbot(w3, signer)
print(f"Sender address: {sender.address}")
print(f"Receiver address: {receiverAddress}")
print(
f"Sender account balance: {Web3.from_wei(w3.eth.get_balance(sender.address), 'ether')} ETH"
)
print(
f"Receiver account balance: {Web3.from_wei(w3.eth.get_balance(receiverAddress), 'ether')} ETH"
)
# bundle two EIP-1559 (type 2) transactions, pre-sign one of them
# NOTE: chainId is necessary for all EIP-1559 txns
# NOTE: nonce is required for signed txns
nonce = w3.eth.get_transaction_count(sender.address)
tx1: TxParams = {
"to": receiverAddress,
"value": Web3.to_wei(0.001, "ether"),
"gas": 21000,
"maxFeePerGas": Web3.to_wei(1000, "gwei"),
"maxPriorityFeePerGas": Web3.to_wei(1000, "gwei"),
"nonce": nonce,
"chainId": CHAIN_ID,
"type": 2,
}
tx1_signed = sender.sign_transaction(tx1)
tx2: TxParams = {
"to": receiverAddress,
"value": Web3.to_wei(0.001, "ether"),
"gas": 21000,
"maxFeePerGas": Web3.to_wei(1000, "gwei"),
"maxPriorityFeePerGas": Web3.to_wei(1000, "gwei"),
"nonce": nonce + 1,
"chainId": CHAIN_ID,
"type": 2,
}
bundle = [
{"signed_transaction": tx1_signed.rawTransaction},
{"signer": sender, "transaction": tx2},
]
# simulate bundle on current block
try:
block = w3.eth.block_number
w3.flashbots.simulate(bundle, block)
print("Simulation successful.")
except Exception as e:
print("Simulation error", e)
print_exc()
return
# keep trying to send bundle until it gets mined
while True:
block = w3.eth.block_number
# send bundle targeting next block
print(f"Sending bundle targeting block {block+1}")
send_result = w3.flashbots.send_bundle(
bundle,
target_block_number=block + 1,
)
print("bundleHash", Web3.to_hex(send_result.bundle_hash()))
send_result.wait()
try:
receipts = send_result.receipts()
print(f"\nBundle was mined in block {receipts[0].blockNumber}\a")
break
except TransactionNotFound:
print(f"Bundle not found in block {block+1}")
# essentially a no-op but it shows that the function works
print(
f"Sender account balance: {Web3.from_wei(w3.eth.get_balance(sender.address), 'ether')} ETH"
)
print(
f"Receiver account balance: {Web3.from_wei(w3.eth.get_balance(receiverAddress), 'ether')} ETH"
)
if __name__ == "__main__":
main()