Skip to content

Commit

Permalink
fix: use webhooks for discord notification (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
jahabeebs authored Nov 6, 2024
1 parent 436a9a8 commit 2c02271
Show file tree
Hide file tree
Showing 18 changed files with 417 additions and 236 deletions.
7 changes: 2 additions & 5 deletions apps/agent/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,5 @@ BLOCK_NUMBER_BLOCKMETA_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuaWNlIjoidH
# Path to the agent YAML configuration file
EBO_AGENT_CONFIG_FILE_PATH="./config.example.yml"

# Discord bot token for notifications
DISCORD_BOT_TOKEN=YOUR_DISCORD_BOT_TOKEN

# Discord channel ID for notifications
DISCORD_CHANNEL_ID=YOUR_DISCORD_CHANNEL_ID
# Discord webhook notifications
DISCORD_WEBHOOK=YOUR_DISCORD_WEBHOOK
19 changes: 9 additions & 10 deletions apps/agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,15 @@ cp .env.example .env

**Environment Variables**:

| Variable | Description | Required |
| ------------------------------- | ------------------------------------------------------------------------- | -------- |
| `PROTOCOL_PROVIDER_PRIVATE_KEY` | Private key for the Protocol Provider | Yes |
| `PROTOCOL_PROVIDER_L1_RPC_URLS` | Comma-separated URLs for Layer 1 RPC endpoints | Yes |
| `PROTOCOL_PROVIDER_L2_RPC_URLS` | Comma-separated URLs for Layer 2 RPC endpoints | Yes |
| `BLOCK_NUMBER_RPC_URLS_MAP` | JSON map of chain IDs to arrays of RPC URLs for Block Number calculations | Yes |
| `BLOCK_NUMBER_BLOCKMETA_TOKEN` | Bearer token for the Blockmeta service (see notes below on how to obtain) | Yes |
| `EBO_AGENT_CONFIG_FILE_PATH` | Path to the agent YAML configuration file | Yes |
| `DISCORD_BOT_TOKEN` | Your Discord bot’s token | Yes |
| `DISCORD_CHANNEL_ID` | Discord channel ID for notifications | Yes |
| Variable | Description | Required |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `PROTOCOL_PROVIDER_PRIVATE_KEY` | Private key for the Protocol Provider | Yes |
| `PROTOCOL_PROVIDER_L1_RPC_URLS` | Comma-separated URLs for Layer 1 RPC endpoints | Yes |
| `PROTOCOL_PROVIDER_L2_RPC_URLS` | Comma-separated URLs for Layer 2 RPC endpoints | Yes |
| `BLOCK_NUMBER_RPC_URLS_MAP` | JSON map of chain IDs to arrays of RPC URLs for Block Number calculations | Yes |
| `BLOCK_NUMBER_BLOCKMETA_TOKEN` | Bearer token for the Blockmeta service (see notes below on how to obtain) | Yes |
| `EBO_AGENT_CONFIG_FILE_PATH` | Path to the agent YAML configuration file | Yes |
| `DISCORD_WEBHOOK` | Your Discord channel webhook for notifications [Learn how to create a Discord webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) | No |

**Notes:**

Expand Down
3 changes: 3 additions & 0 deletions apps/agent/config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ processor:
requestModule: "0x1234567890123456789012345678901234567890" # Address of the Request module
responseModule: "0x1234567890123456789012345678901234567890" # Address of the Response module
escalationModule: "0x1234567890123456789012345678901234567890" # Address of the Escalation module

notifier:
discordDefaultAvatarUrl: "https://cryptologos.cc/logos/the-graph-grt-logo.png" # Default avatar URL for Discord notifications
3 changes: 1 addition & 2 deletions apps/agent/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,5 @@ export const config = {
},
},
processor: { ...configData.processor },
DISCORD_BOT_TOKEN: envData.DISCORD_BOT_TOKEN,
DISCORD_CHANNEL_ID: envData.DISCORD_CHANNEL_ID,
DISCORD_WEBHOOK: envData.DISCORD_WEBHOOK,
} as const;
3 changes: 1 addition & 2 deletions apps/agent/src/config/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ export const envSchema = z.object({
BLOCK_NUMBER_RPC_URLS_MAP: stringToJSONSchema.pipe(chainRpcUrlSchema),
BLOCK_NUMBER_BLOCKMETA_TOKEN: z.string(),
EBO_AGENT_CONFIG_FILE_PATH: z.string(),
DISCORD_BOT_TOKEN: z.string(),
DISCORD_CHANNEL_ID: z.string(),
DISCORD_WEBHOOK: z.string(),
});

const addressSchema = z.string().refine((address) => isAddress(address));
Expand Down
14 changes: 4 additions & 10 deletions apps/agent/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { inspect } from "util";
import { isNativeError } from "util/types";
import {
DiscordNotifier,
EboActorsManager,
EboProcessor,
NotificationService,
ProtocolProvider,
} from "@ebo-agent/automated-dispute";
import { BlockNumberService } from "@ebo-agent/blocknumber";
Expand Down Expand Up @@ -39,17 +39,11 @@ const main = async (): Promise<void> => {

logger.debug("Protocol provider initialized.");

const discordConfig = {
discordBotToken: config.DISCORD_BOT_TOKEN,
discordChannelId: config.DISCORD_CHANNEL_ID,
};

logger.debug("Initializing notifier...");
logger.debug(stringify(discordConfig));

// const notifier = await DiscordNotifier.create(discordConfig, logger);
// FIXME: during E2E DiscordNotifier is not able to start even if setting a valid token
const notifier = { notifyError: (_e, _ctx) => {} } as NotificationService;
const notifier = new DiscordNotifier(config.DISCORD_WEBHOOK, logger);

logger.debug("Notifier initialized...");

const actorsManager = new EboActorsManager();

Expand Down
6 changes: 2 additions & 4 deletions apps/agent/test/e2e/scenarios/01_happy_path/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,7 @@ describe.sequential("single agent", () => {
BLOCK_NUMBER_RPC_URLS_MAP: new Map<Caip2ChainId, string[]>([
[PROTOCOL_L2_CHAIN_ID, [PROTOCOL_L2_LOCAL_URL]],
]),
DISCORD_BOT_TOKEN: "",
DISCORD_CHANNEL_ID: "",
DISCORD_WEBHOOK: "",
},
});

Expand Down Expand Up @@ -524,8 +523,7 @@ describe.sequential("single agent", () => {
BLOCK_NUMBER_RPC_URLS_MAP: new Map<Caip2ChainId, string[]>([
[PROTOCOL_L2_CHAIN_ID, [PROTOCOL_L2_LOCAL_URL]],
]),
DISCORD_BOT_TOKEN: "",
DISCORD_CHANNEL_ID: "",
DISCORD_WEBHOOK: "",
},
});

Expand Down
3 changes: 1 addition & 2 deletions packages/automated-dispute/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { z } from "zod";

const ConfigSchema = z.object({
DISCORD_BOT_TOKEN: z.string().min(1),
DISCORD_CHANNEL_ID: z.string().min(1),
DISCORD_WEBHOOK: z.string().url().optional(),
});

export const config = ConfigSchema.parse(process.env);
6 changes: 5 additions & 1 deletion packages/automated-dispute/src/exceptions/errorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export class ErrorHandler {
this.logger.error(`Error executing custom action: ${actionError}`);
} finally {
if (strategy.shouldNotify) {
await this.notificationService.notifyError(error, context);
await this.notificationService.sendError(
"An error occurred in the custom contract",
context,
error,
);
}

if (strategy.shouldReenqueue && context.reenqueueEvent) {
Expand Down
2 changes: 2 additions & 0 deletions packages/automated-dispute/src/exceptions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export * from "./decodeLogDataFailure.js";
export * from "./eboActor/index.js";
export * from "./eboProcessor/index.js";
export * from "./eboRegistry/index.js";

export * from "./notificationFailure.exception.js";
export * from "./errorFactory.js";
export * from "./invalidAccountOnClient.exception.js";
export * from "./invalidActorState.exception.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class NotificationFailureException extends Error {
constructor(message: string) {
super(`Failed to send notification: ${message}`);
this.name = "NotificationFailureException";
}
}
74 changes: 68 additions & 6 deletions packages/automated-dispute/src/interfaces/notificationService.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,75 @@
/**
* Interface representing a notification service capable of sending error notifications.
* Represents a notification message.
*/
export interface IMessage {
/**
* The main content of the message.
*/
title: string;
/**
* An optional subtitle for the message.
*/
subtitle?: string;
/**
* An optional description providing more details.
*/
description?: string;
/**
* The username to display as the sender.
*/
username?: string;
/**
* The URL of the avatar image to display.
*/
avatarUrl?: string;
/**
* An optional URL associated with the message.
*/
actionUrl?: string;
}

/**
* Interface representing a notification service capable of sending notifications.
*/
export interface NotificationService {
/**
* Sends an error notification along with optional contextual information.
* Sends a notification message.
*
* @param {IMessage} message - The message to send.
* @returns {Promise<void>} A promise that resolves when the message is sent.
*/
send(message: IMessage): Promise<void>;

/**
* Sends a notification message and throws an exception if sending fails.
*
* @param {IMessage} message - The message to send.
* @returns {Promise<void>} A promise that resolves when the message is sent.
* @throws {NotificationFailureException} If sending the message fails.
*/
sendOrThrow(message: IMessage): Promise<void>;

/**
* Creates an IMessage from an error.
*
* @param {string} defaultMessage - A default message describing the error context.
* @param {unknown} [context] - Additional context for the error.
* @param {unknown} [err] - The error object.
* @returns {IMessage} An IMessage object ready to be sent via the notifier.
*/
createErrorMessage(defaultMessage: string, context?: unknown, err?: unknown): IMessage;

/**
* Sends an error notification message.
*
* @param error - The error object containing information about the error that occurred.
* @param context - Additional context or data related to the error
* @returns A promise that resolves when the notification process is complete.
* @param {string} defaultMessage - A default message describing the error context.
* @param {Record<string, unknown>} [context] - Additional context for the error.
* @param {unknown} [err] - The error object.
* @returns {Promise<void>} A promise that resolves when the message is sent.
*/
notifyError(error: Error, context: any): Promise<void>;
sendError(
defaultMessage: string,
context?: Record<string, unknown>,
err?: unknown,
): Promise<void>;
}
Loading

0 comments on commit 2c02271

Please sign in to comment.