TypeScript SDK for interacting with the Slvr protocol on Robinhood Chain.
npm install @slvr-labs/sdk viemShips both ESM and CommonJS builds with TypeScript declarations, so import
and require both work out of the box.
The fastest way — SlvrSDK.connect builds resilient clients (Multicall3
batching, timeouts, retries) and wires the SDK for you:
import { SlvrSDK } from '@slvr-labs/sdk';
const sdk = SlvrSDK.connect(); // read-only, Robinhood Chain
const bot = SlvrSDK.connect({ privateKey: process.env.PK }); // wallet-backed (for bets/claims)
const roundId = await sdk.lottery.currentRoundId();Need the clients yourself? createSlvrClients(opts) returns { publicClient, walletClient?, chain } with the same defaults. Or wire everything manually:
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { SlvrSDK, robinhoodChain, deployments } from '@slvr-labs/sdk';
// `robinhoodChain` and `deployments.robinhood` ship with the SDK — no need to
// hand-roll the chain or copy addresses around.
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const publicClient = createPublicClient({
chain: robinhoodChain,
transport: http(),
});
const walletClient = createWalletClient({
chain: robinhoodChain,
transport: http(),
account,
});
const sdk = new SlvrSDK({
publicClient,
walletClient,
addresses: deployments.robinhood.addresses,
});deployments.robinhood.addresses provides the canonical mainnet lottery,
staking (the veNFT staker), token, and autoCommit (V2) addresses. The
optional hub, registry, and jackpot bindings are only created when their
addresses are supplied — the live Robinhood Chain deployment does not run the hub
architecture, so those are omitted and sdk.hub / sdk.registry / sdk.jackpot
are undefined. To point at a custom or local deployment, pass your own
addresses object instead.
Read-only usage:
walletClientis optional. Omit it to build a read-only SDK (round/pot/reward queries work); any write call then throwsWalletClientRequiredError.
const roundId = await sdk.lottery.currentRoundId();
console.log(`Current round: ${roundId}`);const round = await sdk.lottery.getRound(roundId);
console.log(`Round ${roundId}:`, {
resolved: round.resolved,
winningSquare: round.winningSquare,
totalWager: round.totalWager,
potForWinners: round.potForWinners,
});const currentRound = await sdk.lottery.currentRoundId();
const squares = [0, 1, 2, 3]; // Square indices (0-24)
const amounts = [
1000000000000000000n, // 1 ETH
2000000000000000000n, // 2 ETH
1500000000000000000n, // 1.5 ETH
500000000000000000n, // 0.5 ETH
];
const txHash = await sdk.lottery.bet({
roundId: currentRound,
squares,
amounts,
});
console.log(`Bet placed: ${txHash}`);const txHash = await sdk.lottery.claim({
roundId: roundId,
});
console.log(`Claimed rewards: ${txHash}`);const claimableRounds = await sdk.getClaimableRounds(
userAddress,
startRoundId,
endRoundId
);
if (claimableRounds.length > 0) {
const txHash = await sdk.lottery.batchClaim(claimableRounds);
console.log(`Batch claimed ${claimableRounds.length} rounds: ${txHash}`);
}const squares = await sdk.lottery.getRoundSquares(roundId);
squares.forEach(({ square, total, bettors }) => {
console.log(`Square ${square}: ${total} ETH from ${bettors} bettors`);
});const userBets = await sdk.lottery.getUserBets(roundId, userAddress);
userBets.forEach(({ square, amount }) => {
console.log(`Square ${square}: ${amount} ETH`);
});Staking is the veNFT-based SlvrVoteEscrowStaking contract. Stakers deposit a
vote-escrow NFT by tokenId (not a raw ERC20 amount); rewards accrue per unit of the
token's tracked weight and are claimed per tokenId.
// tokenId is the veNFT you have staked (or want to inspect)
const stakingInfo = await sdk.staking.getStakingInfo(tokenId);
console.log({
totalWeight: stakingInfo.totalWeight, // total tracked weight across all staked tokens
balance: stakingInfo.balance, // this token's tracked weight
rewards: stakingInfo.rewards, // claimable rewards for this token
rewardPerWeightStored: stakingInfo.rewardPerWeightStored, // accumulated reward per weight (1e18)
});// Deposit the vote-escrow NFT by tokenId (the staking contract takes custody of it)
const stakeTx = await sdk.staking.stake(tokenId);
console.log(`Staked: ${stakeTx}`);const unstakeTx = await sdk.staking.unstake(tokenId);
console.log(`Unstaked: ${unstakeTx}`);// Read claimable rewards for a token
const pending = await sdk.staking.getStakerRewards(tokenId);
console.log(`Claimable: ${SlvrSDK.formatToken(pending)}`);
// Claim them
const rewardTx = await sdk.staking.claimStakerRewards(tokenId);
console.log(`Rewards claimed: ${rewardTx}`);// Refresh a token's reward accounting without claiming
await sdk.staking.checkpoint(tokenId);
// Refresh a token's tracked weight (e.g. after its veNFT weight changed)
await sdk.staking.poke(tokenId);
// Refresh many tokens' tracked weight in one call
await sdk.staking.pokeMany([tokenId1, tokenId2]);const balance = await sdk.token.balanceOf(userAddress);
console.log(`Balance: ${SlvrSDK.formatToken(balance)} SLVR`);const amount = SlvrSDK.parseToken('100'); // 100 SLVR
const txHash = await sdk.token.transfer(recipientAddress, amount);
console.log(`Transferred: ${txHash}`);sdk.autoCommit wraps SlvrAutoCommitV2. You configure a repeating bet plan
and fund it; a keeper (anyone) later executes plays on your behalf and is
reimbursed their metered gas plus a small premium out of your plan balance —
there is no flat automation fee, and executeFor / claimFor are non-payable.
const squares = [0, 1, 2, 3, 4];
const bpsAlloc = [2000, 2000, 2000, 2000, 2000]; // 20% each; must sum to 10000
const txHash = await sdk.autoCommit?.configurePlan(
10, // number of plays (use UNLIMITED_PLAYS for open-ended)
1000000000000000000n, // amount per play (1 ETH), split across squares by bpsAlloc
squares,
bpsAlloc,
true // autoClaim: keeper claims winnings back into your balance
);const txHash = await sdk.autoCommit?.configurePlanAndDeposit(
10,
1000000000000000000n,
squares,
bpsAlloc,
true,
10000000000000000000n // deposit 10 ETH (sent as msg.value)
);const depositTx = await sdk.autoCommit?.deposit(5000000000000000000n); // 5 ETH
const withdrawTx = await sdk.autoCommit?.withdraw(1000000000000000000n, to); // 1 ETH to `to`const plan = await sdk.autoCommit?.planInfo(userAddress);
// { enabled, nextRoundId, playsRemaining, amountPerPlay, balance, autoClaim, squares, bpsAlloc, planStartRoundId }
const { ready, reason } = await sdk.autoCommit!.needsExecution(userAddress);// maxPlays caps the number of rounds executed this call; claimRounds are winning
// rounds to claim in the same tx (discover them off-chain; empty array is fine).
const executeTx = await sdk.autoCommit?.executeFor(userAddress, 5, []);
// Claim only, without executing new plays:
const claimTx = await sdk.autoCommit?.claimFor(userAddress, [roundId1, roundId2]);The SlvrHub is the protocol emission/sink router. It gates per-game SLVR emission and
fans many games into a single shared veNFT staker stream and a single shared jackpot.
Available as sdk.hub when a hub address is configured.
This is a read-only, informational surface — the hub's fee-routing writes are keeper/protocol operations and are not part of the SDK.
// Emission stats
const rate = await sdk.hub!.emissionRatePerSec(); // base SLVR/sec across the active game set
const target = await sdk.hub!.targetSupply(); // soft-cap target supply (0 => token MAX_SUPPLY)
const pending = await sdk.hub!.pendingEmission(gameId); // accrued-but-unminted SLVR for a gameThe SlvrGameRegistry is the source of truth for which games exist, their status/tier,
and their share of the shared emission stream. Available as sdk.registry when a
registry address is configured (read-only surface).
const gameId = await sdk.registry!.gameIdOf(gameAddress); // 0 if not registered (ids are 1-based)
const info = await sdk.registry!.gameInfo(gameId); // full GameInfo record
const active = await sdk.registry!.isActive(gameAddress);
const status = await sdk.registry!.statusOf(gameId); // GameStatus enum
const tier = await sdk.registry!.tierOf(gameId); // GameTier enum
const weight = await sdk.registry!.weightOf(gameId); // this game's emission weight
const totalWeight = await sdk.registry!.totalActiveWeight(); // denominator for the emission splitThe SlvrJackpot exposes the shared jackpot pool balances. Available as sdk.jackpot
when a jackpot address is configured (read-only surface).
const ethPool = await sdk.jackpot!.jackpotPool(); // native (ETH) jackpot pool balance
const slvrPool = await sdk.jackpot!.jackpotSlvrPool(); // SLVR jackpot pool balanceThese SDK-level helpers combine hub + registry reads. effectiveEmissionRate requires
both hub and registry addresses; pendingEmission requires hub.
// A game's weighted share of the global emission stream, in SLVR/sec
// (pre-cap: does not apply the per-game maxWeightBps ceiling)
const effRate = await sdk.effectiveEmissionRate(gameId);
// Accrued-but-unminted SLVR currently available to a game (pass-through to hub.pendingEmission)
const pending = await sdk.pendingEmission(gameId);The SDK ships the protocol's grid-mining edge math (the same model as the web calculator) plus a SLVR/ETH price reader, so a bot can decide when a round is worth playing.
The model in one line: the winning square is drawn uniformly each round, so mining SLVR is only profitable while the pot is small enough that your SLVR reward beats the ETH you bleed to the protocol fee. Net EV per round:
netEth = (stake/pot) * emissionPerRound * slvrPriceEth * realize // SLVR mined, valued in ETH
- feeFraction * stake // ETH bleed (the fee; the rest of the pot is a wash)
+ (1/jackpotOdds) * (stake/pot) * jackpotPool // jackpot term
Because both terms scale with stake, the edge depends on pot vs SLVR price,
not bet size — mining pays while the pot is below the break-even pot.
SLVR/ETH comes from the UniswapV2 pair (slvrEthPair). For USD, the SDK reads an
optional Chainlink ETH/USD feed (chainlinkEthUsd) and multiplies.
// SLVR/ETH from the pair (requires slvrEthPair, in deployments.robinhood.addresses).
const ethPerSlvr = await sdk.getSlvrPriceInEth();
// SLVR in BOTH ETH and USD:
const { eth, usd } = await sdk.getSlvrPrice(); // usd uses the Chainlink feed if configured, else null
const quote = await sdk.getSlvrPrice({ ethUsd: 1797.35 }); // …or pass ETH/USD from your own source
// ETH/USD directly (requires a chainlinkEthUsd feed):
const ethUsd = await sdk.getEthPriceUsd();
// Low-level readers:
const { slvrReserve, ethReserve } = await sdk.price!.getReserves();Chainlink feed: Robinhood Chain has a Chainlink ETH/USD feed (
ETH / USD, 8 decimals), wired intodeployments.robinhood.addresses.chainlinkEthUsd, sogetSlvrPrice()/getEthPriceUsd()return USD out of the box. (An SVR proxy with identical price data also exists, at0x5058aDee53b04e374d8bEDbAD634Bc4778F50b22, for protocols integrating Chainlink SVR; for plain reads use the standard proxy that's wired here.) On a chain without a feed, setaddresses.chainlinkEthUsdyourself or passethUsd.ChainlinkPriceFeedis exported if you want to read any aggregator directly.
// Pulls pot, emission and SLVR price on-chain and returns the full breakdown.
const ev = await sdk.estimateRoundEv({
stake: 0.1, // ETH you'd commit this round
cashOut: false, // false = value SLVR at full price (holding); true = net of the 10% refining fee
jackpotPool: 5.2, // optional: ETH in the jackpot (not auto-read)
});
console.log(ev.netEth, 'ETH/round', ev.profitable, `(break-even pot ${ev.breakEvenPot} ETH)`);import { computeGridMiningEv } from '@slvr-labs/sdk';
const ev = computeGridMiningEv({
stake: 0.1,
pot: 0.5,
emissionPerRound: 1, // SLVR minted to the winning square this round
slvrPriceEth: 0.0005, // ETH per SLVR (from sdk.getSlvrPriceInEth())
jackpotPool: 5.2, // optional
});
// -> { share, ethBleed, slvrMined, slvrValueEth, jackpotEvEth, netEth, edgeRatio,
// breakEvenPot, breakEvenSlvrPriceEth, profitable }See the expected-value-strategy.ts example
for a bot that only bets when a round is +EV. Exported constants GRID_SIZE (25),
PROTOCOL_FEE_BPS, REFINING_FEE_BPS, and JACKPOT_ODDS (625) document the on-chain
defaults.
Instead of hand-rolling polling loops, react to on-chain events or await resolution:
// Await a round's resolution (polls getRound under the hood) — e.g. bet, then claim.
const resolved = await sdk.lottery.waitForResolution(roundId, { timeoutMs: 300_000 });
if (await sdk.canClaim(roundId, address)) await sdk.lottery.claim({ roundId });
// Subscribe to events; each returns an unsubscribe function.
const stopResolved = sdk.lottery.watchRoundResolved((e) => {
console.log(`round ${e.roundId} won by square ${e.winningSquare}`);
});
const stopBets = sdk.lottery.watchBets((e) => console.log(`bet of ${e.total} on round ${e.roundId}`));
// later: stopResolved(); stopBets();Catch reverts before spending gas, and get the protocol's custom errors decoded into typed, readable failures:
import { SlvrRevertError, decodeSlvrRevert } from '@slvr-labs/sdk';
// Preflight a bet (eth_call, no tx). Throws a typed SlvrRevertError on revert.
try {
await sdk.lottery.simulateBet({ roundId, squares, amounts });
await sdk.lottery.bet({ roundId, squares, amounts });
} catch (e) {
if (e instanceof SlvrRevertError) console.log(e.errorName, '—', e.message); // e.g. "RoundNotOpen — betting is closed…"
}
// bet() and claim() also decode reverts, and accept gas/nonce overrides:
await sdk.lottery.bet({ roundId, squares, amounts, overrides: { maxFeePerGas, nonce } });
// Decode any caught contract error yourself:
const revert = decodeSlvrRevert(err); // SlvrRevertError | nullsdk.getJackpotPool(roundId?) returns the round's jackpot pool in wei (0 when no
jackpot is set), and estimateRoundEv now reads it automatically.
const formatted = SlvrSDK.formatToken(1500000000000000000n); // "1.5"
// precision caps decimals; trailing zeros are stripped
const precise = SlvrSDK.formatToken(1234567890000000000n, 18, 6); // "1.234567"const amount = SlvrSDK.parseToken('1.5'); // 1500000000000000000nconst amounts = SlvrSDK.calculateBetAmounts(
1000000000000000000n, // 1 ETH total
[25, 25, 25, 25] // 25% each square
);
// Returns: [250000000000000000n, 250000000000000000n, 250000000000000000n, 250000000000000000n]const timeRemaining = await sdk.getTimeRemaining(roundId);
console.log(`Time remaining: ${timeRemaining} seconds`);Main SDK class that provides access to all protocol contracts.
new SlvrSDK(config: SlvrConfig)lottery: SlvrGridLottery- Lottery contract interfacestaking: SlvrStaking- veNFT staking contract interface (SlvrVoteEscrowStaking)token: SlvrToken- Token contract interfaceautoCommit?: SlvrAutoCommit- Auto-commit contract interface (optional)hub?: SlvrHub- Emission/sink router interface, read-only (optional)registry?: SlvrGameRegistry- Game registry interface (optional)jackpot?: SlvrJackpot- Jackpot pool interface (optional)
getPublicClient(): PublicClient- Get the public clientgetWalletClient(): WalletClient | undefined- Get the wallet clientsetWalletClient(walletClient: WalletClient | undefined): void- Update wallet clientgetTimeRemaining(roundId: bigint): Promise<number>- Get time remaining for a roundcanClaim(roundId: bigint, user: Address): Promise<boolean>- Check if user can claimgetClaimableRounds(user: Address, startRoundId: bigint, endRoundId: bigint): Promise<bigint[]>- Get claimable roundseffectiveEmissionRate(gameId: bigint): Promise<bigint>- A game's weighted SLVR/sec share of the global emission stream (requireshub+registry)pendingEmission(gameId: bigint): Promise<bigint>- Accrued-but-unminted SLVR available to a game (requireshub)getSlvrPriceInEth(): Promise<number>- SLVR spot price in ETH (requiresslvrEthPair)getSlvrPrice(opts?: { ethUsd?: number }): Promise<PriceQuote>- SLVR price in both ETH and USD (usdisnullwithout an ETH/USD source)getEthPriceUsd(): Promise<number>- ETH/USD from the Chainlink feed (requireschainlinkEthUsd)estimateRoundEv(params): Promise<GridMiningEv>- per-round grid-mining EV, pulling pot/emission/price on-chain (requiresslvrEthPairunlessslvrPriceEthis passed)
SlvrSDK.formatToken(value: bigint, decimals?: number, precision?: number): stringSlvrSDK.parseToken(value: string, decimals?: number): bigintSlvrSDK.calculateBetAmounts(totalAmount: bigint, percentages: number[]): bigint[]
Interface for the lottery contract.
currentRoundId(): Promise<bigint>roundStart(roundId: bigint): Promise<bigint>roundEnd(roundId: bigint): Promise<bigint>roundOpen(roundId: bigint): Promise<boolean>bettingEnd(roundId: bigint): Promise<bigint>- betting cutoff (unix seconds); can be earlier thanroundEnd— gate bots on thisgetExpectedReward(account: Address, roundId: bigint): Promise<bigint>- estimated reward for an account in a roundlatestResolvedRoundId(): Promise<bigint>getRound(roundId: bigint): Promise<RoundInfo>- flat 16-value round tuplegetRoundState(roundId?: bigint): Promise<{ roundId, open, resolved, bettingEnd, roundEnd, totalWager, winningSquare, secondsUntilBettingClose }>- batched per-tick snapshot;secondsUntilBettingCloseuses on-chainblock.timestamp. Defaults to the current round.getTotalOnSquare(roundId: bigint, square: number): Promise<bigint>getBettorsOnSquare(roundId: bigint, square: number): Promise<bigint>getUserBet(roundId: bigint, square: number, bettor: Address): Promise<bigint>getHasClaimed(roundId: bigint, user: Address): Promise<boolean>getMinerState(account: Address): Promise<MinerState>hasAccount(account: Address): Promise<boolean>getDelegate(user: Address, delegate: Address): Promise<boolean>slvrPerRound(): Promise<bigint>- target SLVR/round; emission is now hub-gated, so this is the requested value, not the amount actually minted (bounded bySlvrHub.pendingEmission)protocolFeeBps(): Promise<number>carryWinnerNativePool(): Promise<bigint>carryStakerNativeOwed(): Promise<bigint>carryJackpotNativeOwed(): Promise<bigint>carrySlvrPool(): Promise<bigint>getRoundSquares(roundId: bigint): Promise<Array<{square: number, total: bigint, bettors: bigint}>>getUserBets(roundId: bigint, user: Address): Promise<Array<{square: number, amount: bigint}>>
bet(params: BetParams): Promise<0x${string}>claim(params: ClaimParams): Promise<0x${string}>claimAdvanced(params: ClaimParamsAdvanced): Promise<0x${string}>batchClaim(roundIds: bigint[], user?: Address, options?: { waitForReceipt?: boolean }): Promise<0x${string}[]>approveDelegate(delegate: Address): Promise<0x${string}>revokeDelegate(delegate: Address): Promise<0x${string}>donateSlvrToJackpot(amount: bigint): Promise<0x${string}>addEthToJackpot(value: bigint): Promise<0x${string}>withdrawUnrefinedSlvr(): Promise<0x${string}>- cash out accumulated mined SLVR (net of the refining fee)checkpoint(account: Address): Promise<0x${string}>- force on-chain settlement of a miner's refined-reward accrual (rarely needed;claim/withdrawUnrefinedSlvrdo it automatically)
veNFT-based staker. All operations are keyed by veNFT tokenId.
getStakerRewards(tokenId: bigint): Promise<bigint>- claimable rewards for a tokengetTotalWeight(): Promise<bigint>totalWeight(): Promise<bigint>balance(tokenId: bigint): Promise<bigint>- a token's tracked weightrewards(tokenId: bigint): Promise<bigint>rewardPerWeightStored(): Promise<bigint>rewardPerWeightPaid(tokenId: bigint): Promise<bigint>unallocated(): Promise<bigint>lastDistributedRoundId(): Promise<bigint>lottery(): Promise<Address>getStakingInfo(tokenId: bigint): Promise<StakingInfo>
stake(tokenId: bigint): Promise<0x${string}>unstake(tokenId: bigint): Promise<0x${string}>claimStakerRewards(tokenId: bigint): Promise<0x${string}>checkpoint(tokenId: bigint): Promise<0x${string}>poke(tokenId: bigint): Promise<0x${string}>pokeMany(tokenIds: bigint[]): Promise<0x${string}>
Emission/sink router. Available as sdk.hub when a hub address is configured.
Informational reads only — the hub's fee-routing writes are keeper/protocol
operations and are not part of the SDK.
pendingEmission(gameId: bigint): Promise<bigint>emissionRatePerSec(): Promise<bigint>targetSupply(): Promise<bigint>maxAccrualSeconds(): Promise<bigint>staking(): Promise<Address>jackpot(): Promise<Address>stakerSeq(): Promise<bigint>pendingStakerRewards(): Promise<bigint>
Read-only game registry. Available as sdk.registry when a registry address is configured.
gameIdOf(game: Address): Promise<bigint>gameInfo(gameId: bigint): Promise<GameInfo>isActive(game: Address): Promise<boolean>statusOf(gameId: bigint): Promise<GameStatus>tierOf(gameId: bigint): Promise<GameTier>weightOf(gameId: bigint): Promise<bigint>maxWeightBpsOf(gameId: bigint): Promise<number>totalActiveWeight(): Promise<bigint>gameCount(): Promise<bigint>
Read-only jackpot pool. Available as sdk.jackpot when a jackpot address is configured.
jackpotPool(): Promise<bigint>- native (ETH) pool balancejackpotSlvrPool(): Promise<bigint>- SLVR pool balance
Interface for the SLVR token contract.
totalSupply(): Promise<bigint>balanceOf(account: Address): Promise<bigint>allowance(owner: Address, spender: Address): Promise<bigint>maxSupply(): Promise<bigint>
transfer(to: Address, amount: bigint): Promise<0x${string}>approve(spender: Address, amount: bigint): Promise<0x${string}>transferFrom(from: Address, to: Address, amount: bigint): Promise<0x${string}>burn(amount: bigint): Promise<0x${string}>
SLVR/ETH spot price reader. Available as sdk.price when a slvrEthPair address
is configured. Reads the UniswapV2 pair's reserves (resolving token order); spot
only, not a manipulation-resistant oracle.
getReserves(): Promise<{ slvrReserve: bigint, ethReserve: bigint, token0IsSlvr: boolean }>getPriceInEth(): Promise<number>- ETH per SLVRgetPriceInEthWad(): Promise<bigint>- ETH per SLVR as a 1e18-scaled bigint
Reads a Chainlink-style AggregatorV3Interface (e.g. an ETH/USD feed). Available
as sdk.ethUsd when a chainlinkEthUsd address is configured — wired for
Robinhood Chain in deployments.robinhood.
getPrice(): Promise<number>- feed answer scaled by its decimalsgetRoundData(): Promise<{ answer: bigint, decimals: number, updatedAt: bigint }>- Constructor accepts
{ maxStalenessSec }to reject stale answers.
Pure functions and constants (no I/O):
computeGridMiningEv(input: GridMiningEvInput): GridMiningEv- per-round EV breakdown- Constants:
GRID_SIZE(25),SINGLE_SQUARE_WIN_PROBABILITY(1/25),PROTOCOL_FEE_BPS(1000),REFINING_FEE_BPS(1000),JACKPOT_ODDS(625)
All TypeScript types are exported from the main module:
import type {
SlvrConfig,
RoundInfo,
MinerState,
TreasuryState,
StakingInfo,
AutoCommitPlan,
GameInfo,
BetParams,
ClaimParams,
ClaimParamsAdvanced,
GridMiningEvInput,
GridMiningEv,
SlvrReserves,
PriceQuote,
SlvrDeployment,
} from '@slvr-labs/sdk';
// GameStatus and GameTier are enums (runtime values), so import them as values:
import { GameStatus, GameTier } from '@slvr-labs/sdk';The StakingInfo shape is now { totalWeight, balance, rewards, rewardPerWeightStored }
(all bigint), reflecting the veNFT weight-based staker.
The SDK ships canonical addresses and a ready-made viem chain so you don't have to copy either around:
import { deployments, robinhoodChain } from '@slvr-labs/sdk';
deployments.robinhood.chainId; // 4663
deployments.robinhood.rpcUrl; // https://rpc.mainnet.chain.robinhood.com
deployments.robinhood.blockExplorer; // https://robinhoodchain.blockscout.com/
deployments.robinhood.subgraphUrl; // hosted subgraph GraphQL endpoint
deployments.robinhood.addresses; // { lottery, staking, token, autoCommit, voteEscrow, slvrEthPair, chainlinkEthUsd, multicall3 }
robinhoodChain; // a viem `Chain` for createPublicClient/createWalletClientTypes: SlvrDeployment describes the shape. To target a local or custom
deployment, construct the SDK with your own addresses object instead.
robinhoodChain registers Multicall3, so the SDK's multi-square reads
(getRoundSquares, getUserBets) collapse from ~50 RPC calls into one. For
even more batching, create your client with batch: { multicall: true } — viem
then auto-batches every concurrent read (great for polling bots):
const publicClient = createPublicClient({
chain: robinhoodChain,
transport: http(),
batch: { multicall: true },
});On a custom chain without Multicall3 configured, these methods transparently fall back to individual reads.
The package ships an agent skill at
skills/slvr-bot/ so a coding agent can build SLVR
bots for you correctly — it knows the API, addresses, EV math, and gotchas.
- Claude Code: copy
skills/slvr-bot/into your project's.claude/skills/(or referencenode_modules/@slvr-labs/sdk/skills/slvr-bot/), then ask e.g. "build me a bot that grid-mines SLVR only on +EV rounds." - Codex / other agents: the skill is plain Markdown — point your
AGENTS.mdatnode_modules/@slvr-labs/sdk/skills/slvr-bot/SKILL.md, or paste it into context.
The package ships runnable examples in the examples/ directory
(also included in the published tarball). Copy the file you want into your project
and change the relative ../src import to @slvr-labs/sdk.
New here? Start with these two:
| File | What it shows |
|---|---|
quickstart-read.ts |
Start here. Read-only tour — no wallet. Prints the round, pot, and SLVR price in ETH + USD |
quickstart-bet.ts |
Minimal wallet flow — EV-check, place one bet, then claim |
Run them from sdk/ts with ts-node (fetched on demand via npx):
npm install
npx ts-node examples/quickstart-read.ts # read-only, no key needed
# Examples that bet read your key from PRIVATE_KEY — set it inline for the command:
PRIVATE_KEY=0xabc... npx ts-node examples/quickstart-bet.tsUse a burner key funded with a little ETH on Robinhood Chain — never a key with
real value, and never commit it. See the examples README
for the full run/setup guide (including .env usage). Then the strategy references:
| File | What it shows |
|---|---|
simple-example.ts |
Minimal read + single bet |
strategy-base.ts |
BettingStrategy base class to extend |
least-allocated-strategy.ts |
Bet the least-crowded squares |
fixed-squares-strategy.ts |
Always bet a fixed set of squares |
combined-strategy.ts |
Threshold + least-allocated bot loop |
automated-betting.ts |
Long-running automated betting bot |
expected-value-strategy.ts |
EV bot — bets only when the round is +EV, using the grid-mining calculator + SLVR price |
custom-strategy-example.ts |
Several custom strategy variants |
See the examples README for a deeper walkthrough.
The snippets below are copied from the examples; after copying a file into your
own project, import the SDK from @slvr-labs/sdk (not ../src).
import { CombinedBettingBot } from './combined-strategy'; // your copied file
import { parseEther } from 'viem';
// Create a bot that bets when round has < 100 ETH
// and targets squares with least allocation
const bot = new CombinedBettingBot(sdk, {
threshold: parseEther('100'), // Bet when round < 100 ETH
betAmount: parseEther('5'), // Bet 5 ETH total
squareCount: 5, // Bet on 5 least allocated squares
checkInterval: 5000, // Check every 5 seconds
});
await bot.start();
// Bot will automatically monitor and betimport { LeastAllocatedStrategy } from './least-allocated-strategy'; // your copied file
const strategy = new LeastAllocatedStrategy(sdk);
const roundId = await sdk.lottery.currentRoundId();
// Get squares with least allocation
const leastAllocated = await strategy.getLeastAllocatedSquares(roundId, 5);
// Place bet on these squares
const txHash = await strategy.betOnLeastAllocated(
roundId,
parseEther('5'), // 5 ETH total
5 // Bet on 5 squares
);See the examples README for more detailed examples and strategies.
This SDK is built for real use — it interacts with live smart contracts on Robinhood Chain and moves real ETH and SLVR. That also means it's provided "as is", without warranty of any kind (see LICENSE); you're responsible for the transactions you send. Sensible precautions: review your code, start with small amounts, and keep your keys safe.
MIT