@fenelabs/fene-sdk 0.3.0 → 0.3.3

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