Keep3r Scripting Utils
Introduction
Keep3r Scripting Utils provides a set of plug-and-play classes denominated broadcastors in charge of making the correct broadcasting of a transaction through different channels as simple as possible.
Broadcastors
Broadcastors are the meat of this library. They are a set of classes in charge of handling the broadcasting of a transaction. This involves:
- Ensuring the transaction won't encounter an error
- Calculating the gas parameters
- Populating the transaction
- Sending the transaction
- Retrying in the case the transaction is sent through flashbots
As of this release, three broadcastors exists:
FlashbotsBroadcastor: Broadcasts a transaction through flashbots. It will attempt to include the transaction for ~15 blocks before dropping it.
MempoolBroadcastor: Broadcasts a transaction through the mempool.
StealthRelayerBroadcastor: Broadcast a transaction through flashbots and through the StealthRelayer contract. This is a specific implementation for certain Yearn strategies, so chances are the keeper won't use it.
Broadcastors are designed to be extremely simple to use. All the keeper has to do is initialize them with his desired parameters and call the chosen broadcastor's tryToWork function.
This allows a flexible and modularized approach where the keeper can separate the logic in charge of what to broadcast from the logic of how to broadcast. The former tends to be job-specific so creating abstractions for all cases is not scalable. The latter however is almost always the same logic, so abstracting it can reduce code duplication and save time.
This library takes care of the latter so the keeper can focus on the logic specific to what to broadcast.
Example Code
Let's say there's a job with a work(address _strategy) method and we want to work it through flashbots. We will divide our script in two files:
init.ts: Will handle all required initializations to work the job.
run.ts: Will handle what to broadcast as well as the broadcasting.
Init.ts
import {getMainnetSdk} from '@dethcrypto/eth-sdk-client';
import {providers, Wallet} from 'ethers';
import {FlashbotsBundleProvider} from '@flashbots/ethers-provider-bundle';
import {FlashbotsBroadcastor, getEnvVariable} from '@keep3r-network/keeper-scripting-utils';
import {run} from './run';
const WORK_FUNCTION = 'work';
const GAS_LIMIT = 10_000_000;
const PRIORITY_FEE = 1.5e9;
(async () => {
const provider = new providers.JsonRpcProvider(getEnvVariable('NODE_URI_MAINNET'));
const txSigner = new Wallet(getEnvVariable('TX_SIGNER_PRIVATE_KEY'), provider);
const bundleSigner = new Wallet(getEnvVariable('BUNDLE_SIGNER_PRIVATE_KEY'), provider);
const jobToWork = getMainnetSdk(txSigner).exampleJob;
const flashbotsProvider = await FlashbotsBundleProvider.create(provider, bundleSigner);
const flashbotBroadcastor = new FlashbotsBroadcastor(flashbotsProvider, PRIORITY_FEE, GAS_LIMIT);
await run(jobToWork, provider, WORK_FUNCTION, flashbotBroadcastor.tryToWorkOnFlashbots.bind(flashbotBroadcastor));
})();
run.ts
import type {providers, Contract} from 'ethers';
import type {Block} from '@ethersproject/abstract-provider';
import {BlockListener, BroadcastorProps} from '@keep3r-network/keeper-scripting-utils';
import {getStrategies} from './batch-requests';
export async function run(
jobContract: Contract,
provider: providers.WebSocketProvider | providers.JsonRpcProvider,
workFunction: string,
broadcastMethod: (props: BroadcastorProps) => Promise<void>,
) {
const blockListener = new BlockListener(provider);
blockListener.stream(async (block: Block) => {
const allStrategies = await jobContract.strategies();
const workableStrategies = await getStrategies(jobContract, allStrategies);
if (workableStrategies.length === 0) {
console.info('Found no workable strategies.');
return
}
for (const [_, strategy] of workableStrategies.entries()) {
try {
await broadcastMethod(jobContract, workFunction, [strategy], block);
break;
} catch (error: unknown) {
if (error instanceof Error) console.log(`Strategy: ${strategy} failed with:`, error.message);
}
}
});
}
And that's it. In about ~60 lines of code you can have a fully competitive and working keeper script.
BlockListener
To make listening to blocks simple, the library provides the BlockListener class.
As shown in the code snippet above, all the keeper has to do is instantiate the class and call the stream method. If the keeper wants to delete the subscription for any reason, this can be done so in the following manner:
import {BlockListener} from '@keep3r-network/keeper-scripting-utils';
const blockListener = new BlockListener();
const clearSubscription = blockListener.stream(async(block: Block) => {
clearSubscription();
})