Socket
Socket
Sign inDemoInstall

@flashbots/matchmaker-ts

Package Overview
Dependencies
22
Maintainers
3
Versions
17
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.4.3 to 0.5.0

build/api.d.ts

9

package.json
{
"name": "@flashbots/matchmaker-ts",
"version": "0.4.3",
"version": "0.5.0",
"description": "Matchmaker client library for Flashbots mev-share.",

@@ -12,4 +12,4 @@ "main": "build/index.js",

"axios": "^1.3.4",
"eventsource": "^2.0.2",
"ethers": "^6.1.0"
"ethers": "^6.3.0",
"eventsource": "^2.0.2"
},

@@ -23,3 +23,2 @@ "devDependencies": {

"eslint": "^8.36.0",
"ethers": "^6.1.0",
"release-it": "^15.8.0",

@@ -30,3 +29,3 @@ "ts-node": "^10.9.1",

"peerDependencies": {
"ethers": "^6.1.0"
"ethers": "^6.3.0"
},

@@ -33,0 +32,0 @@ "scripts": {

@@ -33,9 +33,60 @@ # Flashbots Matchmaker

import { Wallet, JsonRpcProvider } from "ethers"
import Matchmaker, { ShareBundleParams, PendingShareTransaction, ShareTransactionOptions } from "@flashbots/matchmaker-ts"
import Matchmaker, {
BundleParams,
HintPreferences,
IPendingBundle,
IPendingTransaction,
StreamEvent,
TransactionOptions
} from "@flashbots/matchmaker-ts"
const provider = new JsonRpcProvider(GOERLI_RPC_URL)
const provider = new JsonRpcProvider(RPC_URL)
const authSigner = new Wallet(FB_REPUTATION_PRIVATE_KEY, provider)
const matchmaker = new Matchmaker(authSigner, {chainId: 5, name: "goerli"})
```
The `Matchmaker` class has built-in initializers for networks supported by Flashbots.
#### Connect to Ethereum Mainnet
```typescript
const matchmaker = Matchmaker.useEthereumMainnet(authSigner)
```
#### Connect to Ethereum Goerli
```typescript
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).
```typescript
import { JsonRpcProvider } from "ethers" // ethers v6
/** connects to Flashbots matchmaker on goerli */
function connectMatchmaker(provider: JsonRpcProvider) {
return Matchmaker.fromNetwork(provider._network)
}
// manually with a chainId:
const matchmaker = Matchmaker.fromNetwork({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:
```typescript
const matchmaker = new Matchmaker(authSigner, {
name: "mainnet",
chainId: 1,
streamUrl: "https://mev-share.flashbots.net",
apiUrl: "https://relay.flashbots.net"
})
```
See [MatchmakerNetwork](/src/api/interfaces.ts#L15) for more details.
### examples

@@ -55,3 +106,3 @@

This example sends a transaction to mev-share from the account specified by SENDER_PRIVATE_KEY with a hex-encoded string as data.
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.

@@ -64,3 +115,3 @@ ```sh

This example watches the mev-share streaming endpoint for pending mev-share transactions and attempts to backrun them. The example runs until a backrun has been included on-chain.
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.

@@ -71,21 +122,25 @@ ```sh

## API
## Usage
### `onShareTransaction`
See [src/api/interfaces.ts](src/api/interfaces.ts) for interface definitions.
Starts listening for transactions on mev-share, registers the provided callback to be called when a new transaction is detected.
### `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.
```typescript
const callback = (tx: PendingShareTransaction) => {/* handle pending tx */}
const handler = matchmaker.onShareTransaction(callback)
// do some stuff...
const handler = matchmaker.on(StreamEvent.Transaction, (tx: IPendingTransaction) => {
// handle pending tx
})
// before terminating program
handler.close()
```
### `sendShareTransaction`
### `sendTransaction`
Sends a private transaction to Flashbots with specified hint parameters.
Sends a private transaction to the Flashbots Matchmaker with specified hint parameters.
```typescript
const shareTxParams: ShareTransactionOptions = {
const shareTxParams: TransactionOptions = {
hints: {

@@ -95,20 +150,40 @@ logs: true,

functionSelector: true,
contractAddress: true
contractAddress: true,
},
maxBlockNumber: undefined,
}
await matchmaker.sendShareTransaction(SIGNED_TX, shareTxParams)
await matchmaker.sendTransaction(SIGNED_TX, shareTxParams)
```
### `sendShareBundle`
### `sendBundle`
Sends a bundle; an array of transactions; which tries to backrun a pending mev-share transaction. Currently only one share transaction in `shareTxs` is supported.
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](https://github.com/flashbots/mev-share/blob/main/src/mev_sendBundle.md) for detailed descriptions of these parameters.
```typescript
const bundleParams: ShareBundleParams = {
targetBlock: TARGET_BLOCK,
shareTxs: [PENDING_TX_HASH],
backrun: [SIGNED_BACKRUN_TX1, SIGNED_BACKRUN_TX2],
const bundleParams: BundleParams = {
inclusion: {
block: TARGET_BLOCK,
},
body: [
{hash: TX_HASH_FROM_EVENT_STREAM},
{tx: SIGNED_TX, canRevert: false},
],
validity: {
refund: [
{address: SEARCHER_ADDRESS, percent: 10}
]
},
privacy: {
hints: {
calldata: false,
logs: false,
functionSelector: true,
contractAddress: true,
},
targetBuilders: ["all"]
}
}
await matchmaker.sendShareBundle(bundleParams)
await matchmaker.sendBundle(bundleParams)
```

@@ -1,2 +0,3 @@

import { AxiosError } from 'axios';
import { AxiosError } from 'axios'
import { StreamEvent } from './api/interfaces'

@@ -10,2 +11,10 @@ class MatchmakerError extends Error {

export class JsonRpcError extends MatchmakerError {
constructor(error: {code: number, message: string}) {
super(`${error.code}: ${error.message}`)
this.name = `JsonRpcError: ${error.code}`
this.message = error.message
}
}
export class NetworkFailure extends MatchmakerError {

@@ -20,4 +29,4 @@ constructor(e: AxiosError) {

export class UnimplementedNetwork extends MatchmakerError {
constructor(network: {chainId: number, name: string}) {
super(`Unimplemented network: ${JSON.stringify(network)}`)
constructor({chainId}: {chainId: number}) {
super(`Cannot infer network params from chainId: ${chainId}`)
this.name = "UnimplementedNetwork"

@@ -27,2 +36,9 @@ }

export class UnimplementedStreamEvent extends MatchmakerError {
constructor(eventType: StreamEvent) {
super(`Unimplemented stream event type: ${eventType.toString()}`)
this.name = "UnimplementedStreamEvent"
}
}
export default MatchmakerError

@@ -11,4 +11,3 @@ import { JsonRpcProvider, Network, Wallet } from 'ethers'

const authSigner = new Wallet(Env.authKey)
const network = {chainId: 5, name: "goerli"}
const matchmaker = new Matchmaker(authSigner, network)
const matchmaker = Matchmaker.useEthereumGoerli(authSigner)
const wallet = new Wallet(Env.senderKey)

@@ -15,0 +14,0 @@ const feeData = await provider.getFeeData()

import { hexlify, toBigInt, toUtf8Bytes } from 'ethers'
import { JsonRpcProvider, TransactionRequest } from 'ethers/types/providers'
import { HintPreferences } from '../../api'
import { HintPreferences } from '../..'
import { initExample } from './helpers'

@@ -37,6 +37,6 @@

const {matchmaker, signedTx} = await setupTxExample(provider, tip)
const res = await matchmaker.sendShareTransaction(signedTx,
const res = await matchmaker.sendTransaction(signedTx,
{hints, maxBlockNumber}
)
console.debug("send private tx result", res)
console.debug("sent tx", res)
}

@@ -5,8 +5,7 @@ import { JsonRpcProvider, keccak256 } from 'ethers'

// lib
import Matchmaker from '..'
import { ShareBundleParams, PendingShareTransaction } from '../api'
import Matchmaker, { BundleParams, IPendingTransaction, StreamEvent } from '..'
import { getProvider, initExample } from './lib/helpers'
import { sendTx, setupTxExample } from './lib/sendTx'
const NUM_TARGET_BLOCKS = 5
const NUM_TARGET_BLOCKS = 3

@@ -16,4 +15,4 @@ /**

*/
const sendTestBackrunBundle = async (provider: JsonRpcProvider, pendingTx: PendingShareTransaction, matchmaker: Matchmaker, targetBlock: number) => {
// send ofa bundle w/ (basefee + 100)gwei gas fee
const sendTestBackrunBundle = async (provider: JsonRpcProvider, pendingTx: IPendingTransaction, matchmaker: Matchmaker, targetBlock: number) => {
// send bundle w/ (basefee + 100)gwei gas fee
const {tx, wallet} = await setupTxExample(provider, BigInt(1e9) * BigInt(1e2), "im backrunniiiiing")

@@ -24,19 +23,18 @@ const backrunTx = {

}
const backrun = [await wallet.signTransaction(backrunTx)]
const shareTxs = [pendingTx.txHash]
const backrunResults = []
const bundle = [
{hash: pendingTx.hash},
{tx: await wallet.signTransaction(backrunTx), canRevert: false},
]
console.log(`sending backrun bundles targeting next ${NUM_TARGET_BLOCKS} blocks...`)
for (let i = 0; i < NUM_TARGET_BLOCKS; i++) {
const params: ShareBundleParams = {
targetBlock: targetBlock + i,
backrun,
shareTxs,
}
const backrunRes = matchmaker.sendShareBundle(params)
console.debug("sent share bundle", JSON.stringify(params))
backrunResults.push(backrunRes)
const params: BundleParams = {
inclusion: {
block: targetBlock,
maxBlock: targetBlock + NUM_TARGET_BLOCKS,
},
body: bundle,
}
const backrunResult = await matchmaker.sendBundle(params)
return {
backrun,
backrunResults: await Promise.all(backrunResults),
bundle,
backrunResult,
}

@@ -47,3 +45,3 @@ }

const handleBackrun = async (
pendingTx: PendingShareTransaction,
pendingTx: IPendingTransaction,
provider: JsonRpcProvider,

@@ -55,4 +53,7 @@ matchmaker: Matchmaker,

const targetBlock = await provider.getBlockNumber() + 2
const { backrun, backrunResults } = await sendTestBackrunBundle(provider, pendingTx, matchmaker, targetBlock)
console.log("backrun results", backrunResults)
const {
bundle,
backrunResult,
} = await sendTestBackrunBundle(provider, pendingTx, matchmaker, targetBlock)
console.log("backrun result", backrunResult)

@@ -65,4 +66,4 @@ // watch future blocks for backrun tx inclusion

}
console.log(`tx ${pendingTx.txHash} waiting for block`, targetBlock + i)
// poll until block is available
console.log(`tx ${pendingTx.hash} waiting for block`, targetBlock + i)
// stall until target block is available
while (await provider.getBlockNumber() < targetBlock + i) {

@@ -73,6 +74,6 @@ await new Promise(resolve => setTimeout(resolve, 2000))

// check for inclusion of backrun tx in target block
const backrunTxHash = keccak256(backrun[0])
const receipt = await provider.getTransactionReceipt(backrunTxHash)
const checkTxHash = keccak256(bundle[1].tx!)
const receipt = await provider.getTransactionReceipt(checkTxHash)
if (receipt?.status === 1) {
console.log("backrun tx included!")
console.log(`bundle included! (found tx ${receipt.hash})`)
// release mutex so the main thread can exit

@@ -82,3 +83,3 @@ pendingMutex.release()

} else {
console.warn(`tx ${backrunTxHash} not included in block ${targetBlock}`)
console.warn(`backrun tx ${checkTxHash} not included in block ${targetBlock}`)
}

@@ -101,3 +102,3 @@ }

// listen for txs
const txHandler = matchmaker.onShareTransaction(pendingTx => handleBackrun(pendingTx, provider, matchmaker, pendingMutex))
const txHandler = matchmaker.on(StreamEvent.Transaction, (pendingTx: IPendingTransaction) => handleBackrun(pendingTx, provider, matchmaker, pendingMutex))
console.log("listening for transactions...")

@@ -112,3 +113,3 @@

// will block until the handler releases the mutex
// will block until one of the handlers releases the mutex
await pendingMutex.acquire()

@@ -115,0 +116,0 @@ pendingMutex.release()

@@ -1,2 +0,2 @@

import { HintPreferences } from '../api'
import { HintPreferences } from '..'
import { getProvider } from './lib/helpers'

@@ -3,0 +3,0 @@ import { sendTx } from './lib/sendTx'

@@ -5,3 +5,4 @@ import { id as ethersId, Wallet } from "ethers"

id: number,
result: any,
result?: any,
error?: {code: number, message: string},
jsonrpc: string,

@@ -8,0 +9,0 @@ }

@@ -1,111 +0,14 @@

import axios, { AxiosError } from "axios"
import { Wallet } from 'ethers'
import EventSource from "eventsource"
import { NetworkFailure, UnimplementedNetwork } from './error'
import Matchmaker from './matchmaker'
import { getRpcRequest, JsonRpcData } from './flashbots';
import { mungeShareBundleParams, mungePrivateTxParams, ShareBundleParams, PendingShareTransaction, ShareTransactionOptions } from './api'
export {
BundleParams,
HintPreferences,
IPendingBundle,
IPendingTransaction,
TransactionOptions,
StreamEvent,
} from "./api/interfaces"
const bundleApiUrls = {
goerli: "https://relay-goerli.flashbots.net",
}
export {SupportedNetworks} from "./api/networks"
const streamingUrls = {
goerli: "https://mev-share-goerli.flashbots.net",
}
class Matchmaker {
private apiUrl?: string
private streamUrl?: string
constructor(
private authSigner: Wallet,
private network: {
chainId: number,
name: string,
},
mevShareOptions?: {
apiUrl?: string,
streamUrl?: string,
}
) {
if (network.chainId !== 5) {
throw new UnimplementedNetwork(network)
}
this.authSigner = authSigner
this.network = network
this.apiUrl = mevShareOptions?.apiUrl || Object.entries(bundleApiUrls).find(kv => kv[0] === network.name.toLowerCase())?.[1]
this.streamUrl = mevShareOptions?.streamUrl || Object.entries(streamingUrls).find(kv => kv[0] === network.name.toLowerCase())?.[1]
}
private async handleBundleApiRequest({ headers, body }: { headers: any, body: any}) {
if (!this.apiUrl) throw new UnimplementedNetwork(this.network)
try {
const res = await axios.post(this.apiUrl, body, {
headers
})
return (res.data as JsonRpcData).result
} catch (e) {
console.debug(JSON.stringify(body))
throw new NetworkFailure(e as AxiosError)
}
}
/**
* Registers the provided callback to be called when a new mev-share transaction is received.
* @param callback Async function to process pending tx.
* @returns Event listener, which can be used to close the connection.
*
* @deprecated Use {@link onShareTransaction} instead.
*/
public listenForShareTransactions(callback: (data: PendingShareTransaction) => void): EventSource {
if (!this.streamUrl) throw new UnimplementedNetwork(this.network)
const events = new EventSource(this.streamUrl)
events.onmessage = (event) => {
try {
callback(JSON.parse(event.data) as PendingShareTransaction)
} catch (e) {
throw new NetworkFailure(e as AxiosError)
}
}
return events
}
/**
* Registers the provided callback to be called when a new mev-share transaction is received.
* @param callback Async function to process pending tx.
* @returns Event listener, which can be used to close the connection.
*/
public onShareTransaction(callback: (data: PendingShareTransaction) => void): EventSource {
return this.listenForShareTransactions(callback)
}
/** Sends a private transaction with MEV hints to mev-share.
*
* - Calls `eth_sendPrivateTransaction` with hints.
* @param signedTx Signed transaction to send.
* @param options Tx preferences; hints & block range.
* @returns Transaction hash.
*/
public async sendShareTransaction(
signedTx: string,
options?: ShareTransactionOptions,
): Promise<string> {
const params = mungePrivateTxParams(signedTx, options)
const payload = await getRpcRequest(params, "eth_sendPrivateTransaction", this.authSigner)
return await this.handleBundleApiRequest(payload)
}
/** Sends a Share bundle to mev-share.
* @param bundleParams Parameters for the Share bundle.
* @returns Array of bundle hashes.
*/
public async sendShareBundle(bundleParams: ShareBundleParams): Promise<string[]> {
const params = mungeShareBundleParams(bundleParams)
const payload = await getRpcRequest(params, "eth_sendShareBundle", this.authSigner)
return await this.handleBundleApiRequest(payload)
}
}
export type { ShareBundleParams, PendingShareTransaction, ShareTransactionOptions }
export default Matchmaker
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc