@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,255 @@
1
+ /**
2
+ * Example: Expected-value grid-mining bot
3
+ *
4
+ * This bot only bets when the round is **+EV** — i.e. the expected value of
5
+ * mining SLVR (plus the jackpot term) exceeds the ETH you bleed to the protocol
6
+ * fee. It uses the SDK's built-in grid-mining calculator (`computeGridMiningEv` /
7
+ * `sdk.estimateRoundEv`) and the SDK's SLVR price reader (`sdk.price`), so the
8
+ * math matches the protocol's own edge calculator.
9
+ *
10
+ * How the decision works (see the docs on `computeGridMiningEv` for the full
11
+ * model):
12
+ * - SLVR mined/round ≈ (stake / pot) * emissionPerRound
13
+ * - ETH bleed/round = feeFraction * stake (you get the rest of the pot back pro-rata)
14
+ * - net EV/round = slvrValue - bleed + jackpotEV
15
+ * Because both terms scale with your stake, the edge depends on **pot vs SLVR
16
+ * price**, not on bet size: mining is profitable while the pot is *below* the
17
+ * break-even pot. This bot bets when net EV per round clears a threshold.
18
+ *
19
+ * When it bets, it spreads the stake across all 25 squares in proportion to the
20
+ * current pot — the coverage the EV model assumes, so you hold a `stake/pot`
21
+ * slice of whichever square wins. (Tilting toward the least-allocated squares —
22
+ * see `least-allocated-strategy.ts` — would raise SLVR yield *above* this
23
+ * estimate, so treating this EV as a floor is conservative.)
24
+ *
25
+ * Run: set PRIVATE_KEY and `ts-node expected-value-strategy.ts` (or import the
26
+ * class). To adapt into your own project, change the `../src` import to
27
+ * `@slvr-labs/sdk`.
28
+ */
29
+
30
+ import { createPublicClient, createWalletClient, http, parseEther, formatEther } from 'viem';
31
+ import { privateKeyToAccount } from 'viem/accounts';
32
+ import { SlvrSDK, robinhoodChain, deployments, GRID_SIZE, type GridMiningEv } from '../src';
33
+
34
+ export interface ExpectedValueBotOptions {
35
+ /** ETH to commit per round. Default: 0.1 ETH. */
36
+ stakeEth?: number;
37
+ /** Minimum net EV (in ETH per round) required to bet. Default: 0 (bet on any +EV round). */
38
+ minNetEth?: number;
39
+ /** Value SLVR net of the 10% refining fee (cashing out) vs at full price (holding). Default: false (hold). */
40
+ cashOut?: boolean;
41
+ /** ETH currently in the jackpot pool, to include the jackpot term. Default: 0. */
42
+ jackpotPool?: number;
43
+ /** Jackpot odds (1-in-N). Default: the protocol default (625). */
44
+ jackpotOdds?: number;
45
+ /**
46
+ * ETH/USD price override, for logging values in USD. If omitted, the bot uses
47
+ * the SDK's Chainlink feed (`sdk.ethUsd`) — which is wired for Robinhood Chain,
48
+ * so USD figures show automatically. Set this only to override the feed or on a
49
+ * chain that lacks one.
50
+ */
51
+ ethUsd?: number;
52
+ /** How often to check, in milliseconds. Default: 5000. */
53
+ checkInterval?: number;
54
+ }
55
+
56
+ /**
57
+ * A bot that mines the grid only when the expected value is positive.
58
+ */
59
+ export class ExpectedValueBot {
60
+ private sdk: SlvrSDK;
61
+ private stakeEth: number;
62
+ private minNetEth: number;
63
+ private cashOut: boolean;
64
+ private jackpotPool: number;
65
+ private jackpotOdds: number | undefined;
66
+ private ethUsd: number | undefined;
67
+ private checkInterval: number;
68
+ private isRunning = false;
69
+ private intervalId?: ReturnType<typeof setInterval>;
70
+ private lastBetRound?: bigint;
71
+
72
+ constructor(sdk: SlvrSDK, options: ExpectedValueBotOptions = {}) {
73
+ this.sdk = sdk;
74
+ this.stakeEth = options.stakeEth ?? 0.1;
75
+ this.minNetEth = options.minNetEth ?? 0;
76
+ this.cashOut = options.cashOut ?? false;
77
+ this.jackpotPool = options.jackpotPool ?? 0;
78
+ this.jackpotOdds = options.jackpotOdds;
79
+ this.ethUsd = options.ethUsd;
80
+ this.checkInterval = options.checkInterval ?? 5000;
81
+ }
82
+
83
+ /** Evaluate the current round once: log the EV breakdown and bet if it clears the threshold. */
84
+ async checkOnce(): Promise<GridMiningEv | null> {
85
+ const roundId = await this.sdk.lottery.currentRoundId();
86
+
87
+ // SLVR price in both ETH and USD (USD needs an ETH/USD source — the SDK's
88
+ // Chainlink feed if configured, else the `ethUsd` option; otherwise null).
89
+ const price = await this.sdk.getSlvrPrice({ ethUsd: this.ethUsd });
90
+ console.log(
91
+ `SLVR price: ${price.eth.toExponential(4)} ETH` +
92
+ (price.usd !== null ? ` · $${price.usd.toFixed(4)}` : ' · (USD unavailable — no ETH/USD source)')
93
+ );
94
+
95
+ // Compute the expected value for this round (pulls pot, emission and SLVR price on-chain).
96
+ const ev = await this.sdk.estimateRoundEv({
97
+ stake: this.stakeEth,
98
+ roundId,
99
+ cashOut: this.cashOut,
100
+ jackpotPool: this.jackpotPool,
101
+ jackpotOdds: this.jackpotOdds,
102
+ slvrPriceEth: price.eth, // reuse the price we just read
103
+ });
104
+
105
+ // Derive the ETH/USD we ended up using, to also show net EV in USD.
106
+ const ethUsd = price.usd !== null ? price.usd / price.eth : null;
107
+ const netUsd = ethUsd !== null ? ` (${ev.netEth * ethUsd >= 0 ? '+' : ''}$${(ev.netEth * ethUsd).toFixed(4)})` : '';
108
+
109
+ console.log(
110
+ `Round ${roundId}: share ${(ev.share * 100).toFixed(2)}% · ` +
111
+ `mine ${ev.slvrMined.toFixed(4)} SLVR (${ev.slvrValueEth.toFixed(6)} ETH) · ` +
112
+ `bleed ${ev.ethBleed.toFixed(6)} ETH · jackpot +${ev.jackpotEvEth.toFixed(6)} ETH · ` +
113
+ `NET ${ev.netEth >= 0 ? '+' : ''}${ev.netEth.toFixed(6)} ETH/round${netUsd} ` +
114
+ `(edge ${(ev.edgeRatio * 100).toFixed(2)}%, break-even pot ${ev.breakEvenPot.toFixed(4)} ETH)`
115
+ );
116
+
117
+ if (this.lastBetRound === roundId) {
118
+ console.log(' already bet this round — skipping');
119
+ return ev;
120
+ }
121
+
122
+ if (ev.netEth < this.minNetEth) {
123
+ console.log(
124
+ ` skip: net EV ${ev.netEth.toFixed(6)} < threshold ${this.minNetEth} ETH ` +
125
+ `(pot above break-even — wait for it to shrink or SLVR to rise)`
126
+ );
127
+ return ev;
128
+ }
129
+
130
+ if (!(await this.sdk.lottery.roundOpen(roundId))) {
131
+ console.log(' skip: round is not open for betting');
132
+ return ev;
133
+ }
134
+
135
+ await this.placeGridBet(roundId);
136
+ this.lastBetRound = roundId;
137
+ return ev;
138
+ }
139
+
140
+ /**
141
+ * Spread the stake across all {@link GRID_SIZE} squares in proportion to the
142
+ * current pot (with a floor so every square is covered and we always hold the
143
+ * winner). This is the coverage the grid-mining EV model assumes.
144
+ */
145
+ private async placeGridBet(roundId: bigint): Promise<void> {
146
+ const stakeWei = parseEther(this.stakeEth.toString());
147
+
148
+ const squaresData = await this.sdk.lottery.getRoundSquares(roundId);
149
+ const totals: bigint[] = Array.from(
150
+ { length: GRID_SIZE },
151
+ (_, i) => squaresData.find((s) => s.square === i)?.total ?? 0n
152
+ );
153
+ const sumTotals = totals.reduce((a, b) => a + b, 0n);
154
+
155
+ // Floor keeps empty squares covered so we never miss the winning square.
156
+ const floor = sumTotals / BigInt(GRID_SIZE) + 1n;
157
+ const weights = totals.map((t) => t + floor);
158
+ const sumWeights = weights.reduce((a, b) => a + b, 0n);
159
+
160
+ const amounts = weights.map((w) => (stakeWei * w) / sumWeights);
161
+ // Push any rounding dust onto the first square so the amounts sum to exactly stakeWei.
162
+ const allocated = amounts.reduce((a, b) => a + b, 0n);
163
+ amounts[0] = (amounts[0] ?? 0n) + (stakeWei - allocated);
164
+
165
+ const squares = Array.from({ length: GRID_SIZE }, (_, i) => i);
166
+
167
+ console.log(` betting ${formatEther(stakeWei)} ETH across all ${GRID_SIZE} squares…`);
168
+ const txHash = await this.sdk.lottery.bet({ roundId, squares, amounts });
169
+ console.log(` ✅ bet placed: ${txHash}`);
170
+ }
171
+
172
+ /** Start the polling loop. */
173
+ async start(): Promise<void> {
174
+ if (this.isRunning) {
175
+ console.log('Bot is already running');
176
+ return;
177
+ }
178
+ if (!this.sdk.getWalletClient()) {
179
+ throw new Error('Wallet client required for expected-value betting');
180
+ }
181
+
182
+ this.isRunning = true;
183
+ console.log('📈 Expected-value bot started');
184
+ console.log(
185
+ `Stake ${this.stakeEth} ETH/round · bet when net EV ≥ ${this.minNetEth} ETH · ` +
186
+ `mode ${this.cashOut ? 'cash-out (−10% refine)' : 'hold'}`
187
+ );
188
+
189
+ const tick = async () => {
190
+ try {
191
+ await this.checkOnce();
192
+ } catch (err) {
193
+ console.error(' error during check:', err instanceof Error ? err.message : err);
194
+ }
195
+ };
196
+
197
+ await tick();
198
+ this.intervalId = setInterval(tick, this.checkInterval);
199
+ }
200
+
201
+ /** Stop the polling loop. */
202
+ stop(): void {
203
+ if (this.intervalId) clearInterval(this.intervalId);
204
+ this.isRunning = false;
205
+ console.log('🛑 Expected-value bot stopped');
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Runnable entry point: builds a wallet-backed SDK on Robinhood Chain (using the
211
+ * shipped `deployments` + `robinhoodChain`) and starts the bot.
212
+ */
213
+ export async function exampleExpectedValueBot(): Promise<void> {
214
+ const privateKey = process.env.PRIVATE_KEY;
215
+ if (!privateKey) {
216
+ throw new Error('PRIVATE_KEY environment variable is required');
217
+ }
218
+
219
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
220
+ const publicClient = createPublicClient({ chain: robinhoodChain, transport: http() });
221
+ const walletClient = createWalletClient({ chain: robinhoodChain, transport: http(), account });
222
+
223
+ // deployments.robinhood.addresses includes slvrEthPair, so sdk.price is available.
224
+ const sdk = new SlvrSDK({
225
+ publicClient,
226
+ walletClient,
227
+ addresses: deployments.robinhood.addresses,
228
+ });
229
+
230
+ const bot = new ExpectedValueBot(sdk, {
231
+ stakeEth: 0.1, // commit 0.1 ETH per +EV round
232
+ minNetEth: 0, // bet on any positive-EV round
233
+ cashOut: false, // value mined SLVR at full price (holding/staking it)
234
+ // USD comes from the SDK's Chainlink ETH/USD feed automatically on Robinhood
235
+ // Chain. Set ETH_USD to override it (e.g. `ETH_USD=1815 ts-node …`).
236
+ ethUsd: process.env.ETH_USD ? Number(process.env.ETH_USD) : undefined,
237
+ checkInterval: 5000,
238
+ });
239
+
240
+ await bot.start();
241
+
242
+ process.on('SIGINT', () => {
243
+ console.log('\nReceived SIGINT, stopping bot…');
244
+ bot.stop();
245
+ process.exit(0);
246
+ });
247
+ }
248
+
249
+ // Run directly: `PRIVATE_KEY=0x... ts-node expected-value-strategy.ts`
250
+ if (require.main === module) {
251
+ exampleExpectedValueBot().catch((err) => {
252
+ console.error(err);
253
+ process.exit(1);
254
+ });
255
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Example: Fixed Squares Strategy
3
+ *
4
+ * This strategy bets on a fixed set of squares every round
5
+ * when the total wager is below the threshold.
6
+ *
7
+ * Perfect for users who want to consistently bet on specific squares.
8
+ */
9
+
10
+ import { BettingStrategy } from './strategy-base';
11
+ import { parseEther } from 'viem';
12
+ import { SlvrSDK } from '../src';
13
+
14
+ /**
15
+ * Configuration for fixed squares strategy
16
+ */
17
+ export interface FixedSquaresConfig {
18
+ /** Squares to bet on (0-24) */
19
+ squares: number[];
20
+ /** Amount to bet per square (in wei) */
21
+ amountPerSquare: bigint;
22
+ /** Optional: different amount for each square (overrides amountPerSquare) */
23
+ amounts?: bigint[];
24
+ /** Threshold in wei - only bet when round total wager is below this */
25
+ threshold?: bigint;
26
+ /** How often to check rounds (in milliseconds) */
27
+ checkInterval?: number;
28
+ }
29
+
30
+ /**
31
+ * Strategy that bets on fixed squares every round
32
+ */
33
+ export class FixedSquaresStrategy extends BettingStrategy {
34
+ private squares: number[];
35
+ private amountPerSquare: bigint;
36
+ private amounts?: bigint[];
37
+
38
+ constructor(sdk: SlvrSDK, config: FixedSquaresConfig) {
39
+ super(sdk, {
40
+ threshold: config.threshold,
41
+ checkInterval: config.checkInterval,
42
+ });
43
+
44
+ // Validate squares
45
+ if (!config.squares || config.squares.length === 0) {
46
+ throw new Error('At least one square must be specified');
47
+ }
48
+
49
+ for (const square of config.squares) {
50
+ if (square < 0 || square >= 25) {
51
+ throw new Error(`Invalid square: ${square}. Must be 0-24`);
52
+ }
53
+ }
54
+
55
+ // Check for duplicates
56
+ const unique = new Set(config.squares);
57
+ if (unique.size !== config.squares.length) {
58
+ throw new Error('Duplicate squares not allowed');
59
+ }
60
+
61
+ this.squares = config.squares;
62
+ this.amountPerSquare = config.amountPerSquare;
63
+ this.amounts = config.amounts;
64
+
65
+ // Validate amounts array if provided
66
+ if (this.amounts) {
67
+ if (this.amounts.length !== this.squares.length) {
68
+ throw new Error('Amounts array length must match squares array length');
69
+ }
70
+ for (const amount of this.amounts) {
71
+ if (amount <= 0n) {
72
+ throw new Error('All amounts must be greater than 0');
73
+ }
74
+ }
75
+ } else if (this.amountPerSquare <= 0n) {
76
+ throw new Error('Amount per square must be greater than 0');
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Always bet on the fixed squares
82
+ */
83
+ protected async selectSquares(): Promise<number[]> {
84
+ return this.squares;
85
+ }
86
+
87
+ /**
88
+ * Use configured amounts
89
+ */
90
+ protected async calculateAmounts(): Promise<bigint[]> {
91
+ if (this.amounts) {
92
+ return this.amounts;
93
+ }
94
+ return this.squares.map(() => this.amountPerSquare);
95
+ }
96
+
97
+ /**
98
+ * Log configuration
99
+ */
100
+ protected logConfig(): void {
101
+ super.logConfig();
102
+ console.log(` Squares: ${this.squares.join(', ')}`);
103
+ const total = this.amounts
104
+ ? this.amounts.reduce((sum, amt) => sum + amt, 0n)
105
+ : this.amountPerSquare * BigInt(this.squares.length);
106
+ console.log(` Amount per square: ${SlvrSDK.formatToken(this.amountPerSquare)} ETH`);
107
+ console.log(` Total bet per round: ${SlvrSDK.formatToken(total)} ETH`);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Example usage
113
+ */
114
+ export async function exampleFixedSquaresStrategy() {
115
+ const { createPublicClient, createWalletClient, http } = await import('viem');
116
+ const { defineChain } = await import('viem');
117
+ const { privateKeyToAccount } = await import('viem/accounts');
118
+ const { SlvrSDK } = await import('../src');
119
+ const { CONTRACTS, ROBINHOOD_CHAIN } = await import('./constants');
120
+
121
+ // Create clients
122
+ const robinhood = defineChain(ROBINHOOD_CHAIN);
123
+ const publicClient = createPublicClient({
124
+ chain: robinhood,
125
+ transport: http(),
126
+ });
127
+
128
+ const privateKey = process.env.PRIVATE_KEY;
129
+ if (!privateKey) {
130
+ throw new Error('PRIVATE_KEY environment variable is required');
131
+ }
132
+
133
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
134
+ const walletClient = createWalletClient({
135
+ chain: robinhood,
136
+ transport: http(),
137
+ account,
138
+ });
139
+
140
+ // Initialize SDK
141
+ const sdk = new SlvrSDK({
142
+ publicClient,
143
+ walletClient,
144
+ addresses: {
145
+ lottery: CONTRACTS.LOTTERY,
146
+ staking: CONTRACTS.STAKING,
147
+ token: CONTRACTS.TOKEN,
148
+ autoCommit: CONTRACTS.AUTO_COMMIT !== '0x...' ? CONTRACTS.AUTO_COMMIT : undefined,
149
+ },
150
+ });
151
+
152
+ // Create strategy: bet on squares 0, 5, 10, 15, 20 every round
153
+ const strategy = new FixedSquaresStrategy(sdk, {
154
+ squares: [0, 5, 10, 15, 20], // Bet on these 5 squares
155
+ amountPerSquare: parseEther('1'), // 1 ETH per square
156
+ threshold: parseEther('100'), // Only bet when round has < 100 ETH
157
+ checkInterval: 5000, // Check every 5 seconds
158
+ });
159
+
160
+ // Start the strategy
161
+ await strategy.start();
162
+
163
+ // Handle graceful shutdown
164
+ process.on('SIGINT', () => {
165
+ console.log('\nReceived SIGINT, stopping strategy...');
166
+ strategy.stop();
167
+ process.exit(0);
168
+ });
169
+
170
+ process.on('SIGTERM', () => {
171
+ console.log('\nReceived SIGTERM, stopping strategy...');
172
+ strategy.stop();
173
+ process.exit(0);
174
+ });
175
+ }
176
+
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Example exports
3
+ *
4
+ * Import and use these examples to understand how to use the Slvr SDK
5
+ */
6
+
7
+ // Base strategy class for creating custom strategies
8
+ export { BettingStrategy, StrategyConfig, BetDecision } from './strategy-base';
9
+
10
+ // Pre-built strategies
11
+ export { AutomatedBettingBot, exampleAutomatedBetting } from './automated-betting';
12
+ export { LeastAllocatedStrategy, exampleLeastAllocatedStrategy } from './least-allocated-strategy';
13
+ export { CombinedBettingBot, exampleCombinedStrategy } from './combined-strategy';
14
+ export { ExpectedValueBot, exampleExpectedValueBot } from './expected-value-strategy';
15
+ export type { ExpectedValueBotOptions } from './expected-value-strategy';
16
+ export { FixedSquaresStrategy, exampleFixedSquaresStrategy, FixedSquaresConfig } from './fixed-squares-strategy';
17
+
18
+ // Custom strategy examples
19
+ export {
20
+ LeastAllocatedCustomStrategy,
21
+ RandomSquaresStrategy,
22
+ MostBettorsStrategy,
23
+ WeightedAllocationStrategy,
24
+ ConditionalStrategy,
25
+ } from './custom-strategy-example';
26
+
27
+ // Utilities
28
+ export { simpleExample } from './simple-example';
29
+ export { CONTRACTS, ROBINHOOD_CHAIN } from './constants';
30
+
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Example: Betting strategy that targets squares with least allocation
3
+ *
4
+ * This example shows how to:
5
+ * 1. Get all squares and their current allocations
6
+ * 2. Identify squares with the least funds
7
+ * 3. Distribute bets across these squares
8
+ */
9
+
10
+ import { SlvrSDK } from '../src';
11
+ import { parseEther } from 'viem';
12
+
13
+ /**
14
+ * Strategy that bets on squares with the least allocation
15
+ */
16
+ export class LeastAllocatedStrategy {
17
+ private sdk: SlvrSDK;
18
+
19
+ constructor(sdk: SlvrSDK) {
20
+ this.sdk = sdk;
21
+ }
22
+
23
+ /**
24
+ * Get squares sorted by allocation (least to most)
25
+ */
26
+ async getSquaresByAllocation(roundId: bigint): Promise<
27
+ Array<{ square: number; total: bigint; bettors: bigint; percentage: number }>
28
+ > {
29
+ const squares = await this.sdk.lottery.getRoundSquares(roundId);
30
+
31
+ // Calculate total across all squares
32
+ const totalAllocation = squares.reduce((sum, sq) => sum + sq.total, 0n);
33
+
34
+ // Add percentage and sort by total (ascending)
35
+ const withPercentage = squares
36
+ .map(({ square, total, bettors }) => ({
37
+ square,
38
+ total,
39
+ bettors,
40
+ percentage: totalAllocation > 0n
41
+ ? Number((total * 10000n) / totalAllocation) / 100
42
+ : 0,
43
+ }))
44
+ .sort((a, b) => {
45
+ if (a.total < b.total) return -1;
46
+ if (a.total > b.total) return 1;
47
+ return 0;
48
+ });
49
+
50
+ return withPercentage;
51
+ }
52
+
53
+ /**
54
+ * Get the N squares with least allocation
55
+ */
56
+ async getLeastAllocatedSquares(
57
+ roundId: bigint,
58
+ count: number = 5
59
+ ): Promise<number[]> {
60
+ const sorted = await this.getSquaresByAllocation(roundId);
61
+ return sorted.slice(0, count).map(({ square }) => square);
62
+ }
63
+
64
+ /**
65
+ * Calculate bet amounts for least allocated squares
66
+ * Distributes total bet amount evenly across selected squares
67
+ */
68
+ calculateBetAmounts(
69
+ totalAmount: bigint,
70
+ squareCount: number
71
+ ): bigint[] {
72
+ const amountPerSquare = totalAmount / BigInt(squareCount);
73
+ const remainder = totalAmount % BigInt(squareCount);
74
+
75
+ const amounts = Array(squareCount).fill(amountPerSquare);
76
+
77
+ // Add remainder to first square to ensure exact total
78
+ if (remainder > 0n) {
79
+ amounts[0] += remainder;
80
+ }
81
+
82
+ return amounts;
83
+ }
84
+
85
+ /**
86
+ * Place a bet on least allocated squares
87
+ */
88
+ async betOnLeastAllocated(
89
+ roundId: bigint,
90
+ totalBetAmount: bigint,
91
+ squareCount: number = 5
92
+ ): Promise<`0x${string}`> {
93
+ if (!this.sdk.getWalletClient()) {
94
+ throw new Error('Wallet client required for betting');
95
+ }
96
+
97
+ // Get squares with least allocation
98
+ const squares = await this.getLeastAllocatedSquares(roundId, squareCount);
99
+
100
+ if (squares.length === 0) {
101
+ throw new Error('No squares available');
102
+ }
103
+
104
+ // Calculate amounts
105
+ const amounts = this.calculateBetAmounts(totalBetAmount, squares.length);
106
+
107
+ console.log(`🎯 Betting on ${squares.length} least allocated squares:`);
108
+ squares.forEach((square, i) => {
109
+ console.log(` Square ${square}: ${SlvrSDK.formatToken(amounts[i])} ETH`);
110
+ });
111
+ console.log(`💰 Total: ${SlvrSDK.formatToken(totalBetAmount)} ETH`);
112
+
113
+ // Place the bet
114
+ return await this.sdk.lottery.bet({
115
+ roundId,
116
+ squares,
117
+ amounts,
118
+ });
119
+ }
120
+
121
+ /**
122
+ * Get detailed analysis of square allocations
123
+ */
124
+ async analyzeRound(roundId: bigint): Promise<{
125
+ roundId: bigint;
126
+ totalAllocation: bigint;
127
+ squares: Array<{
128
+ square: number;
129
+ total: bigint;
130
+ bettors: bigint;
131
+ percentage: number;
132
+ rank: number;
133
+ }>;
134
+ leastAllocated: number[];
135
+ mostAllocated: number[];
136
+ }> {
137
+ const sorted = await this.getSquaresByAllocation(roundId);
138
+ const totalAllocation = sorted.reduce((sum, sq) => sum + sq.total, 0n);
139
+
140
+ return {
141
+ roundId,
142
+ totalAllocation,
143
+ squares: sorted.map((sq, index) => ({
144
+ ...sq,
145
+ rank: index + 1,
146
+ })),
147
+ leastAllocated: sorted.slice(0, 5).map(({ square }) => square),
148
+ mostAllocated: sorted.slice(-5).reverse().map(({ square }) => square),
149
+ };
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Example usage
155
+ *
156
+ * Set PRIVATE_KEY environment variable to enable betting
157
+ */
158
+ export async function exampleLeastAllocatedStrategy() {
159
+ const { createPublicClient, createWalletClient, http } = await import('viem');
160
+ const { defineChain } = await import('viem');
161
+ const { privateKeyToAccount } = await import('viem/accounts');
162
+ const { parseEther } = await import('viem');
163
+ const { SlvrSDK } = await import('../src');
164
+ const { CONTRACTS, ROBINHOOD_CHAIN } = await import('./constants');
165
+
166
+ // Create clients
167
+ const robinhood = defineChain(ROBINHOOD_CHAIN);
168
+ const publicClient = createPublicClient({
169
+ chain: robinhood,
170
+ transport: http(),
171
+ });
172
+
173
+ const privateKey = process.env.PRIVATE_KEY;
174
+ let walletClient: ReturnType<typeof createWalletClient> | undefined;
175
+
176
+ if (privateKey) {
177
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
178
+ walletClient = createWalletClient({
179
+ chain: robinhood,
180
+ transport: http(),
181
+ account,
182
+ });
183
+ }
184
+
185
+ // Initialize SDK
186
+ const sdk = new SlvrSDK({
187
+ publicClient,
188
+ walletClient,
189
+ addresses: {
190
+ lottery: CONTRACTS.LOTTERY,
191
+ staking: CONTRACTS.STAKING,
192
+ token: CONTRACTS.TOKEN,
193
+ autoCommit: CONTRACTS.AUTO_COMMIT !== '0x...' ? CONTRACTS.AUTO_COMMIT : undefined,
194
+ },
195
+ });
196
+
197
+ const strategy = new LeastAllocatedStrategy(sdk);
198
+ const roundId = await sdk.lottery.currentRoundId();
199
+
200
+ // Analyze the round
201
+ const analysis = await strategy.analyzeRound(roundId);
202
+ console.log('Round Analysis:', {
203
+ totalAllocation: SlvrSDK.formatToken(analysis.totalAllocation),
204
+ leastAllocated: analysis.leastAllocated,
205
+ mostAllocated: analysis.mostAllocated,
206
+ });
207
+
208
+ // Get squares with least allocation
209
+ const leastAllocated = await strategy.getLeastAllocatedSquares(roundId, 5);
210
+ console.log('Least allocated squares:', leastAllocated);
211
+
212
+ // Place bet on these squares (if wallet client is available)
213
+ if (walletClient) {
214
+ const txHash = await strategy.betOnLeastAllocated(
215
+ roundId,
216
+ parseEther('5'), // 5 ETH total
217
+ 5 // Bet on 5 squares
218
+ );
219
+ console.log('Bet placed:', txHash);
220
+ } else {
221
+ console.log('Cannot place bet - set PRIVATE_KEY environment variable');
222
+ }
223
+ }
224
+