• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hicommonwealth / commonwealth / 12796058250

15 Jan 2025 08:00PM UTC coverage: 48.038% (-0.1%) from 48.168%
12796058250

Pull #10529

github

web-flow
Merge e87fef53f into ae253ad15
Pull Request #10529: Add contest bot webhook

1314 of 3052 branches covered (43.05%)

Branch coverage included in aggregate %.

6 of 36 new or added lines in 6 files covered. (16.67%)

1 existing line in 1 file now uncovered.

2592 of 5079 relevant lines covered (51.03%)

34.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.5
/libs/model/src/bot/CreateBotContest.command.ts
1
import {
2
  InvalidState,
3
  logger,
4
  ServerError,
5
  type Command,
6
} from '@hicommonwealth/core';
7
import {
8
  commonProtocol as cp,
9
  deployERC20Contest,
10
  getTokenAttributes,
11
} from '@hicommonwealth/evm-protocols';
12
import { config, publishCast } from '@hicommonwealth/model';
13
import * as schemas from '@hicommonwealth/schemas';
14
import { buildFarcasterContestFrameUrl } from '@hicommonwealth/shared';
15
import { models } from '../database';
16
import { mustExist } from '../middleware/guards';
17
import { TokenAttributes } from '../services';
18
import {
19
  ContestMetadataResponse,
20
  parseBotCommand,
21
} from '../services/openai/parseBotCommand';
22

23
const log = logger(import.meta);
24✔
24

25
export function CreateBotContest(): Command<typeof schemas.CreateBotContest> {
26
  return {
×
27
    ...schemas.CreateBotContest,
28
    auth: [],
29
    body: async ({ payload }) => {
NEW
30
      const { prompt } = payload;
×
NEW
31
      let contestMetadata: ContestMetadataResponse | null = null;
×
NEW
32
      try {
×
NEW
33
        contestMetadata = await parseBotCommand(prompt);
×
34
      } catch (err) {
NEW
35
        log.warn(`failed to parse bot command: ${(err as Error).message}`);
×
NEW
36
        if (payload.castHash) {
×
NEW
37
          await publishCast(
×
38
            payload.castHash,
39
            ({ username }) =>
NEW
40
              `Hey @${username}, something went wrong. Please check your prompt and try again.`,
×
41
          );
42
        }
NEW
43
        return;
×
44
      }
NEW
45
      mustExist('Parsed Contest Metadata', contestMetadata);
×
46

47
      const botNamespace = config.BOT.CONTEST_BOT_NAMESPACE;
×
48

49
      if (!botNamespace || botNamespace === '') {
×
50
        new InvalidState('bot not enabled on given chain');
×
51
      }
52

NEW
53
      const community = await models.Community.findOne({
×
54
        where: {
55
          namespace: botNamespace as string,
56
        },
57
        include: [
58
          {
59
            model: models.ChainNode.scope('withPrivateData'),
60
            required: true,
61
          },
62
        ],
63
      });
64

65
      mustExist('Community', community);
×
66

67
      let tokenMetadata: TokenAttributes;
68
      try {
×
69
        tokenMetadata = await getTokenAttributes(
×
70
          contestMetadata.tokenAddress,
71
          community!.ChainNode!.private_url!,
72
          false,
73
        );
74
      } catch {
75
        new InvalidState('invalid token address');
×
76
      }
77

78
      // use short duration for testnet contests
79
      const contestDurationSecs =
NEW
80
        community.ChainNode!.eth_chain_id === cp.ValidChains.SepoliaBase
×
81
          ? 60 * 60
82
          : 60 * 60 * 24 * 7;
83

84
      const namespaceFactory =
NEW
85
        cp.factoryContracts[
×
86
          community!.ChainNode!.eth_chain_id as cp.ValidChains
87
        ].factory;
88

89
      if (!config.WEB3.CONTEST_BOT_PRIVATE_KEY)
×
90
        throw new ServerError('Contest bot private key not set!');
×
91

92
      const contestAddress = await deployERC20Contest({
×
93
        privateKey: config.WEB3.CONTEST_BOT_PRIVATE_KEY,
94
        namespaceName: botNamespace!,
95
        contestInterval: contestDurationSecs,
96
        winnerShares: contestMetadata.payoutStructure,
97
        voteToken: contestMetadata.tokenAddress,
98
        voterShare: contestMetadata.voterShare,
99
        exchangeToken: contestMetadata.tokenAddress,
100
        namespaceFactory,
101
        rpc: community!.ChainNode!.private_url!,
102
      });
NEW
103
      mustExist('Deployed Contest', contestAddress);
×
104

105
      await models.sequelize.transaction(async (transaction) => {
×
106
        const manager = await models.ContestManager.create(
×
107
          {
108
            name: contestMetadata.contestName,
109
            community_id: community!.id,
110
            created_at: new Date(),
111
            cancelled: false,
112
            farcaster_frame_url: buildFarcasterContestFrameUrl(contestAddress),
113
            is_farcaster_contest: true,
114
            image_url: contestMetadata.image_url,
115
            interval: contestDurationSecs,
116
            funding_token_address: contestMetadata.tokenAddress,
117
            payout_structure: contestMetadata.payoutStructure,
118
            ticker: tokenMetadata.ticker,
119
            decimals: tokenMetadata.decimals,
120
            contest_address: contestAddress,
121
          },
122
          { transaction },
123
        );
124
        return manager;
×
125
      });
126
      return contestAddress;
×
127
    },
128
  };
129
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc