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