-
Notifications
You must be signed in to change notification settings - Fork 210
/
demo.ts
129 lines (113 loc) · 4.71 KB
/
demo.ts
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
import { BigNumber, providers, Wallet } from 'ethers'
import { FlashbotsBundleProvider, FlashbotsBundleResolution } from './index'
import { TransactionRequest } from '@ethersproject/abstract-provider'
import { v4 as uuidv4 } from 'uuid'
const FLASHBOTS_AUTH_KEY = process.env.FLASHBOTS_AUTH_KEY
const GWEI = BigNumber.from(10).pow(9)
const PRIORITY_FEE = GWEI.mul(3)
const LEGACY_GAS_PRICE = GWEI.mul(12)
const BLOCKS_IN_THE_FUTURE = 2
// ===== Uncomment this for mainnet =======
// const CHAIN_ID = 1
// const provider = new providers.JsonRpcProvider(
// { url: process.env.ETHEREUM_RPC_URL || 'http://127.0.0.1:8545' },
// { chainId: CHAIN_ID, ensAddress: '', name: 'mainnet' }
// )
// const FLASHBOTS_EP = 'https://relay.flashbots.net/'
// ===== Uncomment this for mainnet =======
// ===== Uncomment this for Goerli =======
const CHAIN_ID = 5
const provider = new providers.InfuraProvider(CHAIN_ID, process.env.INFURA_API_KEY)
const FLASHBOTS_EP = 'https://relay-goerli.flashbots.net/'
// ===== Uncomment this for Goerli =======
for (const e of ['FLASHBOTS_AUTH_KEY', 'INFURA_API_KEY', 'ETHEREUM_RPC_URL', 'PRIVATE_KEY']) {
if (!process.env[e]) {
// don't warn for skipping ETHEREUM_RPC_URL if using goerli
if (FLASHBOTS_EP.includes('goerli') && e === 'ETHEREUM_RPC_URL') {
continue
}
console.warn(`${e} should be defined as an environment variable`)
}
}
async function main() {
const authSigner = FLASHBOTS_AUTH_KEY ? new Wallet(FLASHBOTS_AUTH_KEY) : Wallet.createRandom()
const wallet = new Wallet(process.env.PRIVATE_KEY || '', provider)
const flashbotsProvider = await FlashbotsBundleProvider.create(provider, authSigner, FLASHBOTS_EP)
const userStats = flashbotsProvider.getUserStats()
if (process.env.TEST_V2) {
try {
const userStats2 = await flashbotsProvider.getUserStatsV2()
console.log('userStatsV2', userStats2)
} catch (e) {
console.error('[v2 error]', e)
}
}
const legacyTransaction = {
to: wallet.address,
gasPrice: LEGACY_GAS_PRICE,
gasLimit: 21000,
data: '0x',
nonce: await provider.getTransactionCount(wallet.address),
chainId: CHAIN_ID
}
provider.on('block', async (blockNumber) => {
const block = await provider.getBlock(blockNumber)
const replacementUuid = uuidv4()
let eip1559Transaction: TransactionRequest
if (block.baseFeePerGas == null) {
console.warn('This chain is not EIP-1559 enabled, defaulting to two legacy transactions for demo')
eip1559Transaction = { ...legacyTransaction }
// We set a nonce in legacyTransaction above to limit validity to a single landed bundle. Delete that nonce for tx#2, and allow bundle provider to calculate it
delete eip1559Transaction.nonce
} else {
const maxBaseFeeInFutureBlock = FlashbotsBundleProvider.getMaxBaseFeeInFutureBlock(block.baseFeePerGas, BLOCKS_IN_THE_FUTURE)
eip1559Transaction = {
to: wallet.address,
type: 2,
maxFeePerGas: PRIORITY_FEE.add(maxBaseFeeInFutureBlock),
maxPriorityFeePerGas: PRIORITY_FEE,
gasLimit: 21000,
data: '0x',
chainId: CHAIN_ID
}
}
const signedTransactions = await flashbotsProvider.signBundle([
{
signer: wallet,
transaction: legacyTransaction
},
{
signer: wallet,
transaction: eip1559Transaction
}
])
const targetBlock = blockNumber + BLOCKS_IN_THE_FUTURE
const simulation = await flashbotsProvider.simulate(signedTransactions, targetBlock)
// Using TypeScript discrimination
if ('error' in simulation) {
console.warn(`Simulation Error: ${simulation.error.message}`)
process.exit(1)
} else {
console.log(`Simulation Success: ${JSON.stringify(simulation, null, 2)}`)
}
const bundleSubmission = await flashbotsProvider.sendRawBundle(signedTransactions, targetBlock, { replacementUuid })
console.log('bundle submitted, waiting')
if ('error' in bundleSubmission) {
throw new Error(bundleSubmission.error.message)
}
const cancelResult = await flashbotsProvider.cancelBundles(replacementUuid)
console.log('cancel response', cancelResult)
const waitResponse = await bundleSubmission.wait()
console.log(`Wait Response: ${FlashbotsBundleResolution[waitResponse]}`)
if (waitResponse === FlashbotsBundleResolution.BundleIncluded || waitResponse === FlashbotsBundleResolution.AccountNonceTooHigh) {
process.exit(0)
} else {
console.log({
bundleStats: await flashbotsProvider.getBundleStats(simulation.bundleHash, targetBlock),
bundleStatsV2: process.env.TEST_V2 && (await flashbotsProvider.getBundleStatsV2(simulation.bundleHash, targetBlock)),
userStats: await userStats
})
}
})
}
main()