@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
package/README.md ADDED
@@ -0,0 +1,849 @@
1
+ # @slvr-labs/sdk
2
+
3
+ TypeScript SDK for interacting with the Slvr protocol on Robinhood Chain.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @slvr-labs/sdk viem
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ The fastest way — `SlvrSDK.connect` builds resilient clients (Multicall3
14
+ batching, timeouts, retries) and wires the SDK for you:
15
+
16
+ ```typescript
17
+ import { SlvrSDK } from '@slvr-labs/sdk';
18
+
19
+ const sdk = SlvrSDK.connect(); // read-only, Robinhood Chain
20
+ const bot = SlvrSDK.connect({ privateKey: process.env.PK }); // wallet-backed (for bets/claims)
21
+
22
+ const roundId = await sdk.lottery.currentRoundId();
23
+ ```
24
+
25
+ Need the clients yourself? `createSlvrClients(opts)` returns `{ publicClient,
26
+ walletClient?, chain }` with the same defaults. Or wire everything manually:
27
+
28
+ ```typescript
29
+ import { createPublicClient, createWalletClient, http } from 'viem';
30
+ import { privateKeyToAccount } from 'viem/accounts';
31
+ import { SlvrSDK, robinhoodChain, deployments } from '@slvr-labs/sdk';
32
+
33
+ // `robinhoodChain` and `deployments.robinhood` ship with the SDK — no need to
34
+ // hand-roll the chain or copy addresses around.
35
+ const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
36
+
37
+ const publicClient = createPublicClient({
38
+ chain: robinhoodChain,
39
+ transport: http(),
40
+ });
41
+
42
+ const walletClient = createWalletClient({
43
+ chain: robinhoodChain,
44
+ transport: http(),
45
+ account,
46
+ });
47
+
48
+ const sdk = new SlvrSDK({
49
+ publicClient,
50
+ walletClient,
51
+ addresses: deployments.robinhood.addresses,
52
+ });
53
+ ```
54
+
55
+ `deployments.robinhood.addresses` provides the canonical mainnet `lottery`,
56
+ `staking` (the veNFT staker), `token`, and `autoCommit` (V2) addresses. The
57
+ optional `hub`, `registry`, and `jackpot` bindings are only created when their
58
+ addresses are supplied — the live Robinhood Chain deployment does not run the hub
59
+ architecture, so those are omitted and `sdk.hub` / `sdk.registry` / `sdk.jackpot`
60
+ are `undefined`. To point at a custom or local deployment, pass your own
61
+ `addresses` object instead.
62
+
63
+ > **Read-only usage:** `walletClient` is optional. Omit it to build a read-only
64
+ > SDK (round/pot/reward queries work); any write call then throws
65
+ > `WalletClientRequiredError`.
66
+
67
+ ## Usage Examples
68
+
69
+ ### Lottery Operations
70
+
71
+ #### Get Current Round
72
+
73
+ ```typescript
74
+ const roundId = await sdk.lottery.currentRoundId();
75
+ console.log(`Current round: ${roundId}`);
76
+ ```
77
+
78
+ #### Get Round Information
79
+
80
+ ```typescript
81
+ const round = await sdk.lottery.getRound(roundId);
82
+ console.log(`Round ${roundId}:`, {
83
+ resolved: round.resolved,
84
+ winningSquare: round.winningSquare,
85
+ totalWager: round.totalWager,
86
+ potForWinners: round.potForWinners,
87
+ });
88
+ ```
89
+
90
+ #### Place a Bet
91
+
92
+ ```typescript
93
+ const currentRound = await sdk.lottery.currentRoundId();
94
+ const squares = [0, 1, 2, 3]; // Square indices (0-24)
95
+ const amounts = [
96
+ 1000000000000000000n, // 1 ETH
97
+ 2000000000000000000n, // 2 ETH
98
+ 1500000000000000000n, // 1.5 ETH
99
+ 500000000000000000n, // 0.5 ETH
100
+ ];
101
+
102
+ const txHash = await sdk.lottery.bet({
103
+ roundId: currentRound,
104
+ squares,
105
+ amounts,
106
+ });
107
+ console.log(`Bet placed: ${txHash}`);
108
+ ```
109
+
110
+ #### Claim Rewards
111
+
112
+ ```typescript
113
+ const txHash = await sdk.lottery.claim({
114
+ roundId: roundId,
115
+ });
116
+ console.log(`Claimed rewards: ${txHash}`);
117
+ ```
118
+
119
+ #### Batch Claim Multiple Rounds
120
+
121
+ ```typescript
122
+ const claimableRounds = await sdk.getClaimableRounds(
123
+ userAddress,
124
+ startRoundId,
125
+ endRoundId
126
+ );
127
+
128
+ if (claimableRounds.length > 0) {
129
+ const txHash = await sdk.lottery.batchClaim(claimableRounds);
130
+ console.log(`Batch claimed ${claimableRounds.length} rounds: ${txHash}`);
131
+ }
132
+ ```
133
+
134
+ #### Get Round Squares Data
135
+
136
+ ```typescript
137
+ const squares = await sdk.lottery.getRoundSquares(roundId);
138
+ squares.forEach(({ square, total, bettors }) => {
139
+ console.log(`Square ${square}: ${total} ETH from ${bettors} bettors`);
140
+ });
141
+ ```
142
+
143
+ #### Get User's Bets
144
+
145
+ ```typescript
146
+ const userBets = await sdk.lottery.getUserBets(roundId, userAddress);
147
+ userBets.forEach(({ square, amount }) => {
148
+ console.log(`Square ${square}: ${amount} ETH`);
149
+ });
150
+ ```
151
+
152
+ ### Staking Operations
153
+
154
+ Staking is the veNFT-based `SlvrVoteEscrowStaking` contract. Stakers deposit a
155
+ vote-escrow NFT **by tokenId** (not a raw ERC20 amount); rewards accrue per unit of the
156
+ token's tracked weight and are claimed per tokenId.
157
+
158
+ #### Get Staking Info
159
+
160
+ ```typescript
161
+ // tokenId is the veNFT you have staked (or want to inspect)
162
+ const stakingInfo = await sdk.staking.getStakingInfo(tokenId);
163
+ console.log({
164
+ totalWeight: stakingInfo.totalWeight, // total tracked weight across all staked tokens
165
+ balance: stakingInfo.balance, // this token's tracked weight
166
+ rewards: stakingInfo.rewards, // claimable rewards for this token
167
+ rewardPerWeightStored: stakingInfo.rewardPerWeightStored, // accumulated reward per weight (1e18)
168
+ });
169
+ ```
170
+
171
+ #### Stake a veNFT
172
+
173
+ ```typescript
174
+ // Deposit the vote-escrow NFT by tokenId (the staking contract takes custody of it)
175
+ const stakeTx = await sdk.staking.stake(tokenId);
176
+ console.log(`Staked: ${stakeTx}`);
177
+ ```
178
+
179
+ #### Unstake a veNFT
180
+
181
+ ```typescript
182
+ const unstakeTx = await sdk.staking.unstake(tokenId);
183
+ console.log(`Unstaked: ${unstakeTx}`);
184
+ ```
185
+
186
+ #### Claim Staking Rewards
187
+
188
+ ```typescript
189
+ // Read claimable rewards for a token
190
+ const pending = await sdk.staking.getStakerRewards(tokenId);
191
+ console.log(`Claimable: ${SlvrSDK.formatToken(pending)}`);
192
+
193
+ // Claim them
194
+ const rewardTx = await sdk.staking.claimStakerRewards(tokenId);
195
+ console.log(`Rewards claimed: ${rewardTx}`);
196
+ ```
197
+
198
+ #### Checkpoint / Poke
199
+
200
+ ```typescript
201
+ // Refresh a token's reward accounting without claiming
202
+ await sdk.staking.checkpoint(tokenId);
203
+
204
+ // Refresh a token's tracked weight (e.g. after its veNFT weight changed)
205
+ await sdk.staking.poke(tokenId);
206
+
207
+ // Refresh many tokens' tracked weight in one call
208
+ await sdk.staking.pokeMany([tokenId1, tokenId2]);
209
+ ```
210
+
211
+ ### Token Operations
212
+
213
+ #### Get Token Balance
214
+
215
+ ```typescript
216
+ const balance = await sdk.token.balanceOf(userAddress);
217
+ console.log(`Balance: ${SlvrSDK.formatToken(balance)} SLVR`);
218
+ ```
219
+
220
+ #### Transfer Tokens
221
+
222
+ ```typescript
223
+ const amount = SlvrSDK.parseToken('100'); // 100 SLVR
224
+ const txHash = await sdk.token.transfer(recipientAddress, amount);
225
+ console.log(`Transferred: ${txHash}`);
226
+ ```
227
+
228
+ ### Auto-Commit Operations
229
+
230
+ `sdk.autoCommit` wraps **SlvrAutoCommitV2**. You configure a repeating bet plan
231
+ and fund it; a keeper (anyone) later executes plays on your behalf and is
232
+ reimbursed their metered gas plus a small premium out of your plan balance —
233
+ there is no flat automation fee, and `executeFor` / `claimFor` are non-payable.
234
+
235
+ #### Configure Auto-Commit Plan
236
+
237
+ ```typescript
238
+ const squares = [0, 1, 2, 3, 4];
239
+ const bpsAlloc = [2000, 2000, 2000, 2000, 2000]; // 20% each; must sum to 10000
240
+
241
+ const txHash = await sdk.autoCommit?.configurePlan(
242
+ 10, // number of plays (use UNLIMITED_PLAYS for open-ended)
243
+ 1000000000000000000n, // amount per play (1 ETH), split across squares by bpsAlloc
244
+ squares,
245
+ bpsAlloc,
246
+ true // autoClaim: keeper claims winnings back into your balance
247
+ );
248
+ ```
249
+
250
+ #### Configure and Deposit in One Transaction
251
+
252
+ ```typescript
253
+ const txHash = await sdk.autoCommit?.configurePlanAndDeposit(
254
+ 10,
255
+ 1000000000000000000n,
256
+ squares,
257
+ bpsAlloc,
258
+ true,
259
+ 10000000000000000000n // deposit 10 ETH (sent as msg.value)
260
+ );
261
+ ```
262
+
263
+ #### Deposit / Withdraw
264
+
265
+ ```typescript
266
+ const depositTx = await sdk.autoCommit?.deposit(5000000000000000000n); // 5 ETH
267
+ const withdrawTx = await sdk.autoCommit?.withdraw(1000000000000000000n, to); // 1 ETH to `to`
268
+ ```
269
+
270
+ #### Inspect a Plan
271
+
272
+ ```typescript
273
+ const plan = await sdk.autoCommit?.planInfo(userAddress);
274
+ // { enabled, nextRoundId, playsRemaining, amountPerPlay, balance, autoClaim, squares, bpsAlloc, planStartRoundId }
275
+
276
+ const { ready, reason } = await sdk.autoCommit!.needsExecution(userAddress);
277
+ ```
278
+
279
+ #### Execute a Plan (keeper side)
280
+
281
+ ```typescript
282
+ // maxPlays caps the number of rounds executed this call; claimRounds are winning
283
+ // rounds to claim in the same tx (discover them off-chain; empty array is fine).
284
+ const executeTx = await sdk.autoCommit?.executeFor(userAddress, 5, []);
285
+
286
+ // Claim only, without executing new plays:
287
+ const claimTx = await sdk.autoCommit?.claimFor(userAddress, [roundId1, roundId2]);
288
+ ```
289
+
290
+ ### Hub Operations (optional)
291
+
292
+ The `SlvrHub` is the protocol emission/sink router. It gates per-game SLVR emission and
293
+ fans many games into a single shared veNFT staker stream and a single shared jackpot.
294
+ Available as `sdk.hub` when a `hub` address is configured.
295
+
296
+ This is a read-only, informational surface — the hub's fee-routing writes are
297
+ keeper/protocol operations and are not part of the SDK.
298
+
299
+ ```typescript
300
+ // Emission stats
301
+ const rate = await sdk.hub!.emissionRatePerSec(); // base SLVR/sec across the active game set
302
+ const target = await sdk.hub!.targetSupply(); // soft-cap target supply (0 => token MAX_SUPPLY)
303
+ const pending = await sdk.hub!.pendingEmission(gameId); // accrued-but-unminted SLVR for a game
304
+ ```
305
+
306
+ ### Registry Operations (optional)
307
+
308
+ The `SlvrGameRegistry` is the source of truth for which games exist, their status/tier,
309
+ and their share of the shared emission stream. Available as `sdk.registry` when a
310
+ `registry` address is configured (read-only surface).
311
+
312
+ ```typescript
313
+ const gameId = await sdk.registry!.gameIdOf(gameAddress); // 0 if not registered (ids are 1-based)
314
+ const info = await sdk.registry!.gameInfo(gameId); // full GameInfo record
315
+ const active = await sdk.registry!.isActive(gameAddress);
316
+ const status = await sdk.registry!.statusOf(gameId); // GameStatus enum
317
+ const tier = await sdk.registry!.tierOf(gameId); // GameTier enum
318
+ const weight = await sdk.registry!.weightOf(gameId); // this game's emission weight
319
+ const totalWeight = await sdk.registry!.totalActiveWeight(); // denominator for the emission split
320
+ ```
321
+
322
+ ### Jackpot Operations (optional)
323
+
324
+ The `SlvrJackpot` exposes the shared jackpot pool balances. Available as `sdk.jackpot`
325
+ when a `jackpot` address is configured (read-only surface).
326
+
327
+ ```typescript
328
+ const ethPool = await sdk.jackpot!.jackpotPool(); // native (ETH) jackpot pool balance
329
+ const slvrPool = await sdk.jackpot!.jackpotSlvrPool(); // SLVR jackpot pool balance
330
+ ```
331
+
332
+ ### Emission Helpers
333
+
334
+ These SDK-level helpers combine hub + registry reads. `effectiveEmissionRate` requires
335
+ both `hub` and `registry` addresses; `pendingEmission` requires `hub`.
336
+
337
+ ```typescript
338
+ // A game's weighted share of the global emission stream, in SLVR/sec
339
+ // (pre-cap: does not apply the per-game maxWeightBps ceiling)
340
+ const effRate = await sdk.effectiveEmissionRate(gameId);
341
+
342
+ // Accrued-but-unminted SLVR currently available to a game (pass-through to hub.pendingEmission)
343
+ const pending = await sdk.pendingEmission(gameId);
344
+ ```
345
+
346
+ ### Expected Value & SLVR Price
347
+
348
+ The SDK ships the protocol's **grid-mining edge** math (the same model as the web
349
+ calculator) plus a SLVR/ETH price reader, so a bot can decide when a round is
350
+ worth playing.
351
+
352
+ **The model in one line:** the winning square is drawn *uniformly* each round, so
353
+ mining SLVR is only profitable while the pot is small enough that your SLVR reward
354
+ beats the ETH you bleed to the protocol fee. Net EV per round:
355
+
356
+ ```
357
+ netEth = (stake/pot) * emissionPerRound * slvrPriceEth * realize // SLVR mined, valued in ETH
358
+ - feeFraction * stake // ETH bleed (the fee; the rest of the pot is a wash)
359
+ + (1/jackpotOdds) * (stake/pot) * jackpotPool // jackpot term
360
+ ```
361
+
362
+ Because both terms scale with `stake`, the edge depends on **pot vs SLVR price**,
363
+ not bet size — mining pays while the pot is below the *break-even pot*.
364
+
365
+ #### Read the SLVR price (ETH and USD)
366
+
367
+ SLVR/ETH comes from the UniswapV2 pair (`slvrEthPair`). For USD, the SDK reads an
368
+ optional **Chainlink ETH/USD feed** (`chainlinkEthUsd`) and multiplies.
369
+
370
+ ```typescript
371
+ // SLVR/ETH from the pair (requires slvrEthPair, in deployments.robinhood.addresses).
372
+ const ethPerSlvr = await sdk.getSlvrPriceInEth();
373
+
374
+ // SLVR in BOTH ETH and USD:
375
+ const { eth, usd } = await sdk.getSlvrPrice(); // usd uses the Chainlink feed if configured, else null
376
+ const quote = await sdk.getSlvrPrice({ ethUsd: 1797.35 }); // …or pass ETH/USD from your own source
377
+
378
+ // ETH/USD directly (requires a chainlinkEthUsd feed):
379
+ const ethUsd = await sdk.getEthPriceUsd();
380
+
381
+ // Low-level readers:
382
+ const { slvrReserve, ethReserve } = await sdk.price!.getReserves();
383
+ ```
384
+
385
+ > **Chainlink feed:** Robinhood Chain has a Chainlink ETH/USD feed (`ETH / USD`,
386
+ > 8 decimals), wired into `deployments.robinhood.addresses.chainlinkEthUsd`, so
387
+ > `getSlvrPrice()` / `getEthPriceUsd()` return USD out of the box. (An SVR proxy
388
+ > with identical price data also exists, at
389
+ > `0x5058aDee53b04e374d8bEDbAD634Bc4778F50b22`, for protocols integrating
390
+ > Chainlink SVR; for plain reads use the standard proxy that's wired here.) On a
391
+ > chain without a feed, set `addresses.chainlinkEthUsd` yourself or pass `ethUsd`.
392
+ > `ChainlinkPriceFeed` is exported if you want to read any aggregator directly.
393
+
394
+ #### Estimate a round's EV (live data)
395
+
396
+ ```typescript
397
+ // Pulls pot, emission and SLVR price on-chain and returns the full breakdown.
398
+ const ev = await sdk.estimateRoundEv({
399
+ stake: 0.1, // ETH you'd commit this round
400
+ cashOut: false, // false = value SLVR at full price (holding); true = net of the 10% refining fee
401
+ jackpotPool: 5.2, // optional: ETH in the jackpot (not auto-read)
402
+ });
403
+
404
+ console.log(ev.netEth, 'ETH/round', ev.profitable, `(break-even pot ${ev.breakEvenPot} ETH)`);
405
+ ```
406
+
407
+ #### Or compute it yourself (pure function, no I/O)
408
+
409
+ ```typescript
410
+ import { computeGridMiningEv } from '@slvr-labs/sdk';
411
+
412
+ const ev = computeGridMiningEv({
413
+ stake: 0.1,
414
+ pot: 0.5,
415
+ emissionPerRound: 1, // SLVR minted to the winning square this round
416
+ slvrPriceEth: 0.0005, // ETH per SLVR (from sdk.getSlvrPriceInEth())
417
+ jackpotPool: 5.2, // optional
418
+ });
419
+ // -> { share, ethBleed, slvrMined, slvrValueEth, jackpotEvEth, netEth, edgeRatio,
420
+ // breakEvenPot, breakEvenSlvrPriceEth, profitable }
421
+ ```
422
+
423
+ See the [`expected-value-strategy.ts`](./examples/expected-value-strategy.ts) example
424
+ for a bot that only bets when a round is +EV. Exported constants `GRID_SIZE` (25),
425
+ `PROTOCOL_FEE_BPS`, `REFINING_FEE_BPS`, and `JACKPOT_ODDS` (625) document the on-chain
426
+ defaults.
427
+
428
+ ### Reactive Helpers
429
+
430
+ Instead of hand-rolling polling loops, react to on-chain events or await
431
+ resolution:
432
+
433
+ ```typescript
434
+ // Await a round's resolution (polls getRound under the hood) — e.g. bet, then claim.
435
+ const resolved = await sdk.lottery.waitForResolution(roundId, { timeoutMs: 300_000 });
436
+ if (await sdk.canClaim(roundId, address)) await sdk.lottery.claim({ roundId });
437
+
438
+ // Subscribe to events; each returns an unsubscribe function.
439
+ const stopResolved = sdk.lottery.watchRoundResolved((e) => {
440
+ console.log(`round ${e.roundId} won by square ${e.winningSquare}`);
441
+ });
442
+ const stopBets = sdk.lottery.watchBets((e) => console.log(`bet of ${e.total} on round ${e.roundId}`));
443
+ // later: stopResolved(); stopBets();
444
+ ```
445
+
446
+ ### Helper Functions
447
+
448
+ #### Format Token Amounts
449
+
450
+ ```typescript
451
+ const formatted = SlvrSDK.formatToken(1500000000000000000n); // "1.5"
452
+ // precision caps decimals; trailing zeros are stripped
453
+ const precise = SlvrSDK.formatToken(1234567890000000000n, 18, 6); // "1.234567"
454
+ ```
455
+
456
+ #### Parse Token Amounts
457
+
458
+ ```typescript
459
+ const amount = SlvrSDK.parseToken('1.5'); // 1500000000000000000n
460
+ ```
461
+
462
+ #### Calculate Bet Amounts from Percentages
463
+
464
+ ```typescript
465
+ const amounts = SlvrSDK.calculateBetAmounts(
466
+ 1000000000000000000n, // 1 ETH total
467
+ [25, 25, 25, 25] // 25% each square
468
+ );
469
+ // Returns: [250000000000000000n, 250000000000000000n, 250000000000000000n, 250000000000000000n]
470
+ ```
471
+
472
+ #### Get Time Remaining
473
+
474
+ ```typescript
475
+ const timeRemaining = await sdk.getTimeRemaining(roundId);
476
+ console.log(`Time remaining: ${timeRemaining} seconds`);
477
+ ```
478
+
479
+ ## API Reference
480
+
481
+ ### SlvrSDK
482
+
483
+ Main SDK class that provides access to all protocol contracts.
484
+
485
+ #### Constructor
486
+
487
+ ```typescript
488
+ new SlvrSDK(config: SlvrConfig)
489
+ ```
490
+
491
+ #### Properties
492
+
493
+ - `lottery: SlvrGridLottery` - Lottery contract interface
494
+ - `staking: SlvrStaking` - veNFT staking contract interface (`SlvrVoteEscrowStaking`)
495
+ - `token: SlvrToken` - Token contract interface
496
+ - `autoCommit?: SlvrAutoCommit` - Auto-commit contract interface (optional)
497
+ - `hub?: SlvrHub` - Emission/sink router interface, read-only (optional)
498
+ - `registry?: SlvrGameRegistry` - Game registry interface (optional)
499
+ - `jackpot?: SlvrJackpot` - Jackpot pool interface (optional)
500
+
501
+ #### Methods
502
+
503
+ - `getPublicClient(): PublicClient` - Get the public client
504
+ - `getWalletClient(): WalletClient | undefined` - Get the wallet client
505
+ - `setWalletClient(walletClient: WalletClient | undefined): void` - Update wallet client
506
+ - `getTimeRemaining(roundId: bigint): Promise<number>` - Get time remaining for a round
507
+ - `canClaim(roundId: bigint, user: Address): Promise<boolean>` - Check if user can claim
508
+ - `getClaimableRounds(user: Address, startRoundId: bigint, endRoundId: bigint): Promise<bigint[]>` - Get claimable rounds
509
+ - `effectiveEmissionRate(gameId: bigint): Promise<bigint>` - A game's weighted SLVR/sec share of the global emission stream (requires `hub` + `registry`)
510
+ - `pendingEmission(gameId: bigint): Promise<bigint>` - Accrued-but-unminted SLVR available to a game (requires `hub`)
511
+ - `getSlvrPriceInEth(): Promise<number>` - SLVR spot price in ETH (requires `slvrEthPair`)
512
+ - `getSlvrPrice(opts?: { ethUsd?: number }): Promise<PriceQuote>` - SLVR price in both ETH and USD (`usd` is `null` without an ETH/USD source)
513
+ - `getEthPriceUsd(): Promise<number>` - ETH/USD from the Chainlink feed (requires `chainlinkEthUsd`)
514
+ - `estimateRoundEv(params): Promise<GridMiningEv>` - per-round grid-mining EV, pulling pot/emission/price on-chain (requires `slvrEthPair` unless `slvrPriceEth` is passed)
515
+
516
+ #### Static Helpers
517
+
518
+ - `SlvrSDK.formatToken(value: bigint, decimals?: number, precision?: number): string`
519
+ - `SlvrSDK.parseToken(value: string, decimals?: number): bigint`
520
+ - `SlvrSDK.calculateBetAmounts(totalAmount: bigint, percentages: number[]): bigint[]`
521
+
522
+ ### SlvrGridLottery
523
+
524
+ Interface for the lottery contract.
525
+
526
+ #### Read Methods
527
+
528
+ - `currentRoundId(): Promise<bigint>`
529
+ - `roundStart(roundId: bigint): Promise<bigint>`
530
+ - `roundEnd(roundId: bigint): Promise<bigint>`
531
+ - `roundOpen(roundId: bigint): Promise<boolean>`
532
+ - `bettingEnd(roundId: bigint): Promise<bigint>` - betting cutoff (unix seconds); can be earlier than `roundEnd` — gate bots on this
533
+ - `getExpectedReward(account: Address, roundId: bigint): Promise<bigint>` - estimated reward for an account in a round
534
+ - `latestResolvedRoundId(): Promise<bigint>`
535
+ - `getRound(roundId: bigint): Promise<RoundInfo>` - flat 16-value round tuple
536
+ - `getTotalOnSquare(roundId: bigint, square: number): Promise<bigint>`
537
+ - `getBettorsOnSquare(roundId: bigint, square: number): Promise<bigint>`
538
+ - `getUserBet(roundId: bigint, square: number, bettor: Address): Promise<bigint>`
539
+ - `getHasClaimed(roundId: bigint, user: Address): Promise<boolean>`
540
+ - `getMinerState(account: Address): Promise<MinerState>`
541
+ - `hasAccount(account: Address): Promise<boolean>`
542
+ - `getDelegate(user: Address, delegate: Address): Promise<boolean>`
543
+ - `slvrPerRound(): Promise<bigint>` - target SLVR/round; **emission is now hub-gated**, so this is the requested value, not the amount actually minted (bounded by `SlvrHub.pendingEmission`)
544
+ - `protocolFeeBps(): Promise<number>`
545
+ - `carryWinnerNativePool(): Promise<bigint>`
546
+ - `carryStakerNativeOwed(): Promise<bigint>`
547
+ - `carryJackpotNativeOwed(): Promise<bigint>`
548
+ - `carrySlvrPool(): Promise<bigint>`
549
+ - `getRoundSquares(roundId: bigint): Promise<Array<{square: number, total: bigint, bettors: bigint}>>`
550
+ - `getUserBets(roundId: bigint, user: Address): Promise<Array<{square: number, amount: bigint}>>`
551
+
552
+ #### Write Methods
553
+
554
+ - `bet(params: BetParams): Promise<0x${string}>`
555
+ - `claim(params: ClaimParams): Promise<0x${string}>`
556
+ - `claimAdvanced(params: ClaimParamsAdvanced): Promise<0x${string}>`
557
+ - `batchClaim(roundIds: bigint[], user?: Address, options?: { waitForReceipt?: boolean }): Promise<0x${string}[]>`
558
+ - `approveDelegate(delegate: Address): Promise<0x${string}>`
559
+ - `revokeDelegate(delegate: Address): Promise<0x${string}>`
560
+ - `donateSlvrToJackpot(amount: bigint): Promise<0x${string}>`
561
+ - `addEthToJackpot(value: bigint): Promise<0x${string}>`
562
+ - `withdrawUnrefinedSlvr(): Promise<0x${string}>` - cash out accumulated mined SLVR (net of the refining fee)
563
+ - `checkpoint(account: Address): Promise<0x${string}>` - force on-chain settlement of a miner's refined-reward accrual (rarely needed; `claim`/`withdrawUnrefinedSlvr` do it automatically)
564
+
565
+ ### SlvrStaking (`SlvrVoteEscrowStaking`)
566
+
567
+ veNFT-based staker. All operations are keyed by veNFT `tokenId`.
568
+
569
+ #### Read Methods
570
+
571
+ - `getStakerRewards(tokenId: bigint): Promise<bigint>` - claimable rewards for a token
572
+ - `getTotalWeight(): Promise<bigint>`
573
+ - `totalWeight(): Promise<bigint>`
574
+ - `balance(tokenId: bigint): Promise<bigint>` - a token's tracked weight
575
+ - `rewards(tokenId: bigint): Promise<bigint>`
576
+ - `rewardPerWeightStored(): Promise<bigint>`
577
+ - `rewardPerWeightPaid(tokenId: bigint): Promise<bigint>`
578
+ - `unallocated(): Promise<bigint>`
579
+ - `lastDistributedRoundId(): Promise<bigint>`
580
+ - `lottery(): Promise<Address>`
581
+ - `getStakingInfo(tokenId: bigint): Promise<StakingInfo>`
582
+
583
+ #### Write Methods
584
+
585
+ - `stake(tokenId: bigint): Promise<0x${string}>`
586
+ - `unstake(tokenId: bigint): Promise<0x${string}>`
587
+ - `claimStakerRewards(tokenId: bigint): Promise<0x${string}>`
588
+ - `checkpoint(tokenId: bigint): Promise<0x${string}>`
589
+ - `poke(tokenId: bigint): Promise<0x${string}>`
590
+ - `pokeMany(tokenIds: bigint[]): Promise<0x${string}>`
591
+
592
+ ### SlvrHub (optional, read-only)
593
+
594
+ Emission/sink router. Available as `sdk.hub` when a `hub` address is configured.
595
+ Informational reads only — the hub's fee-routing writes are keeper/protocol
596
+ operations and are not part of the SDK.
597
+
598
+ #### Read Methods
599
+
600
+ - `pendingEmission(gameId: bigint): Promise<bigint>`
601
+ - `emissionRatePerSec(): Promise<bigint>`
602
+ - `targetSupply(): Promise<bigint>`
603
+ - `maxAccrualSeconds(): Promise<bigint>`
604
+ - `staking(): Promise<Address>`
605
+ - `jackpot(): Promise<Address>`
606
+ - `stakerSeq(): Promise<bigint>`
607
+ - `pendingStakerRewards(): Promise<bigint>`
608
+
609
+ ### SlvrGameRegistry (optional)
610
+
611
+ Read-only game registry. Available as `sdk.registry` when a `registry` address is configured.
612
+
613
+ #### Read Methods
614
+
615
+ - `gameIdOf(game: Address): Promise<bigint>`
616
+ - `gameInfo(gameId: bigint): Promise<GameInfo>`
617
+ - `isActive(game: Address): Promise<boolean>`
618
+ - `statusOf(gameId: bigint): Promise<GameStatus>`
619
+ - `tierOf(gameId: bigint): Promise<GameTier>`
620
+ - `weightOf(gameId: bigint): Promise<bigint>`
621
+ - `maxWeightBpsOf(gameId: bigint): Promise<number>`
622
+ - `totalActiveWeight(): Promise<bigint>`
623
+ - `gameCount(): Promise<bigint>`
624
+
625
+ ### SlvrJackpot (optional)
626
+
627
+ Read-only jackpot pool. Available as `sdk.jackpot` when a `jackpot` address is configured.
628
+
629
+ #### Read Methods
630
+
631
+ - `jackpotPool(): Promise<bigint>` - native (ETH) pool balance
632
+ - `jackpotSlvrPool(): Promise<bigint>` - SLVR pool balance
633
+
634
+ ### SlvrToken
635
+
636
+ Interface for the SLVR token contract.
637
+
638
+ #### Read Methods
639
+
640
+ - `totalSupply(): Promise<bigint>`
641
+ - `balanceOf(account: Address): Promise<bigint>`
642
+ - `allowance(owner: Address, spender: Address): Promise<bigint>`
643
+ - `maxSupply(): Promise<bigint>`
644
+
645
+ #### Write Methods
646
+
647
+ - `transfer(to: Address, amount: bigint): Promise<0x${string}>`
648
+ - `approve(spender: Address, amount: bigint): Promise<0x${string}>`
649
+ - `transferFrom(from: Address, to: Address, amount: bigint): Promise<0x${string}>`
650
+ - `burn(amount: bigint): Promise<0x${string}>`
651
+
652
+ ### SlvrPrice (optional)
653
+
654
+ SLVR/ETH spot price reader. Available as `sdk.price` when a `slvrEthPair` address
655
+ is configured. Reads the UniswapV2 pair's reserves (resolving token order); spot
656
+ only, not a manipulation-resistant oracle.
657
+
658
+ #### Methods
659
+
660
+ - `getReserves(): Promise<{ slvrReserve: bigint, ethReserve: bigint, token0IsSlvr: boolean }>`
661
+ - `getPriceInEth(): Promise<number>` - ETH per SLVR
662
+ - `getPriceInEthWad(): Promise<bigint>` - ETH per SLVR as a 1e18-scaled bigint
663
+
664
+ ### ChainlinkPriceFeed (optional)
665
+
666
+ Reads a Chainlink-style `AggregatorV3Interface` (e.g. an ETH/USD feed). Available
667
+ as `sdk.ethUsd` when a `chainlinkEthUsd` address is configured — wired for
668
+ Robinhood Chain in `deployments.robinhood`.
669
+
670
+ #### Methods
671
+
672
+ - `getPrice(): Promise<number>` - feed answer scaled by its decimals
673
+ - `getRoundData(): Promise<{ answer: bigint, decimals: number, updatedAt: bigint }>`
674
+ - Constructor accepts `{ maxStalenessSec }` to reject stale answers.
675
+
676
+ ### Expected-value math
677
+
678
+ Pure functions and constants (no I/O):
679
+
680
+ - `computeGridMiningEv(input: GridMiningEvInput): GridMiningEv` - per-round EV breakdown
681
+ - Constants: `GRID_SIZE` (25), `SINGLE_SQUARE_WIN_PROBABILITY` (1/25), `PROTOCOL_FEE_BPS` (1000), `REFINING_FEE_BPS` (1000), `JACKPOT_ODDS` (625)
682
+
683
+ ## Types
684
+
685
+ All TypeScript types are exported from the main module:
686
+
687
+ ```typescript
688
+ import type {
689
+ SlvrConfig,
690
+ RoundInfo,
691
+ MinerState,
692
+ TreasuryState,
693
+ StakingInfo,
694
+ AutoCommitPlan,
695
+ GameInfo,
696
+ BetParams,
697
+ ClaimParams,
698
+ ClaimParamsAdvanced,
699
+ GridMiningEvInput,
700
+ GridMiningEv,
701
+ SlvrReserves,
702
+ PriceQuote,
703
+ SlvrDeployment,
704
+ } from '@slvr-labs/sdk';
705
+
706
+ // GameStatus and GameTier are enums (runtime values), so import them as values:
707
+ import { GameStatus, GameTier } from '@slvr-labs/sdk';
708
+ ```
709
+
710
+ The `StakingInfo` shape is now `{ totalWeight, balance, rewards, rewardPerWeightStored }`
711
+ (all `bigint`), reflecting the veNFT weight-based staker.
712
+
713
+ ## Deployments
714
+
715
+ The SDK ships canonical addresses and a ready-made viem chain so you don't have
716
+ to copy either around:
717
+
718
+ ```typescript
719
+ import { deployments, robinhoodChain } from '@slvr-labs/sdk';
720
+
721
+ deployments.robinhood.chainId; // 4663
722
+ deployments.robinhood.rpcUrl; // https://rpc.mainnet.chain.robinhood.com
723
+ deployments.robinhood.blockExplorer; // https://robinhoodchain.blockscout.com/
724
+ deployments.robinhood.subgraphUrl; // hosted subgraph GraphQL endpoint
725
+ deployments.robinhood.addresses; // { lottery, staking, token, autoCommit, voteEscrow, slvrEthPair, chainlinkEthUsd, multicall3 }
726
+
727
+ robinhoodChain; // a viem `Chain` for createPublicClient/createWalletClient
728
+ ```
729
+
730
+ Types: `SlvrDeployment` describes the shape. To target a local or custom
731
+ deployment, construct the SDK with your own `addresses` object instead.
732
+
733
+ ### Batched reads (multicall)
734
+
735
+ `robinhoodChain` registers **Multicall3**, so the SDK's multi-square reads
736
+ (`getRoundSquares`, `getUserBets`) collapse from ~50 RPC calls into **one**. For
737
+ even more batching, create your client with `batch: { multicall: true }` — viem
738
+ then auto-batches every concurrent read (great for polling bots):
739
+
740
+ ```typescript
741
+ const publicClient = createPublicClient({
742
+ chain: robinhoodChain,
743
+ transport: http(),
744
+ batch: { multicall: true },
745
+ });
746
+ ```
747
+
748
+ On a custom chain without Multicall3 configured, these methods transparently fall
749
+ back to individual reads.
750
+
751
+ ## Build with an AI agent (Claude Code / Codex)
752
+
753
+ The package ships an **agent skill** at
754
+ [`skills/slvr-bot/`](./skills/slvr-bot/SKILL.md) so a coding agent can build SLVR
755
+ bots for you correctly — it knows the API, addresses, EV math, and gotchas.
756
+
757
+ - **Claude Code:** copy `skills/slvr-bot/` into your project's `.claude/skills/`
758
+ (or reference `node_modules/@slvr-labs/sdk/skills/slvr-bot/`), then ask e.g.
759
+ *"build me a bot that grid-mines SLVR only on +EV rounds."*
760
+ - **Codex / other agents:** the skill is plain Markdown — point your `AGENTS.md`
761
+ at `node_modules/@slvr-labs/sdk/skills/slvr-bot/SKILL.md`, or paste it into context.
762
+
763
+ ## Examples
764
+
765
+ The package ships runnable examples in the [`examples/`](./examples) directory
766
+ (also included in the published tarball). Copy the file you want into your project
767
+ and change the relative `../src` import to `@slvr-labs/sdk`.
768
+
769
+ **New here? Start with these two:**
770
+
771
+ | File | What it shows |
772
+ | --- | --- |
773
+ | [`quickstart-read.ts`](./examples/quickstart-read.ts) | **Start here.** Read-only tour — no wallet. Prints the round, pot, and SLVR price in ETH + USD |
774
+ | [`quickstart-bet.ts`](./examples/quickstart-bet.ts) | Minimal wallet flow — EV-check, place one bet, then claim |
775
+
776
+ Run them from `sdk/ts` with `ts-node` (fetched on demand via `npx`):
777
+
778
+ ```bash
779
+ npm install
780
+ npx ts-node examples/quickstart-read.ts # read-only, no key needed
781
+
782
+ # Examples that bet read your key from PRIVATE_KEY — set it inline for the command:
783
+ PRIVATE_KEY=0xabc... npx ts-node examples/quickstart-bet.ts
784
+ ```
785
+
786
+ Use a **burner key** funded with a little ETH on Robinhood Chain — never a key with
787
+ real value, and never commit it. See the [examples README](./examples/README.md)
788
+ for the full run/setup guide (including `.env` usage). Then the strategy references:
789
+
790
+ | File | What it shows |
791
+ | --- | --- |
792
+ | [`simple-example.ts`](./examples/simple-example.ts) | Minimal read + single bet |
793
+ | [`strategy-base.ts`](./examples/strategy-base.ts) | `BettingStrategy` base class to extend |
794
+ | [`least-allocated-strategy.ts`](./examples/least-allocated-strategy.ts) | Bet the least-crowded squares |
795
+ | [`fixed-squares-strategy.ts`](./examples/fixed-squares-strategy.ts) | Always bet a fixed set of squares |
796
+ | [`combined-strategy.ts`](./examples/combined-strategy.ts) | Threshold + least-allocated bot loop |
797
+ | [`automated-betting.ts`](./examples/automated-betting.ts) | Long-running automated betting bot |
798
+ | [`expected-value-strategy.ts`](./examples/expected-value-strategy.ts) | **EV bot** — bets only when the round is +EV, using the grid-mining calculator + SLVR price |
799
+ | [`custom-strategy-example.ts`](./examples/custom-strategy-example.ts) | Several custom strategy variants |
800
+
801
+ See the [examples README](./examples/README.md) for a deeper walkthrough.
802
+
803
+ ### Reference: Automated Betting Bot
804
+
805
+ The snippets below are copied from the examples; after copying a file into your
806
+ own project, import the SDK from `@slvr-labs/sdk` (not `../src`).
807
+
808
+ ```typescript
809
+ import { CombinedBettingBot } from './combined-strategy'; // your copied file
810
+ import { parseEther } from 'viem';
811
+
812
+ // Create a bot that bets when round has < 100 ETH
813
+ // and targets squares with least allocation
814
+ const bot = new CombinedBettingBot(sdk, {
815
+ threshold: parseEther('100'), // Bet when round < 100 ETH
816
+ betAmount: parseEther('5'), // Bet 5 ETH total
817
+ squareCount: 5, // Bet on 5 least allocated squares
818
+ checkInterval: 5000, // Check every 5 seconds
819
+ });
820
+
821
+ await bot.start();
822
+ // Bot will automatically monitor and bet
823
+ ```
824
+
825
+ ### Reference: Least Allocated Strategy
826
+
827
+ ```typescript
828
+ import { LeastAllocatedStrategy } from './least-allocated-strategy'; // your copied file
829
+
830
+ const strategy = new LeastAllocatedStrategy(sdk);
831
+ const roundId = await sdk.lottery.currentRoundId();
832
+
833
+ // Get squares with least allocation
834
+ const leastAllocated = await strategy.getLeastAllocatedSquares(roundId, 5);
835
+
836
+ // Place bet on these squares
837
+ const txHash = await strategy.betOnLeastAllocated(
838
+ roundId,
839
+ parseEther('5'), // 5 ETH total
840
+ 5 // Bet on 5 squares
841
+ );
842
+ ```
843
+
844
+ See the [examples README](./examples/README.md) for more detailed examples and strategies.
845
+
846
+ ## License
847
+
848
+ MIT
849
+