@slvr-labs/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +849 -0
- package/dist/connect.d.ts +47 -0
- package/dist/connect.d.ts.map +1 -0
- package/dist/connect.js +56 -0
- package/dist/connect.js.map +1 -0
- package/dist/contracts/autoCommit.d.ts +151 -0
- package/dist/contracts/autoCommit.d.ts.map +1 -0
- package/dist/contracts/autoCommit.js +426 -0
- package/dist/contracts/autoCommit.js.map +1 -0
- package/dist/contracts/hub.d.ts +50 -0
- package/dist/contracts/hub.d.ts.map +1 -0
- package/dist/contracts/hub.js +118 -0
- package/dist/contracts/hub.js.map +1 -0
- package/dist/contracts/index.d.ts +8 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +18 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/jackpot.d.ts +21 -0
- package/dist/contracts/jackpot.d.ts.map +1 -0
- package/dist/contracts/jackpot.js +44 -0
- package/dist/contracts/jackpot.js.map +1 -0
- package/dist/contracts/lottery.d.ts +256 -0
- package/dist/contracts/lottery.d.ts.map +1 -0
- package/dist/contracts/lottery.js +767 -0
- package/dist/contracts/lottery.js.map +1 -0
- package/dist/contracts/registry.d.ts +53 -0
- package/dist/contracts/registry.d.ts.map +1 -0
- package/dist/contracts/registry.js +143 -0
- package/dist/contracts/registry.js.map +1 -0
- package/dist/contracts/staking.d.ts +101 -0
- package/dist/contracts/staking.d.ts.map +1 -0
- package/dist/contracts/staking.js +306 -0
- package/dist/contracts/staking.js.map +1 -0
- package/dist/contracts/token.d.ts +95 -0
- package/dist/contracts/token.d.ts.map +1 -0
- package/dist/contracts/token.js +273 -0
- package/dist/contracts/token.js.map +1 -0
- package/dist/deployments.d.ts +117 -0
- package/dist/deployments.d.ts.map +1 -0
- package/dist/deployments.js +74 -0
- package/dist/deployments.js.map +1 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +67 -0
- package/dist/errors.js.map +1 -0
- package/dist/ev.d.ts +123 -0
- package/dist/ev.d.ts.map +1 -0
- package/dist/ev.js +111 -0
- package/dist/ev.js.map +1 -0
- package/dist/events.d.ts +418 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +87 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +248 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +436 -0
- package/dist/index.js.map +1 -0
- package/dist/oracle.d.ts +48 -0
- package/dist/oracle.d.ts.map +1 -0
- package/dist/oracle.js +77 -0
- package/dist/oracle.js.map +1 -0
- package/dist/price.d.ts +52 -0
- package/dist/price.d.ts.map +1 -0
- package/dist/price.js +78 -0
- package/dist/price.js.map +1 -0
- package/dist/transaction.d.ts +54 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +105 -0
- package/dist/transaction.js.map +1 -0
- package/dist/types.d.ts +162 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +58 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +110 -0
- package/dist/utils.js.map +1 -0
- package/examples/.env.example +22 -0
- package/examples/README.md +417 -0
- package/examples/automated-betting.ts +232 -0
- package/examples/combined-strategy.ts +227 -0
- package/examples/constants.ts +35 -0
- package/examples/custom-strategy-example.ts +231 -0
- package/examples/expected-value-strategy.ts +255 -0
- package/examples/fixed-squares-strategy.ts +176 -0
- package/examples/index.ts +30 -0
- package/examples/least-allocated-strategy.ts +224 -0
- package/examples/local-test.ts +172 -0
- package/examples/quickstart-bet.ts +72 -0
- package/examples/quickstart-read.ts +87 -0
- package/examples/simple-example.ts +122 -0
- package/examples/strategy-base.ts +283 -0
- package/package.json +71 -0
- package/skills/slvr-bot/SKILL.md +193 -0
- package/skills/slvr-bot/references/api.md +117 -0
|
@@ -0,0 +1,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`.
|