@slvr-labs/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +849 -0
  3. package/dist/connect.d.ts +47 -0
  4. package/dist/connect.d.ts.map +1 -0
  5. package/dist/connect.js +56 -0
  6. package/dist/connect.js.map +1 -0
  7. package/dist/contracts/autoCommit.d.ts +151 -0
  8. package/dist/contracts/autoCommit.d.ts.map +1 -0
  9. package/dist/contracts/autoCommit.js +426 -0
  10. package/dist/contracts/autoCommit.js.map +1 -0
  11. package/dist/contracts/hub.d.ts +50 -0
  12. package/dist/contracts/hub.d.ts.map +1 -0
  13. package/dist/contracts/hub.js +118 -0
  14. package/dist/contracts/hub.js.map +1 -0
  15. package/dist/contracts/index.d.ts +8 -0
  16. package/dist/contracts/index.d.ts.map +1 -0
  17. package/dist/contracts/index.js +18 -0
  18. package/dist/contracts/index.js.map +1 -0
  19. package/dist/contracts/jackpot.d.ts +21 -0
  20. package/dist/contracts/jackpot.d.ts.map +1 -0
  21. package/dist/contracts/jackpot.js +44 -0
  22. package/dist/contracts/jackpot.js.map +1 -0
  23. package/dist/contracts/lottery.d.ts +256 -0
  24. package/dist/contracts/lottery.d.ts.map +1 -0
  25. package/dist/contracts/lottery.js +767 -0
  26. package/dist/contracts/lottery.js.map +1 -0
  27. package/dist/contracts/registry.d.ts +53 -0
  28. package/dist/contracts/registry.d.ts.map +1 -0
  29. package/dist/contracts/registry.js +143 -0
  30. package/dist/contracts/registry.js.map +1 -0
  31. package/dist/contracts/staking.d.ts +101 -0
  32. package/dist/contracts/staking.d.ts.map +1 -0
  33. package/dist/contracts/staking.js +306 -0
  34. package/dist/contracts/staking.js.map +1 -0
  35. package/dist/contracts/token.d.ts +95 -0
  36. package/dist/contracts/token.d.ts.map +1 -0
  37. package/dist/contracts/token.js +273 -0
  38. package/dist/contracts/token.js.map +1 -0
  39. package/dist/deployments.d.ts +117 -0
  40. package/dist/deployments.d.ts.map +1 -0
  41. package/dist/deployments.js +74 -0
  42. package/dist/deployments.js.map +1 -0
  43. package/dist/errors.d.ts +39 -0
  44. package/dist/errors.d.ts.map +1 -0
  45. package/dist/errors.js +67 -0
  46. package/dist/errors.js.map +1 -0
  47. package/dist/ev.d.ts +123 -0
  48. package/dist/ev.d.ts.map +1 -0
  49. package/dist/ev.js +111 -0
  50. package/dist/ev.js.map +1 -0
  51. package/dist/events.d.ts +418 -0
  52. package/dist/events.d.ts.map +1 -0
  53. package/dist/events.js +87 -0
  54. package/dist/events.js.map +1 -0
  55. package/dist/index.d.ts +248 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +436 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/oracle.d.ts +48 -0
  60. package/dist/oracle.d.ts.map +1 -0
  61. package/dist/oracle.js +77 -0
  62. package/dist/oracle.js.map +1 -0
  63. package/dist/price.d.ts +52 -0
  64. package/dist/price.d.ts.map +1 -0
  65. package/dist/price.js +78 -0
  66. package/dist/price.js.map +1 -0
  67. package/dist/transaction.d.ts +54 -0
  68. package/dist/transaction.d.ts.map +1 -0
  69. package/dist/transaction.js +105 -0
  70. package/dist/transaction.js.map +1 -0
  71. package/dist/types.d.ts +162 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +23 -0
  74. package/dist/types.js.map +1 -0
  75. package/dist/utils.d.ts +58 -0
  76. package/dist/utils.d.ts.map +1 -0
  77. package/dist/utils.js +110 -0
  78. package/dist/utils.js.map +1 -0
  79. package/examples/.env.example +22 -0
  80. package/examples/README.md +417 -0
  81. package/examples/automated-betting.ts +232 -0
  82. package/examples/combined-strategy.ts +227 -0
  83. package/examples/constants.ts +35 -0
  84. package/examples/custom-strategy-example.ts +231 -0
  85. package/examples/expected-value-strategy.ts +255 -0
  86. package/examples/fixed-squares-strategy.ts +176 -0
  87. package/examples/index.ts +30 -0
  88. package/examples/least-allocated-strategy.ts +224 -0
  89. package/examples/local-test.ts +172 -0
  90. package/examples/quickstart-bet.ts +72 -0
  91. package/examples/quickstart-read.ts +87 -0
  92. package/examples/simple-example.ts +122 -0
  93. package/examples/strategy-base.ts +283 -0
  94. package/package.json +71 -0
  95. package/skills/slvr-bot/SKILL.md +193 -0
  96. package/skills/slvr-bot/references/api.md +117 -0
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Example: Combined strategy that bets when round has less than 100 ETH
3
+ * and targets squares with least allocation
4
+ *
5
+ * This combines both strategies into a single automated bot
6
+ */
7
+
8
+ import { SlvrSDK } from '../src';
9
+ import { PublicClient, WalletClient, parseEther } from 'viem';
10
+ import { LeastAllocatedStrategy } from './least-allocated-strategy';
11
+
12
+ /**
13
+ * Combined automated betting bot
14
+ * - Monitors rounds for low total wager (< 100 ETH)
15
+ * - Bets on squares with least allocation when conditions are met
16
+ */
17
+ export class CombinedBettingBot {
18
+ private sdk: SlvrSDK;
19
+ private strategy: LeastAllocatedStrategy;
20
+ private threshold: bigint;
21
+ private betAmount: bigint;
22
+ private squareCount: number;
23
+ private checkInterval: number;
24
+ private isRunning: boolean = false;
25
+ private intervalId?: NodeJS.Timeout;
26
+
27
+ constructor(
28
+ sdk: SlvrSDK,
29
+ options: {
30
+ threshold?: bigint; // Default: 100 ETH
31
+ betAmount?: bigint; // Default: 5 ETH total
32
+ squareCount?: number; // Default: 5 squares
33
+ checkInterval?: number; // Default: 5000ms
34
+ } = {}
35
+ ) {
36
+ this.sdk = sdk;
37
+ this.strategy = new LeastAllocatedStrategy(sdk);
38
+ this.threshold = options.threshold ?? parseEther('100');
39
+ this.betAmount = options.betAmount ?? parseEther('5');
40
+ this.squareCount = options.squareCount ?? 5;
41
+ this.checkInterval = options.checkInterval ?? 5000;
42
+ }
43
+
44
+ /**
45
+ * Start monitoring and betting
46
+ */
47
+ async start(): Promise<void> {
48
+ if (this.isRunning) {
49
+ console.log('Bot is already running');
50
+ return;
51
+ }
52
+
53
+ if (!this.sdk.getWalletClient()) {
54
+ throw new Error('Wallet client required for automated betting');
55
+ }
56
+
57
+ this.isRunning = true;
58
+ console.log('🚀 Combined betting bot started');
59
+ console.log(`📊 Strategy: Bet when round < ${SlvrSDK.formatToken(this.threshold)} ETH`);
60
+ console.log(`🎯 Target: ${this.squareCount} least allocated squares`);
61
+ console.log(`💵 Bet amount: ${SlvrSDK.formatToken(this.betAmount)} ETH`);
62
+ console.log(`⏱️ Checking every ${this.checkInterval / 1000} seconds\n`);
63
+
64
+ // Check immediately
65
+ await this.checkAndBet();
66
+
67
+ // Then check at intervals
68
+ this.intervalId = setInterval(async () => {
69
+ await this.checkAndBet();
70
+ }, this.checkInterval);
71
+ }
72
+
73
+ /**
74
+ * Stop the bot
75
+ */
76
+ stop(): void {
77
+ if (!this.isRunning) {
78
+ return;
79
+ }
80
+
81
+ this.isRunning = false;
82
+ if (this.intervalId) {
83
+ clearInterval(this.intervalId);
84
+ this.intervalId = undefined;
85
+ }
86
+ console.log('\n🛑 Combined betting bot stopped');
87
+ }
88
+
89
+ /**
90
+ * Check round and place bet if conditions are met
91
+ */
92
+ private async checkAndBet(): Promise<void> {
93
+ try {
94
+ const roundId = await this.sdk.lottery.currentRoundId();
95
+ const round = await this.sdk.lottery.getRound(roundId);
96
+ const isOpen = await this.sdk.lottery.roundOpen(roundId);
97
+
98
+ // Check if round is open
99
+ if (!isOpen) {
100
+ const timeRemaining = await this.sdk.getTimeRemaining(roundId);
101
+ if (timeRemaining > 0) {
102
+ console.log(`⏸️ Round ${roundId} not open yet (${timeRemaining}s remaining)`);
103
+ } else {
104
+ console.log(`✅ Round ${roundId} has ended`);
105
+ }
106
+ return;
107
+ }
108
+
109
+ // Check if total wager is below threshold
110
+ if (round.totalWager >= this.threshold) {
111
+ const remaining = this.threshold - round.totalWager;
112
+ console.log(
113
+ `💰 Round ${roundId}: ${SlvrSDK.formatToken(round.totalWager)} ETH ` +
114
+ `(need ${SlvrSDK.formatToken(remaining < 0n ? 0n : remaining)} more to reach threshold)`
115
+ );
116
+ return;
117
+ }
118
+
119
+ console.log(`\n🎯 Round ${roundId} conditions met!`);
120
+ console.log(` Total wager: ${SlvrSDK.formatToken(round.totalWager)} ETH`);
121
+ console.log(` Threshold: ${SlvrSDK.formatToken(this.threshold)} ETH`);
122
+
123
+ // Get analysis of least allocated squares
124
+ const analysis = await this.strategy.analyzeRound(roundId);
125
+ console.log(` Total allocation: ${SlvrSDK.formatToken(analysis.totalAllocation)} ETH`);
126
+ console.log(` Least allocated squares: ${analysis.leastAllocated.join(', ')}`);
127
+
128
+ // Place bet on least allocated squares
129
+ const squares = analysis.leastAllocated.slice(0, this.squareCount);
130
+ const amounts = this.strategy.calculateBetAmounts(this.betAmount, squares.length);
131
+
132
+ console.log(`\n📊 Placing bet:`);
133
+ squares.forEach((square, i) => {
134
+ const squareData = analysis.squares.find(sq => sq.square === square);
135
+ console.log(
136
+ ` Square ${square}: ${SlvrSDK.formatToken(amounts[i])} ETH ` +
137
+ `(current: ${SlvrSDK.formatToken(squareData?.total || 0n)} ETH, ` +
138
+ `${squareData?.percentage.toFixed(2)}%)`
139
+ );
140
+ });
141
+ console.log(` Total bet: ${SlvrSDK.formatToken(this.betAmount)} ETH`);
142
+
143
+ const txHash = await this.sdk.lottery.bet({
144
+ roundId,
145
+ squares,
146
+ amounts,
147
+ });
148
+
149
+ console.log(`\n✅ Bet placed successfully!`);
150
+ console.log(` Transaction: ${txHash}\n`);
151
+ } catch (error) {
152
+ console.error('❌ Error in combined betting bot:', error);
153
+ if (error instanceof Error) {
154
+ console.error(' Message:', error.message);
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Example usage
162
+ *
163
+ * Set PRIVATE_KEY environment variable to enable betting
164
+ */
165
+ export async function exampleCombinedStrategy() {
166
+ const { createPublicClient, createWalletClient, http } = await import('viem');
167
+ const { defineChain } = await import('viem');
168
+ const { privateKeyToAccount } = await import('viem/accounts');
169
+ const { SlvrSDK } = await import('../src');
170
+ const { CONTRACTS, ROBINHOOD_CHAIN } = await import('./constants');
171
+
172
+ // Create clients
173
+ const robinhood = defineChain(ROBINHOOD_CHAIN);
174
+ const publicClient = createPublicClient({
175
+ chain: robinhood,
176
+ transport: http(),
177
+ });
178
+
179
+ const privateKey = process.env.PRIVATE_KEY;
180
+ if (!privateKey) {
181
+ throw new Error('PRIVATE_KEY environment variable is required');
182
+ }
183
+
184
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
185
+ const walletClient = createWalletClient({
186
+ chain: robinhood,
187
+ transport: http(),
188
+ account,
189
+ });
190
+
191
+ // Initialize SDK
192
+ const sdk = new SlvrSDK({
193
+ publicClient,
194
+ walletClient,
195
+ addresses: {
196
+ lottery: CONTRACTS.LOTTERY,
197
+ staking: CONTRACTS.STAKING,
198
+ token: CONTRACTS.TOKEN,
199
+ autoCommit: CONTRACTS.AUTO_COMMIT !== '0x...' ? CONTRACTS.AUTO_COMMIT : undefined,
200
+ },
201
+ });
202
+
203
+ // Create bot
204
+ const bot = new CombinedBettingBot(sdk, {
205
+ threshold: parseEther('100'), // Bet when round has less than 100 ETH
206
+ betAmount: parseEther('5'), // Bet 5 ETH total
207
+ squareCount: 5, // Bet on 5 least allocated squares
208
+ checkInterval: 5000, // Check every 5 seconds
209
+ });
210
+
211
+ // Start the bot
212
+ await bot.start();
213
+
214
+ // Handle graceful shutdown
215
+ process.on('SIGINT', () => {
216
+ console.log('\nReceived SIGINT, stopping bot...');
217
+ bot.stop();
218
+ process.exit(0);
219
+ });
220
+
221
+ process.on('SIGTERM', () => {
222
+ console.log('\nReceived SIGTERM, stopping bot...');
223
+ bot.stop();
224
+ process.exit(0);
225
+ });
226
+ }
227
+
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Contract addresses and chain config used by the examples.
3
+ *
4
+ * These default to the canonical Robinhood Chain mainnet deployment that ships
5
+ * with the SDK (`deployments.robinhood`). Point them at your own deployment by
6
+ * editing the values below, or import `deployments` / `robinhoodChain` directly
7
+ * from `@slvr-labs/sdk` in your own code.
8
+ */
9
+
10
+ import { Address } from 'viem';
11
+ import { deployments, robinhoodChain } from '../src';
12
+
13
+ const { addresses } = deployments.robinhood;
14
+
15
+ /**
16
+ * Contract addresses on Robinhood Chain.
17
+ */
18
+ export const CONTRACTS = {
19
+ /** SlvrGridLottery contract address */
20
+ LOTTERY: addresses.lottery as Address,
21
+
22
+ /** SlvrVoteEscrowStaking (veNFT staker) contract address */
23
+ STAKING: addresses.staking as Address,
24
+
25
+ /** SlvrToken contract address */
26
+ TOKEN: addresses.token as Address,
27
+
28
+ /** SlvrAutoCommitV2 contract address (optional) */
29
+ AUTO_COMMIT: (addresses.autoCommit ?? '0x...') as Address,
30
+ } as const;
31
+
32
+ /**
33
+ * Robinhood Chain configuration (a ready-made viem chain).
34
+ */
35
+ export const ROBINHOOD_CHAIN = robinhoodChain;
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Example: How to create custom strategies
3
+ *
4
+ * This file shows how easy it is to create custom betting strategies
5
+ * by extending the BettingStrategy base class.
6
+ */
7
+
8
+ import { BettingStrategy } from './strategy-base';
9
+ import { parseEther } from 'viem';
10
+ import { SlvrSDK } from '../src';
11
+
12
+ /**
13
+ * Example 1: Bet on squares with least allocation
14
+ */
15
+ export class LeastAllocatedCustomStrategy extends BettingStrategy {
16
+ private squareCount: number;
17
+ private totalBetAmount: bigint;
18
+
19
+ constructor(sdk: SlvrSDK, config: { squareCount?: number; totalBetAmount?: bigint; threshold?: bigint }) {
20
+ super(sdk, { threshold: config.threshold });
21
+ this.squareCount = config.squareCount ?? 5;
22
+ this.totalBetAmount = config.totalBetAmount ?? parseEther('5');
23
+ }
24
+
25
+ protected async selectSquares(roundId: bigint): Promise<number[]> {
26
+ // Get all squares with their allocations
27
+ const squares = await this.sdk.lottery.getRoundSquares(roundId);
28
+
29
+ // Sort by total (ascending) - least allocated first
30
+ const sorted = squares
31
+ .map(({ square, total }) => ({ square, total }))
32
+ .sort((a, b) => {
33
+ if (a.total < b.total) return -1;
34
+ if (a.total > b.total) return 1;
35
+ return 0;
36
+ });
37
+
38
+ // Return the N least allocated squares
39
+ return sorted.slice(0, this.squareCount).map(({ square }) => square);
40
+ }
41
+
42
+ protected async calculateAmounts(roundId: bigint, round: any, squares: number[]): Promise<bigint[]> {
43
+ // Distribute total bet amount evenly
44
+ const amountPerSquare = this.totalBetAmount / BigInt(squares.length);
45
+ const remainder = this.totalBetAmount % BigInt(squares.length);
46
+
47
+ const amounts = squares.map(() => amountPerSquare);
48
+ if (remainder > 0n) {
49
+ amounts[0] += remainder; // Add remainder to first square
50
+ }
51
+
52
+ return amounts;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Example 2: Bet on random squares
58
+ */
59
+ export class RandomSquaresStrategy extends BettingStrategy {
60
+ private squareCount: number;
61
+ private amountPerSquare: bigint;
62
+
63
+ constructor(sdk: SlvrSDK, config: { squareCount?: number; amountPerSquare?: bigint; threshold?: bigint }) {
64
+ super(sdk, { threshold: config.threshold });
65
+ this.squareCount = config.squareCount ?? 5;
66
+ this.amountPerSquare = config.amountPerSquare ?? parseEther('1');
67
+ }
68
+
69
+ protected async selectSquares(): Promise<number[]> {
70
+ // Generate random squares
71
+ const allSquares = Array.from({ length: 25 }, (_, i) => i);
72
+ const shuffled = [...allSquares].sort(() => Math.random() - 0.5);
73
+ return shuffled.slice(0, this.squareCount);
74
+ }
75
+
76
+ protected async calculateAmounts(roundId: bigint, round: any, squares: number[]): Promise<bigint[]> {
77
+ return squares.map(() => this.amountPerSquare);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Example 3: Bet on squares with most bettors (social proof strategy)
83
+ */
84
+ export class MostBettorsStrategy extends BettingStrategy {
85
+ private squareCount: number;
86
+ private amountPerSquare: bigint;
87
+
88
+ constructor(sdk: SlvrSDK, config: { squareCount?: number; amountPerSquare?: bigint; threshold?: bigint }) {
89
+ super(sdk, { threshold: config.threshold });
90
+ this.squareCount = config.squareCount ?? 5;
91
+ this.amountPerSquare = config.amountPerSquare ?? parseEther('1');
92
+ }
93
+
94
+ protected async selectSquares(roundId: bigint): Promise<number[]> {
95
+ const squares = await this.sdk.lottery.getRoundSquares(roundId);
96
+
97
+ // Sort by number of bettors (descending)
98
+ const sorted = squares
99
+ .map(({ square, bettors }) => ({ square, bettors }))
100
+ .sort((a, b) => {
101
+ if (a.bettors > b.bettors) return -1;
102
+ if (a.bettors < b.bettors) return 1;
103
+ return 0;
104
+ });
105
+
106
+ return sorted.slice(0, this.squareCount).map(({ square }) => square);
107
+ }
108
+
109
+ protected async calculateAmounts(roundId: bigint, round: any, squares: number[]): Promise<bigint[]> {
110
+ return squares.map(() => this.amountPerSquare);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Example 4: Weighted strategy - bet more on squares with less allocation
116
+ */
117
+ export class WeightedAllocationStrategy extends BettingStrategy {
118
+ private squareCount: number;
119
+ private totalBetAmount: bigint;
120
+
121
+ constructor(sdk: SlvrSDK, config: { squareCount?: number; totalBetAmount?: bigint; threshold?: bigint }) {
122
+ super(sdk, { threshold: config.threshold });
123
+ this.squareCount = config.squareCount ?? 5;
124
+ this.totalBetAmount = config.totalBetAmount ?? parseEther('10');
125
+ }
126
+
127
+ protected async selectSquares(roundId: bigint): Promise<number[]> {
128
+ const squares = await this.sdk.lottery.getRoundSquares(roundId);
129
+ const sorted = squares
130
+ .map(({ square, total }) => ({ square, total }))
131
+ .sort((a, b) => {
132
+ if (a.total < b.total) return -1;
133
+ if (a.total > b.total) return 1;
134
+ return 0;
135
+ });
136
+
137
+ return sorted.slice(0, this.squareCount).map(({ square }) => square);
138
+ }
139
+
140
+ protected async calculateAmounts(roundId: bigint, round: any, squares: number[]): Promise<bigint[]> {
141
+ // Get allocations for selected squares
142
+ const squaresData = await this.sdk.lottery.getRoundSquares(roundId);
143
+ const selectedData = squaresData.filter(sq => squares.includes(sq.square));
144
+
145
+ // Calculate total allocation for selected squares
146
+ const totalAllocation = selectedData.reduce((sum, sq) => sum + sq.total, 0n);
147
+
148
+ if (totalAllocation === 0n) {
149
+ // If no allocation, distribute evenly
150
+ const amountPerSquare = this.totalBetAmount / BigInt(squares.length);
151
+ const remainder = this.totalBetAmount % BigInt(squares.length);
152
+ const amounts = squares.map(() => amountPerSquare);
153
+ if (remainder > 0n) amounts[0] += remainder;
154
+ return amounts;
155
+ }
156
+
157
+ // Weight inversely by allocation (less allocation = more bet)
158
+ // Calculate weights: weight = 1 / (allocation + 1) to avoid division by zero
159
+ const weights = selectedData.map(sq => {
160
+ const allocation = sq.total;
161
+ // Use inverse weight: lower allocation gets higher weight
162
+ // Add 1 to avoid division issues
163
+ return Number(1000000n / (allocation + 1n)); // Scale up for precision
164
+ });
165
+
166
+ const totalWeight = weights.reduce((sum, w) => sum + w, 0);
167
+
168
+ // Distribute bet amount proportionally to weights
169
+ const amounts = weights.map(weight => {
170
+ return (this.totalBetAmount * BigInt(Math.round(weight))) / BigInt(totalWeight);
171
+ });
172
+
173
+ // Adjust for rounding
174
+ const total = amounts.reduce((sum, amt) => sum + amt, 0n);
175
+ const diff = this.totalBetAmount - total;
176
+ if (diff !== 0n) {
177
+ amounts[0] += diff;
178
+ }
179
+
180
+ return amounts;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Example 5: Conditional strategy - only bet on certain conditions
186
+ */
187
+ export class ConditionalStrategy extends BettingStrategy {
188
+ private squares: number[];
189
+ private amountPerSquare: bigint;
190
+ private minBettors: number; // Only bet if squares have at least this many bettors
191
+
192
+ constructor(
193
+ sdk: SlvrSDK,
194
+ config: {
195
+ squares: number[];
196
+ amountPerSquare: bigint;
197
+ minBettors?: number;
198
+ threshold?: bigint;
199
+ }
200
+ ) {
201
+ super(sdk, { threshold: config.threshold });
202
+ this.squares = config.squares;
203
+ this.amountPerSquare = config.amountPerSquare;
204
+ this.minBettors = config.minBettors ?? 0;
205
+ }
206
+
207
+ protected async shouldBet(roundId: bigint, round: any): Promise<{ shouldBet: boolean; reason?: string }> {
208
+ // Check if squares have enough bettors
209
+ const squaresData = await this.sdk.lottery.getRoundSquares(roundId);
210
+ const selectedData = squaresData.filter(sq => this.squares.includes(sq.square));
211
+
212
+ const minBettors = Math.min(...selectedData.map(sq => Number(sq.bettors)));
213
+ if (minBettors < this.minBettors) {
214
+ return {
215
+ shouldBet: false,
216
+ reason: `Squares don't have enough bettors (min: ${this.minBettors}, found: ${minBettors})`,
217
+ };
218
+ }
219
+
220
+ return { shouldBet: true };
221
+ }
222
+
223
+ protected async selectSquares(): Promise<number[]> {
224
+ return this.squares;
225
+ }
226
+
227
+ protected async calculateAmounts(roundId: bigint, round: any, squares: number[]): Promise<bigint[]> {
228
+ return squares.map(() => this.amountPerSquare);
229
+ }
230
+ }
231
+