@zyfai/sdk 0.1.0

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 ADDED
@@ -0,0 +1,1890 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ZyfaiSDK: () => ZyfaiSDK,
34
+ getChainConfig: () => getChainConfig,
35
+ getSupportedChainIds: () => getSupportedChainIds,
36
+ isSupportedChain: () => isSupportedChain
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/utils/http-client.ts
41
+ var import_axios = __toESM(require("axios"));
42
+
43
+ // src/config/endpoints.ts
44
+ var API_ENDPOINTS = {
45
+ staging: "https://staging-api.zyf.ai",
46
+ production: "https://api.zyf.ai"
47
+ };
48
+ var DATA_API_ENDPOINTS = {
49
+ staging: "https://staging-defiapi.zyf.ai",
50
+ production: "https://defiapi.zyf.ai"
51
+ };
52
+ var API_VERSION = "/api/v1";
53
+ var DATA_API_VERSION = "/api/v2";
54
+ var ENDPOINTS = {
55
+ // Auth
56
+ AUTH_LOGIN: "/auth/login",
57
+ AUTH_CHALLENGE: "/auth/challenge",
58
+ // User
59
+ USER_ME: "/users/me",
60
+ USER_WITHDRAW: "/users/withdraw",
61
+ PARTIAL_WITHDRAW: "/users/partial-withdraw",
62
+ // Session Keys
63
+ SESSION_KEYS_CONFIG: "/session-keys/config",
64
+ SESSION_KEYS_ADD: "/session-keys/add",
65
+ // Protocols
66
+ PROTOCOLS: (chainId) => `/protocols?chainId=${chainId}`,
67
+ // Data (v1)
68
+ DATA_POSITION: (walletAddress) => `/data/position?walletAddress=${walletAddress}`,
69
+ DATA_HISTORY: (walletAddress, chainId) => `/data/history?walletAddress=${walletAddress}&chainId=${chainId}`,
70
+ DATA_TVL: "/data/tvl",
71
+ DATA_VOLUME: "/data/volume",
72
+ DATA_FIRST_TOPUP: (walletAddress, chainId) => `/data/first-topup?walletAddress=${walletAddress}&chainId=${chainId}`,
73
+ DATA_ACTIVE_WALLETS: (chainId) => `/data/active-wallets?chainId=${chainId}`,
74
+ DATA_BY_EOA: (address) => `/data/by-eoa?address=${address}`,
75
+ DATA_REBALANCE_FREQUENCY: (walletAddress) => `/data/rebalance-frequency?walletAddress=${walletAddress}`
76
+ };
77
+ var DATA_ENDPOINTS = {
78
+ // Earnings
79
+ ONCHAIN_EARNINGS: (walletAddress) => `/usercheck/onchain-earnings?walletAddress=${walletAddress}`,
80
+ CALCULATE_ONCHAIN_EARNINGS: "/usercheck/calculate-onchain-earnings",
81
+ DAILY_EARNINGS: (walletAddress, startDate, endDate) => {
82
+ let url = `/usercheck/daily-earnings?walletAddress=${walletAddress}`;
83
+ if (startDate) url += `&startDate=${startDate}`;
84
+ if (endDate) url += `&endDate=${endDate}`;
85
+ return url;
86
+ },
87
+ // Portfolio
88
+ DEBANK_PORTFOLIO_MULTICHAIN: (address) => `/debank/portfolio/multichain/${address}`,
89
+ // Opportunities
90
+ OPPORTUNITIES_SAFE: (chainId) => chainId ? `/opportunities/safes?chainId=${chainId}` : "/opportunities/safes",
91
+ OPPORTUNITIES_DEGEN: (chainId) => chainId ? `/opportunities/degen-strategies?chainId=${chainId}` : "/opportunities/degen-strategies",
92
+ // APY History
93
+ DAILY_APY_HISTORY_WEIGHTED: (walletAddress, days) => `/daily-apy-history/weighted/${walletAddress}${days ? `?days=${days}` : ""}`,
94
+ // Rebalance
95
+ REBALANCE_INFO: (isCrossChain) => isCrossChain !== void 0 ? `/rebalance/rebalance-info?isCrossChain=${isCrossChain}` : "/rebalance/rebalance-info"
96
+ };
97
+
98
+ // src/utils/http-client.ts
99
+ var HttpClient = class {
100
+ /**
101
+ * Create HTTP client for both Execution API and Data API
102
+ *
103
+ * @param apiKey - API key for Execution API (Utkir's backend)
104
+ * @param environment - 'staging' or 'production'
105
+ * @param dataApiKey - API key for Data API (Sunny's backend) - defaults to apiKey
106
+ */
107
+ constructor(apiKey, environment = "production", dataApiKey) {
108
+ this.authToken = null;
109
+ this.apiKey = apiKey;
110
+ this.dataApiKey = dataApiKey || apiKey;
111
+ const endpoint = API_ENDPOINTS[environment];
112
+ const parsedUrl = new URL(endpoint);
113
+ this.origin = parsedUrl.origin;
114
+ this.host = parsedUrl.host;
115
+ this.client = import_axios.default.create({
116
+ baseURL: `${endpoint}${API_VERSION}`,
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ "X-API-Key": this.apiKey,
120
+ Origin: this.origin
121
+ },
122
+ timeout: 3e4
123
+ });
124
+ const dataEndpoint = DATA_API_ENDPOINTS[environment];
125
+ this.dataClient = import_axios.default.create({
126
+ baseURL: `${dataEndpoint}${DATA_API_VERSION}`,
127
+ headers: {
128
+ "Content-Type": "application/json",
129
+ "X-API-Key": this.dataApiKey
130
+ },
131
+ timeout: 3e4
132
+ });
133
+ this.setupInterceptors();
134
+ this.setupDataInterceptors();
135
+ }
136
+ setAuthToken(token) {
137
+ this.authToken = token;
138
+ }
139
+ clearAuthToken() {
140
+ this.authToken = null;
141
+ }
142
+ getOrigin() {
143
+ return this.origin;
144
+ }
145
+ getHost() {
146
+ return this.host;
147
+ }
148
+ setupInterceptors() {
149
+ this.client.interceptors.request.use(
150
+ (config) => {
151
+ config.headers["X-API-Key"] = this.apiKey;
152
+ config.headers["Origin"] = this.origin;
153
+ if (this.authToken) {
154
+ config.headers["Authorization"] = `Bearer ${this.authToken}`;
155
+ }
156
+ return config;
157
+ },
158
+ (error) => {
159
+ return Promise.reject(error);
160
+ }
161
+ );
162
+ this.client.interceptors.response.use(
163
+ (response) => response,
164
+ (error) => {
165
+ if (error.response) {
166
+ const status = error.response.status;
167
+ const data = error.response.data;
168
+ switch (status) {
169
+ case 401:
170
+ throw new Error("Unauthorized: Invalid API key");
171
+ case 403:
172
+ throw new Error("Forbidden: Access denied");
173
+ case 404:
174
+ throw new Error(
175
+ `Not found: ${data.message || "Resource not found"}`
176
+ );
177
+ case 429:
178
+ throw new Error("Rate limit exceeded. Please try again later.");
179
+ case 500:
180
+ throw new Error("Internal server error. Please try again later.");
181
+ default:
182
+ throw new Error(data.message || "An error occurred");
183
+ }
184
+ } else if (error.request) {
185
+ throw new Error("Network error: Unable to reach the server");
186
+ } else {
187
+ throw new Error(`Request error: ${error.message}`);
188
+ }
189
+ }
190
+ );
191
+ }
192
+ async get(url, config) {
193
+ const response = await this.client.get(url, config);
194
+ return response.data;
195
+ }
196
+ async post(url, data, config) {
197
+ const response = await this.client.post(url, data, config);
198
+ return response.data;
199
+ }
200
+ async patch(url, data, config) {
201
+ const response = await this.client.patch(url, data, config);
202
+ return response.data;
203
+ }
204
+ async delete(url, config) {
205
+ const response = await this.client.delete(url, config);
206
+ return response.data;
207
+ }
208
+ // Data API methods (v2)
209
+ async dataGet(url, config) {
210
+ const response = await this.dataClient.get(url, config);
211
+ return response.data;
212
+ }
213
+ async dataPost(url, data, config) {
214
+ const response = await this.dataClient.post(url, data, config);
215
+ return response.data;
216
+ }
217
+ setupDataInterceptors() {
218
+ this.dataClient.interceptors.request.use(
219
+ (config) => {
220
+ config.headers["X-API-Key"] = this.dataApiKey;
221
+ return config;
222
+ },
223
+ (error) => Promise.reject(error)
224
+ );
225
+ this.dataClient.interceptors.response.use(
226
+ (response) => response,
227
+ (error) => {
228
+ if (error.response) {
229
+ const status = error.response.status;
230
+ const data = error.response.data;
231
+ switch (status) {
232
+ case 401:
233
+ throw new Error("Unauthorized: Invalid API key");
234
+ case 403:
235
+ throw new Error("Forbidden: Access denied to data API");
236
+ case 404:
237
+ throw new Error(
238
+ `Not found: ${data.message || data.error || "Resource not found"}`
239
+ );
240
+ case 429:
241
+ throw new Error("Rate limit exceeded. Please try again later.");
242
+ case 500:
243
+ throw new Error(
244
+ data.error || "Internal server error. Please try again later."
245
+ );
246
+ default:
247
+ throw new Error(
248
+ data.message || data.error || "An error occurred"
249
+ );
250
+ }
251
+ } else if (error.request) {
252
+ throw new Error("Network error: Unable to reach the data server");
253
+ } else {
254
+ throw new Error(`Request error: ${error.message}`);
255
+ }
256
+ }
257
+ );
258
+ }
259
+ };
260
+
261
+ // src/config/abis.ts
262
+ var ERC20_ABI = [
263
+ {
264
+ name: "transfer",
265
+ type: "function",
266
+ stateMutability: "nonpayable",
267
+ inputs: [
268
+ { name: "to", type: "address" },
269
+ { name: "amount", type: "uint256" }
270
+ ],
271
+ outputs: [{ name: "", type: "bool" }]
272
+ },
273
+ {
274
+ name: "approve",
275
+ type: "function",
276
+ stateMutability: "nonpayable",
277
+ inputs: [
278
+ { name: "spender", type: "address" },
279
+ { name: "amount", type: "uint256" }
280
+ ],
281
+ outputs: [{ name: "", type: "bool" }]
282
+ },
283
+ {
284
+ name: "allowance",
285
+ type: "function",
286
+ stateMutability: "view",
287
+ inputs: [
288
+ { name: "owner", type: "address" },
289
+ { name: "spender", type: "address" }
290
+ ],
291
+ outputs: [{ name: "", type: "uint256" }]
292
+ },
293
+ {
294
+ name: "balanceOf",
295
+ type: "function",
296
+ stateMutability: "view",
297
+ inputs: [{ name: "account", type: "address" }],
298
+ outputs: [{ name: "", type: "uint256" }]
299
+ },
300
+ {
301
+ name: "decimals",
302
+ type: "function",
303
+ stateMutability: "view",
304
+ inputs: [],
305
+ outputs: [{ name: "", type: "uint8" }]
306
+ },
307
+ {
308
+ name: "symbol",
309
+ type: "function",
310
+ stateMutability: "view",
311
+ inputs: [],
312
+ outputs: [{ name: "", type: "string" }]
313
+ },
314
+ {
315
+ name: "name",
316
+ type: "function",
317
+ stateMutability: "view",
318
+ inputs: [],
319
+ outputs: [{ name: "", type: "string" }]
320
+ },
321
+ {
322
+ name: "totalSupply",
323
+ type: "function",
324
+ stateMutability: "view",
325
+ inputs: [],
326
+ outputs: [{ name: "", type: "uint256" }]
327
+ }
328
+ ];
329
+
330
+ // src/core/ZyfaiSDK.ts
331
+ var import_accounts2 = require("viem/accounts");
332
+ var import_viem4 = require("viem");
333
+
334
+ // src/config/chains.ts
335
+ var import_viem = require("viem");
336
+ var import_chains = require("viem/chains");
337
+ var import_viem2 = require("viem");
338
+ var plasma = (0, import_viem2.defineChain)({
339
+ id: 9745,
340
+ name: "Plasma",
341
+ nativeCurrency: {
342
+ decimals: 18,
343
+ name: "Plasma",
344
+ symbol: "PLSM"
345
+ },
346
+ rpcUrls: {
347
+ default: {
348
+ http: ["https://rpc.plasma.io"]
349
+ }
350
+ },
351
+ blockExplorers: {
352
+ default: {
353
+ name: "Plasma Explorer",
354
+ url: "https://explorer.plasma.io"
355
+ }
356
+ }
357
+ });
358
+ var DEFAULT_RPC_URLS = {
359
+ 8453: "https://mainnet.base.org",
360
+ 42161: "https://arb1.arbitrum.io/rpc",
361
+ 9745: "https://rpc.plasma.io"
362
+ };
363
+ var CHAINS = {
364
+ 8453: import_chains.base,
365
+ 42161: import_chains.arbitrum,
366
+ 9745: plasma
367
+ };
368
+ var getChainConfig = (chainId) => {
369
+ const chain = CHAINS[chainId];
370
+ if (!chain) {
371
+ throw new Error(`Unsupported chain ID: ${chainId}`);
372
+ }
373
+ const rpcUrl = DEFAULT_RPC_URLS[chainId];
374
+ const publicClient = (0, import_viem.createPublicClient)({
375
+ chain,
376
+ transport: (0, import_viem.http)(rpcUrl)
377
+ });
378
+ return {
379
+ chain,
380
+ rpcUrl,
381
+ publicClient
382
+ };
383
+ };
384
+ var isSupportedChain = (chainId) => {
385
+ return chainId in CHAINS;
386
+ };
387
+ var getSupportedChainIds = () => {
388
+ return Object.keys(CHAINS).map(Number);
389
+ };
390
+ var getBundlerUrl = (chainId, bundlerApiKey, bundlerProvider = "pimlico") => {
391
+ if (!bundlerApiKey) {
392
+ throw new Error("Bundler API key is required for Safe deployment");
393
+ }
394
+ switch (bundlerProvider) {
395
+ case "pimlico":
396
+ return `https://api.pimlico.io/v2/${chainId}/rpc?apikey=${bundlerApiKey}`;
397
+ case "custom":
398
+ return bundlerApiKey;
399
+ default:
400
+ throw new Error(`Unsupported bundler provider: ${bundlerProvider}`);
401
+ }
402
+ };
403
+
404
+ // src/utils/safe-account.ts
405
+ var import_module_sdk = require("@rhinestone/module-sdk");
406
+ var import_permissionless = require("permissionless");
407
+ var import_actions = require("permissionless/actions");
408
+ var import_erc7579 = require("permissionless/actions/erc7579");
409
+ var import_pimlico = require("permissionless/clients/pimlico");
410
+ var import_accounts = require("permissionless/accounts");
411
+ var import_viem3 = require("viem");
412
+ var import_account_abstraction = require("viem/account-abstraction");
413
+ var SAFE_7579_ADDRESS = "0x7579EE8307284F293B1927136486880611F20002";
414
+ var ERC7579_LAUNCHPAD_ADDRESS = "0x7579011aB74c46090561ea277Ba79D510c6C00ff";
415
+ var DEFAULT_ACCOUNT_SALT = "zyfai-staging";
416
+ var getSafeAccount = async (config) => {
417
+ const {
418
+ owner,
419
+ safeOwnerAddress,
420
+ publicClient,
421
+ accountSalt = DEFAULT_ACCOUNT_SALT
422
+ } = config;
423
+ if (!owner || !owner.account) {
424
+ throw new Error("Wallet not connected. Please connect your wallet first.");
425
+ }
426
+ const actualOwnerAddress = safeOwnerAddress || owner.account.address;
427
+ const ownableValidator = (0, import_module_sdk.getOwnableValidator)({
428
+ owners: [actualOwnerAddress],
429
+ threshold: 1
430
+ });
431
+ const saltHex = (0, import_viem3.fromHex)((0, import_viem3.toHex)(accountSalt), "bigint");
432
+ const safeAccount = await (0, import_accounts.toSafeSmartAccount)({
433
+ client: publicClient,
434
+ owners: [owner.account],
435
+ // Use connected wallet for signing
436
+ version: "1.4.1",
437
+ entryPoint: {
438
+ address: import_account_abstraction.entryPoint07Address,
439
+ version: "0.7"
440
+ },
441
+ safe4337ModuleAddress: SAFE_7579_ADDRESS,
442
+ erc7579LaunchpadAddress: ERC7579_LAUNCHPAD_ADDRESS,
443
+ attesters: [import_module_sdk.RHINESTONE_ATTESTER_ADDRESS],
444
+ attestersThreshold: 1,
445
+ validators: [
446
+ {
447
+ address: ownableValidator.address,
448
+ context: ownableValidator.initData
449
+ }
450
+ ],
451
+ saltNonce: saltHex
452
+ });
453
+ return safeAccount;
454
+ };
455
+ var getDeterministicSafeAddress = async (config) => {
456
+ try {
457
+ const safeAccount = await getSafeAccount(config);
458
+ return await safeAccount.getAddress();
459
+ } catch (error) {
460
+ throw new Error(
461
+ `Failed to get deterministic Safe address: ${error.message}`
462
+ );
463
+ }
464
+ };
465
+ var isSafeDeployed = async (address, publicClient) => {
466
+ try {
467
+ const code = await publicClient.getCode({ address });
468
+ return !!code && code !== "0x";
469
+ } catch (error) {
470
+ console.error("Error checking if Safe is deployed:", error);
471
+ return false;
472
+ }
473
+ };
474
+ var getAccountType = async (address, publicClient) => {
475
+ try {
476
+ const code = await publicClient.getCode({ address });
477
+ if (!code || code === "0x" || code.length === 2) {
478
+ return "EOA";
479
+ }
480
+ try {
481
+ const threshold = await publicClient.readContract({
482
+ address,
483
+ abi: [
484
+ {
485
+ inputs: [],
486
+ name: "getThreshold",
487
+ outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
488
+ stateMutability: "view",
489
+ type: "function"
490
+ }
491
+ ],
492
+ functionName: "getThreshold"
493
+ });
494
+ if (threshold !== void 0) {
495
+ return "Safe";
496
+ }
497
+ } catch {
498
+ }
499
+ return "Unknown";
500
+ } catch (error) {
501
+ console.error("Error checking account type:", error);
502
+ return "Unknown";
503
+ }
504
+ };
505
+ var getSmartAccountClient = async (config) => {
506
+ const { publicClient, chain, bundlerUrl } = config;
507
+ const safeAccount = await getSafeAccount(config);
508
+ const bundlerClient = (0, import_pimlico.createPimlicoClient)({
509
+ transport: (0, import_viem3.http)(bundlerUrl),
510
+ entryPoint: {
511
+ address: import_account_abstraction.entryPoint07Address,
512
+ version: "0.7"
513
+ }
514
+ });
515
+ const smartAccountClient = (0, import_permissionless.createSmartAccountClient)({
516
+ account: safeAccount,
517
+ chain,
518
+ bundlerTransport: (0, import_viem3.http)(bundlerUrl),
519
+ paymaster: bundlerClient,
520
+ userOperation: {
521
+ estimateFeesPerGas: async () => {
522
+ return (await bundlerClient.getUserOperationGasPrice()).fast;
523
+ }
524
+ }
525
+ }).extend((0, import_erc7579.erc7579Actions)());
526
+ return smartAccountClient;
527
+ };
528
+ var deploySafeAccount = async (config) => {
529
+ try {
530
+ const { owner, publicClient } = config;
531
+ if (!owner || !owner.account) {
532
+ throw new Error(
533
+ "Wallet not connected. Please connect your wallet first."
534
+ );
535
+ }
536
+ const safeAddress = await getDeterministicSafeAddress(config);
537
+ const isDeployed = await isSafeDeployed(safeAddress, publicClient);
538
+ if (isDeployed) {
539
+ return {
540
+ safeAddress,
541
+ isDeployed: true
542
+ };
543
+ }
544
+ const smartAccountClient = await getSmartAccountClient(config);
545
+ const userOpHash = await smartAccountClient.sendUserOperation({
546
+ calls: [
547
+ {
548
+ to: safeAddress,
549
+ value: BigInt(0),
550
+ data: "0x"
551
+ }
552
+ ]
553
+ });
554
+ const receipt = await smartAccountClient.waitForUserOperationReceipt({
555
+ hash: userOpHash
556
+ });
557
+ return {
558
+ safeAddress,
559
+ txHash: receipt.receipt.transactionHash,
560
+ isDeployed: true
561
+ };
562
+ } catch (error) {
563
+ throw new Error(
564
+ `Failed to deploy Safe account: ${error.message}`
565
+ );
566
+ }
567
+ };
568
+ var signSessionKey = async (config, sessions) => {
569
+ const { owner, publicClient, chain } = config;
570
+ if (!owner || !owner.account) {
571
+ throw new Error("Wallet not connected. Please connect your wallet first.");
572
+ }
573
+ const safeAccount = await getSafeAccount(config);
574
+ const account = (0, import_module_sdk.getAccount)({
575
+ address: safeAccount.address,
576
+ type: "safe"
577
+ });
578
+ const sessionNonces = await Promise.all(
579
+ sessions.map(
580
+ (session) => (0, import_module_sdk.getSessionNonce)({
581
+ client: publicClient,
582
+ account,
583
+ permissionId: (0, import_module_sdk.getPermissionId)({
584
+ session
585
+ })
586
+ })
587
+ )
588
+ );
589
+ const sessionDetails = await (0, import_module_sdk.getEnableSessionDetails)({
590
+ sessions,
591
+ account,
592
+ clients: [publicClient],
593
+ permitGenericPolicy: true,
594
+ sessionNonces
595
+ });
596
+ const signature = await owner.signMessage({
597
+ account: owner.account,
598
+ message: {
599
+ raw: sessionDetails.permissionEnableHash
600
+ }
601
+ });
602
+ return {
603
+ signature,
604
+ sessionNonces
605
+ };
606
+ };
607
+
608
+ // src/core/ZyfaiSDK.ts
609
+ var import_siwe = require("siwe");
610
+ var ZyfaiSDK = class {
611
+ // TODO: The encironment should be removed. Having the same key for staging and production is not ideal, but for now it's fine.
612
+ constructor(config) {
613
+ this.signer = null;
614
+ this.walletClient = null;
615
+ this.isAuthenticated = false;
616
+ // TODO: Check with Utkir for how long the authentication token is valid for.
617
+ this.authenticatedUserId = null;
618
+ const sdkConfig = typeof config === "string" ? { apiKey: config } : config;
619
+ const { apiKey, dataApiKey, environment, bundlerApiKey } = sdkConfig;
620
+ if (!apiKey) {
621
+ throw new Error("API key is required");
622
+ }
623
+ this.environment = environment || "production";
624
+ this.httpClient = new HttpClient(apiKey, this.environment, dataApiKey);
625
+ this.bundlerApiKey = bundlerApiKey;
626
+ }
627
+ /**
628
+ * Authenticate user with SIWE (Sign-In with Ethereum) & JWT token
629
+ * This is required for accessing user-specific endpoints like session-keys/config
630
+ * Uses the connected wallet address for authentication
631
+ *
632
+ * @returns Promise that resolves when authentication is complete
633
+ */
634
+ async authenticateUser() {
635
+ try {
636
+ if (this.isAuthenticated) {
637
+ return;
638
+ }
639
+ const walletClient = this.getWalletClient();
640
+ const userAddress = walletClient.account.address;
641
+ const chainId = walletClient.chain?.id || 8453;
642
+ const challengeResponse = await this.httpClient.post(ENDPOINTS.AUTH_CHALLENGE, {});
643
+ const domain = API_ENDPOINTS[this.environment].split("//")[1];
644
+ const uri = API_ENDPOINTS[this.environment];
645
+ const messageObj = new import_siwe.SiweMessage({
646
+ address: userAddress,
647
+ chainId,
648
+ domain,
649
+ nonce: challengeResponse.nonce,
650
+ statement: "Sign in with Ethereum",
651
+ uri,
652
+ version: "1",
653
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString()
654
+ });
655
+ const messageString = messageObj.prepareMessage();
656
+ const signature = await walletClient.signMessage({
657
+ account: walletClient.account,
658
+ message: messageString
659
+ });
660
+ const loginResponse = await this.httpClient.post(
661
+ ENDPOINTS.AUTH_LOGIN,
662
+ {
663
+ message: messageObj,
664
+ signature
665
+ },
666
+ {
667
+ headers: {
668
+ Origin: uri
669
+ }
670
+ }
671
+ );
672
+ const authToken = loginResponse.accessToken || loginResponse.token;
673
+ if (!authToken) {
674
+ throw new Error("Authentication response missing access token");
675
+ }
676
+ this.httpClient.setAuthToken(authToken);
677
+ this.authenticatedUserId = loginResponse.userId || null;
678
+ this.isAuthenticated = true;
679
+ } catch (error) {
680
+ throw new Error(
681
+ `Failed to authenticate user: ${error.message}`
682
+ );
683
+ }
684
+ }
685
+ /**
686
+ * Update user profile with Smart Wallet address and chain configuration
687
+ * This method requires SIWE authentication and is automatically called after deploySafe
688
+ *
689
+ * @param request - User profile update data
690
+ * @returns Updated user profile information
691
+ *
692
+ * @example
693
+ * ```typescript
694
+ * await sdk.updateUserProfile({
695
+ * smartWallet: "0x1396730...",
696
+ * chains: [8453, 42161],
697
+ * });
698
+ * ```
699
+ */
700
+ async updateUserProfile(request) {
701
+ try {
702
+ await this.authenticateUser();
703
+ const response = await this.httpClient.patch(
704
+ ENDPOINTS.USER_ME,
705
+ request
706
+ );
707
+ return {
708
+ success: true,
709
+ userId: response.userId || response.id,
710
+ smartWallet: response.smartWallet,
711
+ chains: response.chains
712
+ };
713
+ } catch (error) {
714
+ throw new Error(
715
+ `Failed to update user profile: ${error.message}`
716
+ );
717
+ }
718
+ }
719
+ /**
720
+ * Connect account for signing transactions
721
+ * Accepts either a private key string or a modern wallet provider
722
+ *
723
+ * @param account - Private key string or wallet provider object
724
+ * @param chainId - Target chain ID (default: 42161 - Arbitrum)
725
+ * @returns The connected EOA address
726
+ *
727
+ * @example
728
+ * // With private key
729
+ * await sdk.connectAccount('0x...');
730
+ *
731
+ * @example
732
+ * // With wallet provider (e.g., from wagmi, web3-react, etc.)
733
+ * const provider = await connector.getProvider();
734
+ * await sdk.connectAccount(provider);
735
+ */
736
+ async connectAccount(account, chainId = 42161) {
737
+ if (!isSupportedChain(chainId)) {
738
+ throw new Error(`Unsupported chain ID: ${chainId}`);
739
+ }
740
+ this.isAuthenticated = false;
741
+ this.httpClient.clearAuthToken();
742
+ const chainConfig = getChainConfig(chainId);
743
+ if (typeof account === "string") {
744
+ let privateKey = account;
745
+ if (!privateKey.startsWith("0x")) {
746
+ privateKey = `0x${privateKey}`;
747
+ }
748
+ this.signer = (0, import_accounts2.privateKeyToAccount)(privateKey);
749
+ this.walletClient = (0, import_viem4.createWalletClient)({
750
+ account: this.signer,
751
+ chain: chainConfig.chain,
752
+ transport: (0, import_viem4.http)(chainConfig.rpcUrl)
753
+ });
754
+ return this.signer.address;
755
+ }
756
+ const provider = account;
757
+ if (!provider) {
758
+ throw new Error(
759
+ "Invalid account parameter. Expected private key string or wallet provider."
760
+ );
761
+ }
762
+ if (provider.request) {
763
+ const accounts = await provider.request({
764
+ method: "eth_requestAccounts"
765
+ });
766
+ if (!accounts || accounts.length === 0) {
767
+ throw new Error("No accounts found in wallet provider");
768
+ }
769
+ this.walletClient = (0, import_viem4.createWalletClient)({
770
+ account: accounts[0],
771
+ chain: chainConfig.chain,
772
+ transport: (0, import_viem4.custom)(provider)
773
+ });
774
+ return accounts[0];
775
+ }
776
+ if (provider.account && provider.transport) {
777
+ this.walletClient = (0, import_viem4.createWalletClient)({
778
+ account: provider.account,
779
+ chain: chainConfig.chain,
780
+ transport: provider.transport
781
+ });
782
+ return provider.account.address;
783
+ }
784
+ throw new Error(
785
+ "Invalid wallet provider. Expected EIP-1193 provider or viem WalletClient."
786
+ );
787
+ }
788
+ /**
789
+ * Get wallet client (throws if not connected)
790
+ * @private
791
+ */
792
+ getWalletClient(chainId) {
793
+ if (this.signer) {
794
+ return (0, import_viem4.createWalletClient)({
795
+ account: this.signer,
796
+ chain: getChainConfig(chainId || 8453).chain,
797
+ transport: (0, import_viem4.http)(getChainConfig(chainId || 8453).rpcUrl)
798
+ });
799
+ } else {
800
+ if (!this.walletClient) {
801
+ throw new Error("No account connected. Call connectAccount() first");
802
+ }
803
+ return this.walletClient;
804
+ }
805
+ }
806
+ /**
807
+ * Get smart wallet address for a user
808
+ * Returns the deterministic Safe address for an EOA, or the address itself if already a Safe
809
+ *
810
+ * @param userAddress - User's EOA address
811
+ * @param chainId - Target chain ID
812
+ * @returns Smart wallet information including address and deployment status
813
+ */
814
+ async getSmartWalletAddress(userAddress, chainId) {
815
+ if (!userAddress) {
816
+ throw new Error("User address is required");
817
+ }
818
+ if (!isSupportedChain(chainId)) {
819
+ throw new Error(`Unsupported chain ID: ${chainId}`);
820
+ }
821
+ const walletClient = this.getWalletClient();
822
+ const chainConfig = getChainConfig(chainId);
823
+ const safeAddress = await getDeterministicSafeAddress({
824
+ owner: walletClient,
825
+ safeOwnerAddress: userAddress,
826
+ chain: chainConfig.chain,
827
+ publicClient: chainConfig.publicClient
828
+ });
829
+ const isDeployed = await isSafeDeployed(
830
+ safeAddress,
831
+ chainConfig.publicClient
832
+ );
833
+ return {
834
+ address: safeAddress,
835
+ isDeployed
836
+ };
837
+ }
838
+ /**
839
+ * Deploy Safe Smart Wallet for a user
840
+ *
841
+ * @param userAddress - User's EOA address (the connected EOA, not the smart wallet address)
842
+ * @param chainId - Target chain ID
843
+ * @returns Deployment response with Safe address and transaction hash
844
+ */
845
+ async deploySafe(userAddress, chainId) {
846
+ try {
847
+ if (!userAddress) {
848
+ throw new Error("User address is required");
849
+ }
850
+ if (!isSupportedChain(chainId)) {
851
+ throw new Error(`Unsupported chain ID: ${chainId}`);
852
+ }
853
+ if (!this.bundlerApiKey) {
854
+ throw new Error(
855
+ "Bundler API key is required for Safe deployment. Please provide bundlerApiKey in SDK configuration."
856
+ );
857
+ }
858
+ const walletClient = this.getWalletClient(chainId);
859
+ const chainConfig = getChainConfig(chainId);
860
+ const accountType = await getAccountType(
861
+ userAddress,
862
+ chainConfig.publicClient
863
+ );
864
+ if (accountType !== "EOA") {
865
+ throw new Error(
866
+ `Address ${userAddress} is not an EOA. Only EOA addresses can deploy Safe smart wallets.`
867
+ );
868
+ }
869
+ const bundlerUrl = getBundlerUrl(chainId, this.bundlerApiKey);
870
+ const deploymentResult = await deploySafeAccount({
871
+ owner: walletClient,
872
+ safeOwnerAddress: userAddress,
873
+ chain: chainConfig.chain,
874
+ publicClient: chainConfig.publicClient,
875
+ bundlerUrl
876
+ });
877
+ try {
878
+ await this.updateUserProfile({
879
+ smartWallet: deploymentResult.safeAddress,
880
+ chains: [chainId]
881
+ });
882
+ } catch (updateError) {
883
+ console.warn(
884
+ "Failed to update user profile after Safe deployment:",
885
+ updateError.message
886
+ );
887
+ }
888
+ return {
889
+ success: true,
890
+ safeAddress: deploymentResult.safeAddress,
891
+ txHash: deploymentResult.txHash || "0x0",
892
+ status: "deployed"
893
+ };
894
+ } catch (error) {
895
+ console.error("Safe deployment failed:", error);
896
+ throw new Error(`Safe deployment failed: ${error.message}`);
897
+ }
898
+ }
899
+ /**
900
+ * Create session key with auto-fetched configuration from ZyFAI API
901
+ * This is the simplified method that automatically fetches session configuration
902
+ *
903
+ * @param userAddress - User's EOA or Safe address
904
+ * @param chainId - Target chain ID
905
+ * @returns Session key response with signature and nonces
906
+ *
907
+ * @example
908
+ * ```typescript
909
+ * // Simple usage - no need to configure sessions manually
910
+ * const result = await sdk.createSessionKey(userAddress, 8453);
911
+ * console.log("Session created:", result.signature);
912
+ * ```
913
+ */
914
+ async createSessionKey(userAddress, chainId) {
915
+ try {
916
+ await this.authenticateUser();
917
+ if (!this.authenticatedUserId) {
918
+ throw new Error(
919
+ "User ID not available. Please ensure authentication completed successfully."
920
+ );
921
+ }
922
+ const walletClient = this.getWalletClient();
923
+ const chainConfig = getChainConfig(chainId);
924
+ const safeAddress = await getDeterministicSafeAddress({
925
+ owner: walletClient,
926
+ safeOwnerAddress: userAddress,
927
+ chain: chainConfig.chain,
928
+ publicClient: chainConfig.publicClient
929
+ });
930
+ const sessionConfig = await this.httpClient.get(
931
+ ENDPOINTS.SESSION_KEYS_CONFIG
932
+ );
933
+ if (!sessionConfig || sessionConfig.length === 0) {
934
+ throw new Error("No session configuration available from API");
935
+ }
936
+ const sessions = sessionConfig.map((session) => ({
937
+ ...session,
938
+ chainId: BigInt(session.chainId)
939
+ }));
940
+ const signatureResult = await this.signSessionKey(
941
+ userAddress,
942
+ chainId,
943
+ sessions
944
+ );
945
+ const activation = await this.activateSessionKey(
946
+ signatureResult.signature,
947
+ signatureResult.sessionNonces
948
+ );
949
+ return {
950
+ ...signatureResult,
951
+ userId: this.authenticatedUserId,
952
+ sessionActivation: activation
953
+ };
954
+ } catch (error) {
955
+ throw new Error(
956
+ `Failed to create session key: ${error.message}`
957
+ );
958
+ }
959
+ }
960
+ /**
961
+ * Internal method to sign session key
962
+ * @private
963
+ */
964
+ async signSessionKey(userAddress, chainId, sessions) {
965
+ try {
966
+ if (!userAddress) {
967
+ throw new Error("User address is required");
968
+ }
969
+ if (!isSupportedChain(chainId)) {
970
+ throw new Error(`Unsupported chain ID: ${chainId}`);
971
+ }
972
+ if (!sessions || sessions.length === 0) {
973
+ throw new Error("At least one session configuration is required");
974
+ }
975
+ const walletClient = this.getWalletClient();
976
+ const chainConfig = getChainConfig(chainId);
977
+ const accountType = await getAccountType(
978
+ userAddress,
979
+ chainConfig.publicClient
980
+ );
981
+ if (accountType !== "EOA") {
982
+ throw new Error(
983
+ `Invalid account type for ${userAddress}. Must be an EOA.`
984
+ );
985
+ }
986
+ const { signature, sessionNonces } = await signSessionKey(
987
+ {
988
+ owner: walletClient,
989
+ safeOwnerAddress: userAddress,
990
+ chain: chainConfig.chain,
991
+ publicClient: chainConfig.publicClient
992
+ },
993
+ sessions
994
+ );
995
+ const safeAddress = await getDeterministicSafeAddress({
996
+ owner: walletClient,
997
+ safeOwnerAddress: userAddress,
998
+ chain: chainConfig.chain,
999
+ publicClient: chainConfig.publicClient
1000
+ });
1001
+ return {
1002
+ success: true,
1003
+ sessionKeyAddress: safeAddress,
1004
+ signature,
1005
+ sessionNonces
1006
+ };
1007
+ } catch (error) {
1008
+ throw new Error(
1009
+ `Failed to sign session key: ${error.message}`
1010
+ );
1011
+ }
1012
+ }
1013
+ /**
1014
+ * Activate session key via ZyFAI API
1015
+ */
1016
+ async activateSessionKey(signature, sessionNonces) {
1017
+ const nonces = this.normalizeSessionNonces(sessionNonces);
1018
+ const payload = {
1019
+ hash: signature,
1020
+ nonces
1021
+ };
1022
+ return await this.httpClient.post(
1023
+ ENDPOINTS.SESSION_KEYS_ADD,
1024
+ payload
1025
+ );
1026
+ }
1027
+ /**
1028
+ * Convert session nonces from bigint[] to number[]
1029
+ */
1030
+ normalizeSessionNonces(sessionNonces) {
1031
+ if (!sessionNonces || sessionNonces.length === 0) {
1032
+ throw new Error(
1033
+ "Session nonces missing from signature result. Cannot register session key."
1034
+ );
1035
+ }
1036
+ return sessionNonces.map((nonce) => {
1037
+ const value = Number(nonce);
1038
+ if (!Number.isFinite(value) || value < 0) {
1039
+ throw new Error(`Invalid session nonce value: ${nonce.toString()}`);
1040
+ }
1041
+ return value;
1042
+ });
1043
+ }
1044
+ /**
1045
+ * Deposit funds from EOA to Safe smart wallet
1046
+ * Transfers tokens from the connected wallet to the user's Safe and logs the deposit
1047
+ *
1048
+ * @param userAddress - User's address (owner of the Safe)
1049
+ * @param chainId - Target chain ID
1050
+ * @param tokenAddress - Token contract address to deposit
1051
+ * @param amount - Amount in least decimal units (e.g., "100000000" for 100 USDC with 6 decimals)
1052
+ * @returns Deposit response with transaction hash
1053
+ *
1054
+ * @example
1055
+ * ```typescript
1056
+ * // Deposit 100 USDC (6 decimals) to Safe on Arbitrum
1057
+ * const result = await sdk.depositFunds(
1058
+ * "0xUser...",
1059
+ * 42161,
1060
+ * "0xaf88d065e77c8cc2239327c5edb3a432268e5831", // USDC
1061
+ * "100000000" // 100 USDC = 100 * 10^6
1062
+ * );
1063
+ * ```
1064
+ */
1065
+ async depositFunds(userAddress, chainId, tokenAddress, amount) {
1066
+ try {
1067
+ if (!userAddress) {
1068
+ throw new Error("User address is required");
1069
+ }
1070
+ if (!isSupportedChain(chainId)) {
1071
+ throw new Error(`Unsupported chain ID: ${chainId}`);
1072
+ }
1073
+ if (!tokenAddress) {
1074
+ throw new Error("Token address is required");
1075
+ }
1076
+ if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) {
1077
+ throw new Error("Valid amount is required");
1078
+ }
1079
+ const walletClient = this.getWalletClient();
1080
+ const chainConfig = getChainConfig(chainId);
1081
+ const safeAddress = await getDeterministicSafeAddress({
1082
+ owner: walletClient,
1083
+ safeOwnerAddress: userAddress,
1084
+ chain: chainConfig.chain,
1085
+ publicClient: chainConfig.publicClient
1086
+ });
1087
+ const isDeployed = await isSafeDeployed(
1088
+ safeAddress,
1089
+ chainConfig.publicClient
1090
+ );
1091
+ if (!isDeployed) {
1092
+ throw new Error(
1093
+ `Safe not deployed for ${userAddress}. Please deploy the Safe first using deploySafe().`
1094
+ );
1095
+ }
1096
+ const amountBigInt = BigInt(amount);
1097
+ const txHash = await walletClient.writeContract({
1098
+ address: tokenAddress,
1099
+ abi: ERC20_ABI,
1100
+ functionName: "transfer",
1101
+ args: [safeAddress, amountBigInt],
1102
+ chain: chainConfig.chain,
1103
+ account: walletClient.account
1104
+ });
1105
+ const receipt = await chainConfig.publicClient.waitForTransactionReceipt({
1106
+ hash: txHash
1107
+ });
1108
+ if (receipt.status !== "success") {
1109
+ throw new Error("Deposit transaction failed");
1110
+ }
1111
+ return {
1112
+ success: true,
1113
+ txHash,
1114
+ smartWallet: safeAddress,
1115
+ amount: amountBigInt.toString(),
1116
+ status: "confirmed"
1117
+ };
1118
+ } catch (error) {
1119
+ throw new Error(`Deposit failed: ${error.message}`);
1120
+ }
1121
+ }
1122
+ /**
1123
+ * Withdraw funds from Safe smart wallet
1124
+ * Triggers a withdrawal request to the ZyFAI API
1125
+ *
1126
+ * @param userAddress - User's address (owner of the Safe)
1127
+ * @param chainId - Target chain ID
1128
+ * @param amount - Optional: Amount in least decimal units to withdraw (partial withdrawal). If not specified, withdraws all funds
1129
+ * @param receiver - Optional: Receiver address. If not specified, sends to Safe owner
1130
+ * @returns Withdraw response with transaction hash
1131
+ *
1132
+ * @example
1133
+ * ```typescript
1134
+ * // Full withdrawal
1135
+ * const result = await sdk.withdrawFunds("0xUser...", 42161);
1136
+ *
1137
+ * // Partial withdrawal of 50 USDC (6 decimals)
1138
+ * const result = await sdk.withdrawFunds(
1139
+ * "0xUser...",
1140
+ * 42161,
1141
+ * "50000000", // 50 USDC = 50 * 10^6
1142
+ * "0xReceiver..."
1143
+ * );
1144
+ * ```
1145
+ */
1146
+ async withdrawFunds(userAddress, chainId, amount, receiver) {
1147
+ try {
1148
+ if (!userAddress) {
1149
+ throw new Error("User address is required");
1150
+ }
1151
+ if (!isSupportedChain(chainId)) {
1152
+ throw new Error(`Unsupported chain ID: ${chainId}`);
1153
+ }
1154
+ const walletClient = this.getWalletClient();
1155
+ const chainConfig = getChainConfig(chainId);
1156
+ const safeAddress = await getDeterministicSafeAddress({
1157
+ owner: walletClient,
1158
+ safeOwnerAddress: userAddress,
1159
+ chain: chainConfig.chain,
1160
+ publicClient: chainConfig.publicClient
1161
+ });
1162
+ const isDeployed = await isSafeDeployed(
1163
+ safeAddress,
1164
+ chainConfig.publicClient
1165
+ );
1166
+ if (!isDeployed) {
1167
+ throw new Error(
1168
+ `Safe not deployed for ${userAddress}. Please deploy the Safe first using deploySafe().`
1169
+ );
1170
+ }
1171
+ await this.authenticateUser();
1172
+ let response = {};
1173
+ if (amount) {
1174
+ response = await this.httpClient.post(ENDPOINTS.PARTIAL_WITHDRAW, {
1175
+ chainId,
1176
+ amount,
1177
+ receiver: receiver || userAddress
1178
+ });
1179
+ } else {
1180
+ response = await this.httpClient.get(ENDPOINTS.USER_WITHDRAW, {
1181
+ params: { chainId }
1182
+ });
1183
+ }
1184
+ const success = response?.success ?? true;
1185
+ return {
1186
+ success,
1187
+ txHash: response?.txHash || response?.transactionHash || "pending",
1188
+ type: amount ? "partial" : "full",
1189
+ amount: amount || "all",
1190
+ receiver: receiver || userAddress,
1191
+ status: success ? "pending" : "failed"
1192
+ };
1193
+ } catch (error) {
1194
+ throw new Error(`Withdrawal failed: ${error.message}`);
1195
+ }
1196
+ }
1197
+ /**
1198
+ * Get available DeFi protocols and pools for a specific chain
1199
+ *
1200
+ * @param chainId - Target chain ID
1201
+ * @returns List of available protocols with their pools and APY data
1202
+ *
1203
+ * @example
1204
+ * ```typescript
1205
+ * const protocols = await sdk.getAvailableProtocols(42161);
1206
+ * protocols.forEach(protocol => {
1207
+ * console.log(`${protocol.name}: ${protocol.minApy}% - ${protocol.maxApy}% APY`);
1208
+ * });
1209
+ * ```
1210
+ */
1211
+ async getAvailableProtocols(chainId) {
1212
+ try {
1213
+ if (!isSupportedChain(chainId)) {
1214
+ throw new Error(`Unsupported chain ID: ${chainId}`);
1215
+ }
1216
+ const response = await this.httpClient.get(
1217
+ ENDPOINTS.PROTOCOLS(chainId)
1218
+ );
1219
+ return {
1220
+ success: true,
1221
+ chainId,
1222
+ protocols: response
1223
+ };
1224
+ } catch (error) {
1225
+ throw new Error(
1226
+ `Failed to get available protocols: ${error.message}`
1227
+ );
1228
+ }
1229
+ }
1230
+ /**
1231
+ * Get all active DeFi positions for a user
1232
+ *
1233
+ * @param userAddress - User's EOA address
1234
+ * @param chainId - Optional: Filter by specific chain ID
1235
+ * @returns User's positions across all protocols
1236
+ *
1237
+ * @example
1238
+ * ```typescript
1239
+ * // Get all positions across all chains
1240
+ * const positions = await sdk.getPositions(userAddress);
1241
+ *
1242
+ * // Get positions on a specific chain
1243
+ * const arbPositions = await sdk.getPositions(userAddress, 42161);
1244
+ * ```
1245
+ */
1246
+ async getPositions(userAddress, chainId) {
1247
+ try {
1248
+ if (!userAddress) {
1249
+ throw new Error("User address is required");
1250
+ }
1251
+ if (chainId && !isSupportedChain(chainId)) {
1252
+ throw new Error(`Unsupported chain ID: ${chainId}`);
1253
+ }
1254
+ const walletClient = this.getWalletClient(chainId);
1255
+ const chainConfig = getChainConfig(chainId ?? 8453);
1256
+ const safeAddress = await getDeterministicSafeAddress({
1257
+ owner: walletClient,
1258
+ safeOwnerAddress: userAddress,
1259
+ chain: chainConfig.chain,
1260
+ publicClient: chainConfig.publicClient
1261
+ });
1262
+ const response = await this.httpClient.get(
1263
+ ENDPOINTS.DATA_POSITION(safeAddress)
1264
+ );
1265
+ return {
1266
+ success: true,
1267
+ userAddress,
1268
+ totalValueUsd: 0,
1269
+ // API doesn't return this yet
1270
+ positions: response ? [response] : []
1271
+ };
1272
+ } catch (error) {
1273
+ throw new Error(`Failed to get positions: ${error.message}`);
1274
+ }
1275
+ }
1276
+ // ============================================================================
1277
+ // User Details Methods
1278
+ // ============================================================================
1279
+ /**
1280
+ * Get current authenticated user details
1281
+ * Requires SIWE authentication
1282
+ *
1283
+ * @returns User details including smart wallet, chains, protocols, etc.
1284
+ *
1285
+ * @example
1286
+ * ```typescript
1287
+ * await sdk.connectAccount(privateKey, chainId);
1288
+ * const user = await sdk.getUserDetails();
1289
+ * console.log("Smart Wallet:", user.user.smartWallet);
1290
+ * console.log("Chains:", user.user.chains);
1291
+ * ```
1292
+ */
1293
+ async getUserDetails() {
1294
+ try {
1295
+ await this.authenticateUser();
1296
+ const response = await this.httpClient.get(ENDPOINTS.USER_ME);
1297
+ return {
1298
+ success: true,
1299
+ user: {
1300
+ id: response.id,
1301
+ address: response.address,
1302
+ smartWallet: response.smartWallet,
1303
+ chains: response.chains || [],
1304
+ protocols: response.protocols || [],
1305
+ hasActiveSessionKey: response.hasActiveSessionKey || false,
1306
+ email: response.email,
1307
+ strategy: response.strategy,
1308
+ telegramId: response.telegramId,
1309
+ walletType: response.walletType,
1310
+ autoSelectProtocols: response.autoSelectProtocols || false,
1311
+ autocompounding: response.autocompounding,
1312
+ omniAccount: response.omniAccount,
1313
+ crosschainStrategy: response.crosschainStrategy,
1314
+ agentName: response.agentName,
1315
+ customization: response.customization
1316
+ }
1317
+ };
1318
+ } catch (error) {
1319
+ throw new Error(
1320
+ `Failed to get user details: ${error.message}`
1321
+ );
1322
+ }
1323
+ }
1324
+ // ============================================================================
1325
+ // TVL & Volume Methods
1326
+ // ============================================================================
1327
+ /**
1328
+ * Get total value locked (TVL) across all ZyFAI accounts
1329
+ *
1330
+ * @returns Total TVL in USD and breakdown by chain
1331
+ *
1332
+ * @example
1333
+ * ```typescript
1334
+ * const tvl = await sdk.getTVL();
1335
+ * console.log("Total TVL:", tvl.totalTvl);
1336
+ * ```
1337
+ */
1338
+ async getTVL() {
1339
+ try {
1340
+ const response = await this.httpClient.get(ENDPOINTS.DATA_TVL);
1341
+ return {
1342
+ success: true,
1343
+ totalTvl: response.totalTvl || response.tvl || 0,
1344
+ byChain: response.byChain
1345
+ };
1346
+ } catch (error) {
1347
+ throw new Error(`Failed to get TVL: ${error.message}`);
1348
+ }
1349
+ }
1350
+ /**
1351
+ * Get total volume across all ZyFAI accounts
1352
+ *
1353
+ * @returns Total volume in USD
1354
+ *
1355
+ * @example
1356
+ * ```typescript
1357
+ * const volume = await sdk.getVolume();
1358
+ * console.log("Total Volume:", volume.volumeInUSD);
1359
+ * ```
1360
+ */
1361
+ async getVolume() {
1362
+ try {
1363
+ const response = await this.httpClient.get(ENDPOINTS.DATA_VOLUME);
1364
+ return {
1365
+ success: true,
1366
+ volumeInUSD: response.volumeInUSD || "0"
1367
+ };
1368
+ } catch (error) {
1369
+ throw new Error(`Failed to get volume: ${error.message}`);
1370
+ }
1371
+ }
1372
+ // ============================================================================
1373
+ // Active Wallets Methods
1374
+ // ============================================================================
1375
+ /**
1376
+ * Get active wallets for a specific chain
1377
+ *
1378
+ * @param chainId - Chain ID to filter wallets
1379
+ * @returns List of active wallets on the specified chain
1380
+ *
1381
+ * @example
1382
+ * ```typescript
1383
+ * const wallets = await sdk.getActiveWallets(8453); // Base
1384
+ * console.log("Active wallets:", wallets.count);
1385
+ * ```
1386
+ */
1387
+ async getActiveWallets(chainId) {
1388
+ try {
1389
+ if (!chainId) {
1390
+ throw new Error("Chain ID is required");
1391
+ }
1392
+ const response = await this.httpClient.get(
1393
+ ENDPOINTS.DATA_ACTIVE_WALLETS(chainId)
1394
+ );
1395
+ const wallets = Array.isArray(response) ? response : response.wallets || [];
1396
+ return {
1397
+ success: true,
1398
+ chainId,
1399
+ wallets: wallets.map((w) => ({
1400
+ smartWallet: w.smartWallet || w,
1401
+ chains: w.chains || [chainId],
1402
+ hasBalance: w.hasBalance ?? true
1403
+ })),
1404
+ count: wallets.length
1405
+ };
1406
+ } catch (error) {
1407
+ throw new Error(
1408
+ `Failed to get active wallets: ${error.message}`
1409
+ );
1410
+ }
1411
+ }
1412
+ /**
1413
+ * Get smart wallets associated with an EOA address
1414
+ *
1415
+ * @param eoaAddress - EOA (externally owned account) address
1416
+ * @returns List of smart wallets owned by the EOA
1417
+ *
1418
+ * @example
1419
+ * ```typescript
1420
+ * const result = await sdk.getSmartWalletsByEOA("0x...");
1421
+ * console.log("Smart wallets:", result.smartWallets);
1422
+ * ```
1423
+ */
1424
+ async getSmartWalletsByEOA(eoaAddress) {
1425
+ try {
1426
+ if (!eoaAddress) {
1427
+ throw new Error("EOA address is required");
1428
+ }
1429
+ const response = await this.httpClient.get(
1430
+ ENDPOINTS.DATA_BY_EOA(eoaAddress)
1431
+ );
1432
+ const smartWallets = Array.isArray(response) ? response : response.smartWallets || [response.smartWallet].filter(Boolean);
1433
+ return {
1434
+ success: true,
1435
+ eoa: eoaAddress,
1436
+ smartWallets
1437
+ };
1438
+ } catch (error) {
1439
+ throw new Error(
1440
+ `Failed to get smart wallets by EOA: ${error.message}`
1441
+ );
1442
+ }
1443
+ }
1444
+ // ============================================================================
1445
+ // First Topup & History Methods
1446
+ // ============================================================================
1447
+ /**
1448
+ * Get the first topup (deposit) information for a wallet
1449
+ *
1450
+ * @param walletAddress - Smart wallet address
1451
+ * @param chainId - Chain ID
1452
+ * @returns First topup date and details
1453
+ *
1454
+ * @example
1455
+ * ```typescript
1456
+ * const firstTopup = await sdk.getFirstTopup("0x...", 8453);
1457
+ * console.log("First deposit date:", firstTopup.date);
1458
+ * ```
1459
+ */
1460
+ async getFirstTopup(walletAddress, chainId) {
1461
+ try {
1462
+ if (!walletAddress) {
1463
+ throw new Error("Wallet address is required");
1464
+ }
1465
+ if (!chainId) {
1466
+ throw new Error("Chain ID is required");
1467
+ }
1468
+ const response = await this.httpClient.get(
1469
+ ENDPOINTS.DATA_FIRST_TOPUP(walletAddress, chainId)
1470
+ );
1471
+ return {
1472
+ success: true,
1473
+ walletAddress,
1474
+ date: response.date || response.firstTopup?.date || "",
1475
+ amount: response.amount,
1476
+ chainId: response.chainId || chainId
1477
+ };
1478
+ } catch (error) {
1479
+ throw new Error(`Failed to get first topup: ${error.message}`);
1480
+ }
1481
+ }
1482
+ /**
1483
+ * Get transaction history for a wallet
1484
+ *
1485
+ * @param walletAddress - Smart wallet address
1486
+ * @param chainId - Chain ID
1487
+ * @param options - Optional pagination and date filters
1488
+ * @returns Transaction history
1489
+ *
1490
+ * @example
1491
+ * ```typescript
1492
+ * const history = await sdk.getHistory("0x...", 8453, { limit: 50 });
1493
+ * history.data.forEach(tx => console.log(tx.type, tx.amount));
1494
+ * ```
1495
+ */
1496
+ async getHistory(walletAddress, chainId, options) {
1497
+ try {
1498
+ if (!walletAddress) {
1499
+ throw new Error("Wallet address is required");
1500
+ }
1501
+ if (!chainId) {
1502
+ throw new Error("Chain ID is required");
1503
+ }
1504
+ let endpoint = ENDPOINTS.DATA_HISTORY(walletAddress, chainId);
1505
+ if (options?.limit) endpoint += `&limit=${options.limit}`;
1506
+ if (options?.offset) endpoint += `&offset=${options.offset}`;
1507
+ if (options?.fromDate) endpoint += `&fromDate=${options.fromDate}`;
1508
+ if (options?.toDate) endpoint += `&toDate=${options.toDate}`;
1509
+ const response = await this.httpClient.get(endpoint);
1510
+ return {
1511
+ success: true,
1512
+ walletAddress,
1513
+ data: response.data || [],
1514
+ total: response.total || 0
1515
+ };
1516
+ } catch (error) {
1517
+ throw new Error(`Failed to get history: ${error.message}`);
1518
+ }
1519
+ }
1520
+ // ============================================================================
1521
+ // Onchain Earnings Methods (Data API v2)
1522
+ // ============================================================================
1523
+ /**
1524
+ * Get onchain earnings for a wallet
1525
+ *
1526
+ * @param walletAddress - Smart wallet address
1527
+ * @returns Onchain earnings data including total, current, and lifetime
1528
+ *
1529
+ * @example
1530
+ * ```typescript
1531
+ * const earnings = await sdk.getOnchainEarnings("0x...");
1532
+ * console.log("Total earnings:", earnings.data.totalEarnings);
1533
+ * ```
1534
+ */
1535
+ async getOnchainEarnings(walletAddress) {
1536
+ try {
1537
+ if (!walletAddress) {
1538
+ throw new Error("Wallet address is required");
1539
+ }
1540
+ const response = await this.httpClient.dataGet(
1541
+ DATA_ENDPOINTS.ONCHAIN_EARNINGS(walletAddress)
1542
+ );
1543
+ return {
1544
+ success: true,
1545
+ data: {
1546
+ walletAddress,
1547
+ totalEarnings: response.total_earnings || response.totalEarnings || 0,
1548
+ currentEarnings: response.current_earnings || response.currentEarnings || 0,
1549
+ lifetimeEarnings: response.lifetime_earnings || response.lifetimeEarnings || 0,
1550
+ unrealizedEarnings: response.unrealized_earnings,
1551
+ currentEarningsByChain: response.current_earnings_by_chain,
1552
+ unrealizedEarningsByChain: response.unrealized_earnings_by_chain,
1553
+ lastCheckTimestamp: response.last_check_timestamp
1554
+ }
1555
+ };
1556
+ } catch (error) {
1557
+ throw new Error(
1558
+ `Failed to get onchain earnings: ${error.message}`
1559
+ );
1560
+ }
1561
+ }
1562
+ /**
1563
+ * Calculate/refresh onchain earnings for a wallet
1564
+ * This triggers a recalculation of earnings on the backend
1565
+ *
1566
+ * @param walletAddress - Smart wallet address
1567
+ * @returns Updated onchain earnings data
1568
+ *
1569
+ * @example
1570
+ * ```typescript
1571
+ * const earnings = await sdk.calculateOnchainEarnings("0x...");
1572
+ * console.log("Calculated earnings:", earnings.data.totalEarnings);
1573
+ * ```
1574
+ */
1575
+ async calculateOnchainEarnings(walletAddress) {
1576
+ try {
1577
+ if (!walletAddress) {
1578
+ throw new Error("Wallet address is required");
1579
+ }
1580
+ const response = await this.httpClient.dataPost(
1581
+ DATA_ENDPOINTS.CALCULATE_ONCHAIN_EARNINGS,
1582
+ { walletAddress }
1583
+ );
1584
+ const data = response.data || response;
1585
+ return {
1586
+ success: true,
1587
+ data: {
1588
+ walletAddress,
1589
+ totalEarnings: data.total_earnings || data.totalEarnings || 0,
1590
+ currentEarnings: data.current_earnings || data.currentEarnings || 0,
1591
+ lifetimeEarnings: data.lifetime_earnings || data.lifetimeEarnings || 0,
1592
+ unrealizedEarnings: data.unrealized_earnings,
1593
+ lastCheckTimestamp: data.last_check_timestamp
1594
+ }
1595
+ };
1596
+ } catch (error) {
1597
+ throw new Error(
1598
+ `Failed to calculate onchain earnings: ${error.message}`
1599
+ );
1600
+ }
1601
+ }
1602
+ /**
1603
+ * Get daily earnings for a wallet within a date range
1604
+ *
1605
+ * @param walletAddress - Smart wallet address
1606
+ * @param startDate - Start date (YYYY-MM-DD format)
1607
+ * @param endDate - End date (YYYY-MM-DD format)
1608
+ * @returns Daily earnings breakdown
1609
+ *
1610
+ * @example
1611
+ * ```typescript
1612
+ * const daily = await sdk.getDailyEarnings("0x...", "2024-01-01", "2024-01-31");
1613
+ * daily.data.forEach(d => console.log(d.date, d.earnings));
1614
+ * ```
1615
+ */
1616
+ async getDailyEarnings(walletAddress, startDate, endDate) {
1617
+ try {
1618
+ if (!walletAddress) {
1619
+ throw new Error("Wallet address is required");
1620
+ }
1621
+ const response = await this.httpClient.dataGet(
1622
+ DATA_ENDPOINTS.DAILY_EARNINGS(walletAddress, startDate, endDate)
1623
+ );
1624
+ return {
1625
+ success: true,
1626
+ walletAddress,
1627
+ data: response.data || [],
1628
+ count: response.count || 0,
1629
+ filters: {
1630
+ startDate: startDate || null,
1631
+ endDate: endDate || null
1632
+ }
1633
+ };
1634
+ } catch (error) {
1635
+ throw new Error(
1636
+ `Failed to get daily earnings: ${error.message}`
1637
+ );
1638
+ }
1639
+ }
1640
+ // ============================================================================
1641
+ // Portfolio Methods (Data API v2)
1642
+ // ============================================================================
1643
+ /**
1644
+ * Get Debank portfolio for a wallet across multiple chains
1645
+ * Note: This is a paid endpoint and may require authorization
1646
+ *
1647
+ * @param walletAddress - Smart wallet address
1648
+ * @returns Multi-chain portfolio data
1649
+ *
1650
+ * @example
1651
+ * ```typescript
1652
+ * const portfolio = await sdk.getDebankPortfolio("0x...");
1653
+ * console.log("Total value:", portfolio.totalValueUsd);
1654
+ * ```
1655
+ */
1656
+ async getDebankPortfolio(walletAddress) {
1657
+ try {
1658
+ if (!walletAddress) {
1659
+ throw new Error("Wallet address is required");
1660
+ }
1661
+ const response = await this.httpClient.dataGet(
1662
+ DATA_ENDPOINTS.DEBANK_PORTFOLIO_MULTICHAIN(walletAddress)
1663
+ );
1664
+ const data = response.data || response;
1665
+ return {
1666
+ success: true,
1667
+ walletAddress,
1668
+ totalValueUsd: data.totalValueUsd || 0,
1669
+ chains: data.chains || data
1670
+ };
1671
+ } catch (error) {
1672
+ throw new Error(
1673
+ `Failed to get Debank portfolio: ${error.message}`
1674
+ );
1675
+ }
1676
+ }
1677
+ // ============================================================================
1678
+ // Opportunities Methods (Data API v2)
1679
+ // ============================================================================
1680
+ /**
1681
+ * Get safe (low-risk) yield opportunities
1682
+ *
1683
+ * @param chainId - Optional chain ID filter
1684
+ * @returns List of safe yield opportunities
1685
+ *
1686
+ * @example
1687
+ * ```typescript
1688
+ * const opportunities = await sdk.getSafeOpportunities(8453);
1689
+ * opportunities.data.forEach(o => console.log(o.protocolName, o.apy));
1690
+ * ```
1691
+ */
1692
+ async getSafeOpportunities(chainId) {
1693
+ try {
1694
+ const response = await this.httpClient.dataGet(
1695
+ DATA_ENDPOINTS.OPPORTUNITIES_SAFE(chainId)
1696
+ );
1697
+ const data = response.data || response || [];
1698
+ return {
1699
+ success: true,
1700
+ chainId,
1701
+ strategyType: "safe",
1702
+ data: Array.isArray(data) ? data.map((o) => ({
1703
+ id: o.id,
1704
+ protocolId: o.protocol_id || o.protocolId,
1705
+ protocolName: o.protocol_name || o.protocolName,
1706
+ poolName: o.pool_name || o.poolName,
1707
+ chainId: o.chain_id || o.chainId,
1708
+ apy: o.apy || o.pool_apy || 0,
1709
+ tvl: o.tvl || o.zyfiTvl,
1710
+ asset: o.asset || o.underlying_token,
1711
+ risk: o.risk,
1712
+ strategyType: "safe",
1713
+ status: o.status
1714
+ })) : []
1715
+ };
1716
+ } catch (error) {
1717
+ throw new Error(
1718
+ `Failed to get safe opportunities: ${error.message}`
1719
+ );
1720
+ }
1721
+ }
1722
+ /**
1723
+ * Get degen (high-risk, high-reward) yield strategies
1724
+ *
1725
+ * @param chainId - Optional chain ID filter
1726
+ * @returns List of degen strategies
1727
+ *
1728
+ * @example
1729
+ * ```typescript
1730
+ * const strategies = await sdk.getDegenStrategies(8453);
1731
+ * strategies.data.forEach(s => console.log(s.protocolName, s.apy));
1732
+ * ```
1733
+ */
1734
+ async getDegenStrategies(chainId) {
1735
+ try {
1736
+ const response = await this.httpClient.dataGet(
1737
+ DATA_ENDPOINTS.OPPORTUNITIES_DEGEN(chainId)
1738
+ );
1739
+ const data = response.data || response || [];
1740
+ return {
1741
+ success: true,
1742
+ chainId,
1743
+ strategyType: "degen",
1744
+ data: Array.isArray(data) ? data.map((o) => ({
1745
+ id: o.id,
1746
+ protocolId: o.protocol_id || o.protocolId,
1747
+ protocolName: o.protocol_name || o.protocolName,
1748
+ poolName: o.pool_name || o.poolName,
1749
+ chainId: o.chain_id || o.chainId,
1750
+ apy: o.apy || o.pool_apy || 0,
1751
+ tvl: o.tvl || o.zyfiTvl,
1752
+ asset: o.asset || o.underlying_token,
1753
+ risk: o.risk,
1754
+ strategyType: "degen",
1755
+ status: o.status
1756
+ })) : []
1757
+ };
1758
+ } catch (error) {
1759
+ throw new Error(
1760
+ `Failed to get degen strategies: ${error.message}`
1761
+ );
1762
+ }
1763
+ }
1764
+ // ============================================================================
1765
+ // APY History Methods (Data API v2)
1766
+ // ============================================================================
1767
+ /**
1768
+ * Get daily APY history with weighted average for a wallet
1769
+ *
1770
+ * @param walletAddress - Smart wallet address
1771
+ * @param days - Period: "7D", "14D", or "30D" (default: "7D")
1772
+ * @returns Daily APY history with weighted averages
1773
+ *
1774
+ * @example
1775
+ * ```typescript
1776
+ * const apyHistory = await sdk.getDailyApyHistory("0x...", "30D");
1777
+ * console.log("Average APY:", apyHistory.averageWeightedApy);
1778
+ * ```
1779
+ */
1780
+ async getDailyApyHistory(walletAddress, days = "7D") {
1781
+ try {
1782
+ if (!walletAddress) {
1783
+ throw new Error("Wallet address is required");
1784
+ }
1785
+ const response = await this.httpClient.dataGet(
1786
+ DATA_ENDPOINTS.DAILY_APY_HISTORY_WEIGHTED(walletAddress, days)
1787
+ );
1788
+ const data = response.data || response;
1789
+ return {
1790
+ success: true,
1791
+ walletAddress,
1792
+ history: data.history || {},
1793
+ totalDays: data.total_days || data.totalDays || 0,
1794
+ requestedDays: data.requested_days || data.requestedDays,
1795
+ averageWeightedApy: data.average_final_weighted_apy_after_fee || data.averageWeightedApy || 0
1796
+ };
1797
+ } catch (error) {
1798
+ throw new Error(
1799
+ `Failed to get daily APY history: ${error.message}`
1800
+ );
1801
+ }
1802
+ }
1803
+ // ============================================================================
1804
+ // Rebalance Methods
1805
+ // ============================================================================
1806
+ /**
1807
+ * Get rebalance information
1808
+ * Shows yield generated by rebalancing strategies
1809
+ *
1810
+ * @param isCrossChain - Filter by cross-chain or same-chain rebalances
1811
+ * @returns List of rebalance events
1812
+ *
1813
+ * @example
1814
+ * ```typescript
1815
+ * // Get same-chain rebalance info
1816
+ * const rebalances = await sdk.getRebalanceInfo(false);
1817
+ * console.log("Rebalance count:", rebalances.count);
1818
+ * ```
1819
+ */
1820
+ async getRebalanceInfo(isCrossChain) {
1821
+ try {
1822
+ const response = await this.httpClient.dataGet(
1823
+ DATA_ENDPOINTS.REBALANCE_INFO(isCrossChain)
1824
+ );
1825
+ const data = response.data || response || [];
1826
+ return {
1827
+ success: true,
1828
+ data: Array.isArray(data) ? data.map((r) => ({
1829
+ id: r.id,
1830
+ timestamp: r.timestamp || r.created_at,
1831
+ fromProtocol: r.from_protocol || r.fromProtocol,
1832
+ toProtocol: r.to_protocol || r.toProtocol,
1833
+ fromPool: r.from_pool || r.fromPool,
1834
+ toPool: r.to_pool || r.toPool,
1835
+ amount: r.amount,
1836
+ isCrossChain: r.is_cross_chain ?? r.isCrossChain ?? false,
1837
+ fromChainId: r.from_chain_id || r.fromChainId,
1838
+ toChainId: r.to_chain_id || r.toChainId
1839
+ })) : [],
1840
+ count: data.length
1841
+ };
1842
+ } catch (error) {
1843
+ throw new Error(
1844
+ `Failed to get rebalance info: ${error.message}`
1845
+ );
1846
+ }
1847
+ }
1848
+ /**
1849
+ * Get rebalance frequency/tier for a wallet
1850
+ * Determines how often the wallet can be rebalanced based on tier
1851
+ *
1852
+ * @param walletAddress - Smart wallet address
1853
+ * @returns Rebalance frequency tier and details
1854
+ *
1855
+ * @example
1856
+ * ```typescript
1857
+ * const frequency = await sdk.getRebalanceFrequency("0x...");
1858
+ * console.log("Tier:", frequency.tier);
1859
+ * console.log("Max rebalances/day:", frequency.frequency);
1860
+ * ```
1861
+ */
1862
+ async getRebalanceFrequency(walletAddress) {
1863
+ try {
1864
+ if (!walletAddress) {
1865
+ throw new Error("Wallet address is required");
1866
+ }
1867
+ const response = await this.httpClient.get(
1868
+ ENDPOINTS.DATA_REBALANCE_FREQUENCY(walletAddress)
1869
+ );
1870
+ return {
1871
+ success: true,
1872
+ walletAddress,
1873
+ tier: response.tier || "standard",
1874
+ frequency: response.frequency || response.rebalanceFrequency || 1,
1875
+ description: response.description
1876
+ };
1877
+ } catch (error) {
1878
+ throw new Error(
1879
+ `Failed to get rebalance frequency: ${error.message}`
1880
+ );
1881
+ }
1882
+ }
1883
+ };
1884
+ // Annotate the CommonJS export names for ESM import in node:
1885
+ 0 && (module.exports = {
1886
+ ZyfaiSDK,
1887
+ getChainConfig,
1888
+ getSupportedChainIds,
1889
+ isSupportedChain
1890
+ });