@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.
- package/LICENSE +21 -0
- package/README.md +849 -0
- package/dist/connect.d.ts +47 -0
- package/dist/connect.d.ts.map +1 -0
- package/dist/connect.js +56 -0
- package/dist/connect.js.map +1 -0
- package/dist/contracts/autoCommit.d.ts +151 -0
- package/dist/contracts/autoCommit.d.ts.map +1 -0
- package/dist/contracts/autoCommit.js +426 -0
- package/dist/contracts/autoCommit.js.map +1 -0
- package/dist/contracts/hub.d.ts +50 -0
- package/dist/contracts/hub.d.ts.map +1 -0
- package/dist/contracts/hub.js +118 -0
- package/dist/contracts/hub.js.map +1 -0
- package/dist/contracts/index.d.ts +8 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +18 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/jackpot.d.ts +21 -0
- package/dist/contracts/jackpot.d.ts.map +1 -0
- package/dist/contracts/jackpot.js +44 -0
- package/dist/contracts/jackpot.js.map +1 -0
- package/dist/contracts/lottery.d.ts +256 -0
- package/dist/contracts/lottery.d.ts.map +1 -0
- package/dist/contracts/lottery.js +767 -0
- package/dist/contracts/lottery.js.map +1 -0
- package/dist/contracts/registry.d.ts +53 -0
- package/dist/contracts/registry.d.ts.map +1 -0
- package/dist/contracts/registry.js +143 -0
- package/dist/contracts/registry.js.map +1 -0
- package/dist/contracts/staking.d.ts +101 -0
- package/dist/contracts/staking.d.ts.map +1 -0
- package/dist/contracts/staking.js +306 -0
- package/dist/contracts/staking.js.map +1 -0
- package/dist/contracts/token.d.ts +95 -0
- package/dist/contracts/token.d.ts.map +1 -0
- package/dist/contracts/token.js +273 -0
- package/dist/contracts/token.js.map +1 -0
- package/dist/deployments.d.ts +117 -0
- package/dist/deployments.d.ts.map +1 -0
- package/dist/deployments.js +74 -0
- package/dist/deployments.js.map +1 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +67 -0
- package/dist/errors.js.map +1 -0
- package/dist/ev.d.ts +123 -0
- package/dist/ev.d.ts.map +1 -0
- package/dist/ev.js +111 -0
- package/dist/ev.js.map +1 -0
- package/dist/events.d.ts +418 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +87 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +248 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +436 -0
- package/dist/index.js.map +1 -0
- package/dist/oracle.d.ts +48 -0
- package/dist/oracle.d.ts.map +1 -0
- package/dist/oracle.js +77 -0
- package/dist/oracle.js.map +1 -0
- package/dist/price.d.ts +52 -0
- package/dist/price.d.ts.map +1 -0
- package/dist/price.js +78 -0
- package/dist/price.js.map +1 -0
- package/dist/transaction.d.ts +54 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +105 -0
- package/dist/transaction.js.map +1 -0
- package/dist/types.d.ts +162 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +58 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +110 -0
- package/dist/utils.js.map +1 -0
- package/examples/.env.example +22 -0
- package/examples/README.md +417 -0
- package/examples/automated-betting.ts +232 -0
- package/examples/combined-strategy.ts +227 -0
- package/examples/constants.ts +35 -0
- package/examples/custom-strategy-example.ts +231 -0
- package/examples/expected-value-strategy.ts +255 -0
- package/examples/fixed-squares-strategy.ts +176 -0
- package/examples/index.ts +30 -0
- package/examples/least-allocated-strategy.ts +224 -0
- package/examples/local-test.ts +172 -0
- package/examples/quickstart-bet.ts +72 -0
- package/examples/quickstart-read.ts +87 -0
- package/examples/simple-example.ts +122 -0
- package/examples/strategy-base.ts +283 -0
- package/package.json +71 -0
- package/skills/slvr-bot/SKILL.md +193 -0
- 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
|
+
|