Skip to content

Commit

Permalink
Slack: Notification After Success Run (#17)
Browse files Browse the repository at this point in the history
Adding a slack client (requiring a SLACK_TOKEN and SLACK_CHANNEL) that is initialized at runtime setup:

It posts to SLACK_CHANNEL as follows:

After Billing - MEV Billing ran successfully: ${TX_LINK}.
After Drafting - (if there were any drafted account) MEV Drafting ran successfully: ${TX_LINK}.
  • Loading branch information
cowanator committed Jun 17, 2024
1 parent c4a4b0a commit dbb2802
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ BOND_MAP="('0xa489faf6e337d997b8a23e2b6f3a8880b1b61e19', '0xfd39bc23d356a762cf80
# Secrets: Required for both Billing & Drafting
DUNE_API_KEY=
BILLER_PRIVATE_KEY=
SLACK_TOKEN=
SLACK_CHANNEL=
SCAN_URL=
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"@duneanalytics/client-sdk": "^0.1.5",
"@ethersproject/bytes": "^5.7.0",
"@slack/web-api": "^7.1.0",
"@types/node": "^20.11.30",
"ethers": "^6.12.0",
"loglevel": "^1.9.1",
Expand Down
45 changes: 40 additions & 5 deletions src/accountManager.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,66 @@
import { QueryRunner } from "./dune";
import { BillingContract } from "./billingContract";
import { Slack } from "./notify";

export class AccountManager {
private dataFetcher: QueryRunner;
private billingContract: BillingContract;
private slack: Slack;
scanUrl?: string;

constructor(dataFetcher: QueryRunner, billingContract: BillingContract) {
constructor(
dataFetcher: QueryRunner,
billingContract: BillingContract,
slack: Slack,
scanUrl?: string,
) {
this.dataFetcher = dataFetcher;
this.billingContract = billingContract;
this.slack = slack;
if (!scanUrl) {
console.warn("running without scan URL, txHashes will be logged bare");
}
this.scanUrl = scanUrl;
}

static fromEnv(): AccountManager {
return new AccountManager(QueryRunner.fromEnv(), BillingContract.fromEnv());
static async fromEnv(): Promise<AccountManager> {
return new AccountManager(
QueryRunner.fromEnv(),
BillingContract.fromEnv(),
await Slack.fromEnv(),
);
}

async runBilling() {
const today = todaysDate();
console.log("Running Biller for Date", today);
const billingResults = await this.dataFetcher.getBillingData(today);
// TODO - validate results!
await this.billingContract.updatePaymentDetails(billingResults);
const txHash =
await this.billingContract.updatePaymentDetails(billingResults);
await this.slack.post(
`MEV Billing ran successfully: ${this.txLink(txHash)}`,
);
}

async runDrafting() {
console.log("Running Drafter");
const paymentStatuses = await this.dataFetcher.getPaymentStatus();
await this.billingContract.processPaymentStatuses(paymentStatuses);
const txHash =
await this.billingContract.processPaymentStatuses(paymentStatuses);
if (txHash) {
await this.slack.post(
`MEV Drafting ran successfully: ${this.txLink(txHash)}`,
);
} else {
console.log("No accounts drafted");
}
// TODO - check balances after drafting and notify if too low!
// May only need to query balances of those who were drafted.
}

private txLink(hash: string): string {
return `${this.scanUrl}/tx/${hash}`;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/billingContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ethers, formatEther } from "ethers";
import { BillingData, LatestBillingStatus, PaymentStatus } from "./types";
import { BILLING_CONTRACT_ABI, ROLE_MODIFIER_ABI } from "./abis";
import { MetaTransaction, encodeMulti } from "./multisend";
import { getTxCostForGas, maxBigInt, minBigInt } from "./gas";
import { getTxCostForGas, maxBigInt } from "./gas";

interface BillingInput {
addresses: `0x${string}`[];
Expand Down
2 changes: 1 addition & 1 deletion src/gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export async function getTxCostForGas(
return gasEstimate * effectiveGasPrice;
}

export function minBigInt(a: bigint, b: bigint): bigint {
function minBigInt(a: bigint, b: bigint): bigint {
if (a < b) {
return a;
}
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ async function main() {
.command("drafting", "Run drafting process")
.demandCommand(1, "You need to specify a command: billing or drafting")
.help().argv;
const manager = AccountManager.fromEnv();

const manager = await AccountManager.fromEnv();

if (args._.includes("billing")) {
await manager.runBilling();
} else if (args._.includes("drafting")) {
Expand Down
4 changes: 1 addition & 3 deletions src/multisend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ export enum OperationType {
Call = 0,
DelegateCall = 1,
}
/**
*
*/

export interface MetaTransaction {
/// A `uint8` with `0` for a `call` or `1` for a `delegatecall` (=> 1 byte),
readonly operation?: OperationType;
Expand Down
35 changes: 35 additions & 0 deletions src/notify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { WebClient } from "@slack/web-api";

export class Slack {
client: WebClient;
channel: string;

constructor(token: string, channel: string) {
this.client = new WebClient(token);
this.channel = channel;
}

static async fromEnv(): Promise<Slack> {
const { SLACK_TOKEN, SLACK_CHANNEL } = process.env;
const slack = new Slack(SLACK_TOKEN!, SLACK_CHANNEL!);
// Test early!
const result = await slack.client.auth.test();
if (result.ok) {
return slack;
} else {
throw new Error(`couldn't connect to slack: ${result.error}`);
}
}

async post(message: string): Promise<void> {
let result = await this.client.chat.postMessage({
text: message,
channel: this.channel,
});
if (result.ok) {
return;
} else {
throw new Error(`failed to post: ${result.error}`);
}
}
}
3 changes: 1 addition & 2 deletions tests/e2e/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ describe("e2e - Sepolia", () => {
const billingContract = BillingContract.fromEnv();
const draftHash =
await billingContract.processPaymentStatuses(paymentStatus);
// This is a non-deterministic test.
console.log("Drafting Hashe", draftHash);

const provider = billingContract.contract.runner!.provider;
const receipt = await provider!.getTransactionReceipt(draftHash!);
// 2 drafts + 2 fines + 2 safe module transactions.
Expand Down
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"resolveJsonModule": true
"resolveJsonModule": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.test.ts"]
Expand Down
Loading

0 comments on commit dbb2802

Please sign in to comment.