@slvr-labs/sdk 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +42 -0
  2. package/dist/index.d.mts +2117 -0
  3. package/dist/index.d.ts +1903 -34
  4. package/dist/index.js +3091 -427
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +3035 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +19 -11
  9. package/skills/slvr-bot/SKILL.md +9 -8
  10. package/skills/slvr-bot/references/api.md +8 -0
  11. package/dist/connect.d.ts +0 -47
  12. package/dist/connect.d.ts.map +0 -1
  13. package/dist/connect.js +0 -56
  14. package/dist/connect.js.map +0 -1
  15. package/dist/contracts/autoCommit.d.ts +0 -151
  16. package/dist/contracts/autoCommit.d.ts.map +0 -1
  17. package/dist/contracts/autoCommit.js +0 -426
  18. package/dist/contracts/autoCommit.js.map +0 -1
  19. package/dist/contracts/hub.d.ts +0 -50
  20. package/dist/contracts/hub.d.ts.map +0 -1
  21. package/dist/contracts/hub.js +0 -118
  22. package/dist/contracts/hub.js.map +0 -1
  23. package/dist/contracts/index.d.ts +0 -8
  24. package/dist/contracts/index.d.ts.map +0 -1
  25. package/dist/contracts/index.js +0 -18
  26. package/dist/contracts/index.js.map +0 -1
  27. package/dist/contracts/jackpot.d.ts +0 -21
  28. package/dist/contracts/jackpot.d.ts.map +0 -1
  29. package/dist/contracts/jackpot.js +0 -44
  30. package/dist/contracts/jackpot.js.map +0 -1
  31. package/dist/contracts/lottery.d.ts +0 -256
  32. package/dist/contracts/lottery.d.ts.map +0 -1
  33. package/dist/contracts/lottery.js +0 -767
  34. package/dist/contracts/lottery.js.map +0 -1
  35. package/dist/contracts/registry.d.ts +0 -53
  36. package/dist/contracts/registry.d.ts.map +0 -1
  37. package/dist/contracts/registry.js +0 -143
  38. package/dist/contracts/registry.js.map +0 -1
  39. package/dist/contracts/staking.d.ts +0 -101
  40. package/dist/contracts/staking.d.ts.map +0 -1
  41. package/dist/contracts/staking.js +0 -306
  42. package/dist/contracts/staking.js.map +0 -1
  43. package/dist/contracts/token.d.ts +0 -95
  44. package/dist/contracts/token.d.ts.map +0 -1
  45. package/dist/contracts/token.js +0 -273
  46. package/dist/contracts/token.js.map +0 -1
  47. package/dist/deployments.d.ts +0 -117
  48. package/dist/deployments.d.ts.map +0 -1
  49. package/dist/deployments.js +0 -74
  50. package/dist/deployments.js.map +0 -1
  51. package/dist/errors.d.ts +0 -39
  52. package/dist/errors.d.ts.map +0 -1
  53. package/dist/errors.js +0 -67
  54. package/dist/errors.js.map +0 -1
  55. package/dist/ev.d.ts +0 -123
  56. package/dist/ev.d.ts.map +0 -1
  57. package/dist/ev.js +0 -111
  58. package/dist/ev.js.map +0 -1
  59. package/dist/events.d.ts +0 -418
  60. package/dist/events.d.ts.map +0 -1
  61. package/dist/events.js +0 -87
  62. package/dist/events.js.map +0 -1
  63. package/dist/index.d.ts.map +0 -1
  64. package/dist/oracle.d.ts +0 -48
  65. package/dist/oracle.d.ts.map +0 -1
  66. package/dist/oracle.js +0 -77
  67. package/dist/oracle.js.map +0 -1
  68. package/dist/price.d.ts +0 -52
  69. package/dist/price.d.ts.map +0 -1
  70. package/dist/price.js +0 -78
  71. package/dist/price.js.map +0 -1
  72. package/dist/transaction.d.ts +0 -54
  73. package/dist/transaction.d.ts.map +0 -1
  74. package/dist/transaction.js +0 -105
  75. package/dist/transaction.js.map +0 -1
  76. package/dist/types.d.ts +0 -162
  77. package/dist/types.d.ts.map +0 -1
  78. package/dist/types.js +0 -23
  79. package/dist/types.js.map +0 -1
  80. package/dist/utils.d.ts +0 -58
  81. package/dist/utils.d.ts.map +0 -1
  82. package/dist/utils.js +0 -110
  83. package/dist/utils.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,436 +1,3100 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
15
9
  };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.SlvrJackpot = exports.SlvrGameRegistry = exports.SlvrHub = exports.SlvrAutoCommit = exports.SlvrToken = exports.SlvrStaking = exports.SlvrGridLottery = exports.chainFromDeployment = exports.createSlvrClients = exports.ChainlinkPriceFeed = exports.SlvrPrice = exports.SlvrSDK = void 0;
18
- const viem_1 = require("viem");
19
- const lottery_1 = require("./contracts/lottery");
20
- const staking_1 = require("./contracts/staking");
21
- const token_1 = require("./contracts/token");
22
- const autoCommit_1 = require("./contracts/autoCommit");
23
- const hub_1 = require("./contracts/hub");
24
- const registry_1 = require("./contracts/registry");
25
- const jackpot_1 = require("./contracts/jackpot");
26
- const price_1 = require("./price");
27
- const oracle_1 = require("./oracle");
28
- const ev_1 = require("./ev");
29
- const connect_1 = require("./connect");
30
- const deployments_1 = require("./deployments");
31
- const errors_1 = require("./errors");
32
- /**
33
- * Main Slvr SDK class
34
- *
35
- * @example
36
- * ```typescript
37
- * import { createPublicClient, createWalletClient, http } from 'viem';
38
- * import { SlvrSDK } from '@slvr-labs/sdk';
39
- *
40
- * const publicClient = createPublicClient({
41
- * chain: robinhoodChain,
42
- * transport: http(),
43
- * });
44
- *
45
- * const walletClient = createWalletClient({
46
- * chain: robinhoodChain,
47
- * transport: http(),
48
- * account: yourAccount,
49
- * });
50
- *
51
- * const sdk = new SlvrSDK({
52
- * publicClient,
53
- * walletClient,
54
- * addresses: {
55
- * lottery: '0x...',
56
- * staking: '0x...',
57
- * token: '0x...',
58
- * },
59
- * });
60
- *
61
- * // Get current round
62
- * const roundId = await sdk.lottery.currentRoundId();
63
- *
64
- * // Place a bet
65
- * await sdk.lottery.bet({
66
- * roundId,
67
- * squares: [0, 1, 2],
68
- * amounts: [1000000000000000000n, 2000000000000000000n, 3000000000000000000n],
69
- * });
70
- * ```
71
- */
72
- class SlvrSDK {
73
- constructor(config) {
74
- this.config = config;
75
- // Initialize core contracts
76
- this.lottery = new lottery_1.SlvrGridLottery(config.publicClient, config.walletClient, config.addresses.lottery);
77
- this.staking = new staking_1.SlvrStaking(config.publicClient, config.walletClient, config.addresses.staking);
78
- this.token = new token_1.SlvrToken(config.publicClient, config.walletClient, config.addresses.token);
79
- // Initialize optional contracts
80
- if (config.addresses.autoCommit) {
81
- this.autoCommit = new autoCommit_1.SlvrAutoCommit(config.publicClient, config.walletClient, config.addresses.autoCommit);
82
- }
83
- if (config.addresses.hub) {
84
- this.hub = new hub_1.SlvrHub(config.publicClient, config.walletClient, config.addresses.hub);
85
- }
86
- if (config.addresses.registry) {
87
- this.registry = new registry_1.SlvrGameRegistry(config.publicClient, config.walletClient, config.addresses.registry);
88
- }
89
- if (config.addresses.jackpot) {
90
- this.jackpot = new jackpot_1.SlvrJackpot(config.publicClient, config.walletClient, config.addresses.jackpot);
91
- }
92
- // Price reader needs the pair address; SLVR side is resolved from the token address.
93
- if (config.addresses.slvrEthPair) {
94
- this.price = new price_1.SlvrPrice(config.publicClient, config.addresses.slvrEthPair, config.addresses.token);
95
- }
96
- // ETH/USD feed (optional; only on chains that have a Chainlink aggregator).
97
- if (config.addresses.chainlinkEthUsd) {
98
- this.ethUsd = new oracle_1.ChainlinkPriceFeed(config.publicClient, config.addresses.chainlinkEthUsd);
99
- }
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ChainlinkPriceFeed: () => ChainlinkPriceFeed,
24
+ ContractCallError: () => ContractCallError,
25
+ GRID_SIZE: () => GRID_SIZE,
26
+ GameStatus: () => GameStatus,
27
+ GameTier: () => GameTier,
28
+ JACKPOT_ODDS: () => JACKPOT_ODDS,
29
+ PROTOCOL_FEE_BPS: () => PROTOCOL_FEE_BPS,
30
+ REFINING_FEE_BPS: () => REFINING_FEE_BPS,
31
+ SINGLE_SQUARE_WIN_PROBABILITY: () => SINGLE_SQUARE_WIN_PROBABILITY,
32
+ SlvrAutoCommit: () => SlvrAutoCommit,
33
+ SlvrAutoCommitEvents: () => SlvrAutoCommitEvents,
34
+ SlvrGameRegistry: () => SlvrGameRegistry,
35
+ SlvrGridLottery: () => SlvrGridLottery,
36
+ SlvrGridLotteryEvents: () => SlvrGridLotteryEvents,
37
+ SlvrHub: () => SlvrHub,
38
+ SlvrJackpot: () => SlvrJackpot,
39
+ SlvrPrice: () => SlvrPrice,
40
+ SlvrRevertError: () => SlvrRevertError,
41
+ SlvrSDK: () => SlvrSDK,
42
+ SlvrSDKError: () => SlvrSDKError,
43
+ SlvrStaking: () => SlvrStaking,
44
+ SlvrStakingEvents: () => SlvrStakingEvents,
45
+ SlvrToken: () => SlvrToken,
46
+ SlvrTokenEvents: () => SlvrTokenEvents,
47
+ TransactionError: () => TransactionError,
48
+ ValidationError: () => ValidationError,
49
+ WalletClientRequiredError: () => WalletClientRequiredError,
50
+ chainFromDeployment: () => chainFromDeployment,
51
+ computeGridMiningEv: () => computeGridMiningEv,
52
+ createSlvrClients: () => createSlvrClients,
53
+ decodeEvent: () => decodeEvent,
54
+ decodeEvents: () => decodeEvents,
55
+ decodeSlvrRevert: () => decodeSlvrRevert,
56
+ deployments: () => deployments,
57
+ estimateGas: () => estimateGas,
58
+ executeTransaction: () => executeTransaction,
59
+ robinhood: () => robinhood,
60
+ robinhoodChain: () => robinhoodChain,
61
+ validateAddress: () => validateAddress,
62
+ validateAmount: () => validateAmount,
63
+ validateArrayLengths: () => validateArrayLengths,
64
+ validateBps: () => validateBps,
65
+ validateBpsSum: () => validateBpsSum,
66
+ validateSquares: () => validateSquares,
67
+ waitForReceipt: () => waitForReceipt,
68
+ waitForTransactionReceipt: () => waitForTransactionReceipt
69
+ });
70
+ module.exports = __toCommonJS(index_exports);
71
+ var import_viem15 = require("viem");
72
+
73
+ // src/contracts/lottery.ts
74
+ var import_viem4 = require("viem");
75
+
76
+ // src/errors.ts
77
+ var import_viem = require("viem");
78
+ var SlvrSDKError = class _SlvrSDKError extends Error {
79
+ constructor(message, code) {
80
+ super(message);
81
+ this.code = code;
82
+ this.name = "SlvrSDKError";
83
+ Object.setPrototypeOf(this, _SlvrSDKError.prototype);
84
+ }
85
+ };
86
+ var WalletClientRequiredError = class _WalletClientRequiredError extends SlvrSDKError {
87
+ constructor(operation) {
88
+ super(`Wallet client required for ${operation}`, "WALLET_CLIENT_REQUIRED");
89
+ this.name = "WalletClientRequiredError";
90
+ Object.setPrototypeOf(this, _WalletClientRequiredError.prototype);
91
+ }
92
+ };
93
+ var ContractCallError = class _ContractCallError extends SlvrSDKError {
94
+ constructor(message, cause) {
95
+ super(message, "CONTRACT_CALL_ERROR");
96
+ this.cause = cause;
97
+ this.name = "ContractCallError";
98
+ Object.setPrototypeOf(this, _ContractCallError.prototype);
99
+ }
100
+ };
101
+ var ValidationError = class _ValidationError extends SlvrSDKError {
102
+ constructor(message, field) {
103
+ super(message, "VALIDATION_ERROR");
104
+ this.field = field;
105
+ this.name = "ValidationError";
106
+ Object.setPrototypeOf(this, _ValidationError.prototype);
107
+ }
108
+ };
109
+ var TransactionError = class _TransactionError extends SlvrSDKError {
110
+ constructor(message, txHash, cause) {
111
+ super(message, "TRANSACTION_ERROR");
112
+ this.txHash = txHash;
113
+ this.cause = cause;
114
+ this.name = "TransactionError";
115
+ Object.setPrototypeOf(this, _TransactionError.prototype);
116
+ }
117
+ };
118
+ var REVERT_HINTS = {
119
+ InsufficientValue: "msg.value did not cover the bet total (+ the one-time account-opening deposit for a new account).",
120
+ RoundNotOpen: "betting is closed for this round.",
121
+ NotResolved: "the round is not resolved yet.",
122
+ ResolveRequested: "the round is already being resolved; betting is closed.",
123
+ BadClaim: "nothing to claim \u2014 no winning bet on this round, or already claimed.",
124
+ NoAccount: "no miner account for this address yet (place a bet first to open one).",
125
+ BadAmount: "a zero or invalid amount.",
126
+ BadSquare: "a square index outside 0\u201324.",
127
+ DupSquare: "the same square appears twice in one bet.",
128
+ BadArrays: "squares and amounts arrays have mismatched lengths.",
129
+ ValueNotSum: "msg.value does not equal the sum of the bet amounts.",
130
+ MustSumTo100Percent: "allocation percentages must sum to 100%.",
131
+ NotAuthorized: "caller is not authorized (not the user or an approved delegate).",
132
+ NotCurrentRound: "that round is not the current one.",
133
+ CannotDelegateToSelf: "you cannot delegate to your own address.",
134
+ RandomnessNotSettled: "randomness for this round has not settled yet.",
135
+ TransferFailed: "an ETH/token transfer failed.",
136
+ JackpotNotSet: "no jackpot is configured for this round."
137
+ };
138
+ var SlvrRevertError = class _SlvrRevertError extends SlvrSDKError {
139
+ constructor(errorName, message, args, cause) {
140
+ super(message, "CONTRACT_REVERT");
141
+ this.errorName = errorName;
142
+ this.args = args;
143
+ this.cause = cause;
144
+ this.name = "SlvrRevertError";
145
+ Object.setPrototypeOf(this, _SlvrRevertError.prototype);
146
+ }
147
+ };
148
+ function decodeSlvrRevert(err) {
149
+ if (!(err instanceof import_viem.BaseError)) return null;
150
+ const revert = err.walk((e) => e instanceof import_viem.ContractFunctionRevertedError);
151
+ if (!revert) return null;
152
+ const data = revert.data;
153
+ const name = data?.errorName;
154
+ const hint = name ? REVERT_HINTS[name] : void 0;
155
+ const message = name ? `Reverted: ${name}${hint ? ` \u2014 ${hint}` : ""}` : revert.shortMessage || err.shortMessage || "contract reverted";
156
+ return new SlvrRevertError(name, message, data?.args, err);
157
+ }
158
+
159
+ // src/utils.ts
160
+ var import_viem2 = require("viem");
161
+ function validateAddress(address, fieldName = "address") {
162
+ if (!(0, import_viem2.isAddress)(address)) {
163
+ throw new ValidationError(`Invalid ${fieldName}: ${address}`, fieldName);
164
+ }
165
+ return (0, import_viem2.getAddress)(address);
166
+ }
167
+ function validateAmount(amount, fieldName = "amount") {
168
+ if (amount < 0n) {
169
+ throw new ValidationError(`${fieldName} must be non-negative`, fieldName);
170
+ }
171
+ if (amount === 0n) {
172
+ throw new ValidationError(`${fieldName} must be greater than zero`, fieldName);
173
+ }
174
+ }
175
+ function validateSquares(squares, gridSize = 25) {
176
+ if (squares.length === 0) {
177
+ throw new ValidationError("At least one square must be provided", "squares");
178
+ }
179
+ for (const square of squares) {
180
+ if (!Number.isInteger(square) || square < 0 || square >= gridSize) {
181
+ throw new ValidationError(
182
+ `Invalid square index: ${square}. Must be between 0 and ${gridSize - 1}`,
183
+ "squares"
184
+ );
100
185
  }
101
- /**
102
- * One-line setup: build clients (with Multicall3 batching + resilient transport
103
- * defaults) and an SDK for a deployment. Pass a `privateKey`/`account` to enable
104
- * writes; omit it for a read-only SDK.
105
- *
106
- * @example
107
- * ```typescript
108
- * import { SlvrSDK } from '@slvr-labs/sdk';
109
- * const sdk = SlvrSDK.connect(); // read-only, Robinhood Chain
110
- * const bot = SlvrSDK.connect({ privateKey: process.env.PK }); // wallet-backed
111
- * ```
112
- */
113
- static connect(opts = {}) {
114
- const deployment = opts.deployment ?? deployments_1.robinhood;
115
- const { publicClient, walletClient } = (0, connect_1.createSlvrClients)(opts);
116
- return new SlvrSDK({ publicClient, walletClient, addresses: deployment.addresses });
117
- }
118
- /**
119
- * Get the public client
120
- */
121
- getPublicClient() {
122
- return this.config.publicClient;
123
- }
124
- /**
125
- * Get the wallet client (if available)
126
- */
127
- getWalletClient() {
128
- return this.config.walletClient;
129
- }
130
- /**
131
- * Update the wallet client and reinitialize contracts
132
- * @param walletClient New wallet client (or undefined to remove)
133
- */
134
- setWalletClient(walletClient) {
135
- this.config.walletClient = walletClient;
136
- // Reinitialize contracts with new wallet client
137
- this.lottery.setWalletClient(walletClient);
138
- this.staking.setWalletClient(walletClient);
139
- this.token.setWalletClient(walletClient);
140
- if (this.autoCommit) {
141
- this.autoCommit.setWalletClient(walletClient);
142
- }
143
- if (this.hub) {
144
- this.hub.setWalletClient(walletClient);
145
- }
146
- if (this.registry) {
147
- this.registry.setWalletClient(walletClient);
148
- }
149
- if (this.jackpot) {
150
- this.jackpot.setWalletClient(walletClient);
151
- }
186
+ }
187
+ }
188
+ function validateArrayLengths(arrays, fieldNames) {
189
+ if (arrays.length < 2) {
190
+ return;
191
+ }
192
+ const firstLength = arrays[0]?.length ?? 0;
193
+ for (let i = 1; i < arrays.length; i++) {
194
+ const currentArray = arrays[i];
195
+ const currentFieldName = fieldNames[i];
196
+ if (!currentArray || currentArray.length !== firstLength) {
197
+ throw new ValidationError(
198
+ `Array length mismatch: ${fieldNames[0]} has ${firstLength} items, but ${currentFieldName ?? `array ${i}`} has ${currentArray?.length ?? 0} items`,
199
+ currentFieldName
200
+ );
152
201
  }
153
- /**
154
- * Helper: Calculate bet amounts from percentages
155
- * @param totalAmount Total amount to bet
156
- * @param percentages Array of percentages (0-100) for each square
157
- * @returns Array of amounts in wei
158
- * @throws ValidationError if percentages don't sum to 100 or if totalAmount is invalid
159
- */
160
- static calculateBetAmounts(totalAmount, percentages) {
161
- if (percentages.length === 0) {
162
- throw new errors_1.ValidationError('Percentages array cannot be empty', 'percentages');
163
- }
164
- const totalPercent = percentages.reduce((sum, p) => sum + p, 0);
165
- if (Math.abs(totalPercent - 100) > 0.01) {
166
- throw new errors_1.ValidationError(`Percentages must sum to 100 (got ${totalPercent})`, 'percentages');
167
- }
168
- const amounts = [];
169
- let allocated = 0n;
170
- for (let i = 0; i < percentages.length; i++) {
171
- const percentage = percentages[i];
172
- if (percentage === undefined)
173
- continue;
174
- const amount = (totalAmount * BigInt(Math.round(percentage * 100))) / 10000n;
175
- amounts.push(amount);
176
- allocated += amount;
177
- }
178
- // Adjust first amount to account for rounding
179
- if (allocated !== totalAmount && amounts.length > 0 && amounts[0] !== undefined) {
180
- amounts[0] += totalAmount - allocated;
181
- }
182
- return amounts;
183
- }
184
- /**
185
- * Helper: Format bigint to human-readable string.
186
- *
187
- * `precision` caps the number of decimal places shown; trailing zeros are
188
- * stripped (like viem's `formatEther`), so `formatToken(1.5e18)` is `"1.5"`,
189
- * not `"1.5000"`.
190
- * @param value Value in wei
191
- * @param decimals Number of decimals (default 18)
192
- * @param precision Max decimal places to show (default 4)
193
- */
194
- static formatToken(value, decimals = 18, precision = 4) {
195
- const divisor = BigInt(10 ** decimals);
196
- const whole = value / divisor;
197
- const remainder = value % divisor;
198
- const fractional = (remainder * BigInt(10 ** precision)) / divisor;
199
- if (fractional === 0n) {
200
- return whole.toString();
201
- }
202
- const fractionalStr = fractional.toString().padStart(precision, '0').replace(/0+$/, '');
203
- return fractionalStr ? `${whole}.${fractionalStr}` : whole.toString();
204
- }
205
- /**
206
- * Helper: Parse human-readable string to bigint
207
- * @param value Human-readable value (e.g., "1.5")
208
- * @param decimals Number of decimals (default 18)
209
- */
210
- static parseToken(value, decimals = 18) {
211
- const parts = value.split('.');
212
- const whole = BigInt(parts[0] || '0');
213
- const fractional = parts[1] ? BigInt(parts[1].padEnd(decimals, '0').slice(0, decimals)) : 0n;
214
- return whole * BigInt(10 ** decimals) + fractional;
215
- }
216
- /**
217
- * Helper: Calculate time remaining until round ends
218
- * @param roundId Round ID
219
- * @returns Time remaining in seconds, or 0 if round has ended
220
- */
221
- async getTimeRemaining(roundId) {
222
- const roundEnd = await this.lottery.roundEnd(roundId);
223
- const now = BigInt(Math.floor(Date.now() / 1000));
224
- const remaining = roundEnd - now;
225
- return remaining > 0n ? Number(remaining) : 0;
226
- }
227
- /**
228
- * Helper: Check if user can claim rewards for a round
229
- * @param roundId Round ID
230
- * @param user User address
231
- * @returns True if user can claim
232
- */
233
- async canClaim(roundId, user) {
234
- const [round, hasClaimed_] = await Promise.all([
235
- this.lottery.getRound(roundId),
236
- this.lottery.getHasClaimed(roundId, user),
237
- ]);
238
- if (!round.resolved || hasClaimed_) {
239
- return false;
240
- }
241
- // Check if user has a bet on the winning square
242
- const userBet = await this.lottery.getUserBet(roundId, round.winningSquare, user);
243
- return userBet > 0n;
244
- }
245
- /**
246
- * Helper: Get user's claimable rounds
247
- * @param user User address
248
- * @param startRoundId Start round ID to check from
249
- * @param endRoundId End round ID to check to
250
- * @returns Array of round IDs that can be claimed
251
- */
252
- async getClaimableRounds(user, startRoundId, endRoundId) {
253
- const claimable = [];
254
- for (let roundId = startRoundId; roundId <= endRoundId; roundId++) {
255
- if (await this.canClaim(roundId, user)) {
256
- claimable.push(roundId);
257
- }
258
- }
259
- return claimable;
260
- }
261
- /**
262
- * Helper: Compute a game's effective SLVR/sec emission rate.
263
- *
264
- * Mirrors SlvrHub._effectiveRatePerSec: the game's weighted share of the global emission stream,
265
- * i.e. emissionRatePerSec * weightOf(gameId) / totalActiveWeight. NOTE: this does not apply the
266
- * per-game maxWeightBps ceiling that the contract also enforces; it is the pre-cap weighted rate.
267
- *
268
- * Requires both `hub` and `registry` addresses to be configured.
269
- * @param gameId Registry game id
270
- * @returns Effective emission rate in SLVR/sec (0 if there is no active weight)
271
- */
272
- async effectiveEmissionRate(gameId) {
273
- if (!this.hub) {
274
- throw new errors_1.ValidationError('hub address is required for effectiveEmissionRate', 'hub');
275
- }
276
- if (!this.registry) {
277
- throw new errors_1.ValidationError('registry address is required for effectiveEmissionRate', 'registry');
278
- }
279
- const [rate, weight, totalWeight] = await Promise.all([
280
- this.hub.emissionRatePerSec(),
281
- this.registry.weightOf(gameId),
282
- this.registry.totalActiveWeight(),
283
- ]);
284
- if (totalWeight === 0n) {
285
- return 0n;
286
- }
287
- return (rate * weight) / totalWeight;
288
- }
289
- /**
290
- * Helper: Get the accrued-but-unminted SLVR emission currently available to a game.
291
- *
292
- * Thin pass-through to SlvrHub.pendingEmission. Requires `hub` to be configured.
293
- * @param gameId Registry game id
294
- */
295
- async pendingEmission(gameId) {
296
- if (!this.hub) {
297
- throw new errors_1.ValidationError('hub address is required for pendingEmission', 'hub');
298
- }
299
- return await this.hub.pendingEmission(gameId);
300
- }
301
- /**
302
- * Helper: current SLVR spot price in ETH (ETH per SLVR).
303
- *
304
- * Requires a `slvrEthPair` address to be configured (so `sdk.price` exists).
305
- * @throws ValidationError if no pair address was configured.
306
- */
307
- async getSlvrPriceInEth() {
308
- if (!this.price) {
309
- throw new errors_1.ValidationError('slvrEthPair address is required for getSlvrPriceInEth', 'slvrEthPair');
310
- }
311
- return await this.price.getPriceInEth();
312
- }
313
- /**
314
- * Helper: current ETH price in USD from the configured Chainlink feed.
315
- *
316
- * Requires a `chainlinkEthUsd` address (so `sdk.ethUsd` exists). This is wired
317
- * for Robinhood Chain in `deployments.robinhood`; on chains without a feed,
318
- * supply one or pass `ethUsd` to {@link getSlvrPrice}.
319
- * @throws ValidationError if no ETH/USD feed was configured.
320
- */
321
- async getEthPriceUsd() {
322
- if (!this.ethUsd) {
323
- throw new errors_1.ValidationError('chainlinkEthUsd address is required for getEthPriceUsd', 'chainlinkEthUsd');
324
- }
325
- return await this.ethUsd.getPrice();
326
- }
327
- /**
328
- * Helper: current SLVR price in **both ETH and USD**.
329
- *
330
- * SLVR/ETH comes from the UniswapV2 pair (`sdk.price`). The USD value uses, in
331
- * order: an explicit `opts.ethUsd`, else the configured Chainlink ETH/USD feed
332
- * (`sdk.ethUsd`), else `usd` is `null`.
333
- *
334
- * @param opts.ethUsd override the ETH/USD price (e.g. from your own off-chain source)
335
- * @throws ValidationError if no `slvrEthPair` was configured.
336
- *
337
- * @example
338
- * ```typescript
339
- * const { eth, usd } = await sdk.getSlvrPrice(); // uses Chainlink feed if configured
340
- * const q = await sdk.getSlvrPrice({ ethUsd: 1797.35 }); // or supply ETH/USD yourself
341
- * ```
342
- */
343
- async getSlvrPrice(opts) {
344
- if (!this.price) {
345
- throw new errors_1.ValidationError('slvrEthPair address is required for getSlvrPrice', 'slvrEthPair');
346
- }
347
- const eth = await this.price.getPriceInEth();
348
- let ethUsd = opts?.ethUsd;
349
- if (ethUsd === undefined && this.ethUsd) {
350
- ethUsd = await this.ethUsd.getPrice();
202
+ }
203
+ }
204
+ function validateBps(bps, fieldName = "bps") {
205
+ if (!Number.isInteger(bps) || bps < 0 || bps > 1e4) {
206
+ throw new ValidationError(
207
+ `${fieldName} must be an integer between 0 and 10000 (got ${bps})`,
208
+ fieldName
209
+ );
210
+ }
211
+ }
212
+ function validateBpsSum(bpsArray, fieldName = "bpsAlloc") {
213
+ const sum = bpsArray.reduce((acc, bps) => acc + bps, 0);
214
+ if (sum !== 1e4) {
215
+ throw new ValidationError(
216
+ `${fieldName} must sum to 10000 (got ${sum})`,
217
+ fieldName
218
+ );
219
+ }
220
+ }
221
+ async function waitForTransactionReceipt(publicClient, hash, timeout = 12e4) {
222
+ if (!publicClient.waitForTransactionReceipt) {
223
+ throw new Error("Public client does not support waitForTransactionReceipt");
224
+ }
225
+ return await publicClient.waitForTransactionReceipt({ hash, timeout });
226
+ }
227
+
228
+ // src/events.ts
229
+ var import_viem3 = require("viem");
230
+ var SlvrGridLotteryEvents = (0, import_viem3.parseAbi)([
231
+ "event BetPlaced(uint256 indexed roundId, address indexed beneficiary, uint256 total, uint8[] squares)",
232
+ "event Claimed(uint256 indexed roundId, address indexed user, uint256 nativeOut, uint256 slvrOut, uint256 refinedOut, uint256 refiningFee)",
233
+ "event RoundResolved(uint256 indexed roundId, uint8 winningSquare, bool jackpotHit, bool singleMinerRound, address indexed singleMinerWinner, uint256 winnerTotal, uint256 potForWinners, uint256 slvrForWinners, uint256 totalUnclaimedSlvr)",
234
+ "event RandomnessRequested(uint256 indexed roundId, bytes32 randomnessId)"
235
+ ]);
236
+ var SlvrStakingEvents = (0, import_viem3.parseAbi)([
237
+ "event Staked(uint256 indexed tokenId, address indexed user, uint256 weight)",
238
+ "event Unstaked(uint256 indexed tokenId, address indexed user, uint256 weight)",
239
+ "event RewardClaimed(uint256 indexed tokenId, address indexed user, uint256 amount)",
240
+ "event RewardDistributed(uint256 amount)",
241
+ "event Checkpoint(uint256 indexed tokenId, uint256 oldWeight, uint256 newWeight)",
242
+ "event RewardsSettledOnBurn(uint256 indexed tokenId, address indexed owner, uint256 amount)",
243
+ "event PendingRewardsClaimed(address indexed user, uint256 amount)"
244
+ ]);
245
+ var SlvrTokenEvents = (0, import_viem3.parseAbi)([
246
+ "event Transfer(address indexed from, address indexed to, uint256 value)",
247
+ "event Approval(address indexed owner, address indexed spender, uint256 value)"
248
+ ]);
249
+ var SlvrAutoCommitEvents = (0, import_viem3.parseAbi)([
250
+ "event PlanConfigured(address indexed user, uint256 nextRoundId, uint32 plays, uint256 amountPerPlay, bool autoClaim)",
251
+ "event PlanDisabled(address indexed user)",
252
+ "event PlanCancelled(address indexed user, uint256 refundAmount)",
253
+ "event Deposited(address indexed user, uint256 amount)",
254
+ "event Withdrawn(address indexed user, uint256 amount, address to)",
255
+ "event RoundExecuted(address indexed user, uint256 indexed roundId, uint32 playsRemaining)",
256
+ "event Claimed(address indexed user, uint256 indexed roundId, uint256 nativeAmount, uint256 addedToBalance)",
257
+ "event BalanceUpdated(address indexed user, uint256 newBalance, uint256 amountAdded)",
258
+ "event ExecutorFeePaid(address indexed user, address indexed executor, uint256 fee, uint256 gasUsed)"
259
+ ]);
260
+ function decodeEvent(abi, log) {
261
+ try {
262
+ const decoded = (0, import_viem3.decodeEventLog)({
263
+ abi,
264
+ data: log.data,
265
+ topics: log.topics
266
+ });
267
+ return decoded;
268
+ } catch {
269
+ return null;
270
+ }
271
+ }
272
+ function decodeEvents(abi, logs, eventName) {
273
+ const decoded = [];
274
+ for (const log of logs) {
275
+ try {
276
+ const decodedEvent = decodeEvent(abi, log);
277
+ if (decodedEvent) {
278
+ if (!eventName || decodedEvent.eventName === eventName) {
279
+ decoded.push(decodedEvent);
351
280
  }
352
- return { eth, usd: ethUsd !== undefined ? eth * ethUsd : null };
353
- }
354
- /**
355
- * Helper: estimate the per-round expected value of grid mining for the given
356
- * stake, pulling live pot, emission and SLVR price on-chain.
357
- *
358
- * This is the SDK-level convenience around {@link computeGridMiningEv}: it reads
359
- * the round's pot (sum of all squares), the emission target (`slvrPerRound`), the
360
- * protocol fee (`protocolFeeBps`), and the SLVR price (via `sdk.price`), then
361
- * returns the full EV breakdown. Any of those can be overridden via `params`.
362
- *
363
- * The jackpot pool is **not** auto-read (it isn't exposed as a single call on the
364
- * live lottery) — pass `jackpotPool` if you want the jackpot term included.
365
- *
366
- * @remarks `slvrPerRound` is the emission *target*; actual emission is hub-gated
367
- * and may be lower, so treat the result as an upper-ish estimate. Requires a
368
- * configured `slvrEthPair` unless you pass `slvrPriceEth`.
369
- */
370
- async estimateRoundEv(params) {
371
- const roundId = params.roundId ?? (await this.lottery.currentRoundId());
372
- const [potEth, emissionPerRound, slvrPriceEth, feeBps] = await Promise.all([
373
- params.pot !== undefined
374
- ? Promise.resolve(params.pot)
375
- : this.lottery
376
- .getRoundSquares(roundId)
377
- .then((sqs) => Number((0, viem_1.formatEther)(sqs.reduce((sum, s) => sum + s.total, 0n)))),
378
- params.emissionPerRound !== undefined
379
- ? Promise.resolve(params.emissionPerRound)
380
- : this.lottery.slvrPerRound().then((v) => Number((0, viem_1.formatEther)(v))),
381
- params.slvrPriceEth !== undefined
382
- ? Promise.resolve(params.slvrPriceEth)
383
- : this.getSlvrPriceInEth(),
384
- this.lottery.protocolFeeBps(),
385
- ]);
386
- return (0, ev_1.computeGridMiningEv)({
387
- stake: params.stake,
388
- pot: potEth,
389
- emissionPerRound,
390
- slvrPriceEth,
391
- feeBps,
392
- cashOut: params.cashOut,
393
- jackpotPool: params.jackpotPool,
394
- jackpotOdds: params.jackpotOdds,
281
+ }
282
+ } catch {
283
+ }
284
+ }
285
+ return decoded;
286
+ }
287
+
288
+ // src/contracts/lottery.ts
289
+ var _SlvrGridLottery = class _SlvrGridLottery {
290
+ constructor(publicClient, walletClient, address) {
291
+ this.publicClient = publicClient;
292
+ this.walletClient = walletClient;
293
+ this.address = address;
294
+ }
295
+ /**
296
+ * Update the wallet client
297
+ * @param walletClient New wallet client
298
+ */
299
+ setWalletClient(walletClient) {
300
+ this.walletClient = walletClient;
301
+ }
302
+ /**
303
+ * Get the current round ID
304
+ */
305
+ async currentRoundId() {
306
+ return await this.publicClient.readContract({
307
+ address: this.address,
308
+ abi: _SlvrGridLottery.ABI,
309
+ functionName: "currentRoundId"
310
+ });
311
+ }
312
+ /**
313
+ * Get round start time
314
+ */
315
+ async roundStart(roundId) {
316
+ return await this.publicClient.readContract({
317
+ address: this.address,
318
+ abi: _SlvrGridLottery.ABI,
319
+ functionName: "roundStart",
320
+ args: [roundId]
321
+ });
322
+ }
323
+ /**
324
+ * Get round end time
325
+ */
326
+ async roundEnd(roundId) {
327
+ return await this.publicClient.readContract({
328
+ address: this.address,
329
+ abi: _SlvrGridLottery.ABI,
330
+ functionName: "roundEnd",
331
+ args: [roundId]
332
+ });
333
+ }
334
+ /**
335
+ * Check if round is open for betting
336
+ */
337
+ async roundOpen(roundId) {
338
+ return await this.publicClient.readContract({
339
+ address: this.address,
340
+ abi: _SlvrGridLottery.ABI,
341
+ functionName: "roundOpen",
342
+ args: [roundId]
343
+ });
344
+ }
345
+ /**
346
+ * Get the betting cutoff timestamp (unix seconds) for a round.
347
+ *
348
+ * Bets are only accepted until this time, which can be earlier than
349
+ * {@link roundEnd}. Bots deciding whether they can still bet should gate on
350
+ * this rather than on `roundEnd`.
351
+ */
352
+ async bettingEnd(roundId) {
353
+ return await this.publicClient.readContract({
354
+ address: this.address,
355
+ abi: _SlvrGridLottery.ABI,
356
+ functionName: "bettingEnd",
357
+ args: [roundId]
358
+ });
359
+ }
360
+ /**
361
+ * Estimate the reward an account would receive for a round, in wei.
362
+ *
363
+ * Thin pass-through to the contract's `getExpectedReward`. Useful for sizing
364
+ * bets or deciding whether a claim is worth the gas.
365
+ */
366
+ async getExpectedReward(account, roundId) {
367
+ return await this.publicClient.readContract({
368
+ address: this.address,
369
+ abi: _SlvrGridLottery.ABI,
370
+ functionName: "getExpectedReward",
371
+ args: [account, roundId]
372
+ });
373
+ }
374
+ /**
375
+ * Get the latest resolved round ID (type(uint256).max sentinel when none resolved yet)
376
+ */
377
+ async latestResolvedRoundId() {
378
+ return await this.publicClient.readContract({
379
+ address: this.address,
380
+ abi: _SlvrGridLottery.ABI,
381
+ functionName: "latestResolvedRoundId"
382
+ });
383
+ }
384
+ /**
385
+ * Get full round information
386
+ */
387
+ async getRound(roundId) {
388
+ const result = await this.publicClient.readContract({
389
+ address: this.address,
390
+ abi: _SlvrGridLottery.ABI,
391
+ functionName: "getRound",
392
+ args: [roundId]
393
+ });
394
+ return {
395
+ roundId,
396
+ requestedAt: result[0],
397
+ resolved: result[1],
398
+ randomnessId: result[2],
399
+ randomnessValue: result[3],
400
+ winningSquare: Number(result[4]),
401
+ jackpotHit: result[5],
402
+ singleMinerRound: result[6],
403
+ singleMinerWinner: result[7],
404
+ totalWager: result[8],
405
+ fee: result[9],
406
+ winnerTotal: result[10],
407
+ potForWinners: result[11],
408
+ slvrForWinners: result[12],
409
+ payoutMulWad: result[13],
410
+ slvrMulWad: result[14],
411
+ totalUnclaimedSlvr: result[15]
412
+ };
413
+ }
414
+ /**
415
+ * Get a compact, batched snapshot of a round's live state — the fields a bot
416
+ * checks every tick — in one shot. Defaults to the current round.
417
+ *
418
+ * `secondsUntilBettingClose` is computed from the chain's `block.timestamp`
419
+ * (not the local clock), so it's accurate even if your device clock drifts.
420
+ * With a multicall-batching client the reads collapse into ~2 RPC calls.
421
+ */
422
+ async getRoundState(roundId) {
423
+ const id = roundId ?? await this.currentRoundId();
424
+ const [round, open, bettingEnd, roundEnd, block] = await Promise.all([
425
+ this.getRound(id),
426
+ this.roundOpen(id),
427
+ this.bettingEnd(id),
428
+ this.roundEnd(id),
429
+ this.publicClient.getBlock()
430
+ ]);
431
+ const secondsLeft = Number(bettingEnd - block.timestamp);
432
+ return {
433
+ roundId: id,
434
+ open,
435
+ resolved: round.resolved,
436
+ bettingEnd,
437
+ roundEnd,
438
+ totalWager: round.totalWager,
439
+ winningSquare: round.winningSquare,
440
+ secondsUntilBettingClose: secondsLeft > 0 ? secondsLeft : 0
441
+ };
442
+ }
443
+ /**
444
+ * Get the jackpot contract address for a round (zero address if no jackpot is
445
+ * set). Read that contract's `jackpotPool()` for the pool balance — or use the
446
+ * SDK helper `getJackpotPool(roundId)`.
447
+ */
448
+ async getRoundJackpot(roundId) {
449
+ return await this.publicClient.readContract({
450
+ address: this.address,
451
+ abi: _SlvrGridLottery.ABI,
452
+ functionName: "getRoundJackpot",
453
+ args: [roundId]
454
+ });
455
+ }
456
+ /**
457
+ * Get total amount bet on a square for a round
458
+ */
459
+ async getTotalOnSquare(roundId, square) {
460
+ return await this.publicClient.readContract({
461
+ address: this.address,
462
+ abi: _SlvrGridLottery.ABI,
463
+ functionName: "getTotalOnSquare",
464
+ args: [roundId, square]
465
+ });
466
+ }
467
+ /**
468
+ * Get number of bettors on a square for a round
469
+ */
470
+ async getBettorsOnSquare(roundId, square) {
471
+ return await this.publicClient.readContract({
472
+ address: this.address,
473
+ abi: _SlvrGridLottery.ABI,
474
+ functionName: "getBettorsOnSquare",
475
+ args: [roundId, square]
476
+ });
477
+ }
478
+ /**
479
+ * Get a user's bet amount on a square for a round
480
+ */
481
+ async getUserBet(roundId, square, bettor) {
482
+ return await this.publicClient.readContract({
483
+ address: this.address,
484
+ abi: _SlvrGridLottery.ABI,
485
+ functionName: "getUserBet",
486
+ args: [roundId, square, bettor]
487
+ });
488
+ }
489
+ /**
490
+ * Check if a user has claimed rewards for a round
491
+ */
492
+ async getHasClaimed(roundId, user) {
493
+ return await this.publicClient.readContract({
494
+ address: this.address,
495
+ abi: _SlvrGridLottery.ABI,
496
+ functionName: "getHasClaimed",
497
+ args: [roundId, user]
498
+ });
499
+ }
500
+ /**
501
+ * Get miner state for an account
502
+ */
503
+ async getMinerState(account) {
504
+ const result = await this.publicClient.readContract({
505
+ address: this.address,
506
+ abi: _SlvrGridLottery.ABI,
507
+ functionName: "getMinerState",
508
+ args: [account]
509
+ });
510
+ return {
511
+ rewardsSlvr: result[0],
512
+ refinedAccrued: result[1],
513
+ indexSnapshot: result[2],
514
+ hasAccount: result[3]
515
+ };
516
+ }
517
+ /**
518
+ * Check if an account exists
519
+ */
520
+ async hasAccount(account) {
521
+ return await this.publicClient.readContract({
522
+ address: this.address,
523
+ abi: _SlvrGridLottery.ABI,
524
+ functionName: "getHasAccount",
525
+ args: [account]
526
+ });
527
+ }
528
+ /**
529
+ * Get carry-over winner native pool (when no winners)
530
+ */
531
+ async carryWinnerNativePool() {
532
+ return await this.publicClient.readContract({
533
+ address: this.address,
534
+ abi: _SlvrGridLottery.ABI,
535
+ functionName: "carryWinnerNativePool"
536
+ });
537
+ }
538
+ /**
539
+ * Get staker rewards that failed to distribute (owed)
540
+ */
541
+ async carryStakerNativeOwed() {
542
+ return await this.publicClient.readContract({
543
+ address: this.address,
544
+ abi: _SlvrGridLottery.ABI,
545
+ functionName: "carryStakerNativeOwed"
546
+ });
547
+ }
548
+ /**
549
+ * Get jackpot funds that failed to add (owed)
550
+ */
551
+ async carryJackpotNativeOwed() {
552
+ return await this.publicClient.readContract({
553
+ address: this.address,
554
+ abi: _SlvrGridLottery.ABI,
555
+ functionName: "carryJackpotNativeOwed"
556
+ });
557
+ }
558
+ /**
559
+ * Get carry-over SLVR pool
560
+ */
561
+ async carrySlvrPool() {
562
+ return await this.publicClient.readContract({
563
+ address: this.address,
564
+ abi: _SlvrGridLottery.ABI,
565
+ functionName: "carrySlvrPool"
566
+ });
567
+ }
568
+ /**
569
+ * Get the configured SLVR-per-round value.
570
+ *
571
+ * NOTE: This is no longer the authoritative emission number. Emission is hub-gated: the amount
572
+ * actually minted each round is bounded by the game's streamed emission budget on SlvrHub
573
+ * (see SlvrHub.pendingEmission / mintReward). Treat this only as the requested/target value.
574
+ */
575
+ async slvrPerRound() {
576
+ return await this.publicClient.readContract({
577
+ address: this.address,
578
+ abi: _SlvrGridLottery.ABI,
579
+ functionName: "slvrPerRound"
580
+ });
581
+ }
582
+ /**
583
+ * Get protocol fee in basis points
584
+ */
585
+ async protocolFeeBps() {
586
+ return await this.publicClient.readContract({
587
+ address: this.address,
588
+ abi: _SlvrGridLottery.ABI,
589
+ functionName: "protocolFeeBps"
590
+ });
591
+ }
592
+ /**
593
+ * Get grid size (should be 25)
594
+ */
595
+ async grid() {
596
+ return await this.publicClient.readContract({
597
+ address: this.address,
598
+ abi: _SlvrGridLottery.ABI,
599
+ functionName: "GRID"
600
+ });
601
+ }
602
+ /**
603
+ * Get account deposit amount
604
+ */
605
+ async accountDeposit() {
606
+ return await this.publicClient.readContract({
607
+ address: this.address,
608
+ abi: _SlvrGridLottery.ABI,
609
+ functionName: "ACCOUNT_DEPOSIT"
610
+ });
611
+ }
612
+ /**
613
+ * Place a bet on the current round
614
+ * Note: The contract will automatically handle account deposit for new accounts.
615
+ * If the beneficiary is a new account, ensure msg.value >= ACCOUNT_DEPOSIT + total bet amount.
616
+ * @param params Bet parameters
617
+ * @returns Transaction hash
618
+ * @throws WalletClientRequiredError if wallet client is not available
619
+ * @throws ValidationError if inputs are invalid
620
+ * @throws ContractCallError if contract call fails
621
+ */
622
+ async bet(params) {
623
+ if (!this.walletClient) {
624
+ throw new WalletClientRequiredError("betting");
625
+ }
626
+ const { roundId, squares, amounts, beneficiary, overrides } = params;
627
+ if (roundId < 0n) {
628
+ throw new ContractCallError("Round ID must be non-negative");
629
+ }
630
+ validateSquares(squares);
631
+ validateArrayLengths([squares, amounts], ["squares", "amounts"]);
632
+ for (const amount of amounts) {
633
+ validateAmount(amount, "bet amount");
634
+ }
635
+ const totalAmount = amounts.reduce((sum, amt) => sum + amt, 0n);
636
+ try {
637
+ const beneficiaryAddress = beneficiary ? validateAddress(beneficiary, "beneficiary") : this.walletClient.account.address;
638
+ const hasAccount_ = await this.hasAccount(beneficiaryAddress);
639
+ const accountDeposit_ = hasAccount_ ? 0n : await this.accountDeposit();
640
+ const totalValue = totalAmount + accountDeposit_;
641
+ const req = beneficiary ? { functionName: "betFor", args: [roundId, beneficiaryAddress, squares, amounts] } : { functionName: "bet", args: [roundId, squares, amounts] };
642
+ return await this.walletClient.writeContract({
643
+ address: this.address,
644
+ abi: _SlvrGridLottery.ABI,
645
+ functionName: req.functionName,
646
+ args: req.args,
647
+ value: totalValue,
648
+ account: this.walletClient.account,
649
+ chain: null,
650
+ ...overrides ?? {}
651
+ });
652
+ } catch (error) {
653
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
654
+ throw error;
655
+ }
656
+ const revert = decodeSlvrRevert(error);
657
+ if (revert) throw revert;
658
+ throw new ContractCallError(`Failed to place bet: ${error instanceof Error ? error.message : String(error)}`, error);
659
+ }
660
+ }
661
+ /**
662
+ * Preflight a bet with `eth_call` (no transaction sent). Resolves if the bet
663
+ * would succeed; otherwise throws a {@link SlvrRevertError} with the decoded
664
+ * custom error (e.g. `RoundNotOpen`, `InsufficientValue`). Catch reverts before
665
+ * spending gas.
666
+ */
667
+ async simulateBet(params) {
668
+ if (!this.walletClient) {
669
+ throw new WalletClientRequiredError("simulating a bet");
670
+ }
671
+ const { roundId, squares, amounts, beneficiary } = params;
672
+ validateSquares(squares);
673
+ validateArrayLengths([squares, amounts], ["squares", "amounts"]);
674
+ const totalAmount = amounts.reduce((sum, amt) => sum + amt, 0n);
675
+ const beneficiaryAddress = beneficiary ? validateAddress(beneficiary, "beneficiary") : this.walletClient.account.address;
676
+ const hasAccount_ = await this.hasAccount(beneficiaryAddress);
677
+ const value = totalAmount + (hasAccount_ ? 0n : await this.accountDeposit());
678
+ const req = beneficiary ? { functionName: "betFor", args: [roundId, beneficiaryAddress, squares, amounts] } : { functionName: "bet", args: [roundId, squares, amounts] };
679
+ try {
680
+ await this.publicClient.simulateContract({
681
+ address: this.address,
682
+ abi: _SlvrGridLottery.ABI,
683
+ functionName: req.functionName,
684
+ args: req.args,
685
+ value,
686
+ account: this.walletClient.account
687
+ });
688
+ } catch (error) {
689
+ throw decodeSlvrRevert(error) ?? error;
690
+ }
691
+ }
692
+ /**
693
+ * Claim rewards for a round
694
+ * @param params Claim parameters
695
+ * @returns Transaction hash
696
+ * @throws WalletClientRequiredError if wallet client is not available
697
+ * @throws ValidationError if inputs are invalid
698
+ * @throws ContractCallError if contract call fails
699
+ */
700
+ async claim(params) {
701
+ if (!this.walletClient) {
702
+ throw new WalletClientRequiredError("claiming");
703
+ }
704
+ if (params.roundId < 0n) {
705
+ throw new ContractCallError("Round ID must be non-negative");
706
+ }
707
+ try {
708
+ return await this.walletClient.writeContract({
709
+ address: this.address,
710
+ abi: _SlvrGridLottery.ABI,
711
+ functionName: "claim",
712
+ args: [params.roundId],
713
+ account: this.walletClient.account,
714
+ chain: null,
715
+ ...params.overrides ?? {}
716
+ });
717
+ } catch (error) {
718
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
719
+ throw error;
720
+ }
721
+ const revert = decodeSlvrRevert(error);
722
+ if (revert) throw revert;
723
+ throw new ContractCallError(`Failed to claim rewards: ${error instanceof Error ? error.message : String(error)}`, error);
724
+ }
725
+ }
726
+ /**
727
+ * Advanced claim function covering all claim variants
728
+ * Handles: user vs delegate, native/SLVR recipients, bypassFee, ethOnly
729
+ * @param params Advanced claim parameters
730
+ * @returns Transaction hash
731
+ * @throws WalletClientRequiredError if wallet client is not available
732
+ * @throws ValidationError if inputs are invalid
733
+ * @throws ContractCallError if contract call fails
734
+ */
735
+ async claimAdvanced(params) {
736
+ if (!this.walletClient) {
737
+ throw new WalletClientRequiredError("claiming");
738
+ }
739
+ if (params.roundId < 0n) {
740
+ throw new ContractCallError("Round ID must be non-negative");
741
+ }
742
+ const user = validateAddress(params.user, "user");
743
+ const recipientNative = params.recipientNative ? validateAddress(params.recipientNative, "recipientNative") : "0x0000000000000000000000000000000000000000";
744
+ const recipientSlvr = params.recipientSlvr ? validateAddress(params.recipientSlvr, "recipientSlvr") : "0x0000000000000000000000000000000000000000";
745
+ try {
746
+ return await this.walletClient.writeContract({
747
+ address: this.address,
748
+ abi: _SlvrGridLottery.ABI,
749
+ functionName: "claimAdvanced",
750
+ args: [{
751
+ user,
752
+ roundId: params.roundId,
753
+ recipientNative,
754
+ recipientSlvr,
755
+ bypassFee: params.bypassFee || false,
756
+ ethOnly: params.ethOnly || false
757
+ }],
758
+ account: this.walletClient.account,
759
+ chain: null
760
+ });
761
+ } catch (error) {
762
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
763
+ throw error;
764
+ }
765
+ throw new ContractCallError(`Failed to claim rewards: ${error instanceof Error ? error.message : String(error)}`, error);
766
+ }
767
+ }
768
+ /**
769
+ * Batch claim rewards for multiple rounds
770
+ * Note: This sends multiple transactions sequentially. For better UX, consider
771
+ * using a multicall contract or batching in your application layer.
772
+ * @param roundIds Array of round IDs to claim
773
+ * @param user User address to claim for (defaults to wallet client account)
774
+ * @param options Optional configuration
775
+ * @param options.waitForReceipt Whether to wait for each transaction receipt (default: false)
776
+ * @returns Array of transaction hashes
777
+ * @throws WalletClientRequiredError if wallet client is not available
778
+ * @throws ValidationError if inputs are invalid
779
+ * @throws ContractCallError if contract call fails
780
+ */
781
+ async batchClaim(roundIds, user, options) {
782
+ if (!this.walletClient) {
783
+ throw new WalletClientRequiredError("batch claiming");
784
+ }
785
+ if (roundIds.length === 0) {
786
+ throw new ContractCallError("No rounds to claim");
787
+ }
788
+ const claimUser = user ? validateAddress(user, "user") : this.walletClient.account.address;
789
+ const hashes = [];
790
+ for (const roundId of roundIds) {
791
+ if (roundId < 0n) {
792
+ throw new ContractCallError(`Invalid round ID: ${roundId}`);
793
+ }
794
+ try {
795
+ const hash = await this.claimAdvanced({
796
+ user: claimUser,
797
+ roundId
395
798
  });
799
+ hashes.push(hash);
800
+ if (options?.waitForReceipt) {
801
+ await waitForTransactionReceipt(this.publicClient, hash);
802
+ }
803
+ } catch (error) {
804
+ throw new ContractCallError(
805
+ `Failed to claim round ${roundId}: ${error instanceof Error ? error.message : String(error)}`,
806
+ error
807
+ );
808
+ }
809
+ }
810
+ return hashes;
811
+ }
812
+ /**
813
+ * Approve a delegate to claim rewards on your behalf
814
+ */
815
+ async approveDelegate(delegate) {
816
+ if (!this.walletClient) {
817
+ throw new WalletClientRequiredError("approving delegate");
818
+ }
819
+ const delegateAddress = validateAddress(delegate, "delegate");
820
+ return await this.walletClient.writeContract({
821
+ address: this.address,
822
+ abi: _SlvrGridLottery.ABI,
823
+ functionName: "approveDelegate",
824
+ args: [delegateAddress],
825
+ account: this.walletClient.account,
826
+ chain: null
827
+ });
828
+ }
829
+ /**
830
+ * Revoke a delegate's approval to claim on your behalf
831
+ */
832
+ async revokeDelegate(delegate) {
833
+ if (!this.walletClient) {
834
+ throw new WalletClientRequiredError("revoking delegate");
835
+ }
836
+ const delegateAddress = validateAddress(delegate, "delegate");
837
+ return await this.walletClient.writeContract({
838
+ address: this.address,
839
+ abi: _SlvrGridLottery.ABI,
840
+ functionName: "revokeDelegate",
841
+ args: [delegateAddress],
842
+ account: this.walletClient.account,
843
+ chain: null
844
+ });
845
+ }
846
+ /**
847
+ * Check if a delegate is approved for a user
848
+ */
849
+ async getDelegate(user, delegate) {
850
+ return await this.publicClient.readContract({
851
+ address: this.address,
852
+ abi: _SlvrGridLottery.ABI,
853
+ functionName: "getDelegate",
854
+ args: [user, delegate]
855
+ });
856
+ }
857
+ /**
858
+ * Donate SLVR tokens directly to the jackpot pool.
859
+ * Note: caller must have approved the lottery to spend `amount` of SLVR beforehand.
860
+ */
861
+ async donateSlvrToJackpot(amount) {
862
+ if (!this.walletClient) {
863
+ throw new WalletClientRequiredError("donating to jackpot");
864
+ }
865
+ validateAmount(amount, "donation amount");
866
+ return await this.walletClient.writeContract({
867
+ address: this.address,
868
+ abi: _SlvrGridLottery.ABI,
869
+ functionName: "donateSlvrToJackpot",
870
+ args: [amount],
871
+ account: this.walletClient.account,
872
+ chain: null
873
+ });
874
+ }
875
+ /**
876
+ * Add ETH (native) tokens to the jackpot pool
877
+ * @param value Amount of native ETH to deposit
878
+ */
879
+ async addEthToJackpot(value) {
880
+ if (!this.walletClient) {
881
+ throw new WalletClientRequiredError("adding ETH to jackpot");
882
+ }
883
+ validateAmount(value, "jackpot amount");
884
+ return await this.walletClient.writeContract({
885
+ address: this.address,
886
+ abi: _SlvrGridLottery.ABI,
887
+ functionName: "addEthToJackpot",
888
+ value,
889
+ account: this.walletClient.account,
890
+ chain: null
891
+ });
892
+ }
893
+ /**
894
+ * Withdraw your accumulated mined SLVR (the "refine and cash out" path).
895
+ *
896
+ * Settles your miner accrual (auto-checkpoints) and transfers your SLVR out,
897
+ * net of the refining fee. This is the function a mining bot wants to realize
898
+ * SLVR rewards outside of a specific round claim. Reverts if you have nothing to
899
+ * withdraw. The exact `(totalPayout, refiningFee)` are in the emitted `Claimed`
900
+ * event; this returns the transaction hash.
901
+ */
902
+ async withdrawUnrefinedSlvr() {
903
+ if (!this.walletClient) {
904
+ throw new WalletClientRequiredError("withdrawing unrefined SLVR");
905
+ }
906
+ return await this.walletClient.writeContract({
907
+ address: this.address,
908
+ abi: _SlvrGridLottery.ABI,
909
+ functionName: "withdrawUnrefinedSlvr",
910
+ account: this.walletClient.account,
911
+ chain: null
912
+ });
913
+ }
914
+ /**
915
+ * Force on-chain settlement of a miner's refined-reward accrual (lazy
916
+ * "checkpoint"). Permissionless — you can checkpoint any account.
917
+ *
918
+ * This only updates accounting (rolls index growth into `refinedAccrued`); it
919
+ * moves no tokens. You rarely need it: `claim` and {@link withdrawUnrefinedSlvr}
920
+ * both checkpoint internally, and the up-to-date figure can be derived off-chain
921
+ * from {@link getMinerState}. Use it when you want state settled without a
922
+ * claim/withdraw — e.g. to make a subsequent `getMinerState` read exact.
923
+ */
924
+ async checkpoint(account) {
925
+ if (!this.walletClient) {
926
+ throw new WalletClientRequiredError("checkpointing");
927
+ }
928
+ return await this.walletClient.writeContract({
929
+ address: this.address,
930
+ abi: _SlvrGridLottery.ABI,
931
+ functionName: "checkpoint",
932
+ args: [account],
933
+ account: this.walletClient.account,
934
+ chain: null
935
+ });
936
+ }
937
+ /**
938
+ * Get all square data for a round
939
+ */
940
+ async getRoundSquares(roundId) {
941
+ const gridSize = await this.grid();
942
+ const squares = Array.from({ length: gridSize }, (_, i) => i);
943
+ try {
944
+ const contracts = squares.flatMap((sq) => [
945
+ { address: this.address, abi: _SlvrGridLottery.ABI, functionName: "getTotalOnSquare", args: [roundId, sq] },
946
+ { address: this.address, abi: _SlvrGridLottery.ABI, functionName: "getBettorsOnSquare", args: [roundId, sq] }
947
+ ]);
948
+ const res = await this.publicClient.multicall({
949
+ contracts,
950
+ allowFailure: false
951
+ });
952
+ return squares.map((square, i) => ({ square, total: res[i * 2], bettors: res[i * 2 + 1] }));
953
+ } catch {
954
+ const [totals, bettors] = await Promise.all([
955
+ Promise.all(squares.map((sq) => this.getTotalOnSquare(roundId, sq))),
956
+ Promise.all(squares.map((sq) => this.getBettorsOnSquare(roundId, sq)))
957
+ ]);
958
+ return squares.map((square, i) => ({ square, total: totals[i], bettors: bettors[i] }));
959
+ }
960
+ }
961
+ /**
962
+ * Get user's bets for a round
963
+ */
964
+ async getUserBets(roundId, user) {
965
+ const gridSize = await this.grid();
966
+ const squares = Array.from({ length: gridSize }, (_, i) => i);
967
+ let bets;
968
+ try {
969
+ const contracts = squares.map((sq) => ({
970
+ address: this.address,
971
+ abi: _SlvrGridLottery.ABI,
972
+ functionName: "getUserBet",
973
+ args: [roundId, sq, user]
974
+ }));
975
+ bets = await this.publicClient.multicall({ contracts, allowFailure: false });
976
+ } catch {
977
+ bets = await Promise.all(squares.map((sq) => this.getUserBet(roundId, sq, user)));
978
+ }
979
+ return squares.map((square, i) => ({ square, amount: bets[i] })).filter((bet) => bet.amount > 0n);
980
+ }
981
+ // ---------------------------------------------------------------------------
982
+ // Reactive helpers
983
+ // ---------------------------------------------------------------------------
984
+ /**
985
+ * Wait until a round is resolved, resolving with its final {@link RoundInfo}.
986
+ * Polls `getRound` on an interval — handy for "bet, then claim once it settles".
987
+ *
988
+ * @param opts.pollIntervalMs how often to check (default 4000)
989
+ * @param opts.timeoutMs give up after this long (default: wait indefinitely)
990
+ * @throws {Error} if `timeoutMs` elapses before resolution
991
+ */
992
+ async waitForResolution(roundId, opts = {}) {
993
+ const interval = opts.pollIntervalMs ?? 4e3;
994
+ const deadline = opts.timeoutMs !== void 0 ? Date.now() + opts.timeoutMs : Infinity;
995
+ for (; ; ) {
996
+ const round = await this.getRound(roundId);
997
+ if (round.resolved) return round;
998
+ if (Date.now() >= deadline) throw new Error(`round ${roundId} not resolved within ${opts.timeoutMs}ms`);
999
+ await new Promise((r) => setTimeout(r, interval));
1000
+ }
1001
+ }
1002
+ /**
1003
+ * Subscribe to `RoundResolved` events. Returns an unsubscribe function.
1004
+ *
1005
+ * @example
1006
+ * ```typescript
1007
+ * const stop = sdk.lottery.watchRoundResolved((e) => {
1008
+ * console.log(`round ${e.roundId} won by square ${e.winningSquare}`);
1009
+ * });
1010
+ * // later: stop();
1011
+ * ```
1012
+ */
1013
+ watchRoundResolved(onResolved, opts = {}) {
1014
+ return this.publicClient.watchContractEvent({
1015
+ address: this.address,
1016
+ abi: SlvrGridLotteryEvents,
1017
+ eventName: "RoundResolved",
1018
+ onLogs: (logs) => {
1019
+ for (const log of logs) onResolved(log.args);
1020
+ },
1021
+ onError: opts.onError,
1022
+ poll: true
1023
+ });
1024
+ }
1025
+ /**
1026
+ * Subscribe to `BetPlaced` events (optionally for one round). Returns an
1027
+ * unsubscribe function. Useful for reacting to pot changes in real time.
1028
+ */
1029
+ watchBets(onBet, opts = {}) {
1030
+ return this.publicClient.watchContractEvent({
1031
+ address: this.address,
1032
+ abi: SlvrGridLotteryEvents,
1033
+ eventName: "BetPlaced",
1034
+ args: opts.roundId !== void 0 ? { roundId: opts.roundId } : void 0,
1035
+ onLogs: (logs) => {
1036
+ for (const log of logs) onBet(log.args);
1037
+ },
1038
+ onError: opts.onError,
1039
+ poll: true
1040
+ });
1041
+ }
1042
+ };
1043
+ // ABI for SlvrGridLottery contract
1044
+ _SlvrGridLottery.ABI = (0, import_viem4.parseAbi)([
1045
+ // View functions
1046
+ "function currentRoundId() view returns (uint256)",
1047
+ "function roundStart(uint256 roundId) view returns (uint256)",
1048
+ "function roundEnd(uint256 roundId) view returns (uint256)",
1049
+ "function roundOpen(uint256 roundId) view returns (bool)",
1050
+ // Betting cutoff for a round. This is the timestamp bets stop being accepted,
1051
+ // which can be earlier than roundEnd() — gate bots on this, not roundEnd.
1052
+ "function bettingEnd(uint256 roundId) view returns (uint256)",
1053
+ "function getExpectedReward(address account, uint256 roundId) view returns (uint256)",
1054
+ "function latestResolvedRoundId() view returns (uint256)",
1055
+ // getRound returns a flat 16-value tuple (no wrapping struct)
1056
+ "function getRound(uint256 roundId) view returns (uint64 requestedAt, bool resolved, bytes32 randomnessId, uint256 randomnessValue, uint8 winningSquare, bool jackpotHit, bool singleMinerRound, address singleMinerWinner, uint256 totalWager, uint256 fee, uint256 winnerTotal, uint256 potForWinners, uint256 slvrForWinners, uint256 payoutMulWad, uint256 slvrMulWad, uint256 totalUnclaimedSlvr)",
1057
+ "function getTotalOnSquare(uint256 roundId, uint8 square) view returns (uint256)",
1058
+ "function getBettorsOnSquare(uint256 roundId, uint8 square) view returns (uint256)",
1059
+ "function getUserBet(uint256 roundId, uint8 square, address bettor) view returns (uint256)",
1060
+ "function getHasClaimed(uint256 roundId, address user) view returns (bool)",
1061
+ "function getMinerState(address account) view returns (uint256 rewardsSlvr, uint256 refinedAccrued, uint256 indexSnapshot, bool hasAccount)",
1062
+ "function getHasAccount(address account) view returns (bool)",
1063
+ "function getDelegate(address user, address delegate) view returns (bool)",
1064
+ "function carryWinnerNativePool() view returns (uint256)",
1065
+ "function carryStakerNativeOwed() view returns (uint256)",
1066
+ "function carryJackpotNativeOwed() view returns (uint256)",
1067
+ "function carrySlvrPool() view returns (uint256)",
1068
+ // NOTE: slvrPerRound() still exists as a public var but is NO LONGER the authoritative
1069
+ // emission number. Emission is hub-gated (see SlvrHub.pendingEmission / mintReward), so the
1070
+ // amount actually minted per round is bounded by the game's streamed emission budget.
1071
+ "function slvrPerRound() view returns (uint256)",
1072
+ "function protocolFeeBps() view returns (uint16)",
1073
+ "function GRID() view returns (uint8)",
1074
+ "function ACCOUNT_DEPOSIT() view returns (uint256)",
1075
+ // Write functions
1076
+ "function bet(uint256 roundId, uint8[] squares, uint256[] amounts) payable",
1077
+ "function betFor(uint256 roundId, address beneficiary, uint8[] squares, uint256[] amounts) payable",
1078
+ "function claim(uint256 roundId)",
1079
+ "function claimAdvanced((address user, uint256 roundId, address recipientNative, address recipientSlvr, bool bypassFee, bool ethOnly) params)",
1080
+ "function approveDelegate(address delegate)",
1081
+ "function revokeDelegate(address delegate)",
1082
+ "function donateSlvrToJackpot(uint256 amount)",
1083
+ "function addEthToJackpot() payable",
1084
+ "function checkpoint(address account)",
1085
+ "function withdrawUnrefinedSlvr() returns (uint256 totalPayout, uint256 refiningFee)",
1086
+ "function getRoundJackpot(uint256 roundId) view returns (address)",
1087
+ // Custom errors — so viem can decode reverts (see decodeSlvrRevert / simulateBet)
1088
+ "error InsufficientValue()",
1089
+ "error RoundNotOpen()",
1090
+ "error NotResolved()",
1091
+ "error ResolveRequested()",
1092
+ "error BadClaim()",
1093
+ "error NoAccount()",
1094
+ "error BadAmount()",
1095
+ "error BadSquare()",
1096
+ "error DupSquare()",
1097
+ "error BadArrays()",
1098
+ "error ValueNotSum()",
1099
+ "error MustSumTo100Percent()",
1100
+ "error NotAuthorized()",
1101
+ "error NotCurrentRound()",
1102
+ "error CannotDelegateToSelf()",
1103
+ "error RandomnessNotSettled()",
1104
+ "error TransferFailed()",
1105
+ "error JackpotNotSet()"
1106
+ ]);
1107
+ var SlvrGridLottery = _SlvrGridLottery;
1108
+
1109
+ // src/contracts/staking.ts
1110
+ var import_viem5 = require("viem");
1111
+ var _SlvrStaking = class _SlvrStaking {
1112
+ constructor(publicClient, walletClient, address) {
1113
+ this.publicClient = publicClient;
1114
+ this.walletClient = walletClient;
1115
+ this.address = address;
1116
+ }
1117
+ /**
1118
+ * Update the wallet client
1119
+ * @param walletClient New wallet client
1120
+ */
1121
+ setWalletClient(walletClient) {
1122
+ this.walletClient = walletClient;
1123
+ }
1124
+ // ---------------------------------------------------------------------------
1125
+ // Views
1126
+ // ---------------------------------------------------------------------------
1127
+ /**
1128
+ * Get claimable rewards for a staked veNFT tokenId
1129
+ */
1130
+ async getStakerRewards(tokenId) {
1131
+ return await this.publicClient.readContract({
1132
+ address: this.address,
1133
+ abi: _SlvrStaking.ABI,
1134
+ functionName: "getStakerRewards",
1135
+ args: [tokenId]
1136
+ });
1137
+ }
1138
+ /**
1139
+ * Get the total tracked weight across all staked tokens
1140
+ */
1141
+ async getTotalWeight() {
1142
+ return await this.publicClient.readContract({
1143
+ address: this.address,
1144
+ abi: _SlvrStaking.ABI,
1145
+ functionName: "getTotalWeight"
1146
+ });
1147
+ }
1148
+ /**
1149
+ * Get the accumulated reward per unit of weight (1e18 precision)
1150
+ */
1151
+ async rewardPerWeightStored() {
1152
+ return await this.publicClient.readContract({
1153
+ address: this.address,
1154
+ abi: _SlvrStaking.ABI,
1155
+ functionName: "rewardPerWeightStored"
1156
+ });
1157
+ }
1158
+ /**
1159
+ * Get the last rewardPerWeightStored snapshot recorded for a tokenId
1160
+ */
1161
+ async rewardPerWeightPaid(tokenId) {
1162
+ return await this.publicClient.readContract({
1163
+ address: this.address,
1164
+ abi: _SlvrStaking.ABI,
1165
+ functionName: "rewardPerWeightPaid",
1166
+ args: [tokenId]
1167
+ });
1168
+ }
1169
+ /**
1170
+ * Get the tracked weight (balance) for a staked tokenId
1171
+ */
1172
+ async balance(tokenId) {
1173
+ return await this.publicClient.readContract({
1174
+ address: this.address,
1175
+ abi: _SlvrStaking.ABI,
1176
+ functionName: "balance",
1177
+ args: [tokenId]
1178
+ });
1179
+ }
1180
+ /**
1181
+ * Get the accrued (checkpointed) claimable rewards stored for a tokenId
1182
+ */
1183
+ async rewards(tokenId) {
1184
+ return await this.publicClient.readContract({
1185
+ address: this.address,
1186
+ abi: _SlvrStaking.ABI,
1187
+ functionName: "rewards",
1188
+ args: [tokenId]
1189
+ });
1190
+ }
1191
+ /**
1192
+ * Get the total tracked weight (public storage var)
1193
+ */
1194
+ async totalWeight() {
1195
+ return await this.publicClient.readContract({
1196
+ address: this.address,
1197
+ abi: _SlvrStaking.ABI,
1198
+ functionName: "totalWeight"
1199
+ });
1200
+ }
1201
+ /**
1202
+ * Get unallocated rewards (distributed while no weight was staked)
1203
+ */
1204
+ async unallocated() {
1205
+ return await this.publicClient.readContract({
1206
+ address: this.address,
1207
+ abi: _SlvrStaking.ABI,
1208
+ functionName: "unallocated"
1209
+ });
1210
+ }
1211
+ /**
1212
+ * Get the last distributed round ID (type(uint256).max sentinel when none)
1213
+ */
1214
+ async lastDistributedRoundId() {
1215
+ return await this.publicClient.readContract({
1216
+ address: this.address,
1217
+ abi: _SlvrStaking.ABI,
1218
+ functionName: "lastDistributedRoundId"
1219
+ });
1220
+ }
1221
+ /**
1222
+ * Get the lottery address authorized to distribute rewards
1223
+ */
1224
+ async lottery() {
1225
+ return await this.publicClient.readContract({
1226
+ address: this.address,
1227
+ abi: _SlvrStaking.ABI,
1228
+ functionName: "LOTTERY"
1229
+ });
1230
+ }
1231
+ /**
1232
+ * Get complete staking info for a staked veNFT tokenId
1233
+ */
1234
+ async getStakingInfo(tokenId) {
1235
+ const [totalWeight_, balance_, rewards_, rewardPerWeightStored_] = await Promise.all([
1236
+ this.totalWeight(),
1237
+ this.balance(tokenId),
1238
+ this.getStakerRewards(tokenId),
1239
+ this.rewardPerWeightStored()
1240
+ ]);
1241
+ return {
1242
+ totalWeight: totalWeight_,
1243
+ balance: balance_,
1244
+ rewards: rewards_,
1245
+ rewardPerWeightStored: rewardPerWeightStored_
1246
+ };
1247
+ }
1248
+ // ---------------------------------------------------------------------------
1249
+ // Writes
1250
+ // ---------------------------------------------------------------------------
1251
+ /**
1252
+ * Stake a vote-escrow NFT by tokenId
1253
+ * @param tokenId The veNFT token ID to stake
1254
+ * @returns Transaction hash
1255
+ */
1256
+ async stake(tokenId) {
1257
+ if (!this.walletClient) {
1258
+ throw new WalletClientRequiredError("staking");
1259
+ }
1260
+ try {
1261
+ return await this.walletClient.writeContract({
1262
+ address: this.address,
1263
+ abi: _SlvrStaking.ABI,
1264
+ functionName: "stake",
1265
+ args: [tokenId],
1266
+ account: this.walletClient.account,
1267
+ chain: null
1268
+ });
1269
+ } catch (error) {
1270
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1271
+ throw error;
1272
+ }
1273
+ throw new ContractCallError(`Failed to stake: ${error instanceof Error ? error.message : String(error)}`, error);
1274
+ }
1275
+ }
1276
+ /**
1277
+ * Unstake a vote-escrow NFT by tokenId
1278
+ * @param tokenId The veNFT token ID to unstake
1279
+ * @returns Transaction hash
1280
+ */
1281
+ async unstake(tokenId) {
1282
+ if (!this.walletClient) {
1283
+ throw new WalletClientRequiredError("unstaking");
1284
+ }
1285
+ try {
1286
+ return await this.walletClient.writeContract({
1287
+ address: this.address,
1288
+ abi: _SlvrStaking.ABI,
1289
+ functionName: "unstake",
1290
+ args: [tokenId],
1291
+ account: this.walletClient.account,
1292
+ chain: null
1293
+ });
1294
+ } catch (error) {
1295
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1296
+ throw error;
1297
+ }
1298
+ throw new ContractCallError(`Failed to unstake: ${error instanceof Error ? error.message : String(error)}`, error);
1299
+ }
1300
+ }
1301
+ /**
1302
+ * Claim staker rewards for a tokenId
1303
+ * @param tokenId The veNFT token ID to claim rewards for
1304
+ * @returns Transaction hash
1305
+ */
1306
+ async claimStakerRewards(tokenId) {
1307
+ if (!this.walletClient) {
1308
+ throw new WalletClientRequiredError("claiming rewards");
1309
+ }
1310
+ try {
1311
+ return await this.walletClient.writeContract({
1312
+ address: this.address,
1313
+ abi: _SlvrStaking.ABI,
1314
+ functionName: "claimStakerRewards",
1315
+ args: [tokenId],
1316
+ account: this.walletClient.account,
1317
+ chain: null
1318
+ });
1319
+ } catch (error) {
1320
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1321
+ throw error;
1322
+ }
1323
+ throw new ContractCallError(`Failed to claim rewards: ${error instanceof Error ? error.message : String(error)}`, error);
1324
+ }
1325
+ }
1326
+ /**
1327
+ * Checkpoint a token's reward accounting
1328
+ * @param tokenId The veNFT token ID to checkpoint
1329
+ * @returns Transaction hash
1330
+ */
1331
+ async checkpoint(tokenId) {
1332
+ if (!this.walletClient) {
1333
+ throw new WalletClientRequiredError("checkpointing");
1334
+ }
1335
+ return await this.walletClient.writeContract({
1336
+ address: this.address,
1337
+ abi: _SlvrStaking.ABI,
1338
+ functionName: "checkpoint",
1339
+ args: [tokenId],
1340
+ account: this.walletClient.account,
1341
+ chain: null
1342
+ });
1343
+ }
1344
+ /**
1345
+ * Poke a token to refresh its tracked weight
1346
+ * @param tokenId The veNFT token ID to poke
1347
+ * @returns Transaction hash
1348
+ */
1349
+ async poke(tokenId) {
1350
+ if (!this.walletClient) {
1351
+ throw new WalletClientRequiredError("poking");
1352
+ }
1353
+ return await this.walletClient.writeContract({
1354
+ address: this.address,
1355
+ abi: _SlvrStaking.ABI,
1356
+ functionName: "poke",
1357
+ args: [tokenId],
1358
+ account: this.walletClient.account,
1359
+ chain: null
1360
+ });
1361
+ }
1362
+ /**
1363
+ * Poke many tokens to refresh their tracked weight in one call
1364
+ * @param tokenIds The veNFT token IDs to poke
1365
+ * @returns Transaction hash
1366
+ */
1367
+ async pokeMany(tokenIds) {
1368
+ if (!this.walletClient) {
1369
+ throw new WalletClientRequiredError("poking");
1370
+ }
1371
+ return await this.walletClient.writeContract({
1372
+ address: this.address,
1373
+ abi: _SlvrStaking.ABI,
1374
+ functionName: "pokeMany",
1375
+ args: [tokenIds],
1376
+ account: this.walletClient.account,
1377
+ chain: null
1378
+ });
1379
+ }
1380
+ };
1381
+ _SlvrStaking.ABI = (0, import_viem5.parseAbi)([
1382
+ // View functions
1383
+ "function getStakerRewards(uint256 tokenId) view returns (uint256)",
1384
+ "function getTotalWeight() view returns (uint256)",
1385
+ "function rewardPerWeightStored() view returns (uint256)",
1386
+ "function rewardPerWeightPaid(uint256 tokenId) view returns (uint256)",
1387
+ "function balance(uint256 tokenId) view returns (uint256)",
1388
+ "function rewards(uint256 tokenId) view returns (uint256)",
1389
+ "function totalWeight() view returns (uint256)",
1390
+ "function unallocated() view returns (uint256)",
1391
+ "function lastDistributedRoundId() view returns (uint256)",
1392
+ "function LOTTERY() view returns (address)",
1393
+ // Write functions
1394
+ "function stake(uint256 tokenId)",
1395
+ "function unstake(uint256 tokenId)",
1396
+ "function claimStakerRewards(uint256 tokenId)",
1397
+ "function checkpoint(uint256 tokenId)",
1398
+ "function poke(uint256 tokenId)",
1399
+ "function pokeMany(uint256[] tokenIds)"
1400
+ ]);
1401
+ var SlvrStaking = _SlvrStaking;
1402
+
1403
+ // src/contracts/token.ts
1404
+ var import_viem6 = require("viem");
1405
+ var _SlvrToken = class _SlvrToken {
1406
+ constructor(publicClient, walletClient, address) {
1407
+ this.publicClient = publicClient;
1408
+ this.walletClient = walletClient;
1409
+ this.address = address;
1410
+ }
1411
+ /**
1412
+ * Update the wallet client
1413
+ * @param walletClient New wallet client
1414
+ */
1415
+ setWalletClient(walletClient) {
1416
+ this.walletClient = walletClient;
1417
+ }
1418
+ /**
1419
+ * Get total supply
1420
+ */
1421
+ async totalSupply() {
1422
+ return await this.publicClient.readContract({
1423
+ address: this.address,
1424
+ abi: _SlvrToken.ABI,
1425
+ functionName: "totalSupply"
1426
+ });
1427
+ }
1428
+ /**
1429
+ * Get balance of an account
1430
+ */
1431
+ async balanceOf(account) {
1432
+ return await this.publicClient.readContract({
1433
+ address: this.address,
1434
+ abi: _SlvrToken.ABI,
1435
+ functionName: "balanceOf",
1436
+ args: [account]
1437
+ });
1438
+ }
1439
+ /**
1440
+ * Get allowance
1441
+ */
1442
+ async allowance(owner, spender) {
1443
+ return await this.publicClient.readContract({
1444
+ address: this.address,
1445
+ abi: _SlvrToken.ABI,
1446
+ functionName: "allowance",
1447
+ args: [owner, spender]
1448
+ });
1449
+ }
1450
+ /**
1451
+ * Get token name
1452
+ */
1453
+ async name() {
1454
+ return await this.publicClient.readContract({
1455
+ address: this.address,
1456
+ abi: _SlvrToken.ABI,
1457
+ functionName: "name"
1458
+ });
1459
+ }
1460
+ /**
1461
+ * Get token symbol
1462
+ */
1463
+ async symbol() {
1464
+ return await this.publicClient.readContract({
1465
+ address: this.address,
1466
+ abi: _SlvrToken.ABI,
1467
+ functionName: "symbol"
1468
+ });
1469
+ }
1470
+ /**
1471
+ * Get token decimals
1472
+ */
1473
+ async decimals() {
1474
+ return await this.publicClient.readContract({
1475
+ address: this.address,
1476
+ abi: _SlvrToken.ABI,
1477
+ functionName: "decimals"
1478
+ });
1479
+ }
1480
+ /**
1481
+ * Get maximum supply
1482
+ */
1483
+ async maxSupply() {
1484
+ return await this.publicClient.readContract({
1485
+ address: this.address,
1486
+ abi: _SlvrToken.ABI,
1487
+ functionName: "MAX_SUPPLY"
1488
+ });
1489
+ }
1490
+ /**
1491
+ * Transfer tokens
1492
+ * @param to Recipient address
1493
+ * @param amount Amount to transfer
1494
+ * @returns Transaction hash
1495
+ * @throws WalletClientRequiredError if wallet client is not available
1496
+ * @throws ValidationError if inputs are invalid
1497
+ * @throws ContractCallError if contract call fails
1498
+ */
1499
+ async transfer(to, amount) {
1500
+ if (!this.walletClient) {
1501
+ throw new WalletClientRequiredError("transferring");
1502
+ }
1503
+ const recipient = validateAddress(to, "recipient");
1504
+ validateAmount(amount, "transfer amount");
1505
+ try {
1506
+ return await this.walletClient.writeContract({
1507
+ address: this.address,
1508
+ abi: _SlvrToken.ABI,
1509
+ functionName: "transfer",
1510
+ args: [recipient, amount],
1511
+ account: this.walletClient.account,
1512
+ chain: null
1513
+ });
1514
+ } catch (error) {
1515
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1516
+ throw error;
1517
+ }
1518
+ throw new ContractCallError(`Failed to transfer: ${error instanceof Error ? error.message : String(error)}`, error);
1519
+ }
1520
+ }
1521
+ /**
1522
+ * Approve spender
1523
+ * @param spender Address to approve
1524
+ * @param amount Amount to approve
1525
+ * @returns Transaction hash
1526
+ * @throws WalletClientRequiredError if wallet client is not available
1527
+ * @throws ValidationError if inputs are invalid
1528
+ * @throws ContractCallError if contract call fails
1529
+ */
1530
+ async approve(spender, amount) {
1531
+ if (!this.walletClient) {
1532
+ throw new WalletClientRequiredError("approving");
1533
+ }
1534
+ const spenderAddress = validateAddress(spender, "spender");
1535
+ validateAmount(amount, "approval amount");
1536
+ try {
1537
+ return await this.walletClient.writeContract({
1538
+ address: this.address,
1539
+ abi: _SlvrToken.ABI,
1540
+ functionName: "approve",
1541
+ args: [spenderAddress, amount],
1542
+ account: this.walletClient.account,
1543
+ chain: null
1544
+ });
1545
+ } catch (error) {
1546
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1547
+ throw error;
1548
+ }
1549
+ throw new ContractCallError(`Failed to approve: ${error instanceof Error ? error.message : String(error)}`, error);
1550
+ }
1551
+ }
1552
+ /**
1553
+ * Transfer from (requires approval)
1554
+ * @param from Sender address
1555
+ * @param to Recipient address
1556
+ * @param amount Amount to transfer
1557
+ * @returns Transaction hash
1558
+ * @throws WalletClientRequiredError if wallet client is not available
1559
+ * @throws ValidationError if inputs are invalid
1560
+ * @throws ContractCallError if contract call fails
1561
+ */
1562
+ async transferFrom(from, to, amount) {
1563
+ if (!this.walletClient) {
1564
+ throw new WalletClientRequiredError("transferFrom");
1565
+ }
1566
+ const fromAddress = validateAddress(from, "from");
1567
+ const toAddress = validateAddress(to, "to");
1568
+ validateAmount(amount, "transfer amount");
1569
+ try {
1570
+ return await this.walletClient.writeContract({
1571
+ address: this.address,
1572
+ abi: _SlvrToken.ABI,
1573
+ functionName: "transferFrom",
1574
+ args: [fromAddress, toAddress, amount],
1575
+ account: this.walletClient.account,
1576
+ chain: null
1577
+ });
1578
+ } catch (error) {
1579
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1580
+ throw error;
1581
+ }
1582
+ throw new ContractCallError(`Failed to transferFrom: ${error instanceof Error ? error.message : String(error)}`, error);
1583
+ }
1584
+ }
1585
+ /**
1586
+ * Burn tokens
1587
+ * @param amount Amount to burn
1588
+ * @returns Transaction hash
1589
+ * @throws WalletClientRequiredError if wallet client is not available
1590
+ * @throws ValidationError if amount is invalid
1591
+ * @throws ContractCallError if contract call fails
1592
+ */
1593
+ async burn(amount) {
1594
+ if (!this.walletClient) {
1595
+ throw new WalletClientRequiredError("burning");
1596
+ }
1597
+ validateAmount(amount, "burn amount");
1598
+ try {
1599
+ return await this.walletClient.writeContract({
1600
+ address: this.address,
1601
+ abi: _SlvrToken.ABI,
1602
+ functionName: "burn",
1603
+ args: [amount],
1604
+ account: this.walletClient.account,
1605
+ chain: null
1606
+ });
1607
+ } catch (error) {
1608
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1609
+ throw error;
1610
+ }
1611
+ throw new ContractCallError(`Failed to burn: ${error instanceof Error ? error.message : String(error)}`, error);
1612
+ }
1613
+ }
1614
+ /**
1615
+ * Burn tokens from account
1616
+ * @param account Account to burn from
1617
+ * @param amount Amount to burn
1618
+ * @returns Transaction hash
1619
+ * @throws WalletClientRequiredError if wallet client is not available
1620
+ * @throws ValidationError if inputs are invalid
1621
+ * @throws ContractCallError if contract call fails
1622
+ */
1623
+ async burnFrom(account, amount) {
1624
+ if (!this.walletClient) {
1625
+ throw new WalletClientRequiredError("burnFrom");
1626
+ }
1627
+ const accountAddress = validateAddress(account, "account");
1628
+ validateAmount(amount, "burn amount");
1629
+ try {
1630
+ return await this.walletClient.writeContract({
1631
+ address: this.address,
1632
+ abi: _SlvrToken.ABI,
1633
+ functionName: "burnFrom",
1634
+ args: [accountAddress, amount],
1635
+ account: this.walletClient.account,
1636
+ chain: null
1637
+ });
1638
+ } catch (error) {
1639
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1640
+ throw error;
1641
+ }
1642
+ throw new ContractCallError(`Failed to burnFrom: ${error instanceof Error ? error.message : String(error)}`, error);
1643
+ }
1644
+ }
1645
+ };
1646
+ _SlvrToken.ABI = (0, import_viem6.parseAbi)([
1647
+ // ERC20 standard functions
1648
+ "function totalSupply() view returns (uint256)",
1649
+ "function balanceOf(address account) view returns (uint256)",
1650
+ "function transfer(address to, uint256 amount) returns (bool)",
1651
+ "function allowance(address owner, address spender) view returns (uint256)",
1652
+ "function approve(address spender, uint256 amount) returns (bool)",
1653
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)",
1654
+ // SlvrToken specific
1655
+ "function name() view returns (string)",
1656
+ "function symbol() view returns (string)",
1657
+ "function decimals() view returns (uint8)",
1658
+ "function MAX_SUPPLY() view returns (uint256)",
1659
+ "function burn(uint256 amount)",
1660
+ "function burnFrom(address account, uint256 amount)"
1661
+ ]);
1662
+ var SlvrToken = _SlvrToken;
1663
+
1664
+ // src/contracts/autoCommit.ts
1665
+ var import_viem7 = require("viem");
1666
+ var _SlvrAutoCommit = class _SlvrAutoCommit {
1667
+ constructor(publicClient, walletClient, address) {
1668
+ this.publicClient = publicClient;
1669
+ this.walletClient = walletClient;
1670
+ this.address = address;
1671
+ }
1672
+ /**
1673
+ * Update the wallet client
1674
+ * @param walletClient New wallet client
1675
+ */
1676
+ setWalletClient(walletClient) {
1677
+ this.walletClient = walletClient;
1678
+ }
1679
+ /**
1680
+ * Get plan information for a user
1681
+ */
1682
+ async planInfo(user) {
1683
+ const result = await this.publicClient.readContract({
1684
+ address: this.address,
1685
+ abi: _SlvrAutoCommit.ABI,
1686
+ functionName: "planInfo",
1687
+ args: [user]
1688
+ });
1689
+ return {
1690
+ enabled: result[0],
1691
+ nextRoundId: result[1],
1692
+ playsRemaining: Number(result[2]),
1693
+ amountPerPlay: result[3],
1694
+ balance: result[4],
1695
+ autoClaim: result[5],
1696
+ squares: result[6].map((s) => Number(s)),
1697
+ bpsAlloc: result[7].map((b) => Number(b)),
1698
+ planStartRoundId: result[8]
1699
+ };
1700
+ }
1701
+ /**
1702
+ * Check whether a plan is ready to execute, and if not, why
1703
+ */
1704
+ async needsExecution(user) {
1705
+ const [ready, reason] = await this.publicClient.readContract({
1706
+ address: this.address,
1707
+ abi: _SlvrAutoCommit.ABI,
1708
+ functionName: "needsExecution",
1709
+ args: [user]
1710
+ });
1711
+ return { ready, reason };
1712
+ }
1713
+ /**
1714
+ * Check whether a specific round has already been executed for a user
1715
+ */
1716
+ async executedRound(user, roundId) {
1717
+ return await this.publicClient.readContract({
1718
+ address: this.address,
1719
+ abi: _SlvrAutoCommit.ABI,
1720
+ functionName: "executedRounds",
1721
+ args: [user, roundId]
1722
+ });
1723
+ }
1724
+ /**
1725
+ * Get lottery contract address
1726
+ */
1727
+ async lottery() {
1728
+ return await this.publicClient.readContract({
1729
+ address: this.address,
1730
+ abi: _SlvrAutoCommit.ABI,
1731
+ functionName: "LOTTERY"
1732
+ });
1733
+ }
1734
+ /**
1735
+ * Get the sentinel plays value meaning "unlimited plays"
1736
+ */
1737
+ async unlimitedPlays() {
1738
+ return await this.publicClient.readContract({
1739
+ address: this.address,
1740
+ abi: _SlvrAutoCommit.ABI,
1741
+ functionName: "UNLIMITED_PLAYS"
1742
+ });
1743
+ }
1744
+ /**
1745
+ * Get max plays per execution
1746
+ */
1747
+ async maxPlaysPerExecution() {
1748
+ return await this.publicClient.readContract({
1749
+ address: this.address,
1750
+ abi: _SlvrAutoCommit.ABI,
1751
+ functionName: "MAX_PLAYS_PER_EXECUTION"
1752
+ });
1753
+ }
1754
+ /**
1755
+ * Get max claim rounds accepted per execution
1756
+ */
1757
+ async maxClaimsPerExecution() {
1758
+ return await this.publicClient.readContract({
1759
+ address: this.address,
1760
+ abi: _SlvrAutoCommit.ABI,
1761
+ functionName: "MAX_CLAIMS_PER_EXECUTION"
1762
+ });
1763
+ }
1764
+ /**
1765
+ * Get the current cap on the executor fee reimbursed per execution
1766
+ * (owner-tunable within the contract's hard ceiling)
1767
+ */
1768
+ async maxFeePerExecution() {
1769
+ return await this.publicClient.readContract({
1770
+ address: this.address,
1771
+ abi: _SlvrAutoCommit.ABI,
1772
+ functionName: "maxFeePerExecution"
1773
+ });
1774
+ }
1775
+ /**
1776
+ * Get the current executor-fee premium in basis points
1777
+ * (owner-tunable within the contract's hard ceiling)
1778
+ */
1779
+ async feePremiumBps() {
1780
+ return await this.publicClient.readContract({
1781
+ address: this.address,
1782
+ abi: _SlvrAutoCommit.ABI,
1783
+ functionName: "feePremiumBps"
1784
+ });
1785
+ }
1786
+ /**
1787
+ * Deposit native tokens to auto-commit plan
1788
+ * @param value Amount to deposit
1789
+ * @returns Transaction hash
1790
+ * @throws WalletClientRequiredError if wallet client is not available
1791
+ * @throws ValidationError if amount is invalid
1792
+ * @throws ContractCallError if contract call fails
1793
+ */
1794
+ async deposit(value) {
1795
+ if (!this.walletClient) {
1796
+ throw new WalletClientRequiredError("depositing");
1797
+ }
1798
+ validateAmount(value, "deposit amount");
1799
+ try {
1800
+ return await this.walletClient.writeContract({
1801
+ address: this.address,
1802
+ abi: _SlvrAutoCommit.ABI,
1803
+ functionName: "deposit",
1804
+ value,
1805
+ account: this.walletClient.account,
1806
+ chain: null
1807
+ });
1808
+ } catch (error) {
1809
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1810
+ throw error;
1811
+ }
1812
+ throw new ContractCallError(`Failed to deposit: ${error instanceof Error ? error.message : String(error)}`, error);
1813
+ }
1814
+ }
1815
+ /**
1816
+ * Withdraw from auto-commit plan
1817
+ * @param amount Amount to withdraw
1818
+ * @param to Recipient address
1819
+ * @returns Transaction hash
1820
+ * @throws WalletClientRequiredError if wallet client is not available
1821
+ * @throws ValidationError if inputs are invalid
1822
+ * @throws ContractCallError if contract call fails
1823
+ */
1824
+ async withdraw(amount, to) {
1825
+ if (!this.walletClient) {
1826
+ throw new WalletClientRequiredError("withdrawing");
1827
+ }
1828
+ validateAmount(amount, "withdraw amount");
1829
+ const recipient = validateAddress(to, "recipient");
1830
+ try {
1831
+ return await this.walletClient.writeContract({
1832
+ address: this.address,
1833
+ abi: _SlvrAutoCommit.ABI,
1834
+ functionName: "withdraw",
1835
+ args: [amount, recipient],
1836
+ account: this.walletClient.account,
1837
+ chain: null
1838
+ });
1839
+ } catch (error) {
1840
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1841
+ throw error;
1842
+ }
1843
+ throw new ContractCallError(`Failed to withdraw: ${error instanceof Error ? error.message : String(error)}`, error);
1844
+ }
1845
+ }
1846
+ /**
1847
+ * Configure auto-commit plan
1848
+ * @param plays Number of plays (UNLIMITED_PLAYS = 4294967295 for unlimited;
1849
+ * forced to unlimited on-chain when autoClaim is true)
1850
+ * @param amountPerPlay Amount per play
1851
+ * @param squares Square indices to bet on
1852
+ * @param bpsAlloc Basis points allocation for each square (must sum to 10000)
1853
+ * @param autoClaim Whether the keeper should claim winnings back into the plan balance
1854
+ * @returns Transaction hash
1855
+ * @throws WalletClientRequiredError if wallet client is not available
1856
+ * @throws ValidationError if inputs are invalid
1857
+ * @throws ContractCallError if contract call fails
1858
+ */
1859
+ async configurePlan(plays, amountPerPlay, squares, bpsAlloc, autoClaim) {
1860
+ if (!this.walletClient) {
1861
+ throw new WalletClientRequiredError("configuring plan");
1862
+ }
1863
+ this.validatePlanConfig(plays, amountPerPlay, squares, bpsAlloc);
1864
+ try {
1865
+ return await this.walletClient.writeContract({
1866
+ address: this.address,
1867
+ abi: _SlvrAutoCommit.ABI,
1868
+ functionName: "configurePlan",
1869
+ args: [plays, amountPerPlay, squares, bpsAlloc, autoClaim],
1870
+ account: this.walletClient.account,
1871
+ chain: null
1872
+ });
1873
+ } catch (error) {
1874
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1875
+ throw error;
1876
+ }
1877
+ throw new ContractCallError(`Failed to configure plan: ${error instanceof Error ? error.message : String(error)}`, error);
1878
+ }
1879
+ }
1880
+ /**
1881
+ * Configure auto-commit plan and deposit funds in a single transaction
1882
+ * @param plays Number of plays (see configurePlan)
1883
+ * @param amountPerPlay Amount per play
1884
+ * @param squares Square indices to bet on
1885
+ * @param bpsAlloc Basis points allocation for each square (must sum to 10000)
1886
+ * @param autoClaim Whether the keeper should claim winnings back into the plan balance
1887
+ * @param value Amount to deposit
1888
+ * @returns Transaction hash
1889
+ * @throws WalletClientRequiredError if wallet client is not available
1890
+ * @throws ValidationError if inputs are invalid
1891
+ * @throws ContractCallError if contract call fails
1892
+ */
1893
+ async configurePlanAndDeposit(plays, amountPerPlay, squares, bpsAlloc, autoClaim, value) {
1894
+ if (!this.walletClient) {
1895
+ throw new WalletClientRequiredError("configuring plan");
1896
+ }
1897
+ this.validatePlanConfig(plays, amountPerPlay, squares, bpsAlloc);
1898
+ validateAmount(value, "deposit amount");
1899
+ try {
1900
+ return await this.walletClient.writeContract({
1901
+ address: this.address,
1902
+ abi: _SlvrAutoCommit.ABI,
1903
+ functionName: "configurePlanAndDeposit",
1904
+ args: [plays, amountPerPlay, squares, bpsAlloc, autoClaim],
1905
+ value,
1906
+ account: this.walletClient.account,
1907
+ chain: null
1908
+ });
1909
+ } catch (error) {
1910
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1911
+ throw error;
1912
+ }
1913
+ throw new ContractCallError(`Failed to configure plan: ${error instanceof Error ? error.message : String(error)}`, error);
1914
+ }
1915
+ }
1916
+ /**
1917
+ * Disable auto-commit plan
1918
+ * @returns Transaction hash
1919
+ * @throws WalletClientRequiredError if wallet client is not available
1920
+ * @throws ContractCallError if contract call fails
1921
+ */
1922
+ async disablePlan() {
1923
+ if (!this.walletClient) {
1924
+ throw new WalletClientRequiredError("disabling plan");
1925
+ }
1926
+ try {
1927
+ return await this.walletClient.writeContract({
1928
+ address: this.address,
1929
+ abi: _SlvrAutoCommit.ABI,
1930
+ functionName: "disablePlan",
1931
+ account: this.walletClient.account,
1932
+ chain: null
1933
+ });
1934
+ } catch (error) {
1935
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1936
+ throw error;
1937
+ }
1938
+ throw new ContractCallError(`Failed to disable plan: ${error instanceof Error ? error.message : String(error)}`, error);
1939
+ }
1940
+ }
1941
+ /**
1942
+ * Cancel the plan and refund the entire remaining balance to the caller
1943
+ * @returns Transaction hash
1944
+ * @throws WalletClientRequiredError if wallet client is not available
1945
+ * @throws ContractCallError if contract call fails
1946
+ */
1947
+ async cancelPlan() {
1948
+ if (!this.walletClient) {
1949
+ throw new WalletClientRequiredError("cancelling plan");
1950
+ }
1951
+ try {
1952
+ return await this.walletClient.writeContract({
1953
+ address: this.address,
1954
+ abi: _SlvrAutoCommit.ABI,
1955
+ functionName: "cancelPlan",
1956
+ account: this.walletClient.account,
1957
+ chain: null
1958
+ });
1959
+ } catch (error) {
1960
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1961
+ throw error;
1962
+ }
1963
+ throw new ContractCallError(`Failed to cancel plan: ${error instanceof Error ? error.message : String(error)}`, error);
1964
+ }
1965
+ }
1966
+ /**
1967
+ * Execute auto-commit plan for a user (anyone can call).
1968
+ * The caller is reimbursed metered gas + premium from the user's plan balance.
1969
+ * @param user User address
1970
+ * @param maxPlays Maximum number of plays to execute
1971
+ * @param claimRounds Winning round ids to claim into the plan balance before
1972
+ * betting (discovered off-chain; non-claimable entries are no-ops)
1973
+ * @returns Transaction hash
1974
+ * @throws WalletClientRequiredError if wallet client is not available
1975
+ * @throws ValidationError if inputs are invalid
1976
+ * @throws ContractCallError if contract call fails
1977
+ */
1978
+ async executeFor(user, maxPlays, claimRounds = []) {
1979
+ if (!this.walletClient) {
1980
+ throw new WalletClientRequiredError("executing plan");
1981
+ }
1982
+ const userAddress = validateAddress(user, "user");
1983
+ if (!Number.isInteger(maxPlays) || maxPlays <= 0) {
1984
+ throw new ContractCallError("maxPlays must be a positive integer");
1985
+ }
1986
+ try {
1987
+ return await this.walletClient.writeContract({
1988
+ address: this.address,
1989
+ abi: _SlvrAutoCommit.ABI,
1990
+ functionName: "executeFor",
1991
+ args: [userAddress, maxPlays, claimRounds],
1992
+ account: this.walletClient.account,
1993
+ chain: null
1994
+ });
1995
+ } catch (error) {
1996
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
1997
+ throw error;
1998
+ }
1999
+ throw new ContractCallError(`Failed to execute plan: ${error instanceof Error ? error.message : String(error)}`, error);
2000
+ }
2001
+ }
2002
+ /**
2003
+ * Claim winning rounds into a user's plan balance without betting (anyone can
2004
+ * call). Useful when the plan balance is too low to bet until winnings land.
2005
+ * @param user User address
2006
+ * @param claimRounds Winning round ids to claim (discovered off-chain)
2007
+ * @returns Transaction hash
2008
+ * @throws WalletClientRequiredError if wallet client is not available
2009
+ * @throws ValidationError if inputs are invalid
2010
+ * @throws ContractCallError if contract call fails
2011
+ */
2012
+ async claimFor(user, claimRounds) {
2013
+ if (!this.walletClient) {
2014
+ throw new WalletClientRequiredError("claiming");
2015
+ }
2016
+ const userAddress = validateAddress(user, "user");
2017
+ if (claimRounds.length === 0) {
2018
+ throw new ContractCallError("claimRounds must not be empty");
2019
+ }
2020
+ try {
2021
+ return await this.walletClient.writeContract({
2022
+ address: this.address,
2023
+ abi: _SlvrAutoCommit.ABI,
2024
+ functionName: "claimFor",
2025
+ args: [userAddress, claimRounds],
2026
+ account: this.walletClient.account,
2027
+ chain: null
2028
+ });
2029
+ } catch (error) {
2030
+ if (error instanceof WalletClientRequiredError || error instanceof ContractCallError) {
2031
+ throw error;
2032
+ }
2033
+ throw new ContractCallError(`Failed to claim: ${error instanceof Error ? error.message : String(error)}`, error);
2034
+ }
2035
+ }
2036
+ validatePlanConfig(plays, amountPerPlay, squares, bpsAlloc) {
2037
+ if (!Number.isInteger(plays) || plays <= 0) {
2038
+ throw new ContractCallError("Plays must be a positive integer");
2039
+ }
2040
+ validateAmount(amountPerPlay, "amountPerPlay");
2041
+ validateSquares(squares);
2042
+ validateArrayLengths([squares, bpsAlloc], ["squares", "bpsAlloc"]);
2043
+ validateBpsSum(bpsAlloc, "bpsAlloc");
2044
+ }
2045
+ };
2046
+ _SlvrAutoCommit.ABI = (0, import_viem7.parseAbi)([
2047
+ // View functions
2048
+ "function planInfo(address user) view returns (bool enabled, uint256 nextRoundId, uint32 playsRemaining, uint256 amountPerPlay, uint256 balance, bool autoClaim, uint8[] squares, uint16[] bpsAlloc, uint256 planStartRoundId)",
2049
+ "function needsExecution(address user) view returns (bool ready, string reason)",
2050
+ "function executedRounds(address user, uint256 roundId) view returns (bool)",
2051
+ "function LOTTERY() view returns (address)",
2052
+ "function UNLIMITED_PLAYS() view returns (uint32)",
2053
+ "function MAX_PLAYS_PER_EXECUTION() view returns (uint32)",
2054
+ "function MAX_CLAIMS_PER_EXECUTION() view returns (uint256)",
2055
+ "function maxFeePerExecution() view returns (uint256)",
2056
+ "function feePremiumBps() view returns (uint16)",
2057
+ // Write functions
2058
+ "function deposit() payable",
2059
+ "function withdraw(uint256 amount, address to)",
2060
+ "function configurePlan(uint32 plays, uint256 amountPerPlay, uint8[] squares, uint16[] bpsAlloc, bool autoClaim)",
2061
+ "function configurePlanAndDeposit(uint32 plays, uint256 amountPerPlay, uint8[] squares, uint16[] bpsAlloc, bool autoClaim) payable",
2062
+ "function disablePlan()",
2063
+ "function cancelPlan()",
2064
+ "function executeFor(address user, uint32 maxPlays, uint256[] claimRounds)",
2065
+ "function claimFor(address user, uint256[] claimRounds) returns (uint256 claimed)"
2066
+ ]);
2067
+ var SlvrAutoCommit = _SlvrAutoCommit;
2068
+
2069
+ // src/contracts/hub.ts
2070
+ var import_viem8 = require("viem");
2071
+ var _SlvrHub = class _SlvrHub {
2072
+ constructor(publicClient, _walletClient, address) {
2073
+ this.publicClient = publicClient;
2074
+ this.address = address;
2075
+ }
2076
+ // Read-only module; kept for a uniform module interface.
2077
+ setWalletClient(_walletClient) {
2078
+ }
2079
+ // ---------------------------------------------------------------------------
2080
+ // Views
2081
+ // ---------------------------------------------------------------------------
2082
+ /**
2083
+ * Accrued-but-unminted SLVR emission currently available to a game (its bucket + streamed since
2084
+ * last accrual, hard-capped at one maxAccrualSeconds window of the game's effective rate).
2085
+ */
2086
+ async pendingEmission(gameId) {
2087
+ return await this.publicClient.readContract({
2088
+ address: this.address,
2089
+ abi: _SlvrHub.ABI,
2090
+ functionName: "pendingEmission",
2091
+ args: [gameId]
2092
+ });
2093
+ }
2094
+ /**
2095
+ * Base SLVR/sec emission rate across the whole active game set
2096
+ */
2097
+ async emissionRatePerSec() {
2098
+ return await this.publicClient.readContract({
2099
+ address: this.address,
2100
+ abi: _SlvrHub.ABI,
2101
+ functionName: "emissionRatePerSec"
2102
+ });
2103
+ }
2104
+ /**
2105
+ * Soft-cap target supply (0 => use token MAX_SUPPLY)
2106
+ */
2107
+ async targetSupply() {
2108
+ return await this.publicClient.readContract({
2109
+ address: this.address,
2110
+ abi: _SlvrHub.ABI,
2111
+ functionName: "targetSupply"
2112
+ });
2113
+ }
2114
+ /**
2115
+ * Idle-forfeiture window: cap on dt per accrual (seconds)
2116
+ */
2117
+ async maxAccrualSeconds() {
2118
+ return await this.publicClient.readContract({
2119
+ address: this.address,
2120
+ abi: _SlvrHub.ABI,
2121
+ functionName: "maxAccrualSeconds"
2122
+ });
2123
+ }
2124
+ /**
2125
+ * Address of the shared veNFT staking contract
2126
+ */
2127
+ async staking() {
2128
+ return await this.publicClient.readContract({
2129
+ address: this.address,
2130
+ abi: _SlvrHub.ABI,
2131
+ functionName: "staking"
2132
+ });
2133
+ }
2134
+ /**
2135
+ * Address of the shared jackpot contract
2136
+ */
2137
+ async jackpot() {
2138
+ return await this.publicClient.readContract({
2139
+ address: this.address,
2140
+ abi: _SlvrHub.ABI,
2141
+ functionName: "jackpot"
2142
+ });
2143
+ }
2144
+ /**
2145
+ * Hub-owned sequential counter used to feed the staking contract's round numbering
2146
+ */
2147
+ async stakerSeq() {
2148
+ return await this.publicClient.readContract({
2149
+ address: this.address,
2150
+ abi: _SlvrHub.ABI,
2151
+ functionName: "stakerSeq"
2152
+ });
2153
+ }
2154
+ /**
2155
+ * Native staker rewards received but not yet flushed to the staking stream
2156
+ */
2157
+ async pendingStakerRewards() {
2158
+ return await this.publicClient.readContract({
2159
+ address: this.address,
2160
+ abi: _SlvrHub.ABI,
2161
+ functionName: "pendingStakerRewards"
2162
+ });
2163
+ }
2164
+ };
2165
+ _SlvrHub.ABI = (0, import_viem8.parseAbi)([
2166
+ // View functions
2167
+ "function pendingEmission(uint256 gameId) view returns (uint256)",
2168
+ "function emissionRatePerSec() view returns (uint256)",
2169
+ "function targetSupply() view returns (uint256)",
2170
+ "function maxAccrualSeconds() view returns (uint256)",
2171
+ "function staking() view returns (address)",
2172
+ "function jackpot() view returns (address)",
2173
+ "function stakerSeq() view returns (uint256)",
2174
+ "function pendingStakerRewards() view returns (uint256)"
2175
+ ]);
2176
+ var SlvrHub = _SlvrHub;
2177
+
2178
+ // src/contracts/registry.ts
2179
+ var import_viem9 = require("viem");
2180
+ var _SlvrGameRegistry = class _SlvrGameRegistry {
2181
+ constructor(publicClient, _walletClient, address) {
2182
+ this.publicClient = publicClient;
2183
+ this.address = address;
2184
+ }
2185
+ /** Read-only contract: no wallet client needed. Kept for a consistent SDK surface. */
2186
+ setWalletClient(_walletClient) {
2187
+ }
2188
+ /**
2189
+ * Registry id for a game address (0 if not registered; ids are 1-based)
2190
+ */
2191
+ async gameIdOf(game) {
2192
+ return await this.publicClient.readContract({
2193
+ address: this.address,
2194
+ abi: _SlvrGameRegistry.ABI,
2195
+ functionName: "gameIdOf",
2196
+ args: [game]
2197
+ });
2198
+ }
2199
+ /**
2200
+ * Full record for a game id
2201
+ */
2202
+ async gameInfo(gameId) {
2203
+ const result = await this.publicClient.readContract({
2204
+ address: this.address,
2205
+ abi: _SlvrGameRegistry.ABI,
2206
+ functionName: "gameInfo",
2207
+ args: [gameId]
2208
+ });
2209
+ return {
2210
+ game: result.game,
2211
+ gameType: result.gameType,
2212
+ status: result.status,
2213
+ tier: result.tier,
2214
+ emissionWeight: Number(result.emissionWeight),
2215
+ maxWeightBps: Number(result.maxWeightBps),
2216
+ exists: result.exists
2217
+ };
2218
+ }
2219
+ /**
2220
+ * Is this address a registered, Active game?
2221
+ */
2222
+ async isActive(game) {
2223
+ return await this.publicClient.readContract({
2224
+ address: this.address,
2225
+ abi: _SlvrGameRegistry.ABI,
2226
+ functionName: "isActive",
2227
+ args: [game]
2228
+ });
2229
+ }
2230
+ /**
2231
+ * Status enum for a game id
2232
+ */
2233
+ async statusOf(gameId) {
2234
+ const result = await this.publicClient.readContract({
2235
+ address: this.address,
2236
+ abi: _SlvrGameRegistry.ABI,
2237
+ functionName: "statusOf",
2238
+ args: [gameId]
2239
+ });
2240
+ return result;
2241
+ }
2242
+ /**
2243
+ * Tier enum for a game id
2244
+ */
2245
+ async tierOf(gameId) {
2246
+ const result = await this.publicClient.readContract({
2247
+ address: this.address,
2248
+ abi: _SlvrGameRegistry.ABI,
2249
+ functionName: "tierOf",
2250
+ args: [gameId]
2251
+ });
2252
+ return result;
2253
+ }
2254
+ /**
2255
+ * Emission weight (relative share of the global stream) for a game id
2256
+ */
2257
+ async weightOf(gameId) {
2258
+ const result = await this.publicClient.readContract({
2259
+ address: this.address,
2260
+ abi: _SlvrGameRegistry.ABI,
2261
+ functionName: "weightOf",
2262
+ args: [gameId]
2263
+ });
2264
+ return BigInt(result);
2265
+ }
2266
+ /**
2267
+ * Max realized-fraction ceiling (bps of 10000) for a game id
2268
+ */
2269
+ async maxWeightBpsOf(gameId) {
2270
+ const result = await this.publicClient.readContract({
2271
+ address: this.address,
2272
+ abi: _SlvrGameRegistry.ABI,
2273
+ functionName: "maxWeightBpsOf",
2274
+ args: [gameId]
2275
+ });
2276
+ return Number(result);
2277
+ }
2278
+ /**
2279
+ * Sum of emissionWeight across all Active games (denominator for the split)
2280
+ */
2281
+ async totalActiveWeight() {
2282
+ return await this.publicClient.readContract({
2283
+ address: this.address,
2284
+ abi: _SlvrGameRegistry.ABI,
2285
+ functionName: "totalActiveWeight"
2286
+ });
2287
+ }
2288
+ /**
2289
+ * Number of registered games
2290
+ */
2291
+ async gameCount() {
2292
+ return await this.publicClient.readContract({
2293
+ address: this.address,
2294
+ abi: _SlvrGameRegistry.ABI,
2295
+ functionName: "gameCount"
2296
+ });
2297
+ }
2298
+ };
2299
+ _SlvrGameRegistry.ABI = (0, import_viem9.parseAbi)([
2300
+ "function gameIdOf(address game) view returns (uint256)",
2301
+ "function gameInfo(uint256 gameId) view returns ((address game, bytes32 gameType, uint8 status, uint8 tier, uint32 emissionWeight, uint16 maxWeightBps, bool exists))",
2302
+ "function isActive(address game) view returns (bool)",
2303
+ "function statusOf(uint256 gameId) view returns (uint8)",
2304
+ "function tierOf(uint256 gameId) view returns (uint8)",
2305
+ "function weightOf(uint256 gameId) view returns (uint32)",
2306
+ "function maxWeightBpsOf(uint256 gameId) view returns (uint16)",
2307
+ "function totalActiveWeight() view returns (uint256)",
2308
+ "function gameCount() view returns (uint256)"
2309
+ ]);
2310
+ var SlvrGameRegistry = _SlvrGameRegistry;
2311
+
2312
+ // src/contracts/jackpot.ts
2313
+ var import_viem10 = require("viem");
2314
+ var _SlvrJackpot = class _SlvrJackpot {
2315
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2316
+ constructor(publicClient, _walletClient, address) {
2317
+ this.publicClient = publicClient;
2318
+ this.address = address;
2319
+ }
2320
+ /** Read-only contract: no wallet client needed. Kept for a consistent SDK surface. */
2321
+ setWalletClient(_walletClient) {
2322
+ }
2323
+ /**
2324
+ * Native (ETH) jackpot pool balance
2325
+ */
2326
+ async jackpotPool() {
2327
+ return await this.publicClient.readContract({
2328
+ address: this.address,
2329
+ abi: _SlvrJackpot.ABI,
2330
+ functionName: "jackpotPool"
2331
+ });
2332
+ }
2333
+ /**
2334
+ * SLVR jackpot pool balance
2335
+ */
2336
+ async jackpotSlvrPool() {
2337
+ return await this.publicClient.readContract({
2338
+ address: this.address,
2339
+ abi: _SlvrJackpot.ABI,
2340
+ functionName: "jackpotSlvrPool"
2341
+ });
2342
+ }
2343
+ };
2344
+ _SlvrJackpot.ABI = (0, import_viem10.parseAbi)([
2345
+ "function jackpotPool() view returns (uint256)",
2346
+ "function jackpotSlvrPool() view returns (uint256)"
2347
+ ]);
2348
+ var SlvrJackpot = _SlvrJackpot;
2349
+
2350
+ // src/price.ts
2351
+ var import_viem11 = require("viem");
2352
+ var _SlvrPrice = class _SlvrPrice {
2353
+ constructor(publicClient, pairAddress, slvrTokenAddress) {
2354
+ this.publicClient = publicClient;
2355
+ this.pairAddress = pairAddress;
2356
+ this.slvrTokenAddress = slvrTokenAddress;
2357
+ }
2358
+ /** Read the pair reserves and resolve which side is SLVR. */
2359
+ async getReserves() {
2360
+ const [reserves, token0] = await Promise.all([
2361
+ this.publicClient.readContract({
2362
+ address: this.pairAddress,
2363
+ abi: _SlvrPrice.PAIR_ABI,
2364
+ functionName: "getReserves"
2365
+ }),
2366
+ this.publicClient.readContract({
2367
+ address: this.pairAddress,
2368
+ abi: _SlvrPrice.PAIR_ABI,
2369
+ functionName: "token0"
2370
+ })
2371
+ ]);
2372
+ const token0IsSlvr = token0.toLowerCase() === this.slvrTokenAddress.toLowerCase();
2373
+ const slvrReserve = token0IsSlvr ? reserves[0] : reserves[1];
2374
+ const ethReserve = token0IsSlvr ? reserves[1] : reserves[0];
2375
+ return { slvrReserve, ethReserve, token0IsSlvr };
2376
+ }
2377
+ /**
2378
+ * SLVR price in ETH (ETH per SLVR), as a floating-point number.
2379
+ * @throws {Error} if the pair has no SLVR liquidity (zero reserve).
2380
+ */
2381
+ async getPriceInEth() {
2382
+ const { slvrReserve, ethReserve } = await this.getReserves();
2383
+ if (slvrReserve === 0n) {
2384
+ throw new Error("SLVR/ETH pair has zero SLVR reserve; cannot price");
2385
+ }
2386
+ return Number(ethReserve) / Number(slvrReserve);
2387
+ }
2388
+ /**
2389
+ * SLVR price in ETH as a WAD (1e18-scaled `bigint`), for callers that want to
2390
+ * stay in integer math: `priceWad = ethReserve * 1e18 / slvrReserve`.
2391
+ * @throws {Error} if the pair has no SLVR liquidity (zero reserve).
2392
+ */
2393
+ async getPriceInEthWad() {
2394
+ const { slvrReserve, ethReserve } = await this.getReserves();
2395
+ if (slvrReserve === 0n) {
2396
+ throw new Error("SLVR/ETH pair has zero SLVR reserve; cannot price");
2397
+ }
2398
+ return ethReserve * 10n ** 18n / slvrReserve;
2399
+ }
2400
+ };
2401
+ _SlvrPrice.PAIR_ABI = (0, import_viem11.parseAbi)([
2402
+ "function getReserves() view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)",
2403
+ "function token0() view returns (address)"
2404
+ ]);
2405
+ var SlvrPrice = _SlvrPrice;
2406
+
2407
+ // src/oracle.ts
2408
+ var import_viem12 = require("viem");
2409
+ var _ChainlinkPriceFeed = class _ChainlinkPriceFeed {
2410
+ /**
2411
+ * @param publicClient viem public client
2412
+ * @param feedAddress the aggregator address
2413
+ * @param opts.maxStalenessSec if set, {@link getPrice} throws when the feed's
2414
+ * `updatedAt` is older than this many seconds (checked against the local
2415
+ * clock). Off by default — enable it only where the local clock is trusted.
2416
+ */
2417
+ constructor(publicClient, feedAddress, opts) {
2418
+ this.publicClient = publicClient;
2419
+ this.feedAddress = feedAddress;
2420
+ this.maxStalenessSec = opts?.maxStalenessSec;
2421
+ }
2422
+ /** Raw `latestRoundData` answer plus the feed's decimals. */
2423
+ async getRoundData() {
2424
+ const [decimals, roundData] = await Promise.all([
2425
+ this.publicClient.readContract({
2426
+ address: this.feedAddress,
2427
+ abi: _ChainlinkPriceFeed.ABI,
2428
+ functionName: "decimals"
2429
+ }),
2430
+ this.publicClient.readContract({
2431
+ address: this.feedAddress,
2432
+ abi: _ChainlinkPriceFeed.ABI,
2433
+ functionName: "latestRoundData"
2434
+ })
2435
+ ]);
2436
+ return { answer: roundData[1], decimals, updatedAt: roundData[3] };
2437
+ }
2438
+ /**
2439
+ * The feed price as a floating-point number (`answer / 10 ** decimals`).
2440
+ * @throws {Error} if the answer is non-positive, or (when `maxStalenessSec` is
2441
+ * set) if the feed is stale.
2442
+ */
2443
+ async getPrice() {
2444
+ const { answer, decimals, updatedAt } = await this.getRoundData();
2445
+ if (answer <= 0n) {
2446
+ throw new Error(`Chainlink feed ${this.feedAddress} returned a non-positive answer`);
2447
+ }
2448
+ if (this.maxStalenessSec !== void 0) {
2449
+ const nowSec = Math.floor(Date.now() / 1e3);
2450
+ const age = nowSec - Number(updatedAt);
2451
+ if (age > this.maxStalenessSec) {
2452
+ throw new Error(
2453
+ `Chainlink feed ${this.feedAddress} is stale: ${age}s old (max ${this.maxStalenessSec}s)`
2454
+ );
2455
+ }
396
2456
  }
2457
+ return Number(answer) / 10 ** decimals;
2458
+ }
2459
+ };
2460
+ _ChainlinkPriceFeed.ABI = (0, import_viem12.parseAbi)([
2461
+ "function decimals() view returns (uint8)",
2462
+ "function latestRoundData() view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)"
2463
+ ]);
2464
+ var ChainlinkPriceFeed = _ChainlinkPriceFeed;
2465
+
2466
+ // src/ev.ts
2467
+ var GRID_SIZE = 25;
2468
+ var SINGLE_SQUARE_WIN_PROBABILITY = 1 / GRID_SIZE;
2469
+ var PROTOCOL_FEE_BPS = 1e3;
2470
+ var REFINING_FEE_BPS = 1e3;
2471
+ var JACKPOT_ODDS = 625;
2472
+ function computeGridMiningEv(input) {
2473
+ const {
2474
+ stake,
2475
+ pot,
2476
+ emissionPerRound,
2477
+ slvrPriceEth,
2478
+ feeBps = PROTOCOL_FEE_BPS,
2479
+ cashOut = false,
2480
+ refineFeeBps = REFINING_FEE_BPS,
2481
+ jackpotPool = 0,
2482
+ jackpotOdds = JACKPOT_ODDS
2483
+ } = input;
2484
+ if (!(stake > 0)) throw new Error(`stake must be positive (got ${stake})`);
2485
+ if (!(pot > 0)) throw new Error(`pot must be positive (got ${pot})`);
2486
+ const feeFraction = feeBps / 1e4;
2487
+ const realize = cashOut ? 1 - refineFeeBps / 1e4 : 1;
2488
+ const share = stake / pot;
2489
+ const ethBleed = feeFraction * stake;
2490
+ const slvrMined = share * emissionPerRound;
2491
+ const slvrValueEth = slvrMined * slvrPriceEth * realize;
2492
+ const jackpotEvEth = jackpotOdds > 0 && Number.isFinite(jackpotOdds) ? 1 / jackpotOdds * share * jackpotPool : 0;
2493
+ const netEthNoJackpot = slvrValueEth - ethBleed;
2494
+ const netEth = netEthNoJackpot + jackpotEvEth;
2495
+ const perShareEthReward = emissionPerRound * slvrPriceEth * realize + (jackpotOdds > 0 && Number.isFinite(jackpotOdds) ? jackpotPool / jackpotOdds : 0);
2496
+ const breakEvenPot = feeFraction > 0 ? perShareEthReward / feeFraction : Infinity;
2497
+ const slvrRewardDenom = share * emissionPerRound * realize;
2498
+ const breakEvenSlvrPriceEth = slvrRewardDenom > 0 ? (ethBleed - jackpotEvEth) / slvrRewardDenom : Infinity;
2499
+ return {
2500
+ share,
2501
+ ethBleed,
2502
+ slvrMined,
2503
+ slvrValueEth,
2504
+ jackpotEvEth,
2505
+ netEthNoJackpot,
2506
+ netEth,
2507
+ edgeRatio: netEth / stake,
2508
+ breakEvenPot,
2509
+ breakEvenSlvrPriceEth,
2510
+ profitable: netEth > 0
2511
+ };
2512
+ }
2513
+
2514
+ // src/connect.ts
2515
+ var import_viem14 = require("viem");
2516
+ var import_accounts = require("viem/accounts");
2517
+
2518
+ // src/deployments.ts
2519
+ var import_viem13 = require("viem");
2520
+ var robinhood = {
2521
+ chainId: 4663,
2522
+ name: "Robinhood Chain",
2523
+ rpcUrl: "https://rpc.mainnet.chain.robinhood.com",
2524
+ blockExplorer: "https://robinhoodchain.blockscout.com/",
2525
+ subgraphUrl: "https://api.goldsky.com/api/public/project_cmre158qbffn101xe929tflsk/subgraphs/slvr-robinhood/1.2.0/gn",
2526
+ addresses: {
2527
+ lottery: "0x284Eb4016305Fa7FbC162Fb68F27227271001c7f",
2528
+ staking: "0xaF68598eBd245DC3cB92FF16E9Ba1814DD137200",
2529
+ token: "0x791229E3EbD6CFdC3D8157f48722684173C29aD9",
2530
+ autoCommit: "0x314c8D5755468224AC60c36FB5494F0D7D5Abb3B",
2531
+ voteEscrow: "0xd9b8FBD61033145c5496132153CE675756313B71",
2532
+ slvrEthPair: "0xe365b92239097Ed3322131411DbE15a5c4068eff",
2533
+ // Chainlink ETH/USD feed (standard AggregatorV3 proxy; "ETH / USD", 8 decimals).
2534
+ // Verified on-chain. There is also an SVR proxy at
2535
+ // 0x5058aDee53b04e374d8bEDbAD634Bc4778F50b22 with identical price data — that
2536
+ // one is for protocols integrating Chainlink SVR (OEV recapture); for plain
2537
+ // price reads the standard proxy below is the right choice.
2538
+ chainlinkEthUsd: "0x78F3556b67E17Df817D51Ef5a990cDaF09E8d3A9",
2539
+ // Multicall3 at its canonical cross-chain address (verified deployed on Robinhood Chain).
2540
+ multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11"
2541
+ }
2542
+ };
2543
+ var deployments = {
2544
+ robinhood
2545
+ };
2546
+ var robinhoodChain = (0, import_viem13.defineChain)({
2547
+ id: robinhood.chainId,
2548
+ name: robinhood.name,
2549
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
2550
+ rpcUrls: { default: { http: [robinhood.rpcUrl] } },
2551
+ blockExplorers: {
2552
+ default: { name: "Blockscout", url: "https://robinhoodchain.blockscout.com" }
2553
+ },
2554
+ // Registering Multicall3 lets viem batch reads: the SDK's multi-square reads
2555
+ // become one RPC call, and clients created with `batch: { multicall: true }`
2556
+ // auto-batch every concurrent read.
2557
+ contracts: {
2558
+ multicall3: { address: robinhood.addresses.multicall3 }
2559
+ }
2560
+ });
2561
+
2562
+ // src/connect.ts
2563
+ function chainFromDeployment(deployment) {
2564
+ if (deployment.chainId === robinhood.chainId) return robinhoodChain;
2565
+ return (0, import_viem14.defineChain)({
2566
+ id: deployment.chainId,
2567
+ name: deployment.name,
2568
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
2569
+ rpcUrls: { default: { http: [deployment.rpcUrl] } },
2570
+ ...deployment.blockExplorer ? { blockExplorers: { default: { name: "Explorer", url: deployment.blockExplorer } } } : {},
2571
+ ...deployment.addresses.multicall3 ? { contracts: { multicall3: { address: deployment.addresses.multicall3 } } } : {}
2572
+ });
2573
+ }
2574
+ function createSlvrClients(opts = {}) {
2575
+ const deployment = opts.deployment ?? robinhood;
2576
+ const chain = chainFromDeployment(deployment);
2577
+ const transport = opts.transport ?? (0, import_viem14.http)(opts.rpcUrl ?? deployment.rpcUrl, { timeout: 2e4, retryCount: 3 });
2578
+ const publicClient = (0, import_viem14.createPublicClient)({
2579
+ chain,
2580
+ transport,
2581
+ batch: { multicall: opts.batchMulticall ?? true },
2582
+ ...opts.pollingInterval ? { pollingInterval: opts.pollingInterval } : {}
2583
+ });
2584
+ const account = opts.account ?? (opts.privateKey ? (0, import_accounts.privateKeyToAccount)(opts.privateKey) : void 0);
2585
+ const walletClient = account ? (0, import_viem14.createWalletClient)({ chain, transport, account }) : void 0;
2586
+ return { chain, publicClient, walletClient };
2587
+ }
2588
+
2589
+ // src/types.ts
2590
+ var GameStatus = /* @__PURE__ */ ((GameStatus2) => {
2591
+ GameStatus2[GameStatus2["Pending"] = 0] = "Pending";
2592
+ GameStatus2[GameStatus2["Active"] = 1] = "Active";
2593
+ GameStatus2[GameStatus2["Paused"] = 2] = "Paused";
2594
+ GameStatus2[GameStatus2["Retired"] = 3] = "Retired";
2595
+ return GameStatus2;
2596
+ })(GameStatus || {});
2597
+ var GameTier = /* @__PURE__ */ ((GameTier2) => {
2598
+ GameTier2[GameTier2["Core"] = 0] = "Core";
2599
+ GameTier2[GameTier2["Community"] = 1] = "Community";
2600
+ GameTier2[GameTier2["Experimental"] = 2] = "Experimental";
2601
+ return GameTier2;
2602
+ })(GameTier || {});
2603
+
2604
+ // src/transaction.ts
2605
+ async function estimateGas(publicClient, address, abi, functionName, args, value, account) {
2606
+ try {
2607
+ const estimateParams = {
2608
+ address,
2609
+ abi,
2610
+ functionName,
2611
+ args
2612
+ };
2613
+ if (value !== void 0) {
2614
+ estimateParams.value = value;
2615
+ }
2616
+ if (account !== void 0) {
2617
+ estimateParams.account = account;
2618
+ }
2619
+ const gas = await publicClient.estimateContractGas(estimateParams);
2620
+ return gas;
2621
+ } catch (error) {
2622
+ throw new TransactionError(
2623
+ `Failed to estimate gas for ${functionName}: ${error instanceof Error ? error.message : String(error)}`,
2624
+ void 0,
2625
+ error
2626
+ );
2627
+ }
2628
+ }
2629
+ async function waitForReceipt(publicClient, hash, timeout = 12e4) {
2630
+ try {
2631
+ const receipt = await publicClient.waitForTransactionReceipt({
2632
+ hash,
2633
+ timeout
2634
+ });
2635
+ if (receipt && typeof receipt === "object" && "status" in receipt) {
2636
+ if (receipt.status === "reverted") {
2637
+ throw new TransactionError(
2638
+ `Transaction reverted: ${hash}`,
2639
+ hash
2640
+ );
2641
+ }
2642
+ }
2643
+ return receipt;
2644
+ } catch (error) {
2645
+ if (error instanceof TransactionError) {
2646
+ throw error;
2647
+ }
2648
+ throw new TransactionError(
2649
+ `Failed to wait for transaction receipt: ${error instanceof Error ? error.message : String(error)}`,
2650
+ hash,
2651
+ error
2652
+ );
2653
+ }
2654
+ }
2655
+ async function executeTransaction(walletClient, publicClient, address, abi, functionName, args, options = {}) {
2656
+ const {
2657
+ gas,
2658
+ gasPrice,
2659
+ maxFeePerGas,
2660
+ maxPriorityFeePerGas,
2661
+ waitForReceipt: waitForReceipt_,
2662
+ receiptTimeout,
2663
+ value
2664
+ } = options;
2665
+ const writeParams = {
2666
+ address,
2667
+ abi,
2668
+ functionName,
2669
+ args,
2670
+ account: walletClient.account,
2671
+ chain: null,
2672
+ ...value !== void 0 && { value },
2673
+ ...gas !== void 0 && { gas },
2674
+ ...gasPrice !== void 0 && { gasPrice },
2675
+ ...maxFeePerGas !== void 0 && { maxFeePerGas },
2676
+ ...maxPriorityFeePerGas !== void 0 && { maxPriorityFeePerGas }
2677
+ };
2678
+ try {
2679
+ const hash = await walletClient.writeContract(writeParams);
2680
+ if (waitForReceipt_) {
2681
+ return await waitForReceipt(publicClient, hash, receiptTimeout);
2682
+ }
2683
+ return hash;
2684
+ } catch (error) {
2685
+ throw new TransactionError(
2686
+ `Failed to execute transaction: ${error instanceof Error ? error.message : String(error)}`,
2687
+ void 0,
2688
+ error
2689
+ );
2690
+ }
397
2691
  }
398
- exports.SlvrSDK = SlvrSDK;
399
- // Export all types
400
- __exportStar(require("./types"), exports);
401
- // Export known deployments + ready-made viem chain
402
- __exportStar(require("./deployments"), exports);
403
- // Export expected-value math + SLVR price reader
404
- __exportStar(require("./ev"), exports);
405
- var price_2 = require("./price");
406
- Object.defineProperty(exports, "SlvrPrice", { enumerable: true, get: function () { return price_2.SlvrPrice; } });
407
- var oracle_2 = require("./oracle");
408
- Object.defineProperty(exports, "ChainlinkPriceFeed", { enumerable: true, get: function () { return oracle_2.ChainlinkPriceFeed; } });
409
- // Export client factory
410
- var connect_2 = require("./connect");
411
- Object.defineProperty(exports, "createSlvrClients", { enumerable: true, get: function () { return connect_2.createSlvrClients; } });
412
- Object.defineProperty(exports, "chainFromDeployment", { enumerable: true, get: function () { return connect_2.chainFromDeployment; } });
413
- // Export contract classes
414
- var lottery_2 = require("./contracts/lottery");
415
- Object.defineProperty(exports, "SlvrGridLottery", { enumerable: true, get: function () { return lottery_2.SlvrGridLottery; } });
416
- var staking_2 = require("./contracts/staking");
417
- Object.defineProperty(exports, "SlvrStaking", { enumerable: true, get: function () { return staking_2.SlvrStaking; } });
418
- var token_2 = require("./contracts/token");
419
- Object.defineProperty(exports, "SlvrToken", { enumerable: true, get: function () { return token_2.SlvrToken; } });
420
- var autoCommit_2 = require("./contracts/autoCommit");
421
- Object.defineProperty(exports, "SlvrAutoCommit", { enumerable: true, get: function () { return autoCommit_2.SlvrAutoCommit; } });
422
- var hub_2 = require("./contracts/hub");
423
- Object.defineProperty(exports, "SlvrHub", { enumerable: true, get: function () { return hub_2.SlvrHub; } });
424
- var registry_2 = require("./contracts/registry");
425
- Object.defineProperty(exports, "SlvrGameRegistry", { enumerable: true, get: function () { return registry_2.SlvrGameRegistry; } });
426
- var jackpot_2 = require("./contracts/jackpot");
427
- Object.defineProperty(exports, "SlvrJackpot", { enumerable: true, get: function () { return jackpot_2.SlvrJackpot; } });
428
- // Export errors
429
- __exportStar(require("./errors"), exports);
430
- // Export utilities
431
- __exportStar(require("./utils"), exports);
432
- // Export transaction helpers
433
- __exportStar(require("./transaction"), exports);
434
- // Export event utilities
435
- __exportStar(require("./events"), exports);
2692
+
2693
+ // src/index.ts
2694
+ var SlvrSDK = class _SlvrSDK {
2695
+ constructor(config) {
2696
+ this.config = config;
2697
+ this.lottery = new SlvrGridLottery(
2698
+ config.publicClient,
2699
+ config.walletClient,
2700
+ config.addresses.lottery
2701
+ );
2702
+ this.staking = new SlvrStaking(
2703
+ config.publicClient,
2704
+ config.walletClient,
2705
+ config.addresses.staking
2706
+ );
2707
+ this.token = new SlvrToken(
2708
+ config.publicClient,
2709
+ config.walletClient,
2710
+ config.addresses.token
2711
+ );
2712
+ if (config.addresses.autoCommit) {
2713
+ this.autoCommit = new SlvrAutoCommit(
2714
+ config.publicClient,
2715
+ config.walletClient,
2716
+ config.addresses.autoCommit
2717
+ );
2718
+ }
2719
+ if (config.addresses.hub) {
2720
+ this.hub = new SlvrHub(
2721
+ config.publicClient,
2722
+ config.walletClient,
2723
+ config.addresses.hub
2724
+ );
2725
+ }
2726
+ if (config.addresses.registry) {
2727
+ this.registry = new SlvrGameRegistry(
2728
+ config.publicClient,
2729
+ config.walletClient,
2730
+ config.addresses.registry
2731
+ );
2732
+ }
2733
+ if (config.addresses.jackpot) {
2734
+ this.jackpot = new SlvrJackpot(
2735
+ config.publicClient,
2736
+ config.walletClient,
2737
+ config.addresses.jackpot
2738
+ );
2739
+ }
2740
+ if (config.addresses.slvrEthPair) {
2741
+ this.price = new SlvrPrice(
2742
+ config.publicClient,
2743
+ config.addresses.slvrEthPair,
2744
+ config.addresses.token
2745
+ );
2746
+ }
2747
+ if (config.addresses.chainlinkEthUsd) {
2748
+ this.ethUsd = new ChainlinkPriceFeed(config.publicClient, config.addresses.chainlinkEthUsd);
2749
+ }
2750
+ }
2751
+ /**
2752
+ * One-line setup: build clients (with Multicall3 batching + resilient transport
2753
+ * defaults) and an SDK for a deployment. Pass a `privateKey`/`account` to enable
2754
+ * writes; omit it for a read-only SDK.
2755
+ *
2756
+ * @example
2757
+ * ```typescript
2758
+ * import { SlvrSDK } from '@slvr-labs/sdk';
2759
+ * const sdk = SlvrSDK.connect(); // read-only, Robinhood Chain
2760
+ * const bot = SlvrSDK.connect({ privateKey: process.env.PK }); // wallet-backed
2761
+ * ```
2762
+ */
2763
+ static connect(opts = {}) {
2764
+ const deployment = opts.deployment ?? robinhood;
2765
+ const { publicClient, walletClient } = createSlvrClients(opts);
2766
+ return new _SlvrSDK({ publicClient, walletClient, addresses: deployment.addresses });
2767
+ }
2768
+ /**
2769
+ * Get the public client
2770
+ */
2771
+ getPublicClient() {
2772
+ return this.config.publicClient;
2773
+ }
2774
+ /**
2775
+ * Get the wallet client (if available)
2776
+ */
2777
+ getWalletClient() {
2778
+ return this.config.walletClient;
2779
+ }
2780
+ /**
2781
+ * Update the wallet client and reinitialize contracts
2782
+ * @param walletClient New wallet client (or undefined to remove)
2783
+ */
2784
+ setWalletClient(walletClient) {
2785
+ this.config.walletClient = walletClient;
2786
+ this.lottery.setWalletClient(walletClient);
2787
+ this.staking.setWalletClient(walletClient);
2788
+ this.token.setWalletClient(walletClient);
2789
+ if (this.autoCommit) {
2790
+ this.autoCommit.setWalletClient(walletClient);
2791
+ }
2792
+ if (this.hub) {
2793
+ this.hub.setWalletClient(walletClient);
2794
+ }
2795
+ if (this.registry) {
2796
+ this.registry.setWalletClient(walletClient);
2797
+ }
2798
+ if (this.jackpot) {
2799
+ this.jackpot.setWalletClient(walletClient);
2800
+ }
2801
+ }
2802
+ /**
2803
+ * Helper: Calculate bet amounts from percentages
2804
+ * @param totalAmount Total amount to bet
2805
+ * @param percentages Array of percentages (0-100) for each square
2806
+ * @returns Array of amounts in wei
2807
+ * @throws ValidationError if percentages don't sum to 100 or if totalAmount is invalid
2808
+ */
2809
+ static calculateBetAmounts(totalAmount, percentages) {
2810
+ if (percentages.length === 0) {
2811
+ throw new ValidationError("Percentages array cannot be empty", "percentages");
2812
+ }
2813
+ const totalPercent = percentages.reduce((sum, p) => sum + p, 0);
2814
+ if (Math.abs(totalPercent - 100) > 0.01) {
2815
+ throw new ValidationError(`Percentages must sum to 100 (got ${totalPercent})`, "percentages");
2816
+ }
2817
+ const amounts = [];
2818
+ let allocated = 0n;
2819
+ for (let i = 0; i < percentages.length; i++) {
2820
+ const percentage = percentages[i];
2821
+ if (percentage === void 0) continue;
2822
+ const amount = totalAmount * BigInt(Math.round(percentage * 100)) / 10000n;
2823
+ amounts.push(amount);
2824
+ allocated += amount;
2825
+ }
2826
+ if (allocated !== totalAmount && amounts.length > 0 && amounts[0] !== void 0) {
2827
+ amounts[0] += totalAmount - allocated;
2828
+ }
2829
+ return amounts;
2830
+ }
2831
+ /**
2832
+ * Helper: Format bigint to human-readable string.
2833
+ *
2834
+ * `precision` caps the number of decimal places shown; trailing zeros are
2835
+ * stripped (like viem's `formatEther`), so `formatToken(1.5e18)` is `"1.5"`,
2836
+ * not `"1.5000"`.
2837
+ * @param value Value in wei
2838
+ * @param decimals Number of decimals (default 18)
2839
+ * @param precision Max decimal places to show (default 4)
2840
+ */
2841
+ static formatToken(value, decimals = 18, precision = 4) {
2842
+ const divisor = BigInt(10 ** decimals);
2843
+ const whole = value / divisor;
2844
+ const remainder = value % divisor;
2845
+ const fractional = remainder * BigInt(10 ** precision) / divisor;
2846
+ if (fractional === 0n) {
2847
+ return whole.toString();
2848
+ }
2849
+ const fractionalStr = fractional.toString().padStart(precision, "0").replace(/0+$/, "");
2850
+ return fractionalStr ? `${whole}.${fractionalStr}` : whole.toString();
2851
+ }
2852
+ /**
2853
+ * Helper: Parse human-readable string to bigint
2854
+ * @param value Human-readable value (e.g., "1.5")
2855
+ * @param decimals Number of decimals (default 18)
2856
+ */
2857
+ static parseToken(value, decimals = 18) {
2858
+ const parts = value.split(".");
2859
+ const whole = BigInt(parts[0] || "0");
2860
+ const fractional = parts[1] ? BigInt(parts[1].padEnd(decimals, "0").slice(0, decimals)) : 0n;
2861
+ return whole * BigInt(10 ** decimals) + fractional;
2862
+ }
2863
+ /**
2864
+ * Helper: Calculate time remaining until round ends
2865
+ * @param roundId Round ID
2866
+ * @returns Time remaining in seconds, or 0 if round has ended
2867
+ */
2868
+ async getTimeRemaining(roundId) {
2869
+ const [roundEnd, block] = await Promise.all([
2870
+ this.lottery.roundEnd(roundId),
2871
+ this.config.publicClient.getBlock()
2872
+ ]);
2873
+ const remaining = roundEnd - block.timestamp;
2874
+ return remaining > 0n ? Number(remaining) : 0;
2875
+ }
2876
+ /**
2877
+ * Helper: Check if user can claim rewards for a round
2878
+ * @param roundId Round ID
2879
+ * @param user User address
2880
+ * @returns True if user can claim
2881
+ */
2882
+ async canClaim(roundId, user) {
2883
+ const [round, hasClaimed_] = await Promise.all([
2884
+ this.lottery.getRound(roundId),
2885
+ this.lottery.getHasClaimed(roundId, user)
2886
+ ]);
2887
+ if (!round.resolved || hasClaimed_) {
2888
+ return false;
2889
+ }
2890
+ const userBet = await this.lottery.getUserBet(roundId, round.winningSquare, user);
2891
+ return userBet > 0n;
2892
+ }
2893
+ /**
2894
+ * Helper: Get user's claimable rounds
2895
+ * @param user User address
2896
+ * @param startRoundId Start round ID to check from
2897
+ * @param endRoundId End round ID to check to
2898
+ * @returns Array of round IDs that can be claimed
2899
+ */
2900
+ async getClaimableRounds(user, startRoundId, endRoundId) {
2901
+ const rounds = [];
2902
+ for (let roundId = startRoundId; roundId <= endRoundId; roundId++) {
2903
+ rounds.push(roundId);
2904
+ }
2905
+ const flags = await Promise.all(rounds.map((roundId) => this.canClaim(roundId, user)));
2906
+ return rounds.filter((_, i) => flags[i]);
2907
+ }
2908
+ /**
2909
+ * Helper: Compute a game's effective SLVR/sec emission rate.
2910
+ *
2911
+ * Mirrors SlvrHub._effectiveRatePerSec: the game's weighted share of the global emission stream,
2912
+ * i.e. emissionRatePerSec * weightOf(gameId) / totalActiveWeight. NOTE: this does not apply the
2913
+ * per-game maxWeightBps ceiling that the contract also enforces; it is the pre-cap weighted rate.
2914
+ *
2915
+ * Requires both `hub` and `registry` addresses to be configured.
2916
+ * @param gameId Registry game id
2917
+ * @returns Effective emission rate in SLVR/sec (0 if there is no active weight)
2918
+ */
2919
+ async effectiveEmissionRate(gameId) {
2920
+ if (!this.hub) {
2921
+ throw new ValidationError("hub address is required for effectiveEmissionRate", "hub");
2922
+ }
2923
+ if (!this.registry) {
2924
+ throw new ValidationError("registry address is required for effectiveEmissionRate", "registry");
2925
+ }
2926
+ const [rate, weight, totalWeight] = await Promise.all([
2927
+ this.hub.emissionRatePerSec(),
2928
+ this.registry.weightOf(gameId),
2929
+ this.registry.totalActiveWeight()
2930
+ ]);
2931
+ if (totalWeight === 0n) {
2932
+ return 0n;
2933
+ }
2934
+ return rate * weight / totalWeight;
2935
+ }
2936
+ /**
2937
+ * Helper: Get the accrued-but-unminted SLVR emission currently available to a game.
2938
+ *
2939
+ * Thin pass-through to SlvrHub.pendingEmission. Requires `hub` to be configured.
2940
+ * @param gameId Registry game id
2941
+ */
2942
+ async pendingEmission(gameId) {
2943
+ if (!this.hub) {
2944
+ throw new ValidationError("hub address is required for pendingEmission", "hub");
2945
+ }
2946
+ return await this.hub.pendingEmission(gameId);
2947
+ }
2948
+ /**
2949
+ * Helper: current SLVR spot price in ETH (ETH per SLVR).
2950
+ *
2951
+ * Requires a `slvrEthPair` address to be configured (so `sdk.price` exists).
2952
+ * @throws ValidationError if no pair address was configured.
2953
+ */
2954
+ async getSlvrPriceInEth() {
2955
+ if (!this.price) {
2956
+ throw new ValidationError("slvrEthPair address is required for getSlvrPriceInEth", "slvrEthPair");
2957
+ }
2958
+ return await this.price.getPriceInEth();
2959
+ }
2960
+ /**
2961
+ * Helper: current ETH price in USD from the configured Chainlink feed.
2962
+ *
2963
+ * Requires a `chainlinkEthUsd` address (so `sdk.ethUsd` exists). This is wired
2964
+ * for Robinhood Chain in `deployments.robinhood`; on chains without a feed,
2965
+ * supply one or pass `ethUsd` to {@link getSlvrPrice}.
2966
+ * @throws ValidationError if no ETH/USD feed was configured.
2967
+ */
2968
+ async getEthPriceUsd() {
2969
+ if (!this.ethUsd) {
2970
+ throw new ValidationError("chainlinkEthUsd address is required for getEthPriceUsd", "chainlinkEthUsd");
2971
+ }
2972
+ return await this.ethUsd.getPrice();
2973
+ }
2974
+ /**
2975
+ * Helper: current SLVR price in **both ETH and USD**.
2976
+ *
2977
+ * SLVR/ETH comes from the UniswapV2 pair (`sdk.price`). The USD value uses, in
2978
+ * order: an explicit `opts.ethUsd`, else the configured Chainlink ETH/USD feed
2979
+ * (`sdk.ethUsd`), else `usd` is `null`.
2980
+ *
2981
+ * @param opts.ethUsd override the ETH/USD price (e.g. from your own off-chain source)
2982
+ * @throws ValidationError if no `slvrEthPair` was configured.
2983
+ *
2984
+ * @example
2985
+ * ```typescript
2986
+ * const { eth, usd } = await sdk.getSlvrPrice(); // uses Chainlink feed if configured
2987
+ * const q = await sdk.getSlvrPrice({ ethUsd: 1797.35 }); // or supply ETH/USD yourself
2988
+ * ```
2989
+ */
2990
+ async getSlvrPrice(opts) {
2991
+ if (!this.price) {
2992
+ throw new ValidationError("slvrEthPair address is required for getSlvrPrice", "slvrEthPair");
2993
+ }
2994
+ const eth = await this.price.getPriceInEth();
2995
+ let ethUsd = opts?.ethUsd;
2996
+ if (ethUsd === void 0 && this.ethUsd) {
2997
+ ethUsd = await this.ethUsd.getPrice();
2998
+ }
2999
+ return { eth, usd: ethUsd !== void 0 ? eth * ethUsd : null };
3000
+ }
3001
+ /**
3002
+ * Helper: estimate the per-round expected value of grid mining for the given
3003
+ * stake, pulling live pot, emission and SLVR price on-chain.
3004
+ *
3005
+ * This is the SDK-level convenience around {@link computeGridMiningEv}: it reads
3006
+ * the round's pot (sum of all squares), the emission target (`slvrPerRound`), the
3007
+ * protocol fee (`protocolFeeBps`), and the SLVR price (via `sdk.price`), then
3008
+ * returns the full EV breakdown. Any of those can be overridden via `params`.
3009
+ *
3010
+ * The jackpot pool is **not** auto-read (it isn't exposed as a single call on the
3011
+ * live lottery) — pass `jackpotPool` if you want the jackpot term included.
3012
+ *
3013
+ * @remarks `slvrPerRound` is the emission *target*; actual emission is hub-gated
3014
+ * and may be lower, so treat the result as an upper-ish estimate. Requires a
3015
+ * configured `slvrEthPair` unless you pass `slvrPriceEth`.
3016
+ */
3017
+ async estimateRoundEv(params) {
3018
+ const roundId = params.roundId ?? await this.lottery.currentRoundId();
3019
+ const [potEth, emissionPerRound, slvrPriceEth, feeBps, jackpotPoolEth] = await Promise.all([
3020
+ params.pot !== void 0 ? Promise.resolve(params.pot) : this.lottery.getRoundSquares(roundId).then((sqs) => Number((0, import_viem15.formatEther)(sqs.reduce((sum, s) => sum + s.total, 0n)))),
3021
+ params.emissionPerRound !== void 0 ? Promise.resolve(params.emissionPerRound) : this.lottery.slvrPerRound().then((v) => Number((0, import_viem15.formatEther)(v))),
3022
+ params.slvrPriceEth !== void 0 ? Promise.resolve(params.slvrPriceEth) : this.getSlvrPriceInEth(),
3023
+ this.lottery.protocolFeeBps(),
3024
+ // Auto-read the jackpot pool (0 if no jackpot is set for the round / read fails).
3025
+ params.jackpotPool !== void 0 ? Promise.resolve(params.jackpotPool) : this.getJackpotPool(roundId).then((v) => Number((0, import_viem15.formatEther)(v))).catch(() => 0)
3026
+ ]);
3027
+ return computeGridMiningEv({
3028
+ stake: params.stake,
3029
+ pot: potEth,
3030
+ emissionPerRound,
3031
+ slvrPriceEth,
3032
+ feeBps,
3033
+ cashOut: params.cashOut,
3034
+ jackpotPool: jackpotPoolEth,
3035
+ jackpotOdds: params.jackpotOdds
3036
+ });
3037
+ }
3038
+ /**
3039
+ * Helper: current jackpot pool for a round, in wei (0 if no jackpot is set).
3040
+ *
3041
+ * Resolves the round's jackpot contract via `getRoundJackpot` and reads its
3042
+ * `jackpotPool()`. Defaults to the current round.
3043
+ */
3044
+ async getJackpotPool(roundId) {
3045
+ const id = roundId ?? await this.lottery.currentRoundId();
3046
+ const jackpotAddress = await this.lottery.getRoundJackpot(id);
3047
+ if (jackpotAddress === "0x0000000000000000000000000000000000000000") return 0n;
3048
+ return await new SlvrJackpot(this.config.publicClient, void 0, jackpotAddress).jackpotPool();
3049
+ }
3050
+ };
3051
+ // Annotate the CommonJS export names for ESM import in node:
3052
+ 0 && (module.exports = {
3053
+ ChainlinkPriceFeed,
3054
+ ContractCallError,
3055
+ GRID_SIZE,
3056
+ GameStatus,
3057
+ GameTier,
3058
+ JACKPOT_ODDS,
3059
+ PROTOCOL_FEE_BPS,
3060
+ REFINING_FEE_BPS,
3061
+ SINGLE_SQUARE_WIN_PROBABILITY,
3062
+ SlvrAutoCommit,
3063
+ SlvrAutoCommitEvents,
3064
+ SlvrGameRegistry,
3065
+ SlvrGridLottery,
3066
+ SlvrGridLotteryEvents,
3067
+ SlvrHub,
3068
+ SlvrJackpot,
3069
+ SlvrPrice,
3070
+ SlvrRevertError,
3071
+ SlvrSDK,
3072
+ SlvrSDKError,
3073
+ SlvrStaking,
3074
+ SlvrStakingEvents,
3075
+ SlvrToken,
3076
+ SlvrTokenEvents,
3077
+ TransactionError,
3078
+ ValidationError,
3079
+ WalletClientRequiredError,
3080
+ chainFromDeployment,
3081
+ computeGridMiningEv,
3082
+ createSlvrClients,
3083
+ decodeEvent,
3084
+ decodeEvents,
3085
+ decodeSlvrRevert,
3086
+ deployments,
3087
+ estimateGas,
3088
+ executeTransaction,
3089
+ robinhood,
3090
+ robinhoodChain,
3091
+ validateAddress,
3092
+ validateAmount,
3093
+ validateArrayLengths,
3094
+ validateBps,
3095
+ validateBpsSum,
3096
+ validateSquares,
3097
+ waitForReceipt,
3098
+ waitForTransactionReceipt
3099
+ });
436
3100
  //# sourceMappingURL=index.js.map