Flashbots Matchmaker
Client library for Flashbots MEV-share
Matchmaker.
Based on prospective API docs.
quickstart
Install from npm:
yarn add @flashbots/matchmaker-ts
npm i @flashbots/matchmaker-ts
Alternatively, clone the library & build from source:
git clone https://github.com/flashbots/matchmaker-ts
cd matchmaker-ts
yarn install && yarn build
yarn add ../matchmaker-ts
use matchmaker in your project
:warning: Variables denoted in ALL_CAPS
are placeholders; the code does not compile. examples/ contains compilable demos.
In your project:
import { Wallet, JsonRpcProvider } from "ethers"
import Matchmaker, {
BundleParams,
HintPreferences,
IPendingBundle,
IPendingTransaction,
TransactionOptions
} from "@flashbots/matchmaker-ts"
const provider = new JsonRpcProvider(RPC_URL)
const authSigner = new Wallet(FB_REPUTATION_PRIVATE_KEY, provider)
The Matchmaker
class has built-in initializers for networks supported by Flashbots.
Connect to Ethereum Mainnet
const matchmaker = Matchmaker.useEthereumMainnet(authSigner)
Connect to Ethereum Goerli
const matchmaker = Matchmaker.useEthereumGoerli(authSigner)
Connect with an Ethers Provider or Chain ID
Networks supported by Flashbots have presets built-in. If it's more convenient, you can instantiate a Matchmaker using a chainId
(or a ethers.js Network
object, which has a chainId
param).
import { JsonRpcProvider, Wallet } from "ethers"
const provider = new JsonRpcProvider("http://localhost:8545", {chainId: 5, name: "goerli"})
const authSigner = new Wallet("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")
.connect(provider)
const matchmaker = Matchmaker.fromNetwork(authSigner, provider._network)
const matchmaker = Matchmaker.fromNetwork(authSigner, {chainId: 5})
Connect to a custom network
To use custom network parameters, you can instantiate a new Matchmaker directly. This example is what the client uses to connect to mainnet:
const matchmaker = new Matchmaker(authSigner, {
name: "mainnet",
chainId: 1,
streamUrl: "https://mev-share.flashbots.net",
apiUrl: "https://relay.flashbots.net"
})
See MatchmakerNetwork for more details.
examples
Source code
:information_source: Examples require a .env
file (or that you populate your environment directly with the appropriate variables).
cd src/examples
cp .env.example .env
vim .env
send a tx with hints
This example sends a transaction to the Flashbots Goerli Matchmaker from the account specified by SENDER_PRIVATE_KEY with a hex-encoded string as calldata.
yarn example.tx
backrun a pending tx
This example watches the mev-share streaming endpoint for pending mev-share transactions and attempts to backrun them all. The example runs until a backrun has been included on-chain.
yarn example.backrun
Usage
See src/api/interfaces.ts for interface definitions.
on
Use on
to start listening for events on mev-share. The function registers the provided callback to be called when a new event is detected.
const handler = matchmaker.on("transaction", (tx: IPendingTransaction) => {
})
handler.close()
sendTransaction
Sends a private transaction to the Flashbots Matchmaker with specified hint parameters.
const wallet = new Wallet(PRIVATE_KEY)
const tx = {
to: "0xfb000000387627910184cc42fc92995913806333",
value: BigInt(1e13 * 275),
data: "0x646f637320626179626565652121",
gasLimit: 42000,
maxFeePerGas: BigInt(1e9) * BigInt(42),
maxPriorityFeePerGas: BigInt(1e9) * BigInt(2),
chainId: 5,
type: 2,
}
const shareTxParams: TransactionOptions = {
hints: {
logs: true,
calldata: false,
functionSelector: true,
contractAddress: true,
},
maxBlockNumber: undefined,
builders: ["flashbots"]
}
const signedTx = await wallet.signTransaction(tx)
await matchmaker.sendTransaction(SIGNED_TX, shareTxParams)
sendBundle
Sends a bundle; an array of transactions with parameters to specify conditions for inclusion and MEV kickbacks. Transactions are placed in the body
parameter with wrappers to indicate whether they're a new signed transaction or a pending transaction from the event stream.
See MEV-Share Docs for detailed descriptions of these parameters.
const targetBlock = 1 + await provider.getBlockNumber()
const bundleParams: BundleParams = {
inclusion: {
block: targetBlock,
},
body: [
{hash: TX_HASH_FROM_EVENT_STREAM},
{tx: SIGNED_TX, canRevert: false},
],
}
await matchmaker.sendBundle(bundleParams)
Bundles that only contain signed transactions can share hints about the transactions in their bundle by setting the privacy
parameter:
const targetBlock = 1 + await provider.getBlockNumber()
const bundleParams: BundleParams = {
inclusion: {
block: targetBlock,
maxBlock: targetBlock + 5,
},
body: [
{tx: await wallet.signTransaction(TX1), canRevert: false},
{tx: await wallet.signTransaction(TX2), canRevert: false},
],
privacy: {
hints: {
txHash: true,
calldata: true,
logs: true,
functionSelector: true,
contractAddress: true,
},
}
}
const backrunResult = await matchmaker.sendBundle(bundleParams)
simulateBundle
Simulates a bundle. Accepts options to modify block header for simulation.
const bundle: BundleParams = {
inclusion: {
block: TARGET_BLOCK,
maxBlock: TARGET_BLOCK + 3,
},
body: [
{hash: "0xTARGET_TX_HASH"},
{tx: "0xSIGNED_BACKRUN_TX", canRevert: false}
],
}
const simBundleOptions: SimBundleOptions = {
parentBlock: TARGET_BLOCK - 1,
blockNumber: TARGET_BLOCK,
}
const simResult = await matchmaker.simulateBundle(bundle, simBundleOptions)
This example uses the state of parentBlock
, but overrides the state's blockNumber
value. Setting more fields in SimBundleOptions
is useful when testing smart contracts which have specific criteria that must be met, like the block being a certain number, or a specific timestamp having passed.
getEventHistoryInfo
Get information about the event history endpoint for use in getEventHistory
.
Example:
const info = await matchmaker.getEventHistoryInfo()
console.log(info)
returns something like this:
{
count: 56934,
minBlock: 9091377,
maxBlock: 9190024,
minTimestamp: 1685452445,
maxTimestamp: 1686943324,
maxLimit: 500
}
getEventHistory
Get historical event stream data.
Using the data from our getEventHistoryInfo
call, we can read events starting from the beginning. The data is paginated, so to read all of it, you'll have to make multiple calls to iterate through the it.
const info = await matchmaker.getEventHistoryInfo()
for (let i = 0; i < Math.ceil(info.count / info.maxLimit); i++) {
const events = await matchmaker.getEventHistory({
limit: info.maxLimit,
offset: i * info.maxLimit,
blockStart: info.minBlock,
})
console.log(events)
}
You can also filter events by timestamp:
const events = await matchmaker.getEventHistory({
limit: info.maxLimit,
offset: i * info.maxLimit,
timestampStart: 1686942023,
})