From f5ea27eca556f8662bdb24f41db7d1a5ec9763ec Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Thu, 28 Sep 2023 19:51:01 -0700 Subject: [PATCH] Added proposal execution and closure inbox notification webhooks. --- src/data/webhooks/index.ts | 8 +- src/data/webhooks/notify/proposal.ts | 142 +++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/src/data/webhooks/index.ts b/src/data/webhooks/index.ts index fd982200..1ef6b68b 100644 --- a/src/data/webhooks/index.ts +++ b/src/data/webhooks/index.ts @@ -4,7 +4,11 @@ import { State } from '@/db' import { makeProposalCreated } from './discordNotifier' import { makeIndexerCwReceiptPaid } from './indexerCwReceipt' import { makeInboxJoinedDao } from './notify/dao' -import { makeInboxProposalCreated } from './notify/proposal' +import { + makeInboxProposalClosed, + makeInboxProposalCreated, + makeInboxProposalExecuted, +} from './notify/proposal' import { makeBroadcastVoteCast, makeProposalStatusChanged } from './websockets' let processedWebhooks: ProcessedWebhook[] | undefined @@ -18,6 +22,8 @@ export const getProcessedWebhooks = ( makeProposalCreated, makeInboxJoinedDao, makeInboxProposalCreated, + makeInboxProposalExecuted, + makeInboxProposalClosed, makeIndexerCwReceiptPaid, makeBroadcastVoteCast, makeProposalStatusChanged, diff --git a/src/data/webhooks/notify/proposal.ts b/src/data/webhooks/notify/proposal.ts index c43864a9..ab909d1f 100644 --- a/src/data/webhooks/notify/proposal.ts +++ b/src/data/webhooks/notify/proposal.ts @@ -81,3 +81,145 @@ export const makeInboxProposalCreated: WebhookMaker = (config, state) => ({ } }, }) + +// Fire webhook when a proposal is executed. +export const makeInboxProposalExecuted: WebhookMaker = (config, state) => ({ + filter: { + codeIdsKeys: CODE_IDS_KEYS, + matches: (event) => + // Starts with proposals or proposals_v2. + (event.key.startsWith(KEY_PREFIX_PROPOSALS) || + event.key.startsWith(KEY_PREFIX_PROPOSALS_V2)) && + (event.valueJson.status === Status.Executed || + event.valueJson.status === Status.ExecutionFailed), + }, + endpoint: { + type: WebhookType.Url, + url: 'https://notifier.daodao.zone/notify', + method: 'POST', + headers: { + 'x-api-key': config.notifierSecret, + }, + }, + getValue: async (event, getLastValue, env) => { + // Only fire the webhook if the last event was not executed. + const lastValue = await getLastValue() + if ( + lastValue && + (lastValue.status === Status.Executed || + lastValue.status === Status.ExecutionFailed) + ) { + return + } + + // Get DAO config and proposal modules for this DAO so we can retrieve the + // DAO's name and the prefix for this proposal module. + const daoAddress = await getDaoAddressForProposalModule(env) + if (!daoAddress) { + return + } + + const daoConfig = await daoCoreConfig.compute({ + ...env, + contractAddress: daoAddress, + }) + const proposalModules = await activeProposalModules.compute({ + ...env, + contractAddress: daoAddress, + }) + const proposalModule = proposalModules?.find( + (proposalModule) => proposalModule.address === event.contractAddress + ) + + if (!daoConfig || !proposalModule) { + return + } + + // "proposals"|"proposals_v2", proposalNum + const [, proposalNum] = dbKeyToKeys(event.key, [false, true]) + const proposalId = `${proposalModule.prefix}${proposalNum}` + const proposal: SingleChoiceProposal | MultipleChoiceProposal = + event.valueJson + + return { + type: 'proposal_executed', + data: { + chainId: state.chainId, + dao: daoAddress, + daoName: daoConfig.name, + imageUrl: daoConfig.image_url ?? undefined, + proposalId, + proposalTitle: proposal.title, + failed: event.valueJson.status === Status.ExecutionFailed, + }, + } + }, +}) + +// Fire webhook when a proposal is closed. +export const makeInboxProposalClosed: WebhookMaker = (config, state) => ({ + filter: { + codeIdsKeys: CODE_IDS_KEYS, + matches: (event) => + // Starts with proposals or proposals_v2. + (event.key.startsWith(KEY_PREFIX_PROPOSALS) || + event.key.startsWith(KEY_PREFIX_PROPOSALS_V2)) && + event.valueJson.status === Status.Closed, + }, + endpoint: { + type: WebhookType.Url, + url: 'https://notifier.daodao.zone/notify', + method: 'POST', + headers: { + 'x-api-key': config.notifierSecret, + }, + }, + getValue: async (event, getLastValue, env) => { + // Only fire the webhook if the last event was not closed. + const lastValue = await getLastValue() + if (lastValue && lastValue.status === Status.Closed) { + return + } + + // Get DAO config and proposal modules for this DAO so we can retrieve the + // DAO's name and the prefix for this proposal module. + const daoAddress = await getDaoAddressForProposalModule(env) + if (!daoAddress) { + return + } + + const daoConfig = await daoCoreConfig.compute({ + ...env, + contractAddress: daoAddress, + }) + const proposalModules = await activeProposalModules.compute({ + ...env, + contractAddress: daoAddress, + }) + const proposalModule = proposalModules?.find( + (proposalModule) => proposalModule.address === event.contractAddress + ) + + if (!daoConfig || !proposalModule) { + return + } + + // "proposals"|"proposals_v2", proposalNum + const [, proposalNum] = dbKeyToKeys(event.key, [false, true]) + const proposalId = `${proposalModule.prefix}${proposalNum}` + const proposal: SingleChoiceProposal | MultipleChoiceProposal = + event.valueJson + + return { + type: 'proposal_closed', + data: { + chainId: state.chainId, + dao: daoAddress, + daoName: daoConfig.name, + imageUrl: daoConfig.image_url ?? undefined, + proposalId, + proposalTitle: proposal.title, + }, + } + }, +})