@slvr-labs/sdk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/index.d.mts +2117 -0
- package/dist/index.d.ts +1903 -34
- package/dist/index.js +3091 -427
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3035 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +17 -10
- package/skills/slvr-bot/SKILL.md +9 -8
- package/skills/slvr-bot/references/api.md +8 -0
- package/dist/connect.d.ts +0 -47
- package/dist/connect.d.ts.map +0 -1
- package/dist/connect.js +0 -56
- package/dist/connect.js.map +0 -1
- package/dist/contracts/autoCommit.d.ts +0 -151
- package/dist/contracts/autoCommit.d.ts.map +0 -1
- package/dist/contracts/autoCommit.js +0 -426
- package/dist/contracts/autoCommit.js.map +0 -1
- package/dist/contracts/hub.d.ts +0 -50
- package/dist/contracts/hub.d.ts.map +0 -1
- package/dist/contracts/hub.js +0 -118
- package/dist/contracts/hub.js.map +0 -1
- package/dist/contracts/index.d.ts +0 -8
- package/dist/contracts/index.d.ts.map +0 -1
- package/dist/contracts/index.js +0 -18
- package/dist/contracts/index.js.map +0 -1
- package/dist/contracts/jackpot.d.ts +0 -21
- package/dist/contracts/jackpot.d.ts.map +0 -1
- package/dist/contracts/jackpot.js +0 -44
- package/dist/contracts/jackpot.js.map +0 -1
- package/dist/contracts/lottery.d.ts +0 -256
- package/dist/contracts/lottery.d.ts.map +0 -1
- package/dist/contracts/lottery.js +0 -767
- package/dist/contracts/lottery.js.map +0 -1
- package/dist/contracts/registry.d.ts +0 -53
- package/dist/contracts/registry.d.ts.map +0 -1
- package/dist/contracts/registry.js +0 -143
- package/dist/contracts/registry.js.map +0 -1
- package/dist/contracts/staking.d.ts +0 -101
- package/dist/contracts/staking.d.ts.map +0 -1
- package/dist/contracts/staking.js +0 -306
- package/dist/contracts/staking.js.map +0 -1
- package/dist/contracts/token.d.ts +0 -95
- package/dist/contracts/token.d.ts.map +0 -1
- package/dist/contracts/token.js +0 -273
- package/dist/contracts/token.js.map +0 -1
- package/dist/deployments.d.ts +0 -117
- package/dist/deployments.d.ts.map +0 -1
- package/dist/deployments.js +0 -74
- package/dist/deployments.js.map +0 -1
- package/dist/errors.d.ts +0 -39
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -67
- package/dist/errors.js.map +0 -1
- package/dist/ev.d.ts +0 -123
- package/dist/ev.d.ts.map +0 -1
- package/dist/ev.js +0 -111
- package/dist/ev.js.map +0 -1
- package/dist/events.d.ts +0 -418
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js +0 -87
- package/dist/events.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/oracle.d.ts +0 -48
- package/dist/oracle.d.ts.map +0 -1
- package/dist/oracle.js +0 -77
- package/dist/oracle.js.map +0 -1
- package/dist/price.d.ts +0 -52
- package/dist/price.d.ts.map +0 -1
- package/dist/price.js +0 -78
- package/dist/price.js.map +0 -1
- package/dist/transaction.d.ts +0 -54
- package/dist/transaction.d.ts.map +0 -1
- package/dist/transaction.js +0 -105
- package/dist/transaction.js.map +0 -1
- package/dist/types.d.ts +0 -162
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -23
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -58
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -110
- package/dist/utils.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,436 +1,3100 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|