@fenelabs/fene-sdk 0.3.2 → 0.3.6

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/dist/index.mjs CHANGED
@@ -1,1083 +1,398 @@
1
1
  // src/errors.ts
2
- var ResonanceAPIError = class extends Error {
3
- constructor(statusCode, code, message) {
2
+ var ErrorCode = {
3
+ // Validator errors
4
+ VALIDATOR_NOT_FOUND: "VALIDATOR_NOT_FOUND",
5
+ VALIDATOR_INACTIVE: "VALIDATOR_INACTIVE",
6
+ // Delegator errors
7
+ DELEGATOR_NOT_FOUND: "DELEGATOR_NOT_FOUND",
8
+ NOT_WHITELISTED: "NOT_WHITELISTED",
9
+ // Referral errors
10
+ REFERRAL_KEY_INVALID: "REFERRAL_KEY_INVALID",
11
+ REFERRAL_KEY_EXPIRED: "REFERRAL_KEY_EXPIRED",
12
+ REFERRAL_KEY_USED: "REFERRAL_KEY_USED",
13
+ // Auth errors
14
+ INVALID_SIGNATURE: "INVALID_SIGNATURE",
15
+ NONCE_EXPIRED: "NONCE_EXPIRED",
16
+ UNAUTHORIZED: "UNAUTHORIZED",
17
+ // Service errors (graceful degradation)
18
+ RPC_UNAVAILABLE: "RPC_UNAVAILABLE",
19
+ CACHE_UNAVAILABLE: "CACHE_UNAVAILABLE",
20
+ DATABASE_UNAVAILABLE: "DATABASE_UNAVAILABLE",
21
+ // General errors
22
+ BAD_REQUEST: "BAD_REQUEST",
23
+ INTERNAL_ERROR: "INTERNAL_ERROR",
24
+ RATE_LIMITED: "RATE_LIMITED",
25
+ TIMEOUT: "TIMEOUT",
26
+ NETWORK_ERROR: "NETWORK_ERROR"
27
+ };
28
+ var ResonanceError = class _ResonanceError extends Error {
29
+ constructor(code, message, details, statusCode) {
4
30
  super(message);
5
- this.statusCode = statusCode;
31
+ this.name = "ResonanceError";
6
32
  this.code = code;
7
- this.name = "ResonanceAPIError";
8
- }
9
- };
10
-
11
- // src/client.ts
12
- var HTTPClient = class {
13
- constructor(config) {
14
- this.baseUrl = config.apiUrl.replace(/\/$/, "");
15
- this.timeout = config.timeout || 3e4;
16
- this.headers = {
17
- "Content-Type": "application/json",
18
- ...config.headers
19
- };
20
- }
21
- /**
22
- * Build URL with query parameters
23
- */
24
- buildUrl(path, params) {
25
- const url = new URL(`${this.baseUrl}${path}`);
26
- if (params) {
27
- Object.entries(params).forEach(([key, value]) => {
28
- if (value !== void 0 && value !== null) {
29
- url.searchParams.append(key, String(value));
30
- }
31
- });
33
+ this.details = details;
34
+ this.statusCode = statusCode;
35
+ if (Error.captureStackTrace) {
36
+ Error.captureStackTrace(this, _ResonanceError);
32
37
  }
33
- return url.toString();
34
38
  }
35
39
  /**
36
- * Set authorization token
40
+ * Check if this is a specific error code
37
41
  */
38
- setAuthToken(token) {
39
- if (token) {
40
- this.headers["Authorization"] = `Bearer ${token}`;
41
- } else {
42
- delete this.headers["Authorization"];
43
- }
42
+ is(code) {
43
+ return this.code === code;
44
44
  }
45
45
  /**
46
- * Set custom header
46
+ * Convert to JSON representation
47
47
  */
48
- setHeader(key, value) {
49
- this.headers[key] = value;
48
+ toJSON() {
49
+ return {
50
+ code: this.code,
51
+ message: this.message,
52
+ details: this.details
53
+ };
50
54
  }
51
- /**
52
- * Make HTTP GET request
53
- */
54
- async get(path, params) {
55
- const url = this.buildUrl(path, params);
56
- const controller = new AbortController();
57
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
58
- try {
59
- const response = await fetch(url, {
60
- method: "GET",
61
- headers: this.headers,
62
- signal: controller.signal
63
- });
64
- clearTimeout(timeoutId);
65
- if (!response.ok) {
66
- await this.handleError(response);
67
- }
68
- const json = await response.json();
69
- return json.data !== void 0 ? json.data : json;
70
- } catch (error) {
71
- if (error instanceof Error && error.name === "AbortError") {
72
- throw new Error(`Request timeout after ${this.timeout}ms`);
55
+ };
56
+ function isResonanceError(error) {
57
+ return error instanceof ResonanceError;
58
+ }
59
+ function hasErrorCode(error, code) {
60
+ return isResonanceError(error) && error.is(code);
61
+ }
62
+ function isNetworkError(error) {
63
+ if (isResonanceError(error)) {
64
+ return error.is(ErrorCode.RPC_UNAVAILABLE) || error.is(ErrorCode.CACHE_UNAVAILABLE) || error.is(ErrorCode.DATABASE_UNAVAILABLE);
65
+ }
66
+ return false;
67
+ }
68
+ function isAuthError(error) {
69
+ if (isResonanceError(error)) {
70
+ return error.is(ErrorCode.UNAUTHORIZED) || error.is(ErrorCode.INVALID_SIGNATURE) || error.is(ErrorCode.NONCE_EXPIRED);
71
+ }
72
+ return false;
73
+ }
74
+ function isNotFoundError(error) {
75
+ if (isResonanceError(error)) {
76
+ return error.is(ErrorCode.VALIDATOR_NOT_FOUND) || error.is(ErrorCode.DELEGATOR_NOT_FOUND);
77
+ }
78
+ return false;
79
+ }
80
+
81
+ // src/client.ts
82
+ var DEFAULT_CONFIG = {
83
+ timeout: 3e4,
84
+ retries: 2,
85
+ retryDelay: 1e3
86
+ };
87
+ function validateConfig(config) {
88
+ if (config.timeout !== void 0 && (config.timeout < 0 || config.timeout > 3e5)) {
89
+ throw new Error("timeout must be between 0 and 300000ms (5 minutes)");
90
+ }
91
+ if (config.retries !== void 0 && (config.retries < 0 || config.retries > 5)) {
92
+ throw new Error("retries must be between 0 and 5");
93
+ }
94
+ if (config.retryDelay !== void 0 && (config.retryDelay < 0 || config.retryDelay > 1e4)) {
95
+ throw new Error("retryDelay must be between 0 and 10000ms");
96
+ }
97
+ if (config.baseUrl && !config.baseUrl.match(/^https?:\/\//)) {
98
+ throw new Error("baseUrl must start with http:// or https://");
99
+ }
100
+ }
101
+ var ENV_API_URL = "RESONANCE_API_URL";
102
+ var ENV_API_TOKEN = "RESONANCE_API_TOKEN";
103
+ function getEnv(key) {
104
+ if (typeof process !== "undefined" && process.env) {
105
+ return process.env[key];
106
+ }
107
+ if (typeof import.meta !== "undefined" && import.meta.env) {
108
+ return import.meta.env[`VITE_${key}`] || import.meta.env[`NEXT_PUBLIC_${key}`];
109
+ }
110
+ return void 0;
111
+ }
112
+ var ResonanceClient = class {
113
+ constructor(config = {}) {
114
+ validateConfig(config);
115
+ const defaultUrl = "http://localhost:8080";
116
+ const envUrl = getEnv(ENV_API_URL);
117
+ const baseUrl = config.baseUrl || envUrl || defaultUrl;
118
+ this.baseUrl = baseUrl.replace(/\/$/, "");
119
+ this.token = config.token || getEnv(ENV_API_TOKEN);
120
+ this.timeout = config.timeout ?? DEFAULT_CONFIG.timeout;
121
+ this.retries = config.retries ?? DEFAULT_CONFIG.retries;
122
+ this.retryDelay = config.retryDelay ?? DEFAULT_CONFIG.retryDelay;
123
+ this.onTokenExpired = config.onTokenExpired;
124
+ this.onRequest = config.onRequest;
125
+ this.onResponse = config.onResponse;
126
+ this.onError = config.onError;
127
+ }
128
+ // ============================================
129
+ // Helper Methods
130
+ // ============================================
131
+ setToken(token) {
132
+ this.token = token;
133
+ }
134
+ clearToken() {
135
+ this.token = void 0;
136
+ }
137
+ async request(path, options = {}) {
138
+ let lastError = null;
139
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
140
+ try {
141
+ return await this.executeRequest(path, options, attempt);
142
+ } catch (error) {
143
+ lastError = error;
144
+ if (error instanceof ResonanceError) {
145
+ if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
146
+ throw error;
147
+ }
148
+ }
149
+ if (attempt >= this.retries) {
150
+ throw error;
151
+ }
152
+ const delay = this.retryDelay * Math.pow(2, attempt);
153
+ await new Promise((resolve) => setTimeout(resolve, delay));
73
154
  }
74
- throw error;
75
155
  }
156
+ throw lastError || new Error("Request failed");
76
157
  }
77
- /**
78
- * Make HTTP POST request
79
- */
80
- async post(path, body) {
81
- const url = this.buildUrl(path);
82
- const controller = new AbortController();
83
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
158
+ async executeRequest(path, options, attemptNumber) {
159
+ const startTime = Date.now();
160
+ const url = `${this.baseUrl}${path}`;
161
+ const headers = {
162
+ "Content-Type": "application/json",
163
+ ...options.headers
164
+ };
165
+ if (this.token) {
166
+ headers["Authorization"] = `Bearer ${this.token}`;
167
+ }
168
+ const requestContext = {
169
+ method: options.method || "GET",
170
+ url,
171
+ headers,
172
+ body: options.body,
173
+ timestamp: startTime,
174
+ attemptNumber
175
+ };
84
176
  try {
85
- const response = await fetch(url, {
86
- method: "POST",
87
- headers: this.headers,
88
- body: body ? JSON.stringify(body) : void 0,
89
- signal: controller.signal
90
- });
91
- clearTimeout(timeoutId);
92
- if (!response.ok) {
93
- await this.handleError(response);
94
- }
95
- const json = await response.json();
96
- return json.data !== void 0 ? json.data : json;
97
- } catch (error) {
98
- if (error instanceof Error && error.name === "AbortError") {
99
- throw new Error(`Request timeout after ${this.timeout}ms`);
100
- }
101
- throw error;
177
+ this.onRequest?.(requestContext);
178
+ } catch (hookError) {
179
+ console.error("onRequest hook error:", hookError);
102
180
  }
103
- }
104
- /**
105
- * Make HTTP DELETE request
106
- */
107
- async delete(path) {
108
- const url = this.buildUrl(path);
109
181
  const controller = new AbortController();
110
182
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
111
183
  try {
112
184
  const response = await fetch(url, {
113
- method: "DELETE",
114
- headers: this.headers,
185
+ ...options,
186
+ headers,
115
187
  signal: controller.signal
116
188
  });
117
189
  clearTimeout(timeoutId);
190
+ const duration = Date.now() - startTime;
191
+ if (response.status === 401) {
192
+ this.onTokenExpired?.();
193
+ }
118
194
  if (!response.ok) {
119
- await this.handleError(response);
195
+ const error = await response.json().catch(() => null);
196
+ const resonanceError = error && typeof error === "object" && "code" in error && "message" in error ? new ResonanceError(
197
+ error.code,
198
+ error.message,
199
+ error.details,
200
+ response.status
201
+ ) : new ResonanceError(
202
+ "INTERNAL_ERROR",
203
+ error?.error || `HTTP ${response.status}`,
204
+ void 0,
205
+ response.status
206
+ );
207
+ try {
208
+ this.onError?.(resonanceError, requestContext);
209
+ } catch (hookError) {
210
+ console.error("onError hook error:", hookError);
211
+ }
212
+ throw resonanceError;
120
213
  }
121
- const json = await response.json();
122
- return json.data !== void 0 ? json.data : json;
214
+ const data = await response.json();
215
+ const responseContext = {
216
+ ...requestContext,
217
+ status: response.status,
218
+ statusText: response.statusText,
219
+ duration,
220
+ data
221
+ };
222
+ try {
223
+ this.onResponse?.(responseContext);
224
+ } catch (hookError) {
225
+ console.error("onResponse hook error:", hookError);
226
+ }
227
+ return data;
123
228
  } catch (error) {
229
+ clearTimeout(timeoutId);
124
230
  if (error instanceof Error && error.name === "AbortError") {
125
- throw new Error(`Request timeout after ${this.timeout}ms`);
126
- }
127
- throw error;
128
- }
129
- }
130
- /**
131
- * Handle HTTP errors
132
- */
133
- async handleError(response) {
134
- let errorCode = "UNKNOWN_ERROR";
135
- let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
136
- try {
137
- const errorData = await response.json();
138
- if (errorData.error) {
139
- errorCode = errorData.error.code;
140
- errorMessage = errorData.error.message;
231
+ const timeoutError = new ResonanceError(
232
+ "TIMEOUT",
233
+ `Request timed out after ${this.timeout}ms`,
234
+ { url, timeout: this.timeout },
235
+ 408
236
+ );
237
+ try {
238
+ this.onError?.(timeoutError, requestContext);
239
+ } catch (hookError) {
240
+ console.error("onError hook error:", hookError);
241
+ }
242
+ throw timeoutError;
141
243
  }
142
- } catch {
143
- }
144
- throw new ResonanceAPIError(response.status, errorCode, errorMessage);
145
- }
146
- /*
147
- * Automatic Retry transient errors network
148
- */
149
- async fetchWithRetry(url, options, retries = 3) {
150
- for (let i = 0; i < retries; i++) {
151
- try {
152
- return await fetch(url, options);
153
- } catch (error) {
154
- if (i === retries - 1) throw error;
155
- await new Promise((resolve) => setTimeout(resolve, 1e3 * (i + 1)));
244
+ if (error instanceof Error) {
245
+ const networkError = error instanceof ResonanceError ? error : new ResonanceError("NETWORK_ERROR", error.message, void 0, 0);
246
+ try {
247
+ this.onError?.(networkError, requestContext);
248
+ } catch (hookError) {
249
+ console.error("onError hook error:", hookError);
250
+ }
251
+ throw networkError;
156
252
  }
253
+ throw error;
157
254
  }
158
- throw new Error("Max retries reached");
159
- }
160
- };
161
-
162
- // src/auth.ts
163
- var AuthService = class {
164
- constructor(apiUrl) {
165
- this.tokenKey = "resonance_jwt_token";
166
- this.apiUrl = apiUrl;
167
255
  }
168
- /**
169
- * Get nonce for wallet authentication
170
- * POST /eth/v1/auth/nonce
171
- */
256
+ // ============================================
257
+ // Auth
258
+ // ============================================
172
259
  async getNonce(address) {
173
- const response = await fetch(`${this.apiUrl}/eth/v1/auth/nonce`, {
174
- method: "POST",
175
- headers: { "Content-Type": "application/json" },
176
- body: JSON.stringify({ address })
177
- });
178
- if (!response.ok) {
179
- throw new Error("Failed to get nonce");
180
- }
181
- const result = await response.json();
182
- return result.data;
183
- }
184
- /**
185
- * Verify wallet signature and get JWT token
186
- * POST /eth/v1/auth/verify
187
- */
188
- async verify(params) {
189
- const response = await fetch(`${this.apiUrl}/eth/v1/auth/verify`, {
190
- method: "POST",
191
- headers: { "Content-Type": "application/json" },
192
- body: JSON.stringify(params)
193
- });
194
- if (!response.ok) {
195
- throw new Error("Authentication failed");
196
- }
197
- const result = await response.json();
198
- const token = result.data.token;
199
- if (typeof window !== "undefined") {
200
- sessionStorage.setItem(this.tokenKey, token);
201
- }
202
- return { token };
260
+ return this.request(`/eth/v1/auth/nonce?address=${address}`);
203
261
  }
204
- /**
205
- * Connect wallet and authenticate
206
- */
207
- async connectWallet(provider, role = "delegator") {
208
- const signer = await provider.getSigner();
209
- const address = await signer.getAddress();
210
- const timestamp = Math.floor(Date.now() / 1e3);
211
- const nonce = Math.floor(Math.random() * 1e6);
212
- const message = `Sign this message to login to Resonance Dashboard
213
-
214
- Address: ${address}
215
- Role: ${role}
216
- Nonce: ${nonce}
217
- Timestamp: ${timestamp}
218
-
219
- This will not trigger any blockchain transaction or cost gas fees.`;
220
- const signature = await signer.signMessage(message);
221
- const response = await fetch(`${this.apiUrl}/auth/login`, {
262
+ async verify(address, signature) {
263
+ const result = await this.request("/eth/v1/auth/verify", {
222
264
  method: "POST",
223
- headers: { "Content-Type": "application/json" },
224
- body: JSON.stringify({
225
- address,
226
- message,
227
- signature,
228
- role,
229
- timestamp,
230
- nonce
231
- })
265
+ body: JSON.stringify({ address, signature })
232
266
  });
233
- if (!response.ok) {
234
- throw new Error("Authentication failed");
235
- }
236
- const data = await response.json();
237
- if (typeof window !== "undefined") {
238
- sessionStorage.setItem(this.tokenKey, data.token);
239
- }
240
- return data;
241
- }
242
- /**
243
- * Get stored JWT token
244
- */
245
- getToken() {
246
- if (typeof window === "undefined") return null;
247
- return sessionStorage.getItem(this.tokenKey);
248
- }
249
- /**
250
- * Check if user is authenticated
251
- */
252
- isAuthenticated() {
253
- const token = this.getToken();
254
- if (!token) return false;
255
- try {
256
- const claims = this.parseToken(token);
257
- return claims.exp * 1e3 > Date.now();
258
- } catch {
259
- return false;
260
- }
261
- }
262
- /**
263
- * Parse JWT token to get claims
264
- */
265
- parseToken(token) {
266
- const payload = token.split(".")[1];
267
- const decoded = atob(payload);
268
- return JSON.parse(decoded);
269
- }
270
- /**
271
- * Get user info from token
272
- */
273
- getUserInfo() {
274
- const token = this.getToken();
275
- if (!token) return null;
276
- try {
277
- const claims = this.parseToken(token);
278
- return {
279
- address: claims.address,
280
- role: claims.role
281
- };
282
- } catch {
283
- return null;
284
- }
285
- }
286
- /**
287
- * Logout user
288
- */
289
- logout() {
290
- if (typeof window !== "undefined") {
291
- sessionStorage.removeItem(this.tokenKey);
292
- }
293
- }
294
- /**
295
- * Check if token is expiring soon (within 5 minutes)
296
- */
297
- isTokenExpiringSoon() {
298
- const token = this.getToken();
299
- if (!token) return true;
300
- try {
301
- const claims = this.parseToken(token);
302
- const expiresAt = claims.exp * 1e3;
303
- const fiveMinutes = 5 * 60 * 1e3;
304
- return expiresAt - Date.now() < fiveMinutes;
305
- } catch {
306
- return true;
307
- }
308
- }
309
- };
310
-
311
- // src/validators.ts
312
- var ValidatorsAPI = class {
313
- constructor(client) {
314
- this.client = client;
315
- }
316
- /**
317
- * Get all validators
318
- * GET /eth/v1/validators
319
- */
320
- async getAll() {
321
- return this.client.get("/eth/v1/validators");
322
- }
323
- /**
324
- * Get validator details
325
- * GET /eth/v1/validators/:address
326
- */
327
- async get(address) {
328
- return this.client.get(`/eth/v1/validators/${address}`);
329
- }
330
- /**
331
- * Get validator delegators
332
- * GET /eth/v1/validators/:address/delegators
333
- */
334
- async getDelegators(address) {
335
- return this.client.get(`/eth/v1/validators/${address}/delegators`);
336
- }
337
- /**
338
- * Get validator stake breakdown
339
- * GET /eth/v1/validators/:address/stake
340
- */
341
- async getStakeBreakdown(address) {
342
- return this.client.get(`/eth/v1/validators/${address}/stake`);
267
+ this.token = result.token;
268
+ return result;
343
269
  }
344
- /**
345
- * Get validator epoch details
346
- * GET /eth/v1/validators/:address/epochs/:epoch
347
- */
348
- async getEpoch(address, epoch) {
349
- return this.client.get(`/eth/v1/validators/${address}/epochs/${epoch}`);
270
+ // ============================================
271
+ // Validators
272
+ // ============================================
273
+ async getValidators() {
274
+ return this.request("/eth/v1/validators");
350
275
  }
351
- /**
352
- * Get validator history
353
- * GET /eth/v1/validators/:address/history
354
- * @param address - Validator address
355
- * @param fromEpoch - Optional starting epoch
356
- * @param toEpoch - Optional ending epoch
357
- */
358
- async getHistory(address, fromEpoch, toEpoch) {
359
- const params = {};
360
- if (fromEpoch !== void 0) params.from_epoch = fromEpoch;
361
- if (toEpoch !== void 0) params.to_epoch = toEpoch;
362
- return this.client.get(
363
- `/eth/v1/validators/${address}/history`,
364
- params
365
- );
276
+ async getActiveValidators() {
277
+ return this.request("/eth/v1/validators/active");
366
278
  }
367
- /**
368
- * Get validator withdrawals
369
- * GET /eth/v1/validators/:address/withdrawals
370
- * @param address - Validator address
371
- * @param limit - Optional limit
372
- * @param offset - Optional offset
373
- */
374
- async getWithdrawals(address, limit, offset) {
375
- const params = {};
376
- if (limit !== void 0) params.limit = limit;
377
- if (offset !== void 0) params.offset = offset;
378
- return this.client.get(
379
- `/eth/v1/validators/${address}/withdrawals`,
380
- params
381
- );
279
+ async getCandidates() {
280
+ return this.request("/eth/v1/validators/candidates");
382
281
  }
383
- /**
384
- * Get validator metrics
385
- * GET /eth/v1/validators/:address/metrics
386
- */
387
- async getMetrics(address) {
388
- return this.client.get(`/eth/v1/validators/${address}/metrics`);
282
+ async getValidator(address) {
283
+ return this.request(`/eth/v1/validators/${address}`);
389
284
  }
390
- /**
391
- * Get validator slashing events
392
- * GET /eth/v1/validators/:address/slashing
393
- */
394
- async getSlashing(address) {
395
- return this.client.get(`/eth/v1/validators/${address}/slashing`);
285
+ async getValidatorDelegators(address) {
286
+ return this.request(`/eth/v1/validators/${address}/delegators`);
396
287
  }
397
- /**
398
- * Get validator APR
399
- * GET /eth/v1/validators/:address/apr
400
- */
401
- async getAPR(address) {
402
- return this.client.get(`/eth/v1/validators/${address}/apr`);
288
+ // ============================================
289
+ // Delegators
290
+ // ============================================
291
+ async getDelegator(address) {
292
+ return this.request(`/eth/v1/delegators/${address}`);
403
293
  }
404
- };
405
-
406
- // src/delegators.ts
407
- var DelegatorsAPI = class {
408
- constructor(client) {
409
- this.client = client;
294
+ async getDelegatorStakes(address) {
295
+ return this.request(`/eth/v1/delegators/${address}/stakes`);
410
296
  }
411
- /**
412
- * Get all delegators
413
- * GET /eth/v1/delegators
414
- */
415
- async getAll() {
416
- return this.client.get("/eth/v1/delegators");
297
+ async getDelegatorRewards(address) {
298
+ return this.request(`/eth/v1/delegators/${address}/rewards`);
417
299
  }
418
- /**
419
- * Get delegator details
420
- * GET /eth/v1/delegators/:address
421
- */
422
- async get(address) {
423
- return this.client.get(`/eth/v1/delegators/${address}`);
300
+ // ============================================
301
+ // Referral
302
+ // ============================================
303
+ async getReferralKey(key) {
304
+ return this.request(`/eth/v1/referral/key/${key}`);
424
305
  }
425
- /**
426
- * Get delegator stakes breakdown per validator
427
- * GET /eth/v1/delegators/:address/stakes
428
- */
429
- async getStakes(address) {
430
- return this.client.get(`/eth/v1/delegators/${address}/stakes`);
431
- }
432
- /**
433
- * Get delegator rewards history
434
- * GET /eth/v1/delegators/:address/rewards
435
- * @param address - Delegator address
436
- * @param limit - Optional limit
437
- * @param offset - Optional offset
438
- */
439
- async getRewards(address, limit, offset) {
440
- const params = {};
441
- if (limit !== void 0) params.limit = limit;
442
- if (offset !== void 0) params.offset = offset;
443
- return this.client.get(
444
- `/eth/v1/delegators/${address}/rewards`,
445
- params
446
- );
447
- }
448
- /**
449
- * Get delegator withdrawals history
450
- * GET /eth/v1/delegators/:address/withdrawals
451
- * @param address - Delegator address
452
- * @param limit - Optional limit
453
- * @param offset - Optional offset
454
- */
455
- async getWithdrawals(address, limit, offset) {
456
- const params = {};
457
- if (limit !== void 0) params.limit = limit;
458
- if (offset !== void 0) params.offset = offset;
459
- return this.client.get(
460
- `/eth/v1/delegators/${address}/withdrawals`,
461
- params
462
- );
463
- }
464
- /**
465
- * Get delegator unbonding status
466
- * GET /eth/v1/delegators/:address/unbonding
467
- */
468
- async getUnbonding(address) {
469
- return this.client.get(`/eth/v1/delegators/${address}/unbonding`);
306
+ async getValidatorKeys(address) {
307
+ return this.request(`/eth/v1/referral/validator/${address}`);
470
308
  }
471
- /**
472
- * Get delegator active validators
473
- * GET /eth/v1/delegators/:address/validators
474
- */
475
- async getValidators(address) {
476
- return this.client.get(`/eth/v1/delegators/${address}/validators`);
477
- }
478
- };
479
-
480
- // src/referrals.ts
481
- var ReferralsAPI = class {
482
- constructor(client) {
483
- this.client = client;
484
- }
485
- /**
486
- * Validate a referral code
487
- * GET /eth/v1/referrals/validate?code=CODE
488
- */
489
- async validate(referralCode) {
490
- return this.client.get("/eth/v1/referrals/validate", {
491
- code: referralCode
309
+ async checkWhitelist(data) {
310
+ return this.request("/eth/v1/referral/whitelist", {
311
+ method: "POST",
312
+ body: JSON.stringify(data)
492
313
  });
493
314
  }
494
- /**
495
- * Create a new referral code
496
- * POST /eth/v1/referrals
497
- * @param request - Referral creation request
498
- */
499
- async create(request) {
500
- return this.client.post("/eth/v1/referrals", request);
501
- }
502
- /**
503
- * Apply a referral code
504
- * POST /eth/v1/referrals/apply
505
- * @param request - Referral application request
506
- */
507
- async apply(request) {
508
- return this.client.post("/eth/v1/referrals/apply", request);
509
- }
510
- /**
511
- * Delete a referral code
512
- * DELETE /eth/v1/referrals/:referral_code
513
- * @param referralCode - The referral code to delete
514
- */
515
- async delete(referralCode) {
516
- return this.client.delete(`/eth/v1/referrals/${referralCode}`);
517
- }
518
- /**
519
- * Unlink a delegator from referral
520
- * DELETE /eth/v1/referrals/unlink/:delegator_address
521
- * @param delegatorAddress - The delegator address to unlink
522
- */
523
- async unlink(delegatorAddress) {
524
- return this.client.delete(`/eth/v1/referrals/unlink/${delegatorAddress}`);
315
+ // ============================================
316
+ // Geo
317
+ // ============================================
318
+ async getGeoNodes() {
319
+ return this.request("/eth/v1/geo/nodes");
525
320
  }
526
- /**
527
- * Get delegator referral information
528
- * GET /eth/v1/referrals/delegators/:delegator_address
529
- */
530
- async getDelegatorReferral(delegatorAddress) {
531
- return this.client.get(
532
- `/eth/v1/referrals/delegators/${delegatorAddress}`
533
- );
321
+ async getGeoValidators() {
322
+ return this.request("/eth/v1/geo/validators");
534
323
  }
535
- /**
536
- * Get validator referral information
537
- * GET /eth/v1/referrals/validators/:validator_address
538
- */
539
- async getValidatorReferral(validatorAddress) {
540
- return this.client.get(
541
- `/eth/v1/referrals/validators/${validatorAddress}`
542
- );
324
+ async getGeoStats() {
325
+ return this.request("/eth/v1/geo/stats");
543
326
  }
544
- };
545
-
546
- // src/global.ts
547
- var GlobalAPI = class {
548
- constructor(client) {
549
- this.client = client;
327
+ async updateGeoLocation(data) {
328
+ return this.request("/eth/v1/geo/update", {
329
+ method: "POST",
330
+ body: JSON.stringify(data)
331
+ });
550
332
  }
551
- /**
552
- * Get global network statistics
553
- * GET /eth/v1/global
554
- */
555
- async getStats() {
556
- return this.client.get("/eth/v1/global");
333
+ // ============================================
334
+ // Stats
335
+ // ============================================
336
+ async getNetworkStats() {
337
+ return this.request("/eth/v1/stats/network");
557
338
  }
558
- /**
559
- * Get network APR (from /global endpoint)
560
- * GET /eth/v1/global/network_apr
561
- */
562
- async getNetworkAPRFromGlobal() {
563
- return this.client.get("/eth/v1/global/network_apr");
339
+ async getCurrentEpoch() {
340
+ return this.request("/eth/v1/stats/epoch/current");
564
341
  }
565
- /**
566
- * Get network APR (from /network endpoint)
567
- * GET /eth/v1/network/apr
568
- */
342
+ // ============================================
343
+ // APR
344
+ // ============================================
569
345
  async getNetworkAPR() {
570
- return this.client.get("/eth/v1/network/apr");
571
- }
572
- /**
573
- * Get network APR breakdown
574
- * GET /eth/v1/network/apr/breakdown
575
- */
576
- async getNetworkAPRBreakdown() {
577
- return this.client.get("/eth/v1/network/apr/breakdown");
578
- }
579
- /**
580
- * Get delegator leaderboard
581
- * GET /eth/v1/leaderboard/delegators
582
- * @param limit - Optional limit (default backend value)
583
- * @param offset - Optional offset for pagination
584
- */
585
- async getLeaderboardDelegators(limit, offset) {
586
- const params = {};
587
- if (limit !== void 0) params.limit = limit;
588
- if (offset !== void 0) params.offset = offset;
589
- return this.client.get(
590
- "/eth/v1/leaderboard/delegators",
591
- params
592
- );
593
- }
594
- /**
595
- * Get validator leaderboard
596
- * GET /eth/v1/leaderboard/validators
597
- * @param limit - Optional limit (default backend value)
598
- * @param offset - Optional offset for pagination
599
- */
600
- async getLeaderboardValidators(limit, offset) {
601
- const params = {};
602
- if (limit !== void 0) params.limit = limit;
603
- if (offset !== void 0) params.offset = offset;
604
- return this.client.get(
605
- "/eth/v1/leaderboard/validators",
606
- params
607
- );
608
- }
609
- };
610
-
611
- // src/slashing.ts
612
- var SlashingAPI = class {
613
- constructor(client) {
614
- this.client = client;
615
- }
616
- /**
617
- * Get slashing events
618
- * GET /eth/v1/slashing/events
619
- * @param validatorAddress - Optional validator address filter
620
- * @param limit - Optional limit
621
- * @param offset - Optional offset
622
- */
623
- async getEvents(validatorAddress, limit, offset) {
624
- const params = {};
625
- if (validatorAddress) params.validator_address = validatorAddress;
626
- if (limit !== void 0) params.limit = limit;
627
- if (offset !== void 0) params.offset = offset;
628
- return this.client.get("/eth/v1/slashing/events", params);
629
- }
630
- };
631
-
632
- // src/modules/analytics.ts
633
- var AnalyticsAPI = class {
634
- constructor(client) {
635
- this.basePath = "/eth/v1/analytics";
636
- this.client = client;
637
- }
638
- /**
639
- * Get protocol-level statistics
640
- *
641
- * Returns aggregated statistics about the entire protocol including
642
- * total staking, validator counts, and rewards distribution.
643
- *
644
- * @returns Protocol statistics from subgraph
645
- *
646
- * @example
647
- * ```typescript
648
- * const stats = await sdk.analytics.getProtocolStats();
649
- * console.log(`Total Staking: ${stats.TotalStaking}`);
650
- * console.log(`Active Validators: ${stats.ActiveValidators}`);
651
- * ```
652
- */
653
- async getProtocolStats() {
654
- return this.client.get(`${this.basePath}/protocol`);
655
- }
656
- /**
657
- * Get sync status of analytics workers
658
- *
659
- * Returns the current synchronization status of background workers
660
- * that index blockchain data into the analytics database.
661
- *
662
- * @returns Sync status for each worker
663
- *
664
- * @example
665
- * ```typescript
666
- * const status = await sdk.analytics.getSyncStatus();
667
- * if (status.protocol_sync.status === 'success') {
668
- * console.log(`Last synced at block: ${status.protocol_sync.last_block}`);
669
- * }
670
- * ```
671
- */
672
- async getSyncStatus() {
673
- return this.client.get(`${this.basePath}/sync-status`);
674
- }
675
- /**
676
- * Get all validators with analytics data
677
- *
678
- * Returns a paginated list of validators with their analytics metrics
679
- * including uptime, signed blocks, and staker counts.
680
- *
681
- * @param options - Pagination and filter options
682
- * @returns Paginated list of validators
683
- *
684
- * @example
685
- * ```typescript
686
- * // Get first 20 validators
687
- * const result = await sdk.analytics.getAllValidators({ limit: 20 });
688
- *
689
- * // Get next page
690
- * const nextPage = await sdk.analytics.getAllValidators({
691
- * limit: 20,
692
- * offset: 20
693
- * });
694
- *
695
- * // Filter by status
696
- * const active = await sdk.analytics.getAllValidators({
697
- * status: 'active',
698
- * limit: 50
699
- * });
700
- * ```
701
- */
702
- async getAllValidators(options) {
703
- const params = {};
704
- if (options?.limit) params.limit = options.limit;
705
- if (options?.offset) params.offset = options.offset;
706
- if (options?.status) params.status = options.status;
707
- const response = await this.client.get(
708
- `${this.basePath}/validators`,
709
- params
710
- );
711
- if (Array.isArray(response)) {
712
- return {
713
- count: response.length,
714
- data: response
715
- };
346
+ return this.request("/eth/v1/apr/network");
347
+ }
348
+ async getValidatorAPR(address) {
349
+ return this.request(`/eth/v1/apr/validator/${address}`);
350
+ }
351
+ // ============================================
352
+ // Storage
353
+ // ============================================
354
+ async uploadAvatar(file) {
355
+ const formData = new FormData();
356
+ formData.append("file", file);
357
+ const headers = {};
358
+ if (this.token) {
359
+ headers["Authorization"] = `Bearer ${this.token}`;
716
360
  }
717
- return response;
718
- }
719
- /**
720
- * Get top validators by uptime
721
- *
722
- * Returns validators sorted by uptime percentage in descending order.
723
- * Useful for displaying leaderboards or finding most reliable validators.
724
- *
725
- * @param limit - Maximum number of validators to return (default: 10)
726
- * @returns Top validators by uptime
727
- *
728
- * @example
729
- * ```typescript
730
- * // Get top 5 validators
731
- * const top5 = await sdk.analytics.getTopValidators(5);
732
- *
733
- * top5.data.forEach((validator, index) => {
734
- * console.log(`#${index + 1}: ${validator.moniker} - ${validator.uptime}% uptime`);
735
- * });
736
- * ```
737
- */
738
- async getTopValidators(limit = 10) {
739
- const response = await this.client.get(
740
- `${this.basePath}/validators/top`,
741
- { limit }
742
- );
743
- if (Array.isArray(response)) {
744
- return {
745
- count: response.length,
746
- data: response
747
- };
361
+ const response = await fetch(`${this.baseUrl}/eth/v1/storage/avatar`, {
362
+ method: "POST",
363
+ headers,
364
+ body: formData
365
+ });
366
+ if (!response.ok) {
367
+ const error = await response.json().catch(() => ({ error: "Upload failed" }));
368
+ throw new Error(error.error);
748
369
  }
749
- return response;
370
+ return response.json();
750
371
  }
751
- /**
752
- * Get analytics data for a specific validator
753
- *
754
- * Returns detailed analytics metrics for a single validator including
755
- * performance statistics and current status.
756
- *
757
- * @param address - Validator address (with or without 0x prefix)
758
- * @returns Validator analytics data
759
- *
760
- * @throws {ResonanceAPIError} 404 if validator not found
761
- *
762
- * @example
763
- * ```typescript
764
- * const validator = await sdk.analytics.getValidatorAnalytics('0x1234...');
765
- * console.log(`${validator.moniker}: ${validator.uptime}% uptime`);
766
- * console.log(`Signed: ${validator.signed_blocks}, Missed: ${validator.missed_blocks}`);
767
- * ```
768
- */
769
- async getValidatorAnalytics(address) {
770
- const cleanAddress = address.toLowerCase().replace(/^0x/, "");
771
- return this.client.get(
772
- `${this.basePath}/validators/0x${cleanAddress}`
773
- );
372
+ async getAvatar(address) {
373
+ return this.request(`/eth/v1/storage/avatar/${address}`);
774
374
  }
775
- /**
776
- * Get validator rewards with pagination
777
- *
778
- * Returns detailed reward and stake information for a validator.
779
- * This is a heavy endpoint that MUST use pagination.
780
- *
781
- * @param address - Validator address (with or without 0x prefix)
782
- * @param options - Pagination options (required)
783
- * @returns Validator rewards, stakes, and summary
784
- *
785
- * @throws {ResonanceAPIError} 404 if validator not found
786
- *
787
- * @remarks
788
- * This endpoint can return large amounts of data. Always use pagination
789
- * with reasonable limit values (recommended: 50-100).
790
- *
791
- * @example
792
- * ```typescript
793
- * // Get first page of rewards
794
- * const page1 = await sdk.analytics.getValidatorRewards('0x1234...', {
795
- * limit: 50,
796
- * offset: 0
797
- * });
798
- *
799
- * console.log(`Total stakers: ${page1.summary.total_stakers}`);
800
- * console.log(`Has more data: ${page1.metadata.has_more}`);
801
- *
802
- * // Get next page if available
803
- * if (page1.metadata.has_more) {
804
- * const page2 = await sdk.analytics.getValidatorRewards('0x1234...', {
805
- * limit: 50,
806
- * offset: 50
807
- * });
808
- * }
809
- * ```
810
- */
811
- async getValidatorRewards(address, options) {
812
- const cleanAddress = address.toLowerCase().replace(/^0x/, "");
813
- const limit = options.limit || 50;
814
- const offset = options.offset || 0;
815
- return this.client.get(
816
- `${this.basePath}/validators/0x${cleanAddress}/rewards`,
817
- { limit, offset }
818
- );
375
+ // ============================================
376
+ // Analytics
377
+ // ============================================
378
+ async getDailyBlockStats(days = 7) {
379
+ return this.request(`/eth/v1/analytics/blocks?days=${days}`);
819
380
  }
820
- /**
821
- * Get all validator rewards with automatic pagination
822
- *
823
- * Automatically fetches all pages of validator rewards data.
824
- * Use with caution as this can make multiple API calls.
825
- *
826
- * @param address - Validator address (with or without 0x prefix)
827
- * @param options - Batch size and safety limits
828
- * @returns Complete validator rewards data
829
- *
830
- * @throws {ResonanceAPIError} 404 if validator not found
831
- * @throws {Error} If max pages limit is reached
832
- *
833
- * @remarks
834
- * This method will make multiple API calls. Use maxPages to prevent
835
- * infinite loops or excessive API usage.
836
- *
837
- * @example
838
- * ```typescript
839
- * // Fetch all rewards with default settings
840
- * const allRewards = await sdk.analytics.getAllValidatorRewards('0x1234...');
841
- *
842
- * // Custom batch size and limit
843
- * const rewards = await sdk.analytics.getAllValidatorRewards('0x1234...', {
844
- * batchSize: 100,
845
- * maxPages: 5
846
- * });
847
- *
848
- * console.log(`Total rewards: ${rewards.summary.total_rewards}`);
849
- * console.log(`Total API calls made: ${Math.ceil(rewards.stakes.length / 100)}`);
850
- * ```
851
- */
852
- async getAllValidatorRewards(address, options) {
853
- const batchSize = options?.batchSize || 50;
854
- const maxPages = options?.maxPages || 10;
855
- let offset = 0;
856
- let page = 0;
857
- let firstResponse = null;
858
- const allRewards = [];
859
- const allStakes = [];
860
- while (page < maxPages) {
861
- const response = await this.getValidatorRewards(address, {
862
- limit: batchSize,
863
- offset
864
- });
865
- if (!firstResponse) {
866
- firstResponse = response;
867
- }
868
- allRewards.push(...response.rewards);
869
- allStakes.push(...response.stakes);
870
- if (!response.metadata.has_more) {
871
- break;
872
- }
873
- offset += batchSize;
874
- page++;
875
- }
876
- if (page >= maxPages && firstResponse?.metadata.has_more) {
877
- throw new Error(
878
- `Reached maximum page limit (${maxPages}). Use manual pagination for more control.`
879
- );
880
- }
881
- return {
882
- ...firstResponse,
883
- rewards: allRewards,
884
- stakes: allStakes,
885
- metadata: {
886
- ...firstResponse.metadata,
887
- has_more: false,
888
- limit: allRewards.length,
889
- offset: 0
890
- }
891
- };
381
+ async getValidatorRewardHistory(address, limit = 100) {
382
+ return this.request(`/eth/v1/analytics/rewards/${address}?limit=${limit}`);
892
383
  }
893
384
  };
894
-
895
- // src/utils/analytics-helpers.ts
896
- function isValidatorNotFound(error) {
897
- return error instanceof ResonanceAPIError && error.statusCode === 404;
898
- }
899
- function isServiceUnavailable(error) {
900
- return error instanceof ResonanceAPIError && error.statusCode === 503;
901
- }
902
- function isTimeout(error) {
903
- return error instanceof Error && error.message.includes("timeout");
904
- }
905
- function isStaleData(response) {
906
- return response.metadata?.stale === true;
907
- }
908
- function isCachedData(response) {
909
- return response.metadata?.cached === true;
910
- }
911
- function isSyncHealthy(syncStatus, workerName = "protocol_sync") {
912
- const worker = syncStatus[workerName];
913
- return worker?.status === "success";
914
- }
915
- function isSyncDegraded(syncStatus, workerName = "protocol_sync") {
916
- const worker = syncStatus[workerName];
917
- return worker?.status === "degraded";
918
- }
919
- function getSyncLag(syncStatus, currentBlock, workerName = "protocol_sync") {
920
- const worker = syncStatus[workerName];
921
- if (!worker?.last_block) return null;
922
- return currentBlock - worker.last_block;
385
+ function createResonanceClient(config) {
386
+ return new ResonanceClient(config);
923
387
  }
924
- function getDataFreshness(response, currentBlock) {
925
- const subgraphBlock = response.metadata?.subgraph_block;
926
- if (!subgraphBlock) return 0;
927
- const blocksBehind = currentBlock - subgraphBlock;
928
- if (blocksBehind <= 0) return 100;
929
- if (blocksBehind <= 10) return 100 - blocksBehind;
930
- if (blocksBehind <= 100) return 90 - Math.floor((blocksBehind - 10) * 0.44);
931
- return Math.max(0, 50 - Math.floor((blocksBehind - 100) * 0.5));
932
- }
933
- function validatePagination(limit, offset) {
934
- if (limit !== void 0 && (limit <= 0 || limit > 1e3)) {
935
- throw new Error("Limit must be between 1 and 1000");
936
- }
937
- if (offset !== void 0 && offset < 0) {
938
- throw new Error("Offset must be non-negative");
939
- }
940
- }
941
- function getOptimalBatchSize(totalItems, maxBatchSize = 100) {
942
- if (totalItems <= maxBatchSize) return totalItems;
943
- const targetBatches = 7;
944
- const calculatedSize = Math.ceil(totalItems / targetBatches);
945
- return Math.min(calculatedSize, maxBatchSize);
946
- }
947
- function formatNumber(value) {
948
- const num = typeof value === "string" ? value : value.toString();
949
- return num.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
950
- }
951
- function weiToEther(wei, decimals = 4) {
952
- const weiNum = BigInt(wei);
953
- const etherNum = Number(weiNum) / 1e18;
954
- return etherNum.toFixed(decimals);
955
- }
956
-
957
- // src/utils/retry.ts
958
- function defaultShouldRetry(error, attempt) {
959
- if (attempt >= 3) return false;
960
- if (error instanceof Error && error.message.includes("timeout")) {
961
- return true;
962
- }
963
- if (error.statusCode === 503) {
964
- return true;
965
- }
966
- if (error instanceof Error && error.message.includes("fetch failed")) {
967
- return true;
968
- }
969
- return false;
970
- }
971
- async function withRetry(fn, options = {}) {
972
- const {
973
- maxRetries = 3,
974
- initialDelay = 1e3,
975
- maxDelay = 1e4,
976
- backoffMultiplier = 2,
977
- shouldRetry = defaultShouldRetry,
978
- onRetry
979
- } = options;
980
- let lastError;
981
- let delay = initialDelay;
982
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
983
- try {
984
- return await fn();
985
- } catch (error) {
986
- lastError = error;
987
- if (attempt < maxRetries && shouldRetry(error, attempt)) {
988
- if (onRetry) {
989
- onRetry(error, attempt + 1, delay);
990
- }
991
- await sleep(delay);
992
- delay = Math.min(delay * backoffMultiplier, maxDelay);
993
- } else {
994
- throw error;
995
- }
996
- }
997
- }
998
- throw lastError;
999
- }
1000
- function sleep(ms) {
1001
- return new Promise((resolve) => setTimeout(resolve, ms));
1002
- }
1003
- function createRetryWrapper(fn, options = {}) {
1004
- return ((...args) => {
1005
- return withRetry(() => fn(...args), options);
1006
- });
1007
- }
1008
-
1009
- // src/index.ts
1010
- var ResonanceSDK = class {
1011
- constructor(config) {
1012
- this.client = new HTTPClient(config);
1013
- this.auth = new AuthService(config.apiUrl);
1014
- this.validators = new ValidatorsAPI(this.client);
1015
- this.delegators = new DelegatorsAPI(this.client);
1016
- this.referrals = new ReferralsAPI(this.client);
1017
- this.global = new GlobalAPI(this.client);
1018
- this.slashing = new SlashingAPI(this.client);
1019
- this.analytics = new AnalyticsAPI(this.client);
1020
- }
1021
- /**
1022
- * Set authentication token for API requests
1023
- * @param token - JWT token
1024
- */
1025
- setAuthToken(token) {
1026
- this.client.setAuthToken(token);
1027
- }
1028
- /**
1029
- * Set custom header for API requests
1030
- * @param key - Header key
1031
- * @param value - Header value
1032
- */
1033
- setHeader(key, value) {
1034
- this.client.setHeader(key, value);
1035
- }
1036
- /**
1037
- * Get the current auth token from storage
1038
- */
1039
- getAuthToken() {
1040
- return this.auth.getToken();
1041
- }
1042
- /**
1043
- * Check if user is authenticated
1044
- */
1045
- isAuthenticated() {
1046
- return this.auth.isAuthenticated();
1047
- }
1048
- /**
1049
- * Logout user and clear auth token
1050
- */
1051
- logout() {
1052
- this.auth.logout();
1053
- this.client.setAuthToken(null);
1054
- }
1055
- };
1056
- var index_default = ResonanceSDK;
1057
388
  export {
1058
- AnalyticsAPI,
1059
- AuthService,
1060
- DelegatorsAPI,
1061
- GlobalAPI,
1062
- HTTPClient,
1063
- ReferralsAPI,
1064
- ResonanceSDK,
1065
- SlashingAPI,
1066
- ValidatorsAPI,
1067
- createRetryWrapper,
1068
- index_default as default,
1069
- formatNumber,
1070
- getDataFreshness,
1071
- getOptimalBatchSize,
1072
- getSyncLag,
1073
- isCachedData,
1074
- isServiceUnavailable,
1075
- isStaleData,
1076
- isSyncDegraded,
1077
- isSyncHealthy,
1078
- isTimeout,
1079
- isValidatorNotFound,
1080
- validatePagination,
1081
- weiToEther,
1082
- withRetry
389
+ ErrorCode,
390
+ ResonanceClient,
391
+ ResonanceError,
392
+ createResonanceClient,
393
+ hasErrorCode,
394
+ isAuthError,
395
+ isNetworkError,
396
+ isNotFoundError,
397
+ isResonanceError
1083
398
  };