@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
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
|
+
|