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.6.0 to 0.7.0

build/examples/lib/async.d.ts

4

build/api/events.d.ts

@@ -9,2 +9,4 @@ import { LogParams } from 'ethers';

callData?: string;
mevGasPrice?: bigint;
gasUsed?: bigint;
constructor(event: IMatchmakerEvent);

@@ -20,4 +22,6 @@ }

}[];
mevGasPrice?: bigint;
gasUsed?: bigint;
constructor(event: IMatchmakerEvent);
}
//# sourceMappingURL=events.d.ts.map

@@ -11,2 +11,4 @@ "use strict";

this.callData = event.txs && event.txs[0].callData;
this.gasUsed = event.gasUsed ? BigInt(event.gasUsed) : undefined;
this.mevGasPrice = event.mevGasPrice ? BigInt(event.mevGasPrice) : undefined;
}

@@ -20,2 +22,4 @@ }

this.txs = event.txs;
this.gasUsed = event.gasUsed ? BigInt(event.gasUsed) : undefined;
this.mevGasPrice = event.mevGasPrice ? BigInt(event.mevGasPrice) : undefined;
}

@@ -22,0 +26,0 @@ }

@@ -9,2 +9,3 @@ import { LogParams } from 'ethers';

}
export type StreamEventName = `${StreamEvent}`;
/**

@@ -39,3 +40,3 @@ * Configuration used to connect to the Matchmaker.

/**
* Parameters accepted by the `sendShareTransaction` function.
* Parameters accepted by the `sendTransaction` function.
*/

@@ -47,2 +48,3 @@ export interface TransactionOptions {

maxBlockNumber?: number;
builders?: string[];
}

@@ -93,3 +95,3 @@ /**

/** Builders that are allowed to receive this bundle. See [mev-share spec](https://github.com/flashbots/mev-share/blob/main/builders/registration.json) for supported builders. */
targetBuilders?: Array<string>;
builders?: Array<string>;
};

@@ -100,4 +102,68 @@ metadata?: {

}
/** Response received from matchmaker API */
interface ISendBundleResponse {
/** Bundle hash. */
bundleHash: string;
}
/** Bundle details. */
export interface ISendBundleResult {
/** Bundle hash. */
bundleHash: string;
}
/** Decodes a raw sendBundle response. */
export declare const SendBundleResult: (response: ISendBundleResponse) => ISendBundleResult;
/** Optional fields to override simulation state. */
export interface SimBundleOptions {
/** Block used for simulation state. Defaults to latest block.
*
* Block header data will be derived from parent block by default.
* Specify other params in this interface to override the default values.
*
* Can be a block number or block hash.
*/
parentBlock?: number | string;
/** default = parentBlock.number + 1 */
blockNumber?: number;
/** default = parentBlock.coinbase */
coinbase?: string;
/** default = parentBlock.timestamp + 12 */
timestamp?: number;
/** default = parentBlock.gasLimit */
gasLimit?: number;
/** default = parentBlock.baseFeePerGas */
baseFee?: bigint;
/** default = 5 (defined in seconds) */
timeout?: number;
}
/** Logs returned by mev_simBundle. */
export interface SimBundleLogs {
txLogs?: LogParams[];
bundleLogs?: SimBundleLogs[];
}
/** Response received from matchmaker api. */
interface ISimBundleResponse {
success: boolean;
error?: string;
stateBlock: string;
mevGasPrice: string;
profit: string;
refundableValue: string;
gasUsed: string;
logs?: SimBundleLogs[];
}
/** Simulation details. */
export interface ISimBundleResult {
success: boolean;
error?: string;
stateBlock: number;
mevGasPrice: bigint;
profit: bigint;
refundableValue: bigint;
gasUsed: bigint;
logs?: SimBundleLogs[];
}
/** Decodes a raw simBundle response. */
export declare const SimBundleResult: (response: ISimBundleResponse) => ISimBundleResult;
/**
* General API wrapper for events received by the SSE stream (via `matchmaker.on(...)`)
* General API wrapper for events received by the SSE stream (via `matchmaker.on(...)`).
*/

@@ -117,9 +183,42 @@ export interface IMatchmakerEvent {

}>;
/**
* Change in coinbase value after inserting tx/bundle, divided by gas used.
*
* Can be used to determine the minimum payment to the builder to make your backrun look more profitable to builders.
* _Note: this only applies to builders like Flashbots who order bundles by MEV gas price._
*/
mevGasPrice?: string;
/** Gas used by the tx/bundle, rounded up to 2 most significant digits.
*
* _Note: EXPERIMENTAL; only implemented on Goerli_ */
gasUsed?: string;
}
export interface IPendingTransaction extends Omit<IMatchmakerEvent, 'txs'> {
/**
* Pending transaction from the matchmaker stream.
*/
export interface IPendingTransaction extends Omit<Omit<Omit<IMatchmakerEvent, 'txs'>, 'mevGasPrice'>, 'gasUsed'> {
to?: string;
functionSelector?: string;
callData?: string;
/**
* {@link IMatchmakerEvent.mevGasPrice}
*/
mevGasPrice?: bigint;
/**
* {@link IMatchmakerEvent.gasUsed}
*/
gasUsed?: bigint;
}
export type IPendingBundle = IMatchmakerEvent;
/** Pending bundle from the matchmaker stream. */
export interface IPendingBundle extends Omit<Omit<IMatchmakerEvent, 'mevGasPrice'>, 'gasUsed'> {
/**
* {@link IMatchmakerEvent.mevGasPrice}
*/
mevGasPrice?: bigint;
/**
* {@link IMatchmakerEvent.gasUsed}
*/
gasUsed?: bigint;
}
export {};
//# sourceMappingURL=interfaces.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamEvent = void 0;
exports.SimBundleResult = exports.SendBundleResult = exports.StreamEvent = void 0;
/**

@@ -12,2 +12,19 @@ * Used to specify which type of event to listen for.

})(StreamEvent = exports.StreamEvent || (exports.StreamEvent = {}));
/** Decodes a raw sendBundle response. */
const SendBundleResult = (response) => ({
bundleHash: response.bundleHash,
});
exports.SendBundleResult = SendBundleResult;
/** Decodes a raw simBundle response. */
const SimBundleResult = (response) => ({
success: response.success,
error: response.error,
stateBlock: parseInt(response.stateBlock, 16),
mevGasPrice: BigInt(response.mevGasPrice),
profit: BigInt(response.profit),
refundableValue: BigInt(response.refundableValue),
gasUsed: BigInt(response.gasUsed),
logs: response.logs,
});
exports.SimBundleResult = SimBundleResult;
//# sourceMappingURL=interfaces.js.map

20

build/api/mungers.d.ts

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

import { BundleParams, TransactionOptions } from './interfaces';
import { BundleParams, SimBundleOptions, TransactionOptions } from './interfaces';
/**

@@ -13,5 +13,6 @@ * Converts user-specified parameters into parameters for a sendPrivateTransaction call to the Matchmaker API.

fast: boolean;
auction: {
hint: string[];
privacy: {
hints: string[];
} | undefined;
builders: string[] | undefined;
};

@@ -43,3 +44,3 @@ }[];

hints: string[] | undefined;
targetBuilders?: string[] | undefined;
builders?: string[] | undefined;
} | undefined;

@@ -49,3 +50,12 @@ metadata?: {

} | undefined;
}[];
};
export declare function mungeSimBundleOptions(params: SimBundleOptions): {
parentBlock: string | 0 | undefined;
blockNumber: string | 0 | undefined;
timestamp: string | 0 | undefined;
gasLimit: string | 0 | undefined;
baseFee: string | 0n | undefined;
coinbase?: string | undefined;
timeout?: number | undefined;
};
//# sourceMappingURL=mungers.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mungeBundleParams = exports.mungePrivateTxParams = void 0;
exports.mungeSimBundleOptions = exports.mungeBundleParams = exports.mungePrivateTxParams = void 0;
/**

@@ -39,6 +39,7 @@ * Convert name format of user-specified hints for Matchmaker API requests.

fast: true,
// auction uses default (Stable) config if no hints specified
auction: (options === null || options === void 0 ? void 0 : options.hints) && {
hint: extractSpecifiedHints(options.hints),
// privacy uses default (Stable) config if no hints specified
privacy: (options === null || options === void 0 ? void 0 : options.hints) && {
hints: extractSpecifiedHints(options.hints),
},
builders: options === null || options === void 0 ? void 0 : options.builders,
},

@@ -56,8 +57,14 @@ }];

const mungedBundle = params.body.map((i) => i.bundle ? mungeBundleParams(i.bundle) : i);
return [Object.assign(Object.assign({}, params), { body: mungedBundle, version: params.version || "beta-1", inclusion: Object.assign(Object.assign({}, params.inclusion), { block: `0x${params.inclusion.block.toString(16)}`, maxBlock: params.inclusion.maxBlock ? `0x${params.inclusion.maxBlock.toString(16)}` : undefined }), validity: params.validity ? params.validity : {
refund: [],
refundConfig: [],
}, privacy: params.privacy && Object.assign(Object.assign({}, params.privacy), { hints: params.privacy.hints && extractSpecifiedHints(params.privacy.hints) }) })];
return Object.assign(Object.assign({}, params), { body: mungedBundle, version: params.version || "v0.1", inclusion: Object.assign(Object.assign({}, params.inclusion), { block: `0x${params.inclusion.block.toString(16)}`, maxBlock: params.inclusion.maxBlock ? `0x${params.inclusion.maxBlock.toString(16)}` : undefined }), validity: params.validity ? params.validity : {
refund: [],
refundConfig: [],
}, privacy: params.privacy && Object.assign(Object.assign({}, params.privacy), { hints: params.privacy.hints && extractSpecifiedHints(params.privacy.hints) }) });
}
exports.mungeBundleParams = mungeBundleParams;
function mungeSimBundleOptions(params) {
return Object.assign(Object.assign({}, params), {
// coinbase & timeout can be left as they are
parentBlock: params.parentBlock && `0x${BigInt(params.parentBlock).toString(16)}`, blockNumber: params.blockNumber && `0x${BigInt(params.blockNumber).toString(16)}`, timestamp: params.timestamp && `0x${BigInt(params.timestamp).toString(16)}`, gasLimit: params.gasLimit && `0x${BigInt(params.gasLimit).toString(16)}`, baseFee: params.baseFee && `0x${params.baseFee.toString(16)}` });
}
exports.mungeSimBundleOptions = mungeSimBundleOptions;
//# sourceMappingURL=mungers.js.map

@@ -25,12 +25,9 @@ "use strict";

return __awaiter(this, void 0, void 0, function* () {
const authSigner = new ethers_1.Wallet(env_1.default.authKey);
const matchmaker = __1.default.useEthereumGoerli(authSigner);
const wallet = new ethers_1.Wallet(env_1.default.senderKey);
const feeData = yield provider.getFeeData();
const authSigner = new ethers_1.Wallet(env_1.default.authKey).connect(provider);
return {
provider,
wallet: wallet.connect(provider),
authSigner: authSigner.connect(provider),
matchmaker,
feeData,
wallet: new ethers_1.Wallet(env_1.default.senderKey).connect(provider),
authSigner,
matchmaker: __1.default.useEthereumGoerli(authSigner),
feeData: yield provider.getFeeData(),
};

@@ -37,0 +34,0 @@ });

@@ -10,3 +10,3 @@ import { JsonRpcProvider, TransactionRequest } from 'ethers/types/providers';

}>;
export declare const sendTx: (provider: JsonRpcProvider, hints?: HintPreferences, maxBlockNumber?: number, tip?: BigInt) => Promise<void>;
export declare const sendTx: (provider: JsonRpcProvider, hints?: HintPreferences, maxBlockNumber?: number, tip?: BigInt) => Promise<string>;
//# sourceMappingURL=sendTx.d.ts.map

@@ -40,6 +40,5 @@ "use strict";

const { matchmaker, signedTx } = yield (0, exports.setupTxExample)(provider, tip);
const res = yield matchmaker.sendTransaction(signedTx, { hints, maxBlockNumber });
console.debug("sent tx", res);
return yield matchmaker.sendTransaction(signedTx, { hints, maxBlockNumber });
});
exports.sendTx = sendTx;
//# sourceMappingURL=sendTx.js.map

@@ -14,6 +14,5 @@ "use strict";

const async_mutex_1 = require("async-mutex");
// lib
const __1 = require("..");
const helpers_1 = require("./lib/helpers");
const sendTx_1 = require("./lib/sendTx");
const async_1 = require("./lib/async");
const NUM_TARGET_BLOCKS = 3;

@@ -25,3 +24,3 @@ /**

// send bundle w/ (basefee + 100)gwei gas fee
const { tx, wallet } = yield (0, sendTx_1.setupTxExample)(provider, BigInt(1e9) * BigInt(1e2), "im backrunniiiiing");
const { tx, wallet } = yield (0, sendTx_1.setupTxExample)(provider, BigInt(1e9) * BigInt(1e3), "im backrunniiiiing");
const backrunTx = Object.assign(Object.assign({}, tx), { nonce: tx.nonce ? tx.nonce + 1 : undefined });

@@ -33,3 +32,3 @@ const bundle = [

console.log(`sending backrun bundles targeting next ${NUM_TARGET_BLOCKS} blocks...`);
const params = {
const bundleParams = {
inclusion: {

@@ -41,5 +40,5 @@ block: targetBlock,

};
const backrunResult = yield matchmaker.sendBundle(params);
const backrunResult = yield matchmaker.sendBundle(bundleParams);
return {
bundle,
bundleParams,
backrunResult,

@@ -49,9 +48,17 @@ };

/** Async handler which backruns an mev-share tx with another basic example tx. */
const handleBackrun = (pendingTx, provider, matchmaker, pendingMutex) => __awaiter(void 0, void 0, void 0, function* () {
console.log("pending tx", pendingTx);
const targetBlock = (yield provider.getBlockNumber()) + 2;
const { bundle, backrunResult, } = yield sendTestBackrunBundle(provider, pendingTx, matchmaker, targetBlock);
const handleBackrun = (pendingTx, provider, matchmaker, pendingMutex, pendingTxHashes) => __awaiter(void 0, void 0, void 0, function* () {
pendingTx.mevGasPrice;
if (!pendingTxHashes.includes(pendingTx.hash)) {
// ignore txs we didn't send. they break the bundle (nonce error) bc we're using one account to do everything
return;
}
else {
console.log("pending tx", pendingTx);
}
const targetBlock = (yield provider.getBlockNumber()) + 1;
const { bundleParams, backrunResult, } = yield sendTestBackrunBundle(provider, pendingTx, matchmaker, targetBlock);
console.log("backrun result", backrunResult);
// watch future blocks for backrun tx inclusion
for (let i = 0; i < NUM_TARGET_BLOCKS; i++) {
const currentBlock = targetBlock + i;
if (!pendingMutex.isLocked()) {

@@ -61,20 +68,32 @@ // mutex was released by another handler, so we can exit

}
console.log(`tx ${pendingTx.hash} waiting for block`, targetBlock + i);
console.log(`tx ${pendingTx.hash} waiting for block`, currentBlock);
// stall until target block is available
while ((yield provider.getBlockNumber()) < targetBlock + i) {
yield new Promise(resolve => setTimeout(resolve, 2000));
while ((yield provider.getBlockNumber()) < currentBlock) {
yield new Promise(resolve => setTimeout(resolve, 6000));
}
// check for inclusion of backrun tx in target block
const checkTxHash = (0, ethers_1.keccak256)(bundle[1].tx);
const receipt = yield provider.getTransactionReceipt(checkTxHash);
if ((receipt === null || receipt === void 0 ? void 0 : receipt.status) === 1) {
console.log(`bundle included! (found tx ${receipt.hash})`);
// release mutex so the main thread can exit
pendingMutex.release();
break;
const backrunTx = bundleParams.body[1].tx;
if (backrunTx) {
const checkTxHash = (0, ethers_1.keccak256)(backrunTx);
const receipt = yield provider.getTransactionReceipt(checkTxHash);
if ((receipt === null || receipt === void 0 ? void 0 : receipt.status) === 1) {
console.log(`bundle included! (found tx ${receipt.hash})`);
// simulate for funzies
const simOptions = {
parentBlock: receipt.blockNumber - 1,
};
const simResult = yield matchmaker.simulateBundle(bundleParams, simOptions);
console.log(`simResult (simOptions=${JSON.stringify(simOptions, null, 2)})`, simResult);
console.log(`profit: ${(0, ethers_1.formatEther)(simResult.profit)} ETH`);
// release mutex so the main thread can exit
pendingMutex.release();
break;
}
else {
console.warn(`backrun tx ${checkTxHash} not included in block ${currentBlock}`);
}
}
else {
console.warn(`backrun tx ${checkTxHash} not included in block ${targetBlock}`);
}
}
yield pendingTxHashes.filter(hash => hash !== pendingTx.hash);
console.log("dropped target tx", pendingTx.hash);
});

@@ -89,6 +108,11 @@ /**

const { matchmaker } = yield (0, helpers_1.initExample)(provider);
// used for tracking txs we sent. we only want to backrun txs we sent,
// since we're using one account and incrementing the nonce of the bundle's 2nd tx
const pendingTxHashes = new async_1.AsyncArray();
// used for blocking this thread until the handler is done processing
const pendingMutex = new async_mutex_1.Mutex();
// listen for txs
const txHandler = matchmaker.on(__1.StreamEvent.Transaction, (pendingTx) => handleBackrun(pendingTx, provider, matchmaker, pendingMutex));
const txHandler = matchmaker.on("transaction", (pendingTx) => __awaiter(void 0, void 0, void 0, function* () {
yield handleBackrun(pendingTx, provider, matchmaker, pendingMutex, pendingTxHashes);
}));
console.log("listening for transactions...");

@@ -98,4 +122,8 @@ yield pendingMutex.acquire();

// tx will be backrun independently by the `handleBackrun` callback
const blockHandler = yield provider.on("block", () => __awaiter(void 0, void 0, void 0, function* () {
yield (0, sendTx_1.sendTx)(provider, { logs: true, contractAddress: true, calldata: true, functionSelector: true });
const blockHandler = yield provider.on("block", (blockNum) => __awaiter(void 0, void 0, void 0, function* () {
if ((yield pendingTxHashes.length()) === 0) {
const res = yield (0, sendTx_1.sendTx)(provider, { logs: true, contractAddress: true, calldata: true, functionSelector: true }, blockNum + NUM_TARGET_BLOCKS);
console.log("sent tx", res);
pendingTxHashes.push(res);
}
}));

@@ -108,5 +136,6 @@ // will block until one of the handlers releases the mutex

yield blockHandler.removeAllListeners();
console.log("block listener relieved of duty. waiting for handler threads to finish...");
});
main();
main().then(() => {
process.exit(0);
});
//# sourceMappingURL=sendBackrunBundle.js.map
import { Wallet } from 'ethers';
import EventSource from "eventsource";
import { BundleParams, MatchmakerNetwork, TransactionOptions, StreamEvent, IPendingTransaction, IPendingBundle } from './api/interfaces';
import { BundleParams, MatchmakerNetwork, TransactionOptions, StreamEvent, IPendingTransaction, IPendingBundle, SimBundleOptions, ISimBundleResult, ISendBundleResult, StreamEventName } from './api/interfaces';
export default class Matchmaker {

@@ -38,3 +38,3 @@ private authSigner;

*/
on(eventType: StreamEvent, callback: (data: IPendingBundle | IPendingTransaction) => void): EventSource;
on(eventType: StreamEvent | StreamEventName, callback: (data: IPendingBundle | IPendingTransaction) => void): EventSource;
/** Sends a private transaction with MEV hints to the Flashbots Matchmaker.

@@ -50,5 +50,4 @@ * @param signedTx Signed transaction to send.

*/
sendBundle(params: BundleParams): Promise<{
bundleHash: string;
}>;
sendBundle(params: BundleParams): Promise<ISendBundleResult>;
private simBundle;
/** Simulates a matched bundle.

@@ -61,4 +60,4 @@ *

*/
simulateBundle(params: BundleParams): Promise<any>;
simulateBundle(params: BundleParams, simOptions?: SimBundleOptions): Promise<ISimBundleResult>;
}
//# sourceMappingURL=matchmaker.d.ts.map

@@ -158,6 +158,13 @@ "use strict";

return __awaiter(this, void 0, void 0, function* () {
const mungedParams = (0, mungers_1.mungeBundleParams)(params);
return yield this.handleApiRequest(mungedParams, "mev_sendBundle");
return (0, interfaces_1.SendBundleResult)(yield this.handleApiRequest([(0, mungers_1.mungeBundleParams)(params)], "mev_sendBundle"));
});
}
simBundle(params, simOptions) {
return __awaiter(this, void 0, void 0, function* () {
return (0, interfaces_1.SimBundleResult)(yield this.handleApiRequest([
(0, mungers_1.mungeBundleParams)(params),
simOptions ? (0, mungers_1.mungeSimBundleOptions)(simOptions) : {}
], "mev_simBundle"));
});
}
/** Simulates a matched bundle.

@@ -170,3 +177,3 @@ *

*/
simulateBundle(params) {
simulateBundle(params, simOptions) {
return __awaiter(this, void 0, void 0, function* () {

@@ -181,3 +188,3 @@ const firstTx = params.body[0];

}
const waitForTx = (blockNumber) => __awaiter(this, void 0, void 0, function* () {
const waitForTx = () => __awaiter(this, void 0, void 0, function* () {
const tx = yield provider.getTransaction(firstTx.hash);

@@ -187,3 +194,3 @@ if (tx) {

const signedTx = ethers_1.Transaction.from(tx).serialized;
console.log(`Found transaction hash: ${firstTx.hash} onchain at block number:${blockNumber}`);
console.log(`Found transaction hash: ${firstTx.hash} onchain at block number: ${tx.blockNumber}`);
// TODO: Add params.inclusion.block target to mev_simBundle, not currently implemented in API

@@ -196,3 +203,3 @@ const paramsWithSignedTx = Object.assign(Object.assign({}, params), { body: [

] });
resolve(this.handleApiRequest((0, mungers_1.mungeBundleParams)(paramsWithSignedTx), "mev_simBundle"));
resolve(this.simBundle(paramsWithSignedTx, simOptions));
}

@@ -208,3 +215,3 @@ });

}
return yield this.handleApiRequest((0, mungers_1.mungeBundleParams)(params), "mev_simBundle");
return yield this.simBundle(params, simOptions);
});

@@ -211,0 +218,0 @@ }

{
"name": "@flashbots/matchmaker-ts",
"version": "0.6.0",
"version": "0.7.0",
"description": "Matchmaker client library for Flashbots mev-share.",

@@ -22,2 +22,3 @@ "main": "build/index.js",

"eslint": "^8.36.0",
"eslint-plugin-tsdoc": "^0.2.17",
"release-it": "^15.8.0",

@@ -24,0 +25,0 @@ "ts-node": "^10.9.1",

@@ -25,2 +25,7 @@ # Flashbots Matchmaker

```sh
# in your project, assuming it has the same parent directory as matchmaker-ts
yarn add ../matchmaker-ts
```
### use matchmaker in your project

@@ -39,3 +44,2 @@

IPendingTransaction,
StreamEvent,
TransactionOptions

@@ -67,11 +71,13 @@ } from "@flashbots/matchmaker-ts"

```typescript
import { JsonRpcProvider } from "ethers" // ethers v6
import { JsonRpcProvider, Wallet } from "ethers" // ethers v6
/** connects to Flashbots matchmaker on goerli */
function connectMatchmaker(provider: JsonRpcProvider) {
return Matchmaker.fromNetwork(provider._network)
}
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)
// manually with a chainId:
const matchmaker = Matchmaker.fromNetwork({chainId: 5})
const matchmaker = Matchmaker.fromNetwork(authSigner, {chainId: 5})
```

@@ -131,7 +137,7 @@

```typescript
const handler = matchmaker.on(StreamEvent.Transaction, (tx: IPendingTransaction) => {
const handler = matchmaker.on("transaction", (tx: IPendingTransaction) => {
// handle pending tx
})
// before terminating program
// ... before terminating program
handler.close()

@@ -145,2 +151,15 @@ ```

```typescript
const wallet = new Wallet(PRIVATE_KEY)
const tx = {
to: "0xfb000000387627910184cc42fc92995913806333",
value: BigInt(1e13 * 275), // price of a beer if ETH is $2000
data: "0x646f637320626179626565652121",
gasLimit: 42000,
maxFeePerGas: BigInt(1e9) * BigInt(42), // 42 gwei / gas
maxPriorityFeePerGas: BigInt(1e9) * BigInt(2), // 2 gwei / gas
chainId: 5,
type: 2,
}
// privacy & inclusion settings
const shareTxParams: TransactionOptions = {

@@ -154,3 +173,6 @@ hints: {

maxBlockNumber: undefined,
builders: ["flashbots"]
}
const signedTx = await wallet.signTransaction(tx)
await matchmaker.sendTransaction(SIGNED_TX, shareTxParams)

@@ -186,3 +208,3 @@ ```

},
targetBuilders: ["all"]
// builders: ["flashbots"]
}

@@ -192,1 +214,39 @@ }

```
### `simulateBundle`
Simulates a bundle. Accepts options to modify block header for simulation.
```typescript
const bundle: BundleParams = {
inclusion: {
block: TARGET_BLOCK,
maxBlock: TARGET_BLOCK + 3,
},
body: [
{hash: "0xTARGET_TX_HASH"},
{tx: "0xSIGNED_BACKRUN_TX", canRevert: false}
],
// ...
}
// ...
// assume you sent the bundle and it didn't land, and you want to see if it would have landed in the previous block, but need the tx to think it's in the target block
const simBundleOptions: SimBundleOptions = {
parentBlock: TARGET_BLOCK - 1,
blockNumber: TARGET_BLOCK,
/*
Set any of these (block header) fields to override their respective values in the simulation context:
*/
// coinbase: string,
// timestamp: number,
// gasLimit: number,
// baseFee: bigint,
// timeout: number,
}
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.

@@ -10,2 +10,4 @@ import { LogParams } from 'ethers';

callData?: string
mevGasPrice?: bigint
gasUsed?: bigint

@@ -18,2 +20,4 @@ constructor(event: IMatchmakerEvent) {

this.callData = event.txs && event.txs[0].callData
this.gasUsed = event.gasUsed ? BigInt(event.gasUsed) : undefined
this.mevGasPrice = event.mevGasPrice ? BigInt(event.mevGasPrice) : undefined
}

@@ -26,2 +30,4 @@ }

txs?: { to?: string, functionSelector?: string, callData?: string }[]
mevGasPrice?: bigint
gasUsed?: bigint

@@ -32,3 +38,5 @@ constructor(event: IMatchmakerEvent) {

this.txs = event.txs
this.gasUsed = event.gasUsed ? BigInt(event.gasUsed) : undefined
this.mevGasPrice = event.mevGasPrice ? BigInt(event.mevGasPrice) : undefined
}
}

@@ -11,2 +11,4 @@ import { LogParams } from 'ethers'

export type StreamEventName = `${StreamEvent}`
/**

@@ -43,3 +45,3 @@ * Configuration used to connect to the Matchmaker.

/**
* Parameters accepted by the `sendShareTransaction` function.
* Parameters accepted by the `sendTransaction` function.
*/

@@ -51,2 +53,3 @@ export interface TransactionOptions {

maxBlockNumber?: number,
builders?: string[],
}

@@ -95,3 +98,3 @@

/** Builders that are allowed to receive this bundle. See [mev-share spec](https://github.com/flashbots/mev-share/blob/main/builders/registration.json) for supported builders. */
targetBuilders?: Array<string>,
builders?: Array<string>,
},

@@ -103,4 +106,89 @@ metadata?: {

/** Response received from matchmaker API */
interface ISendBundleResponse {
/** Bundle hash. */
bundleHash: string,
}
/** Bundle details. */
export interface ISendBundleResult {
/** Bundle hash. */
bundleHash: string,
}
/** Decodes a raw sendBundle response. */
export const SendBundleResult = (response: ISendBundleResponse): ISendBundleResult => ({
bundleHash: response.bundleHash,
})
/** Optional fields to override simulation state. */
export interface SimBundleOptions {
/** Block used for simulation state. Defaults to latest block.
*
* Block header data will be derived from parent block by default.
* Specify other params in this interface to override the default values.
*
* Can be a block number or block hash.
*/
parentBlock?: number | string,
// override the default values for the parentBlock header
/** default = parentBlock.number + 1 */
blockNumber?: number,
/** default = parentBlock.coinbase */
coinbase?: string,
/** default = parentBlock.timestamp + 12 */
timestamp?: number,
/** default = parentBlock.gasLimit */
gasLimit?: number,
/** default = parentBlock.baseFeePerGas */
baseFee?: bigint,
/** default = 5 (defined in seconds) */
timeout?: number,
}
/** Logs returned by mev_simBundle. */
export interface SimBundleLogs {
txLogs?: LogParams[],
bundleLogs?: SimBundleLogs[],
}
/** Response received from matchmaker api. */
interface ISimBundleResponse {
success: boolean,
error?: string,
stateBlock: string,
mevGasPrice: string,
profit: string,
refundableValue: string,
gasUsed: string,
logs?: SimBundleLogs[],
}
/** Simulation details. */
export interface ISimBundleResult {
success: boolean,
error?: string,
stateBlock: number,
mevGasPrice: bigint,
profit: bigint,
refundableValue: bigint,
gasUsed: bigint,
logs?: SimBundleLogs[],
}
/** Decodes a raw simBundle response. */
export const SimBundleResult = (response: ISimBundleResponse): ISimBundleResult => ({
success: response.success,
error: response.error,
stateBlock: parseInt(response.stateBlock, 16),
mevGasPrice: BigInt(response.mevGasPrice),
profit: BigInt(response.profit),
refundableValue: BigInt(response.refundableValue),
gasUsed: BigInt(response.gasUsed),
logs: response.logs,
})
/**
* General API wrapper for events received by the SSE stream (via `matchmaker.on(...)`)
* General API wrapper for events received by the SSE stream (via `matchmaker.on(...)`).
*/

@@ -119,11 +207,43 @@ export interface IMatchmakerEvent {

callData?: string,
}>
}>,
/**
* Change in coinbase value after inserting tx/bundle, divided by gas used.
*
* Can be used to determine the minimum payment to the builder to make your backrun look more profitable to builders.
* _Note: this only applies to builders like Flashbots who order bundles by MEV gas price._
*/
mevGasPrice?: string, // hex string
/** Gas used by the tx/bundle, rounded up to 2 most significant digits.
*
* _Note: EXPERIMENTAL; only implemented on Goerli_ */
gasUsed?: string, // hex string
}
export interface IPendingTransaction extends Omit<IMatchmakerEvent, 'txs'> {
/**
* Pending transaction from the matchmaker stream.
*/
export interface IPendingTransaction extends Omit<Omit<Omit<IMatchmakerEvent, 'txs'>, 'mevGasPrice'>, 'gasUsed'> {
to?: string,
functionSelector?: string,
callData?: string,
/**
* {@link IMatchmakerEvent.mevGasPrice}
*/
mevGasPrice?: bigint,
/**
* {@link IMatchmakerEvent.gasUsed}
*/
gasUsed?: bigint,
}
export type IPendingBundle = IMatchmakerEvent
/** Pending bundle from the matchmaker stream. */
export interface IPendingBundle extends Omit<Omit<IMatchmakerEvent, 'mevGasPrice'>, 'gasUsed'> {
/**
* {@link IMatchmakerEvent.mevGasPrice}
*/
mevGasPrice?: bigint,
/**
* {@link IMatchmakerEvent.gasUsed}
*/
gasUsed?: bigint,
}

@@ -1,6 +0,6 @@

import { BundleParams, HintPreferences, TransactionOptions } from './interfaces'
import { BundleParams, HintPreferences, SimBundleOptions, TransactionOptions } from './interfaces'
/**
* Convert name format of user-specified hints for Matchmaker API requests.
* @param hints Hints specified by the user.
* @param hints - Hints specified by the user.
*/

@@ -20,3 +20,3 @@ const mungeHintPreferences = (hints: HintPreferences) => {

* Converts user-specified hints into the array format accepted by the API.
* @param hints Hints specified by the user.
* @param hints - Hints specified by the user.
*/

@@ -31,4 +31,4 @@ const extractSpecifiedHints = (hints: HintPreferences): string[] => {

* Converts user-specified parameters into parameters for a sendPrivateTransaction call to the Matchmaker API.
* @param signedTx Signed transaction to send.
* @param options Privacy/execution settings for the transaction.
* @param signedTx - Signed transaction to send.
* @param options - Privacy/execution settings for the transaction.
* @returns Single-element array containing params object for sendPrivateTransaction call.

@@ -42,6 +42,7 @@ */

fast: true, // deprecated but required; setting has no effect
// auction uses default (Stable) config if no hints specified
auction: options?.hints && {
hint: extractSpecifiedHints(options.hints),
// privacy uses default (Stable) config if no hints specified
privacy: options?.hints && {
hints: extractSpecifiedHints(options.hints),
},
builders: options?.builders,
},

@@ -53,3 +54,3 @@ }]

* Converts user-specified parameters into parameters for a mev_sendBundle call to the Matchmaker API.
* @param params Privacy/execution parameters for the bundle
* @param params - Privacy/execution parameters for the bundle
* @returns Single-element array containing params object for sendPrivateTransaction call.

@@ -61,6 +62,6 @@ */

const mungedBundle: any[] = params.body.map((i: AnyBundleItem) => i.bundle ? mungeBundleParams(i.bundle) : i)
return [{
return {
...params,
body: mungedBundle,
version: params.version || "beta-1", // default latest
version: params.version || "v0.1",
inclusion: {

@@ -79,3 +80,16 @@ ...params.inclusion,

}
}]
}
}
/** Convert SimBundleOptions into format required by eth_simBundle. */
export function mungeSimBundleOptions(params: SimBundleOptions) {
return {
...params,
// coinbase & timeout can be left as they are
parentBlock: params.parentBlock && `0x${BigInt(params.parentBlock).toString(16)}`,
blockNumber: params.blockNumber && `0x${BigInt(params.blockNumber).toString(16)}`,
timestamp: params.timestamp && `0x${BigInt(params.timestamp).toString(16)}`,
gasLimit: params.gasLimit && `0x${BigInt(params.gasLimit).toString(16)}`,
baseFee: params.baseFee && `0x${params.baseFee.toString(16)}`,
}
}

@@ -10,14 +10,11 @@ import { JsonRpcProvider, Network, Wallet } from 'ethers'

export async function initExample(provider: JsonRpcProvider) {
const authSigner = new Wallet(Env.authKey)
const matchmaker = Matchmaker.useEthereumGoerli(authSigner)
const wallet = new Wallet(Env.senderKey)
const feeData = await provider.getFeeData()
const authSigner = new Wallet(Env.authKey).connect(provider)
return {
provider,
wallet: wallet.connect(provider),
authSigner: authSigner.connect(provider),
matchmaker,
feeData,
wallet: new Wallet(Env.senderKey).connect(provider),
authSigner,
matchmaker: Matchmaker.useEthereumGoerli(authSigner),
feeData: await provider.getFeeData(),
}
}

@@ -37,6 +37,5 @@ import { hexlify, toBigInt, toUtf8Bytes } from 'ethers'

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

@@ -1,8 +0,9 @@

import { JsonRpcProvider, keccak256 } from 'ethers'
import { JsonRpcProvider, formatEther, keccak256 } from 'ethers'
import { Mutex } from "async-mutex"
// lib
import Matchmaker, { BundleParams, IPendingTransaction, StreamEvent } from '..'
import Matchmaker, { BundleParams, IPendingTransaction } from '..'
import { getProvider, initExample } from './lib/helpers'
import { sendTx, setupTxExample } from './lib/sendTx'
import { AsyncArray } from './lib/async'

@@ -16,3 +17,3 @@ const NUM_TARGET_BLOCKS = 3

// send bundle w/ (basefee + 100)gwei gas fee
const {tx, wallet} = await setupTxExample(provider, BigInt(1e9) * BigInt(1e2), "im backrunniiiiing")
const {tx, wallet} = await setupTxExample(provider, BigInt(1e9) * BigInt(1e3), "im backrunniiiiing")
const backrunTx = {

@@ -27,3 +28,3 @@ ...tx,

console.log(`sending backrun bundles targeting next ${NUM_TARGET_BLOCKS} blocks...`)
const params: BundleParams = {
const bundleParams: BundleParams = {
inclusion: {

@@ -35,5 +36,5 @@ block: targetBlock,

}
const backrunResult = await matchmaker.sendBundle(params)
const backrunResult = await matchmaker.sendBundle(bundleParams)
return {
bundle,
bundleParams,
backrunResult,

@@ -49,7 +50,14 @@ }

pendingMutex: Mutex,
) => {
console.log("pending tx", pendingTx)
const targetBlock = await provider.getBlockNumber() + 2
pendingTxHashes: AsyncArray<string>,
): Promise<void> => {
pendingTx.mevGasPrice
if (!(await pendingTxHashes.includes(pendingTx.hash))) {
// ignore txs we didn't send. they break the bundle (nonce error) bc we're using one account to do everything
return
} else {
console.log("pending tx", pendingTx)
}
const targetBlock = await provider.getBlockNumber() + 1
const {
bundle,
bundleParams,
backrunResult,

@@ -61,2 +69,3 @@ } = await sendTestBackrunBundle(provider, pendingTx, matchmaker, targetBlock)

for (let i = 0; i < NUM_TARGET_BLOCKS; i++) {
const currentBlock = targetBlock + i
if (!pendingMutex.isLocked()) {

@@ -66,20 +75,34 @@ // mutex was released by another handler, so we can exit

}
console.log(`tx ${pendingTx.hash} waiting for block`, targetBlock + i)
console.log(`tx ${pendingTx.hash} waiting for block`, currentBlock)
// stall until target block is available
while (await provider.getBlockNumber() < targetBlock + i) {
await new Promise(resolve => setTimeout(resolve, 2000))
while (await provider.getBlockNumber() < currentBlock) {
await new Promise(resolve => setTimeout(resolve, 6000))
}
// check for inclusion of backrun tx in target block
const checkTxHash = keccak256(bundle[1].tx!)
const receipt = await provider.getTransactionReceipt(checkTxHash)
if (receipt?.status === 1) {
console.log(`bundle included! (found tx ${receipt.hash})`)
// release mutex so the main thread can exit
pendingMutex.release()
break
} else {
console.warn(`backrun tx ${checkTxHash} not included in block ${targetBlock}`)
const backrunTx = (bundleParams.body[1] as any).tx
if (backrunTx) {
const checkTxHash = keccak256(backrunTx)
const receipt = await provider.getTransactionReceipt(checkTxHash)
if (receipt?.status === 1) {
console.log(`bundle included! (found tx ${receipt.hash})`)
// simulate for funzies
const simOptions = {
parentBlock: receipt.blockNumber - 1,
}
const simResult = await matchmaker.simulateBundle(bundleParams, simOptions)
console.log(`simResult (simOptions=${JSON.stringify(simOptions, null, 2)})`, simResult)
console.log(`profit: ${formatEther(simResult.profit)} ETH`)
// release mutex so the main thread can exit
pendingMutex.release()
break
} else {
console.warn(`backrun tx ${checkTxHash} not included in block ${currentBlock}`)
}
}
}
await pendingTxHashes.filter(hash => hash !== pendingTx.hash)
console.log("dropped target tx", pendingTx.hash)
}

@@ -96,2 +119,6 @@

// used for tracking txs we sent. we only want to backrun txs we sent,
// since we're using one account and incrementing the nonce of the bundle's 2nd tx
const pendingTxHashes = new AsyncArray<string>()
// used for blocking this thread until the handler is done processing

@@ -101,3 +128,5 @@ const pendingMutex = new Mutex()

// listen for txs
const txHandler = matchmaker.on(StreamEvent.Transaction, (pendingTx: IPendingTransaction) => handleBackrun(pendingTx, provider, matchmaker, pendingMutex))
const txHandler = matchmaker.on("transaction", async (pendingTx: IPendingTransaction) => {
await handleBackrun(pendingTx, provider, matchmaker, pendingMutex, pendingTxHashes)
})
console.log("listening for transactions...")

@@ -108,4 +137,8 @@

// tx will be backrun independently by the `handleBackrun` callback
const blockHandler = await provider.on("block", async () => {
await sendTx(provider, {logs: true, contractAddress: true, calldata: true, functionSelector: true})
const blockHandler = await provider.on("block", async (blockNum) => {
if (await pendingTxHashes.length() === 0) {
const res = await sendTx(provider, {logs: true, contractAddress: true, calldata: true, functionSelector: true}, blockNum + NUM_TARGET_BLOCKS)
console.log("sent tx", res)
pendingTxHashes.push(res)
}
})

@@ -120,5 +153,6 @@

await blockHandler.removeAllListeners()
console.log("block listener relieved of duty. waiting for handler threads to finish...")
}
main()
main().then(() => {
process.exit(0)
})

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

* Standardized RPC request for talking to Bundle API (mev-geth/mev-share) directly.
* @param params JSON data params
* @param method JSON-RPC method
* @param authSigner Wallet used to sign Flashbots auth header; for reputation
* @param params - JSON data params
* @param method - JSON-RPC method
* @param authSigner - Wallet used to sign Flashbots auth header; for reputation
* @returns Parameters of payload to send to Bundle API

@@ -17,0 +17,0 @@ */

@@ -7,4 +7,18 @@ import axios, { AxiosError } from "axios"

import { getRpcRequest, JsonRpcData } from './flashbots';
import { BundleParams, MatchmakerNetwork, TransactionOptions, StreamEvent, IMatchmakerEvent, IPendingTransaction, IPendingBundle } from './api/interfaces'
import { mungeBundleParams, mungePrivateTxParams } from "./api/mungers"
import {
BundleParams,
MatchmakerNetwork,
TransactionOptions,
StreamEvent,
IMatchmakerEvent,
IPendingTransaction,
IPendingBundle,
SimBundleOptions,
SimBundleResult,
ISimBundleResult,
ISendBundleResult,
SendBundleResult,
StreamEventName
} from './api/interfaces'
import { mungeBundleParams, mungePrivateTxParams, mungeSimBundleOptions } from "./api/mungers"
import { SupportedNetworks } from './api/networks'

@@ -25,2 +39,3 @@ import { PendingBundle, PendingTransaction } from './api/events';

/** Connect to Flashbots Mainnet Matchmaker. */
static useEthereumMainnet(authSigner: Wallet): Matchmaker {

@@ -30,2 +45,3 @@ return new Matchmaker(authSigner, SupportedNetworks.mainnet)

/** Connect to Flashbots Goerli Matchmaker. */
static useEthereumGoerli(authSigner: Wallet): Matchmaker {

@@ -35,2 +51,3 @@ return new Matchmaker(authSigner, SupportedNetworks.goerli)

/** Connect to supported networks by specifying a network with a `chainId`. */
static fromNetwork(authSigner: Wallet, {chainId}: {chainId: number}): Matchmaker {

@@ -43,7 +60,7 @@ const network = SupportedNetworks.getNetwork(chainId)

* Sends a POST request to the Matchmaker API and returns the data.
* @param params JSON-RPC params.
* @param method JSON-RPC method.
* @param params - JSON-RPC params.
* @param method - JSON-RPC method.
* @returns Response data from the API request.
*/
private async handleApiRequest(params: any, method: any): Promise<any> {
private async handleApiRequest(params: Array<any>, method: any): Promise<any> {
const {body, headers} = await getRpcRequest(params, method, this.authSigner)

@@ -70,4 +87,4 @@ try {

* Registers the provided callback to be called when a new MEV-Share transaction is received.
* @param event The event received from the event stream.
* @param callback Async function to process pending tx.
* @param event - The event received from the event stream.
* @param callback - Async function to process pending tx.
*/

@@ -85,4 +102,4 @@ private onTransaction(

* Registers the provided callback to be called when a new MEV-Share bundle is received.
* @param event The event received from the event stream.
* @param callback Async function to process pending tx.
* @param event - The event received from the event stream.
* @param callback - Async function to process pending tx.
*/

@@ -98,10 +115,11 @@ private onBundle(

/**
* Starts listening to the Matchmaker event stream and registers the given callback to be invoked when the given event type is received.
* @param eventType The type of event to listen for. Options specified by StreamEvent enum.
* @param callback The function to call when a new event is received.
* @param eventType - The type of event to listen for. Options specified by StreamEvent enum.
* @param callback - The function to call when a new event is received.
* @returns Stream handler. Call `.close()` on it before terminating your program.
*/
public on(
eventType: StreamEvent,
eventType: StreamEvent | StreamEventName,
callback: (data: IPendingBundle | IPendingTransaction) => void

@@ -132,4 +150,4 @@ ): EventSource {

/** Sends a private transaction with MEV hints to the Flashbots Matchmaker.
* @param signedTx Signed transaction to send.
* @param options Tx preferences; hints & block range.
* @param signedTx - Signed transaction to send.
* @param options - Tx preferences; hints & block range.
* @returns Transaction hash.

@@ -146,18 +164,35 @@ */

/** Sends a bundle to mev-share.
* @param params Parameters for the bundle.
* @param params - Parameters for the bundle.
* @returns Array of bundle hashes.
*/
public async sendBundle(params: BundleParams): Promise<{bundleHash: string}> {
const mungedParams = mungeBundleParams(params)
return await this.handleApiRequest(mungedParams, "mev_sendBundle")
public async sendBundle(params: BundleParams): Promise<ISendBundleResult> {
return SendBundleResult(await this.handleApiRequest([mungeBundleParams(params)], "mev_sendBundle"))
}
/** Simulates a matched bundle.
/**
* Internal mev_simBundle call.
*
* Note: This may only be used on matched bundles.
* Simulating unmatched bundles (i.e. bundles with a hash present) will throw an error.
* @param params Parameters for the bundle.
* @returns Simulation data object.
* @param params - Parameters for the bundle.
* @param simOptions - Simulation options; override block header data for simulation.
* @returns Simulation result.
*/
public async simulateBundle(params: BundleParams): Promise<any> {
private async simBundle(params: BundleParams, simOptions?: SimBundleOptions): Promise<ISimBundleResult> {
return SimBundleResult(await this.handleApiRequest([
mungeBundleParams(params),
simOptions ? mungeSimBundleOptions(simOptions) : {}
], "mev_simBundle"))
}
/** Simulates a bundle specified by `params`.
*
* Bundles containing pending transactions (specified by `{hash}` instead of `{tx}` in `params.body`) may
* only be simulated after those transactions have landed on chain. If the bundle contains
* pending transactions, this method will wait for the transactions to land before simulating.
* @param params - Parameters for the bundle.
* @param simOptions - Simulation options; override block header data for simulation.
* @returns Simulation result.
*/
public async simulateBundle(params: BundleParams, simOptions?: SimBundleOptions): Promise<ISimBundleResult> {
const firstTx = params.body[0]

@@ -171,3 +206,3 @@ if ('hash' in firstTx) {

}
const waitForTx = async (blockNumber: number) => {
const waitForTx = async () => {
const tx = await provider.getTransaction(firstTx.hash)

@@ -177,3 +212,3 @@ if (tx) {

const signedTx = Transaction.from(tx).serialized
console.log(`Found transaction hash: ${ firstTx.hash } onchain at block number:${ blockNumber }`)
console.log(`Found transaction hash: ${ firstTx.hash } onchain at block number: ${ tx.blockNumber }`)
// TODO: Add params.inclusion.block target to mev_simBundle, not currently implemented in API

@@ -189,3 +224,3 @@ const paramsWithSignedTx = {

}
resolve(this.handleApiRequest(mungeBundleParams(paramsWithSignedTx), "mev_simBundle"))
resolve(this.simBundle(paramsWithSignedTx, simOptions))
}

@@ -203,4 +238,4 @@ }

}
return await this.handleApiRequest(mungeBundleParams(params), "mev_simBundle")
return await this.simBundle(params, simOptions)
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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