@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,283 @@
1
+ /**
2
+ * Base strategy class for creating custom betting strategies
3
+ *
4
+ * This provides a flexible framework for implementing betting strategies.
5
+ * Extend this class and override methods to customize behavior.
6
+ */
7
+
8
+ import { SlvrSDK } from '../src';
9
+ import { parseEther } from 'viem';
10
+
11
+ // Re-export for convenience
12
+ export { SlvrSDK };
13
+
14
+ /**
15
+ * Configuration for a betting strategy
16
+ */
17
+ export interface StrategyConfig {
18
+ /** Threshold in wei - only bet when round total wager is below this */
19
+ threshold?: bigint;
20
+ /** How often to check rounds (in milliseconds) */
21
+ checkInterval?: number;
22
+ /** Whether to start checking immediately */
23
+ startImmediately?: boolean;
24
+ }
25
+
26
+ /**
27
+ * Result of evaluating whether to bet
28
+ */
29
+ export interface BetDecision {
30
+ shouldBet: boolean;
31
+ reason?: string;
32
+ }
33
+
34
+ /**
35
+ * Base class for betting strategies
36
+ *
37
+ * Override methods to customize behavior:
38
+ * - `shouldBet()` - Determine if conditions are met to place a bet
39
+ * - `selectSquares()` - Choose which squares to bet on
40
+ * - `calculateAmounts()` - Calculate bet amounts for each square
41
+ * - `onBetPlaced()` - Called after a successful bet
42
+ * - `onError()` - Called when an error occurs
43
+ */
44
+ export abstract class BettingStrategy {
45
+ protected sdk: SlvrSDK;
46
+ protected config: Required<StrategyConfig>;
47
+ protected isRunning: boolean = false;
48
+ protected intervalId?: NodeJS.Timeout;
49
+ protected currentRoundId?: bigint;
50
+
51
+ constructor(sdk: SlvrSDK, config: StrategyConfig = {}) {
52
+ this.sdk = sdk;
53
+ this.config = {
54
+ threshold: config.threshold ?? parseEther('100'),
55
+ checkInterval: config.checkInterval ?? 5000,
56
+ startImmediately: config.startImmediately ?? true,
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Start the strategy
62
+ */
63
+ async start(): Promise<void> {
64
+ if (this.isRunning) {
65
+ console.log('Strategy is already running');
66
+ return;
67
+ }
68
+
69
+ if (!this.sdk.getWalletClient()) {
70
+ throw new Error('Wallet client required for betting strategies');
71
+ }
72
+
73
+ this.isRunning = true;
74
+ console.log('🚀 Strategy started');
75
+ this.logConfig();
76
+
77
+ if (this.config.startImmediately) {
78
+ await this.checkAndBet();
79
+ }
80
+
81
+ this.intervalId = setInterval(async () => {
82
+ await this.checkAndBet();
83
+ }, this.config.checkInterval);
84
+ }
85
+
86
+ /**
87
+ * Stop the strategy
88
+ */
89
+ stop(): void {
90
+ if (!this.isRunning) {
91
+ return;
92
+ }
93
+
94
+ this.isRunning = false;
95
+ if (this.intervalId) {
96
+ clearInterval(this.intervalId);
97
+ this.intervalId = undefined;
98
+ }
99
+ console.log('🛑 Strategy stopped');
100
+ }
101
+
102
+ /**
103
+ * Check if strategy is running
104
+ */
105
+ get running(): boolean {
106
+ return this.isRunning;
107
+ }
108
+
109
+ /**
110
+ * Main loop: check round and place bet if conditions are met
111
+ */
112
+ protected async checkAndBet(): Promise<void> {
113
+ try {
114
+ const roundId = await this.sdk.lottery.currentRoundId();
115
+ this.currentRoundId = roundId;
116
+
117
+ // Check if round is open
118
+ const isOpen = await this.sdk.lottery.roundOpen(roundId);
119
+ if (!isOpen) {
120
+ const timeRemaining = await this.sdk.getTimeRemaining(roundId);
121
+ if (timeRemaining > 0) {
122
+ this.onRoundNotOpen(roundId, timeRemaining);
123
+ } else {
124
+ this.onRoundEnded(roundId);
125
+ }
126
+ return;
127
+ }
128
+
129
+ // Get round data
130
+ const round = await this.sdk.lottery.getRound(roundId);
131
+
132
+ // Check threshold
133
+ if (round.totalWager >= this.config.threshold) {
134
+ this.onThresholdNotMet(roundId, round.totalWager);
135
+ return;
136
+ }
137
+
138
+ // Evaluate if we should bet (custom logic)
139
+ const decision = await this.shouldBet(roundId, round);
140
+ if (!decision.shouldBet) {
141
+ this.onBetSkipped(roundId, decision.reason);
142
+ return;
143
+ }
144
+
145
+ // Select squares to bet on (custom logic)
146
+ const squares = await this.selectSquares(roundId, round);
147
+ if (squares.length === 0) {
148
+ this.onBetSkipped(roundId, 'No squares selected');
149
+ return;
150
+ }
151
+
152
+ // Calculate amounts (custom logic)
153
+ const amounts = await this.calculateAmounts(roundId, round, squares);
154
+ if (amounts.length !== squares.length) {
155
+ throw new Error('Amounts array length must match squares array length');
156
+ }
157
+
158
+ // Place the bet
159
+ this.logBetDetails(roundId, squares, amounts, round);
160
+ const txHash = await this.sdk.lottery.bet({
161
+ roundId,
162
+ squares,
163
+ amounts,
164
+ });
165
+
166
+ await this.onBetPlaced(roundId, txHash, squares, amounts);
167
+ } catch (error) {
168
+ await this.onError(error);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Override this to implement custom bet decision logic
174
+ * @param roundId Current round ID
175
+ * @param round Round data
176
+ * @returns Decision whether to bet
177
+ */
178
+ protected async shouldBet(roundId: bigint, round: any): Promise<BetDecision> {
179
+ // Default: bet if threshold is met
180
+ return { shouldBet: true };
181
+ }
182
+
183
+ /**
184
+ * Override this to implement custom square selection
185
+ * @param roundId Current round ID
186
+ * @param round Round data
187
+ * @returns Array of square indices (0-24) to bet on
188
+ */
189
+ protected abstract selectSquares(roundId: bigint, round: any): Promise<number[]>;
190
+
191
+ /**
192
+ * Override this to implement custom amount calculation
193
+ * @param roundId Current round ID
194
+ * @param round Round data
195
+ * @param squares Selected squares
196
+ * @returns Array of bet amounts (one per square, in wei)
197
+ */
198
+ protected abstract calculateAmounts(
199
+ roundId: bigint,
200
+ round: any,
201
+ squares: number[]
202
+ ): Promise<bigint[]>;
203
+
204
+ /**
205
+ * Called after a successful bet
206
+ * Override to add custom logic (logging, notifications, etc.)
207
+ */
208
+ protected async onBetPlaced(
209
+ roundId: bigint,
210
+ txHash: `0x${string}`,
211
+ squares: number[],
212
+ amounts: bigint[]
213
+ ): Promise<void> {
214
+ console.log(`✅ Bet placed! Round ${roundId}, TX: ${txHash}`);
215
+ }
216
+
217
+ /**
218
+ * Called when an error occurs
219
+ * Override to add custom error handling
220
+ */
221
+ protected async onError(error: unknown): Promise<void> {
222
+ console.error('❌ Error in strategy:', error);
223
+ if (error instanceof Error) {
224
+ console.error(' Message:', error.message);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Called when round is not open yet
230
+ */
231
+ protected onRoundNotOpen(roundId: bigint, timeRemaining: number): void {
232
+ // Override for custom behavior
233
+ }
234
+
235
+ /**
236
+ * Called when round has ended
237
+ */
238
+ protected onRoundEnded(roundId: bigint): void {
239
+ // Override for custom behavior
240
+ }
241
+
242
+ /**
243
+ * Called when threshold is not met
244
+ */
245
+ protected onThresholdNotMet(roundId: bigint, totalWager: bigint): void {
246
+ // Override for custom behavior
247
+ }
248
+
249
+ /**
250
+ * Called when bet is skipped
251
+ */
252
+ protected onBetSkipped(roundId: bigint, reason?: string): void {
253
+ // Override for custom behavior
254
+ }
255
+
256
+ /**
257
+ * Log configuration
258
+ */
259
+ protected logConfig(): void {
260
+ console.log(` Threshold: ${SlvrSDK.formatToken(this.config.threshold)} ETH`);
261
+ console.log(` Check interval: ${this.config.checkInterval / 1000}s`);
262
+ }
263
+
264
+ /**
265
+ * Log bet details before placing
266
+ */
267
+ protected logBetDetails(
268
+ roundId: bigint,
269
+ squares: number[],
270
+ amounts: bigint[],
271
+ round: any
272
+ ): void {
273
+ const total = amounts.reduce((sum, amt) => sum + amt, 0n);
274
+ console.log(`\n🎯 Round ${roundId} - Placing bet:`);
275
+ console.log(` Total wager: ${SlvrSDK.formatToken(round.totalWager)} ETH`);
276
+ console.log(` Squares: ${squares.join(', ')}`);
277
+ squares.forEach((square, i) => {
278
+ console.log(` Square ${square}: ${SlvrSDK.formatToken(amounts[i])} ETH`);
279
+ });
280
+ console.log(` Total bet: ${SlvrSDK.formatToken(total)} ETH`);
281
+ }
282
+ }
283
+
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@slvr-labs/sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for interacting with the Slvr protocol on Robinhood Chain",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "module": "dist/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "examples",
18
+ "skills",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "prepublishOnly": "npm run build",
25
+ "clean": "rm -rf dist",
26
+ "typecheck": "tsc --noEmit",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "mirror": "bash scripts/publish-mirror.sh"
30
+ },
31
+ "keywords": [
32
+ "slvr",
33
+ "blockchain",
34
+ "ethereum",
35
+ "robinhood",
36
+ "lottery",
37
+ "staking",
38
+ "defi",
39
+ "sdk",
40
+ "typescript"
41
+ ],
42
+ "author": "slvr-dev",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/slvr-fun/sdk.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/slvr-fun/sdk/issues"
50
+ },
51
+ "homepage": "https://github.com/slvr-fun/sdk#readme",
52
+ "engines": {
53
+ "node": ">=18.0.0"
54
+ },
55
+ "dependencies": {
56
+ "viem": "^2.40.3"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^20",
60
+ "typescript": "^5.0.0",
61
+ "vitest": "^2.1.9"
62
+ },
63
+ "peerDependencies": {
64
+ "viem": "^2.0.0"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "viem": {
68
+ "optional": false
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: building-slvr-bots
3
+ description: >-
4
+ Build bots, scripts, keepers, or dApps that interact with SLVR — the on-chain
5
+ grid lottery / grid-mining game on Robinhood Chain — using the @slvr-labs/sdk
6
+ TypeScript SDK (built on viem). Use this whenever the user wants to place bets,
7
+ claim winnings, "grid mine" SLVR, compute expected value (EV) of a round, read
8
+ the SLVR price, stake veNFTs, automate an executor/keeper, or otherwise script
9
+ anything involving SLVR, the SLVR grid lottery, @slvr-labs/sdk, or Robinhood
10
+ Chain (chain id 4663) — even if the user doesn't name the SDK explicitly.
11
+ ---
12
+
13
+ # Building SLVR bots with @slvr-labs/sdk
14
+
15
+ SLVR is an on-chain game on **Robinhood Chain** (EVM, chain id `4663`). Each
16
+ round has a **5×5 grid (25 squares)**. Players bet native ETH on squares; when the
17
+ round resolves, one square wins **uniformly at random** (`randomnessValue % 25`),
18
+ and that square's bettors split the ETH pot (pro-rata) plus freshly minted **SLVR**
19
+ ("grid mining"). `@slvr-labs/sdk` wraps all of this with a typed, `viem`-based API.
20
+
21
+ This skill teaches you to build against it correctly. For the exhaustive method
22
+ list, read [`references/api.md`](references/api.md). For runnable end-to-end
23
+ programs, read the SDK's `examples/` (`quickstart-read.ts`, `quickstart-bet.ts`,
24
+ `expected-value-strategy.ts`).
25
+
26
+ ## Setup
27
+
28
+ ```bash
29
+ npm install @slvr-labs/sdk viem
30
+ ```
31
+
32
+ The SDK **ships the chain and addresses** — never hand-roll `defineChain` or paste
33
+ contract addresses. Fastest path is `SlvrSDK.connect` (builds resilient clients
34
+ with Multicall3 batching + retries):
35
+
36
+ ```typescript
37
+ import { SlvrSDK } from '@slvr-labs/sdk';
38
+ const sdk = SlvrSDK.connect(); // read-only
39
+ const bot = SlvrSDK.connect({ privateKey: process.env.PK }); // wallet-backed
40
+ ```
41
+
42
+ Or construct it yourself — read-only needs just a public client; add a wallet
43
+ client for transactions:
44
+
45
+ ```typescript
46
+ import { createPublicClient, createWalletClient, http } from 'viem';
47
+ import { privateKeyToAccount } from 'viem/accounts';
48
+ import { SlvrSDK, robinhoodChain, deployments } from '@slvr-labs/sdk';
49
+
50
+ // Read-only (no key needed — reads only):
51
+ const publicClient = createPublicClient({ chain: robinhoodChain, transport: http() });
52
+ const sdk = new SlvrSDK({ publicClient, addresses: deployments.robinhood.addresses });
53
+
54
+ // Wallet-backed (for bet/claim/stake/etc.):
55
+ const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
56
+ const walletClient = createWalletClient({ chain: robinhoodChain, transport: http(), account });
57
+ const sdkW = new SlvrSDK({ publicClient, walletClient, addresses: deployments.robinhood.addresses });
58
+ ```
59
+
60
+ `deployments.robinhood.addresses` covers `lottery`, `staking` (the veNFT staker),
61
+ `token`, `autoCommit`, `voteEscrow`, `slvrEthPair`, and `chainlinkEthUsd`. Pass your
62
+ own `addresses` object to target a custom/local deployment.
63
+
64
+ **Value/units:** on-chain amounts are `bigint` **wei** — use viem's `parseEther`
65
+ (ETH string → wei) and `formatEther` (wei → string) at the boundaries. Squares are
66
+ `0`–`24`. Write methods require the wallet client and return a tx hash; wait for it
67
+ with `publicClient.waitForTransactionReceipt`.
68
+
69
+ ## Recipe: read the current round
70
+
71
+ ```typescript
72
+ const roundId = await sdk.lottery.currentRoundId();
73
+ const [open, squares] = await Promise.all([
74
+ sdk.lottery.roundOpen(roundId),
75
+ sdk.lottery.getRoundSquares(roundId), // [{ square, total, bettors }] × 25
76
+ ]);
77
+ const pot = squares.reduce((sum, s) => sum + s.total, 0n);
78
+ ```
79
+
80
+ Gate "can I still bet?" on **`bettingEnd(roundId)`** (a unix-seconds timestamp),
81
+ not on `roundEnd` — betting can close before the round ends.
82
+
83
+ ## Recipe: place a bet
84
+
85
+ Bets are paid in **native ETH** (the SDK sends `value` = sum of `amounts`). **No
86
+ token approval is needed.** Spread a stake across one or more squares:
87
+
88
+ ```typescript
89
+ import { parseEther } from 'viem';
90
+ const txHash = await sdkW.lottery.bet({
91
+ roundId,
92
+ squares: [3, 7, 12],
93
+ amounts: [parseEther('0.001'), parseEther('0.001'), parseEther('0.001')],
94
+ });
95
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
96
+ ```
97
+
98
+ Confirm it landed with `sdk.lottery.getUserBets(roundId, address)`.
99
+
100
+ ## Recipe: claim winnings
101
+
102
+ You can only claim a round you **won** (held the winning square) and haven't already
103
+ claimed. Let the SDK check for you:
104
+
105
+ ```typescript
106
+ if (await sdk.canClaim(roundId, account.address)) {
107
+ await sdkW.lottery.claim({ roundId });
108
+ }
109
+ // Multiple rounds:
110
+ const claimable = await sdk.getClaimableRounds(account.address, fromRound, toRound);
111
+ if (claimable.length) await sdkW.lottery.batchClaim(claimable);
112
+ ```
113
+
114
+ To realize accumulated **mined SLVR** (separate from a round claim), use
115
+ `sdkW.lottery.withdrawUnrefinedSlvr()` (pays out SLVR net of the refining fee).
116
+
117
+ ## Recipe: SLVR price in ETH and USD
118
+
119
+ SLVR/ETH comes from the UniswapV2 pair; USD uses the on-chain Chainlink ETH/USD
120
+ feed (wired for Robinhood Chain).
121
+
122
+ ```typescript
123
+ const { eth, usd } = await sdk.getSlvrPrice(); // { eth: ETH per SLVR, usd: number | null }
124
+ const ethUsd = await sdk.getEthPriceUsd(); // Chainlink ETH/USD
125
+ ```
126
+
127
+ `usd` is `null` only if there's no ETH/USD source; you can also pass one:
128
+ `sdk.getSlvrPrice({ ethUsd: 1815 })`.
129
+
130
+ ## Recipe: bet only when the round is +EV (grid mining)
131
+
132
+ This is the key strategy. Because the winning square is uniform, mining SLVR is
133
+ profitable **only while the pot is small** — your SLVR reward must beat the ETH you
134
+ bleed to the protocol fee. The SDK ships the exact edge math:
135
+
136
+ ```typescript
137
+ const ev = await sdk.estimateRoundEv({ stake: 0.1 /* ETH */, cashOut: false });
138
+ // ev: { share, ethBleed, slvrMined, slvrValueEth, jackpotEvEth, netEth,
139
+ // edgeRatio, breakEvenPot, breakEvenSlvrPriceEth, profitable }
140
+ if (ev.profitable) {
141
+ // pot is below break-even → mine this round
142
+ }
143
+ ```
144
+
145
+ `estimateRoundEv` pulls pot/emission/price on-chain. For a pure calculation (no
146
+ I/O), use `computeGridMiningEv({ stake, pot, emissionPerRound, slvrPriceEth, ... })`.
147
+ Mental model: **ETH is ~a wash minus the ~10% fee; SLVR emission is the real
148
+ yield; bet when `pot < ev.breakEvenPot`.** Bet size cancels out — the edge depends
149
+ on pot vs SLVR price, not how much you bet.
150
+
151
+ See `examples/expected-value-strategy.ts` for a complete EV bot loop.
152
+
153
+ ## Recipe: veNFT staking
154
+
155
+ Staking is by veNFT **tokenId** (not a raw ERC20 amount):
156
+
157
+ ```typescript
158
+ await sdkW.staking.stake(tokenId);
159
+ const pending = await sdk.staking.getStakerRewards(tokenId);
160
+ await sdkW.staking.claimStakerRewards(tokenId);
161
+ await sdkW.staking.unstake(tokenId);
162
+ ```
163
+
164
+ ## Gotchas that will bite you
165
+
166
+ - **Bets are native ETH**, not an ERC20 — no `approve` step; the SDK attaches `value`.
167
+ - **Gate on `bettingEnd`**, not `roundEnd`. A closed round's `bet()` reverts.
168
+ - **`getUserBet` returns 0 after a claim** (the bet is zeroed on claim). To tell
169
+ "didn't bet" from "already claimed", pair it with `getHasClaimed(roundId, user)`.
170
+ - **Emission is hub-gated**: `slvrPerRound` is a *target*; actual minted SLVR can be
171
+ lower. Treat EV as an estimate, not a guarantee.
172
+ - **Read-only vs write**: reads work without a wallet client; any write throws
173
+ `WalletClientRequiredError` if you forgot to pass one.
174
+ - The SDK exposes **public-use** methods only. Keeper/admin plumbing (round
175
+ resolution, fee routing) is intentionally not included.
176
+
177
+ ## Building a long-running bot
178
+
179
+ 1. Poll `currentRoundId` on an interval (5–15s is plenty).
180
+ 2. Skip rounds where `!roundOpen(roundId)` or you've already bet this round.
181
+ 3. Decide with `estimateRoundEv` (or your own strategy over `getRoundSquares`).
182
+ 4. Size + place the bet, wait for the receipt, log it.
183
+ 5. Periodically sweep `getClaimableRounds` → `batchClaim`.
184
+ Handle RPC errors per-tick (try/catch) so one failure doesn't kill the loop, and
185
+ start with tiny stakes on a funded burner key.
186
+
187
+ ## Using this skill with Codex (or any agent)
188
+
189
+ `SKILL.md` is plain, self-contained Markdown. To use it outside Claude Code, point
190
+ your agent at this file — e.g. reference `node_modules/@slvr-labs/sdk/skills/slvr-bot/SKILL.md`
191
+ from your `AGENTS.md`, or paste its contents into the agent's context. The
192
+ [`references/api.md`](references/api.md) file is the full method reference to load
193
+ when you need a signature you don't see above.
@@ -0,0 +1,117 @@
1
+ # @slvr-labs/sdk — API reference
2
+
3
+ Load this when you need a signature not shown in SKILL.md. All ETH/token amounts
4
+ are `bigint` **wei** unless noted; convert with viem `parseEther`/`formatEther`.
5
+ Squares are `0`–`24`. Write methods need a wallet client and return a tx hash
6
+ (`0x${string}`); wait with `publicClient.waitForTransactionReceipt`.
7
+
8
+ ## Contents
9
+ - [Construction](#construction)
10
+ - [Deployments & chain](#deployments--chain)
11
+ - [SDK-level helpers](#sdk-level-helpers)
12
+ - [lottery](#sdklottery)
13
+ - [token](#sdktoken)
14
+ - [staking (veNFT)](#sdkstaking-venft)
15
+ - [autoCommit (V2)](#sdkautocommit-v2)
16
+ - [price / ethUsd](#sdkprice--sdkethusd)
17
+ - [Expected-value math](#expected-value-math-pure)
18
+ - [Optional read-only: hub / registry / jackpot](#optional-read-only-modules)
19
+ - [Events](#events)
20
+
21
+ ## Construction
22
+ ```typescript
23
+ // Fastest — builds resilient clients (multicall batching, timeouts, retries):
24
+ SlvrSDK.connect(opts?: ConnectOptions): SlvrSDK // opts: { deployment?, rpcUrl?, privateKey?, account?, batchMulticall?, pollingInterval? }
25
+ createSlvrClients(opts?): { chain, publicClient, walletClient? } // if you want the clients yourself
26
+
27
+ // Or construct directly:
28
+ new SlvrSDK({ publicClient, walletClient?, addresses })
29
+ ```
30
+ - `addresses` shape: `{ lottery, staking, token, autoCommit?, voteEscrow?, slvrEthPair?, chainlinkEthUsd?, multicall3?, hub?, registry?, jackpot? }`
31
+ - **Batching:** `robinhoodChain` registers Multicall3, so `getRoundSquares`/`getUserBets` are one RPC call each. For polling bots, create the client with `batch: { multicall: true }` to auto-batch all concurrent reads.
32
+ - Use `deployments.robinhood.addresses` for mainnet.
33
+ - `sdk.getPublicClient()`, `sdk.getWalletClient()`, `sdk.setWalletClient(wc)`.
34
+ - Static utils: `SlvrSDK.formatToken(value, decimals?, precision?)`, `SlvrSDK.parseToken(str, decimals?)`, `SlvrSDK.calculateBetAmounts(total, percentages[])`.
35
+
36
+ ## Deployments & chain
37
+ - `deployments.robinhood`: `{ chainId: 4663, name, rpcUrl, blockExplorer, subgraphUrl, addresses }`.
38
+ - `robinhoodChain`: a ready-made viem `Chain`.
39
+ - `SlvrDeployment` type describes the shape.
40
+
41
+ ## SDK-level helpers
42
+ - `getTimeRemaining(roundId): Promise<number>` — seconds until round end (0 if past).
43
+ - `canClaim(roundId, user): Promise<boolean>`.
44
+ - `getClaimableRounds(user, startRoundId, endRoundId): Promise<bigint[]>`.
45
+ - `getSlvrPriceInEth(): Promise<number>` — ETH per SLVR (needs `slvrEthPair`).
46
+ - `getSlvrPrice(opts?: { ethUsd?: number }): Promise<{ eth: number, usd: number | null }>`.
47
+ - `getEthPriceUsd(): Promise<number>` — Chainlink feed (needs `chainlinkEthUsd`).
48
+ - `estimateRoundEv(params): Promise<GridMiningEv>` — see [EV](#expected-value-math-pure).
49
+ - `effectiveEmissionRate(gameId)`, `pendingEmission(gameId)` — require `hub`(+`registry`).
50
+
51
+ ## sdk.lottery
52
+ Reads:
53
+ - `currentRoundId()`, `roundStart(id)`, `roundEnd(id)`, `roundOpen(id)`, `bettingEnd(id)`, `latestResolvedRoundId()`.
54
+ - `getRound(id): Promise<RoundInfo>` — flat struct: `{ roundId, requestedAt, resolved, randomnessId, randomnessValue, winningSquare, jackpotHit, singleMinerRound, singleMinerWinner, totalWager, fee, winnerTotal, potForWinners, slvrForWinners, payoutMulWad, slvrMulWad, totalUnclaimedSlvr }`.
55
+ - `getTotalOnSquare(id, square)`, `getBettorsOnSquare(id, square)`, `getUserBet(id, square, bettor)`, `getHasClaimed(id, user)`, `hasAccount(account)`, `getDelegate(user, delegate)`.
56
+ - `getExpectedReward(account, id)` — estimated reward (wei).
57
+ - `getMinerState(account): Promise<MinerState>` — `{ rewardsSlvr, refinedAccrued, indexSnapshot, hasAccount }`.
58
+ - `getRoundSquares(id): Promise<Array<{ square, total, bettors }>>` (25 entries).
59
+ - `getUserBets(id, user): Promise<Array<{ square, amount }>>` (only nonzero).
60
+ - `slvrPerRound()` (emission target — hub-gated), `protocolFeeBps()`, `grid()`, `hasAccount(account)`, carry pools (`carryWinnerNativePool`, `carryStakerNativeOwed`, `carryJackpotNativeOwed`, `carrySlvrPool`).
61
+
62
+ Writes:
63
+ - `bet({ roundId, squares, amounts, beneficiary? })` — native ETH; `amounts` sum is the `value`.
64
+ - `betFor(...)` via `beneficiary` in `bet` params (bets on someone else's behalf).
65
+ - `claim({ roundId })`.
66
+ - `claimAdvanced({ user, roundId, recipientNative?, recipientSlvr?, bypassFee?, ethOnly? })`.
67
+ - `batchClaim(roundIds[], user?, { waitForReceipt? }?)` → `0x${string}[]`.
68
+ - `approveDelegate(delegate)`, `revokeDelegate(delegate)` — let a delegate claim for you.
69
+ - `donateSlvrToJackpot(amount)`, `addEthToJackpot(value)`.
70
+ - `withdrawUnrefinedSlvr()` — cash out mined SLVR (net of refining fee).
71
+ - `checkpoint(account)` — force-settle miner accrual (rarely needed; claim/withdraw do it).
72
+
73
+ Reactive (prefer over polling):
74
+ - `waitForResolution(roundId, { pollIntervalMs?, timeoutMs? }): Promise<RoundInfo>` — resolves when the round settles.
75
+ - `watchRoundResolved(cb, { onError? }): () => void` — subscribe to RoundResolved; returns unsubscribe.
76
+ - `watchBets(cb, { roundId?, onError? }): () => void` — subscribe to BetPlaced; returns unsubscribe.
77
+
78
+ ## sdk.token
79
+ Reads: `totalSupply()`, `balanceOf(account)`, `allowance(owner, spender)`, `maxSupply()`, `name()`, `symbol()`, `decimals()`.
80
+ Writes: `transfer(to, amount)`, `approve(spender, amount)`, `transferFrom(from, to, amount)`, `burn(amount)`, `burnFrom(from, amount)`.
81
+
82
+ ## sdk.staking (veNFT)
83
+ Keyed by veNFT `tokenId` (the `SlvrVoteEscrowStaking` contract).
84
+ Reads: `getStakerRewards(tokenId)`, `getTotalWeight()`/`totalWeight()`, `balance(tokenId)`, `rewards(tokenId)`, `rewardPerWeightStored()`, `rewardPerWeightPaid(tokenId)`, `unallocated()`, `lastDistributedRoundId()`, `lottery()`, `getStakingInfo(tokenId): Promise<{ totalWeight, balance, rewards, rewardPerWeightStored }>`.
85
+ Writes: `stake(tokenId)`, `unstake(tokenId)`, `claimStakerRewards(tokenId)`, `checkpoint(tokenId)`, `poke(tokenId)`, `pokeMany(tokenIds[])`.
86
+ > To stake, the veNFT must be owned/approved to the staking contract (`voteEscrow` address).
87
+
88
+ ## sdk.autoCommit (V2)
89
+ A repeating bet plan; a keeper executes plays for you and is reimbursed metered gas
90
+ from your plan balance (`executeFor`/`claimFor` are permissionless — anyone can run
91
+ an executor and earn the fee). Present only if `autoCommit` address is configured.
92
+ - Reads: `planInfo(user)`, `needsExecution(user): { ready, reason }`, `executedRounds(user, roundId)`, `maxFeePerExecution()`, `feePremiumBps()`, consts (`UNLIMITED_PLAYS`, `MAX_PLAYS_PER_EXECUTION`, `MAX_CLAIMS_PER_EXECUTION`), `LOTTERY()`.
93
+ - Writes: `deposit(value)`, `withdraw(amount, to)`, `configurePlan(plays, amountPerPlay, squares, bpsAlloc, autoClaim)`, `configurePlanAndDeposit(..., value)`, `disablePlan()`, `cancelPlan()`, `executeFor(user, maxPlays, claimRounds[])`, `claimFor(user, claimRounds[])`.
94
+ - `bpsAlloc` entries must sum to `10000`.
95
+
96
+ ## sdk.price / sdk.ethUsd
97
+ - `sdk.price` (`SlvrPrice`, present with `slvrEthPair`): `getReserves(): { slvrReserve, ethReserve, token0IsSlvr }`, `getPriceInEth(): number`, `getPriceInEthWad(): bigint`.
98
+ - `sdk.ethUsd` (`ChainlinkPriceFeed`, present with `chainlinkEthUsd`): `getPrice(): number`, `getRoundData(): { answer, decimals, updatedAt }`. Constructor accepts `{ maxStalenessSec }` to reject stale answers.
99
+
100
+ ## Expected-value math (pure)
101
+ `computeGridMiningEv(input): GridMiningEv` — no I/O.
102
+ - Input: `{ stake, pot, emissionPerRound, slvrPriceEth, feeBps?, cashOut?, refineFeeBps?, jackpotPool?, jackpotOdds? }` (ETH-denominated numbers; `slvrPriceEth` = ETH per SLVR).
103
+ - Output `GridMiningEv`: `{ share, ethBleed, slvrMined, slvrValueEth, jackpotEvEth, netEthNoJackpot, netEth, edgeRatio, breakEvenPot, breakEvenSlvrPriceEth, profitable }`.
104
+ - Constants: `GRID_SIZE` (25), `SINGLE_SQUARE_WIN_PROBABILITY` (1/25), `PROTOCOL_FEE_BPS` (1000), `REFINING_FEE_BPS` (1000), `JACKPOT_ODDS` (625).
105
+ - `sdk.estimateRoundEv({ stake, roundId?, cashOut?, jackpotPool?, jackpotOdds?, emissionPerRound?, slvrPriceEth?, pot? })` reads pot/emission/price on-chain then calls `computeGridMiningEv`.
106
+
107
+ ## Optional read-only modules
108
+ Only present when their address is configured (the live Robinhood deployment omits them).
109
+ - `sdk.registry` (`SlvrGameRegistry`): `gameIdOf`, `gameInfo`, `isActive`, `statusOf`, `tierOf`, `weightOf`, `maxWeightBpsOf`, `totalActiveWeight`, `gameCount`. Enums `GameStatus`, `GameTier`.
110
+ - `sdk.jackpot` (`SlvrJackpot`): `jackpotPool()`, `jackpotSlvrPool()`.
111
+ - `sdk.hub` (`SlvrHub`, read-only): `pendingEmission`, `emissionRatePerSec`, `targetSupply`, `maxAccrualSeconds`, `staking`, `jackpot`, `stakerSeq`, `pendingStakerRewards`.
112
+
113
+ ## Events
114
+ Exported ABI groups for decoding logs (via `decodeEvent`/`decodeEvents`):
115
+ `SlvrGridLotteryEvents` (BetPlaced, Claimed, RoundResolved, RandomnessRequested),
116
+ `SlvrStakingEvents` (Staked, Unstaked, RewardClaimed, …, tokenId-keyed),
117
+ `SlvrTokenEvents` (Transfer, Approval), `SlvrAutoCommitEvents`.