diff --git a/libs/model/src/bot/CreateBotContest.command.ts b/libs/model/src/bot/CreateBotContest.command.ts index 31876375faa..c7eae3eb872 100644 --- a/libs/model/src/bot/CreateBotContest.command.ts +++ b/libs/model/src/bot/CreateBotContest.command.ts @@ -131,6 +131,7 @@ export function CreateBotContest(): Command { ticker: tokenMetadata.ticker, decimals: tokenMetadata.decimals, contest_address: contestAddress, + farcaster_author_cast_hash: payload.castHash, }, { transaction }, ); diff --git a/libs/model/src/contest/Contests.projection.ts b/libs/model/src/contest/Contests.projection.ts index b8104e95593..ff8b6250c15 100644 --- a/libs/model/src/contest/Contests.projection.ts +++ b/libs/model/src/contest/Contests.projection.ts @@ -9,11 +9,16 @@ import { } from '@hicommonwealth/evm-protocols'; import { config } from '@hicommonwealth/model'; import { events } from '@hicommonwealth/schemas'; -import { buildContestLeaderboardUrl, getBaseUrl } from '@hicommonwealth/shared'; +import { + buildContestLeaderboardUrl, + buildFarcasterContestFrameUrl, + getBaseUrl, +} from '@hicommonwealth/shared'; import { QueryTypes } from 'sequelize'; import { models } from '../database'; import { mustExist } from '../middleware/guards'; import { EvmEventSourceAttributes } from '../models'; +import { DEFAULT_CONTEST_BOT_PARAMS } from '../services/openai/parseBotCommand'; import { getWeightedNumTokens } from '../services/stakeHelper'; import { decodeThreadContentUrl, @@ -258,6 +263,31 @@ export function Contests(): Projection { true, payload.block_number, ); + + // if bot-created farcaster contest, notify author + const contestManager = await models.ContestManager.findOne({ + where: { + contest_address: payload.contest_address, + }, + }); + mustExist('Contest Manager', contestManager); + + if (contestManager.farcaster_author_cast_hash) { + await publishCast( + contestManager.farcaster_author_cast_hash, + ({ username }) => { + const { + payoutStructure: [winner1, winner2, winner3], + voterShare, + } = DEFAULT_CONTEST_BOT_PARAMS; + return `Hey @${username}, your contest has been created. The prize distribution is ${winner1}% to winner, ${winner2}% to second place, ${winner3}% to third , and ${voterShare}% going to voters. The contest will run for 7 days. Anyone who replies to a cast containing the frame enters the contest.`; + }, + { + // eslint-disable-next-line max-len + embed: `${getBaseUrl(config.APP_ENV, config.CONTESTS.FARCASTER_NGROK_DOMAIN!)}${buildFarcasterContestFrameUrl(payload.contest_address)}`, + }, + ); + } }, // This happens for each recurring contest _after_ the initial contest diff --git a/libs/model/src/models/contest_manager.ts b/libs/model/src/models/contest_manager.ts index edf3e70adf8..00d138a9ede 100644 --- a/libs/model/src/models/contest_manager.ts +++ b/libs/model/src/models/contest_manager.ts @@ -61,6 +61,7 @@ export default ( topic_id: { type: Sequelize.INTEGER, allowNull: true }, is_farcaster_contest: { type: Sequelize.BOOLEAN, allowNull: false }, vote_weight_multiplier: { type: Sequelize.FLOAT, allowNull: true }, + farcaster_author_cast_hash: { type: Sequelize.STRING, allowNull: true }, }, { tableName: 'ContestManagers', diff --git a/libs/model/src/policies/FarcasterWorker.policy.ts b/libs/model/src/policies/FarcasterWorker.policy.ts index 1e64f421d6a..a1ce9e81720 100644 --- a/libs/model/src/policies/FarcasterWorker.policy.ts +++ b/libs/model/src/policies/FarcasterWorker.policy.ts @@ -1,21 +1,12 @@ import { command, logger, Policy } from '@hicommonwealth/core'; import { events } from '@hicommonwealth/schemas'; -import { - buildFarcasterContestFrameUrl, - getBaseUrl, -} from '@hicommonwealth/shared'; import { NeynarAPIClient } from '@neynar/nodejs-sdk'; import { Op } from 'sequelize'; import { config, models } from '..'; import { CreateBotContest } from '../bot/CreateBotContest.command'; import { systemActor } from '../middleware'; import { mustExist } from '../middleware/guards'; -import { DEFAULT_CONTEST_BOT_PARAMS } from '../services/openai/parseBotCommand'; -import { - buildFarcasterContentUrl, - buildFarcasterWebhookName, - publishCast, -} from '../utils'; +import { buildFarcasterContentUrl, buildFarcasterWebhookName } from '../utils'; import { createOnchainContestContent, createOnchainContestVote, @@ -210,29 +201,13 @@ export function FarcasterWorker(): Policy { }); }, FarcasterContestBotMentioned: async ({ payload }) => { - const contestAddress = await command(CreateBotContest(), { + await command(CreateBotContest(), { actor: systemActor({}), payload: { castHash: payload.hash!, prompt: payload.text, }, }); - if (contestAddress) { - await publishCast( - payload.hash, - ({ username }) => { - const { - payoutStructure: [winner1, winner2, winner3], - voterShare, - } = DEFAULT_CONTEST_BOT_PARAMS; - return `Hey @${username}, your contest has been created. The prize distribution is ${winner1}% to winner, ${winner2}% to second place, ${winner3}% to third , and ${voterShare}% going to voters. The contest will run for 7 days. Anyone who replies to a cast containing the frame enters the contest.`; - }, - { - // eslint-disable-next-line max-len - embed: `${getBaseUrl(config.APP_ENV, config.CONTESTS.FARCASTER_NGROK_DOMAIN!)}${buildFarcasterContestFrameUrl(contestAddress)}`, - }, - ); - } }, }, }; diff --git a/libs/schemas/src/entities/contest-manager.schemas.ts b/libs/schemas/src/entities/contest-manager.schemas.ts index 6c20eec9195..788cfe261e9 100644 --- a/libs/schemas/src/entities/contest-manager.schemas.ts +++ b/libs/schemas/src/entities/contest-manager.schemas.ts @@ -67,5 +67,11 @@ export const ContestManager = z .gt(0) .nullish() .describe('Vote weight multiplier'), + farcaster_author_cast_hash: z + .string() + .optional() + .describe( + "For bot-created contests, the hash of the farcaster author's cast that created the contest", + ), }) .describe('On-Chain Contest Manager'); diff --git a/packages/commonwealth/server/migrations/20250214193451-bot-contest-farcaster-author-cast-hash.js b/packages/commonwealth/server/migrations/20250214193451-bot-contest-farcaster-author-cast-hash.js new file mode 100644 index 00000000000..ba7b0c13b04 --- /dev/null +++ b/packages/commonwealth/server/migrations/20250214193451-bot-contest-farcaster-author-cast-hash.js @@ -0,0 +1,22 @@ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.addColumn( + 'ContestManagers', + 'farcaster_author_cast_hash', + { + type: Sequelize.STRING, + allowNull: true, + }, + ); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.removeColumn( + 'ContestManagers', + 'farcaster_author_cast_hash', + ); + }, +};