@zyfai/sdk 0.2.32 → 0.2.34

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/cli/index.js DELETED
@@ -1,3981 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") {
11
- for (let key of __getOwnPropNames(from))
12
- if (!__hasOwnProp.call(to, key) && key !== except)
13
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
- }
15
- return to;
16
- };
17
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
- // If the importer is in node compatibility mode or this is not an ESM
19
- // file that has been converted to a CommonJS file using a Babel-
20
- // compatible transform (i.e. "__esModule" has not been set), then set
21
- // "default" to the CommonJS "module.exports" for node compatibility.
22
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
- mod
24
- ));
25
-
26
- // src/cli/index.ts
27
- var import_commander = require("commander");
28
-
29
- // src/cli/utils/config.ts
30
- var fs = __toESM(require("fs"));
31
- var path = __toESM(require("path"));
32
- var os = __toESM(require("os"));
33
- var CONFIG_DIR = path.join(os.homedir(), ".config", "zyfai");
34
- var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
35
- function ensureConfigDir() {
36
- if (!fs.existsSync(CONFIG_DIR)) {
37
- fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
38
- }
39
- }
40
- function loadConfig() {
41
- try {
42
- if (fs.existsSync(CONFIG_FILE)) {
43
- const content = fs.readFileSync(CONFIG_FILE, "utf-8");
44
- return JSON.parse(content);
45
- }
46
- } catch {
47
- }
48
- return {};
49
- }
50
- function saveConfig(config) {
51
- ensureConfigDir();
52
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), {
53
- mode: 384
54
- });
55
- }
56
- function getConfigValue(key) {
57
- const config = loadConfig();
58
- return config[key];
59
- }
60
- function setConfigValue(key, value) {
61
- const config = loadConfig();
62
- config[key] = value;
63
- saveConfig(config);
64
- }
65
- function getApiKey() {
66
- console.log("configKey");
67
- const configKey = getConfigValue("apiKey");
68
- if (configKey) {
69
- return configKey;
70
- }
71
- throw new Error(
72
- "API key not found. Set it with: zyfai config set api-key YOUR_KEY\nOr set ZYFAI_API_KEY environment variable"
73
- );
74
- }
75
- function getDefaultChain() {
76
- return getConfigValue("defaultChain") || "base";
77
- }
78
- function clearConfig() {
79
- if (fs.existsSync(CONFIG_FILE)) {
80
- fs.unlinkSync(CONFIG_FILE);
81
- }
82
- }
83
- function getConfigPath() {
84
- return CONFIG_FILE;
85
- }
86
-
87
- // src/cli/utils/output.ts
88
- function outputSuccess(data) {
89
- const result = {
90
- success: true,
91
- data
92
- };
93
- console.log(JSON.stringify(result, null, 2));
94
- }
95
- function outputError(error) {
96
- const message = error instanceof Error ? error.message : error;
97
- const result = {
98
- success: false,
99
- error: message
100
- };
101
- console.log(JSON.stringify(result, null, 2));
102
- process.exit(1);
103
- }
104
-
105
- // src/cli/commands/config.ts
106
- function registerConfigCommand(program2) {
107
- const configCmd = program2.command("config").description("Manage Zyfai CLI configuration");
108
- configCmd.command("set <key> <value>").description("Set a configuration value (api-key, default-chain, default-wallet)").action((key, value) => {
109
- try {
110
- const normalizedKey = key.replace(/-/g, "");
111
- switch (normalizedKey) {
112
- case "apikey":
113
- setConfigValue("apiKey", value);
114
- outputSuccess({ message: "API key saved", path: getConfigPath() });
115
- break;
116
- case "defaultchain":
117
- if (!["base", "arbitrum", "plasma"].includes(value.toLowerCase())) {
118
- throw new Error("Invalid chain. Supported: base, arbitrum, plasma");
119
- }
120
- setConfigValue("defaultChain", value.toLowerCase());
121
- outputSuccess({ message: `Default chain set to ${value}` });
122
- break;
123
- case "defaultwallet":
124
- setConfigValue("defaultWallet", value);
125
- outputSuccess({ message: `Default wallet set to ${value}` });
126
- break;
127
- default:
128
- throw new Error(
129
- `Unknown config key: ${key}. Supported: api-key, default-chain, default-wallet`
130
- );
131
- }
132
- } catch (error) {
133
- outputError(error);
134
- }
135
- });
136
- configCmd.command("get [key]").description("Get configuration value(s)").action((key) => {
137
- try {
138
- const config = loadConfig();
139
- if (key) {
140
- const normalizedKey = key.replace(/-/g, "");
141
- let value;
142
- switch (normalizedKey) {
143
- case "apikey":
144
- value = config.apiKey ? "***" + config.apiKey.slice(-4) : void 0;
145
- break;
146
- case "defaultchain":
147
- value = config.defaultChain;
148
- break;
149
- case "defaultwallet":
150
- value = config.defaultWallet;
151
- break;
152
- default:
153
- throw new Error(`Unknown config key: ${key}`);
154
- }
155
- outputSuccess({ [key]: value });
156
- } else {
157
- outputSuccess({
158
- apiKey: config.apiKey ? "***" + config.apiKey.slice(-4) : void 0,
159
- defaultChain: config.defaultChain,
160
- defaultWallet: config.defaultWallet,
161
- path: getConfigPath()
162
- });
163
- }
164
- } catch (error) {
165
- outputError(error);
166
- }
167
- });
168
- configCmd.command("clear").description("Clear all configuration").action(() => {
169
- try {
170
- clearConfig();
171
- outputSuccess({ message: "Configuration cleared" });
172
- } catch (error) {
173
- outputError(error);
174
- }
175
- });
176
- configCmd.command("path").description("Show configuration file path").action(() => {
177
- outputSuccess({ path: getConfigPath() });
178
- });
179
- }
180
-
181
- // src/utils/http-client.ts
182
- var import_axios = __toESM(require("axios"));
183
-
184
- // src/config/endpoints.ts
185
- var API_ENDPOINT = "https://api.zyf.ai";
186
- var DATA_API_ENDPOINT = "https://defiapi.zyf.ai";
187
- var API_VERSION = "/api/v1";
188
- var DATA_API_VERSION = "/api/v2";
189
- var ENDPOINTS = {
190
- // Auth
191
- AUTH_LOGIN: "/auth/login",
192
- AUTH_CHALLENGE: "/auth/challenge",
193
- // User
194
- USER_ME: "/users/me",
195
- USER_WITHDRAW: "/users/withdraw",
196
- PARTIAL_WITHDRAW: "/users/partial-withdraw",
197
- LOG_DEPOSIT: "/users/log_deposit",
198
- // Safe Deployment (single endpoint)
199
- SAFE_DEPLOY: "/users/safe-deploy",
200
- // Session Keys
201
- SESSION_KEYS_CONFIG: "/session-keys/config",
202
- SESSION_KEYS_ADD: "/session-keys/add",
203
- // Protocols
204
- PROTOCOLS: (chainId) => chainId ? `/protocols?chainId=${chainId}` : "/protocols",
205
- // Data (v1)
206
- DATA_POSITION: (walletAddress) => `/data/position?walletAddress=${walletAddress}`,
207
- DATA_PORTFOLIO: (walletAddress) => `/data/wallet-portfolio?walletAddress=${walletAddress}`,
208
- DATA_HISTORY: (walletAddress, chainId) => `/data/history?walletAddress=${walletAddress}&chainId=${chainId}`,
209
- DATA_TVL: "/data/usd-tvl",
210
- DATA_VOLUME: (assetType) => `/data/volume?assetType=${assetType}`,
211
- DATA_FIRST_TOPUP: (walletAddress, chainId) => `/data/first-topup?walletAddress=${walletAddress}&chainId=${chainId}`,
212
- DATA_ACTIVE_WALLETS: (chainId) => `/data/active-wallets?chainId=${chainId}`,
213
- DATA_BY_EOA: (address) => `/data/by-eoa?address=${address}`,
214
- DATA_REBALANCE_FREQUENCY: (walletAddress) => `/data/rebalance-frequency?walletAddress=${walletAddress}`,
215
- // SDK Keys
216
- SDK_ALLOWED_WALLETS: "/data/sdk-allowed-wallets",
217
- SDK_TVL: "/data/sdk-tvl",
218
- // Agent Identity Registry
219
- AGENT_TOKEN_URI: "/users/me/agent-token-uri",
220
- // Customization
221
- CUSTOMIZE_BATCH: "/customization/customize-batch",
222
- CUSTOMIZATION_POOLS: (protocolId, strategy) => `/customization/pools?protocolId=${protocolId}${strategy ? `&strategy=${strategy}` : ""}`,
223
- CUSTOMIZATION_SELECTED_POOLS: (protocolId, chainId) => `/customization/selected-pools?protocolId=${protocolId}&chainId=${chainId}`
224
- };
225
- var DATA_ENDPOINTS = {
226
- // User Initialization
227
- USER_INITIALIZE: "/api/earnings/initialize",
228
- // Earnings
229
- ONCHAIN_EARNINGS: (walletAddress) => `/onchain-earnings/onchain-earnings?walletAddress=${walletAddress}`,
230
- CALCULATE_ONCHAIN_EARNINGS: "/onchain-earnings/calculate-onchain-earnings",
231
- DAILY_EARNINGS: (walletAddress, startDate, endDate) => {
232
- let url = `/onchain-earnings/daily-earnings?walletAddress=${walletAddress}`;
233
- if (startDate) url += `&startDate=${startDate}`;
234
- if (endDate) url += `&endDate=${endDate}`;
235
- return url;
236
- },
237
- // Opportunities
238
- OPPORTUNITIES_SAFE: (chainId, asset, status) => {
239
- const params = [];
240
- if (chainId !== void 0) params.push(`chainId=${chainId}`);
241
- if (asset) params.push(`asset=${asset}`);
242
- if (status) params.push(`status=${status}`);
243
- return params.length > 0 ? `/opportunities/safe?${params.join("&")}` : "/opportunities/safe";
244
- },
245
- OPPORTUNITIES_DEGEN: (chainId, asset, status) => {
246
- const params = [];
247
- if (chainId !== void 0) params.push(`chainId=${chainId}`);
248
- if (asset) params.push(`asset=${asset}`);
249
- if (status) params.push(`status=${status}`);
250
- return params.length > 0 ? `/opportunities/degen-strategies?${params.join("&")}` : "/opportunities/degen-strategies";
251
- },
252
- // APY History
253
- DAILY_APY_HISTORY_WEIGHTED: (walletAddress, days) => `/daily-apy-history/weighted-multi-asset/${walletAddress}${days ? `?days=${days}` : ""}`,
254
- // Rebalance
255
- REBALANCE_INFO: (options) => {
256
- const params = [];
257
- if (options?.isCrossChain !== void 0) params.push(`isCrossChain=${options.isCrossChain}`);
258
- if (options?.tokenSymbol) params.push(`tokenSymbol=${options.tokenSymbol}`);
259
- return params.length > 0 ? `/rebalance/rebalance-info?${params.join("&")}` : "/rebalance/rebalance-info";
260
- },
261
- // APY Per Strategy
262
- APY_PER_STRATEGY: (options = {}) => {
263
- const params = [];
264
- if (options.isCrossChain !== void 0) params.push(`isCrossChain=${options.isCrossChain}`);
265
- if (options.days !== void 0) params.push(`days=${options.days}`);
266
- if (options.strategy) params.push(`strategy=${options.strategy}`);
267
- if (options.chainId !== void 0) params.push(`chainId=${options.chainId}`);
268
- if (options.tokenSymbol) params.push(`tokenSymbol=${options.tokenSymbol}`);
269
- return params.length > 0 ? `/rebalance/rebalance-info?${params.join("&")}` : "/rebalance/rebalance-info";
270
- }
271
- };
272
-
273
- // src/utils/http-client.ts
274
- var HttpClient = class {
275
- /**
276
- * Create HTTP client for both Execution API and Data API
277
- *
278
- * @param apiKey - API key for both Execution API and Data API
279
- */
280
- constructor(apiKey) {
281
- this.authToken = null;
282
- this.apiKey = apiKey;
283
- const parsedUrl = new URL(API_ENDPOINT);
284
- this.origin = parsedUrl.origin;
285
- this.host = parsedUrl.host;
286
- this.client = import_axios.default.create({
287
- baseURL: `${API_ENDPOINT}${API_VERSION}`,
288
- headers: {
289
- "Content-Type": "application/json",
290
- "X-API-Key": this.apiKey
291
- },
292
- timeout: 3e4
293
- });
294
- this.dataClient = import_axios.default.create({
295
- baseURL: `${DATA_API_ENDPOINT}${DATA_API_VERSION}`,
296
- headers: {
297
- "Content-Type": "application/json",
298
- "X-API-Key": this.apiKey
299
- },
300
- timeout: 3e4
301
- });
302
- this.setupInterceptors();
303
- this.setupDataInterceptors();
304
- }
305
- setAuthToken(token) {
306
- this.authToken = token;
307
- }
308
- clearAuthToken() {
309
- this.authToken = null;
310
- }
311
- getOrigin() {
312
- return this.origin;
313
- }
314
- getHost() {
315
- return this.host;
316
- }
317
- setupInterceptors() {
318
- this.client.interceptors.request.use(
319
- (config) => {
320
- config.headers["X-API-Key"] = this.apiKey;
321
- if (this.authToken) {
322
- config.headers["Authorization"] = `Bearer ${this.authToken}`;
323
- }
324
- return config;
325
- },
326
- (error) => {
327
- return Promise.reject(error);
328
- }
329
- );
330
- this.client.interceptors.response.use(
331
- (response) => response,
332
- (error) => {
333
- if (error.response) {
334
- const status = error.response.status;
335
- const data = error.response.data;
336
- switch (status) {
337
- case 401:
338
- throw new Error("Unauthorized: Invalid API key");
339
- case 403:
340
- throw new Error("Forbidden: Access denied");
341
- case 404:
342
- throw new Error(
343
- `Not found: ${data.message || "Resource not found"}`
344
- );
345
- case 429:
346
- throw new Error("Rate limit exceeded. Please try again later.");
347
- case 500:
348
- throw new Error(data?.message || data?.error || "Internal server error. Please try again later.");
349
- default:
350
- throw new Error(data.message || "An error occurred");
351
- }
352
- } else if (error.request) {
353
- throw new Error("Network error: Unable to reach the server");
354
- } else {
355
- throw new Error(`Request error: ${error.message}`);
356
- }
357
- }
358
- );
359
- }
360
- async get(url, config) {
361
- const response = await this.client.get(url, config);
362
- return response.data;
363
- }
364
- async post(url, data, config) {
365
- const response = await this.client.post(url, data, config);
366
- return response.data;
367
- }
368
- async patch(url, data, config) {
369
- const response = await this.client.patch(url, data, config);
370
- return response.data;
371
- }
372
- async delete(url, config) {
373
- const response = await this.client.delete(url, config);
374
- return response.data;
375
- }
376
- // Data API methods (v2)
377
- async dataGet(url, config) {
378
- const response = await this.dataClient.get(url, config);
379
- return response.data;
380
- }
381
- async dataPost(url, data, config) {
382
- const response = await this.dataClient.post(url, data, config);
383
- return response.data;
384
- }
385
- /**
386
- * Make a POST request to Data API with a custom path (bypasses /api/v2 baseURL)
387
- * Useful for endpoints that don't follow the /api/v2 pattern
388
- *
389
- * @param path - API path (e.g., "/api/earnings/initialize")
390
- * @param data - Request body
391
- * @param config - Additional axios config
392
- */
393
- async dataPostCustom(path2, data, config) {
394
- const fullUrl = `${DATA_API_ENDPOINT}${path2}`;
395
- const headers = {
396
- "Content-Type": "application/json",
397
- "X-API-Key": this.apiKey,
398
- ...config?.headers
399
- };
400
- if (this.authToken) {
401
- headers["Authorization"] = `Bearer ${this.authToken}`;
402
- }
403
- const response = await import_axios.default.post(fullUrl, data, {
404
- ...config,
405
- headers,
406
- timeout: config?.timeout || 3e4
407
- });
408
- return response.data;
409
- }
410
- setupDataInterceptors() {
411
- this.dataClient.interceptors.request.use(
412
- (config) => {
413
- config.headers["X-API-Key"] = this.apiKey;
414
- if (this.authToken) {
415
- config.headers["Authorization"] = `Bearer ${this.authToken}`;
416
- }
417
- return config;
418
- },
419
- (error) => Promise.reject(error)
420
- );
421
- this.dataClient.interceptors.response.use(
422
- (response) => response,
423
- (error) => {
424
- if (error.response) {
425
- const status = error.response.status;
426
- const data = error.response.data;
427
- switch (status) {
428
- case 401:
429
- throw new Error("Unauthorized: Invalid API key");
430
- case 403:
431
- throw new Error("Forbidden: Access denied to data API");
432
- case 404:
433
- throw new Error(
434
- `Not found: ${data.message || data.error || "Resource not found"}`
435
- );
436
- case 429:
437
- throw new Error("Rate limit exceeded. Please try again later.");
438
- case 500:
439
- throw new Error(
440
- data.error || "Internal server error. Please try again later."
441
- );
442
- default:
443
- throw new Error(
444
- data.message || data.error || "An error occurred"
445
- );
446
- }
447
- } else if (error.request) {
448
- throw new Error("Network error: Unable to reach the data server");
449
- } else {
450
- throw new Error(`Request error: ${error.message}`);
451
- }
452
- }
453
- );
454
- }
455
- };
456
-
457
- // src/config/abis.ts
458
- var import_viem = require("viem");
459
- var ERC20_ABI = [
460
- {
461
- name: "transfer",
462
- type: "function",
463
- stateMutability: "nonpayable",
464
- inputs: [
465
- { name: "to", type: "address" },
466
- { name: "amount", type: "uint256" }
467
- ],
468
- outputs: [{ name: "", type: "bool" }]
469
- },
470
- {
471
- name: "approve",
472
- type: "function",
473
- stateMutability: "nonpayable",
474
- inputs: [
475
- { name: "spender", type: "address" },
476
- { name: "amount", type: "uint256" }
477
- ],
478
- outputs: [{ name: "", type: "bool" }]
479
- },
480
- {
481
- name: "allowance",
482
- type: "function",
483
- stateMutability: "view",
484
- inputs: [
485
- { name: "owner", type: "address" },
486
- { name: "spender", type: "address" }
487
- ],
488
- outputs: [{ name: "", type: "uint256" }]
489
- },
490
- {
491
- name: "balanceOf",
492
- type: "function",
493
- stateMutability: "view",
494
- inputs: [{ name: "account", type: "address" }],
495
- outputs: [{ name: "", type: "uint256" }]
496
- },
497
- {
498
- name: "decimals",
499
- type: "function",
500
- stateMutability: "view",
501
- inputs: [],
502
- outputs: [{ name: "", type: "uint8" }]
503
- },
504
- {
505
- name: "symbol",
506
- type: "function",
507
- stateMutability: "view",
508
- inputs: [],
509
- outputs: [{ name: "", type: "string" }]
510
- },
511
- {
512
- name: "name",
513
- type: "function",
514
- stateMutability: "view",
515
- inputs: [],
516
- outputs: [{ name: "", type: "string" }]
517
- },
518
- {
519
- name: "totalSupply",
520
- type: "function",
521
- stateMutability: "view",
522
- inputs: [],
523
- outputs: [{ name: "", type: "uint256" }]
524
- }
525
- ];
526
- var IDENTITY_REGISTRY_ABI = (0, import_viem.parseAbi)([
527
- "function register() external returns (uint256 agentId)",
528
- "function register(string tokenUri) external returns (uint256 agentId)",
529
- "function register(string tokenUri, (string key, bytes value)[] metadata) external returns (uint256 agentId)"
530
- ]);
531
- var IDENTITY_REGISTRY_ADDRESS = "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432";
532
- var VAULT_ABI = [
533
- {
534
- name: "deposit",
535
- type: "function",
536
- stateMutability: "nonpayable",
537
- inputs: [
538
- { name: "assets", type: "uint256" },
539
- { name: "receiver", type: "address" }
540
- ],
541
- outputs: [{ name: "shares", type: "uint256" }]
542
- },
543
- {
544
- name: "requestRedeem",
545
- type: "function",
546
- stateMutability: "nonpayable",
547
- inputs: [
548
- { name: "shares", type: "uint256" },
549
- { name: "controller", type: "address" },
550
- { name: "owner", type: "address" }
551
- ],
552
- outputs: [{ name: "requestId", type: "uint256" }]
553
- },
554
- {
555
- name: "claim",
556
- type: "function",
557
- stateMutability: "nonpayable",
558
- inputs: [{ name: "withdrawKey", type: "bytes32" }],
559
- outputs: [{ name: "assets", type: "uint256" }]
560
- },
561
- {
562
- name: "getWithdrawKey",
563
- type: "function",
564
- stateMutability: "view",
565
- inputs: [
566
- { name: "user", type: "address" },
567
- { name: "nonce", type: "uint256" }
568
- ],
569
- outputs: [{ name: "", type: "bytes32" }]
570
- },
571
- {
572
- name: "nonces",
573
- type: "function",
574
- stateMutability: "view",
575
- inputs: [{ name: "user", type: "address" }],
576
- outputs: [{ name: "", type: "uint256" }]
577
- },
578
- {
579
- name: "isClaimable",
580
- type: "function",
581
- stateMutability: "view",
582
- inputs: [{ name: "withdrawKey", type: "bytes32" }],
583
- outputs: [{ name: "", type: "bool" }]
584
- },
585
- {
586
- name: "isClaimed",
587
- type: "function",
588
- stateMutability: "view",
589
- inputs: [{ name: "withdrawKey", type: "bytes32" }],
590
- outputs: [{ name: "", type: "bool" }]
591
- },
592
- {
593
- name: "maxRequestRedeem",
594
- type: "function",
595
- stateMutability: "view",
596
- inputs: [{ name: "owner", type: "address" }],
597
- outputs: [{ name: "", type: "uint256" }]
598
- },
599
- {
600
- name: "balanceOf",
601
- type: "function",
602
- stateMutability: "view",
603
- inputs: [{ name: "account", type: "address" }],
604
- outputs: [{ name: "", type: "uint256" }]
605
- },
606
- {
607
- name: "symbol",
608
- type: "function",
609
- stateMutability: "view",
610
- inputs: [],
611
- outputs: [{ name: "", type: "string" }]
612
- }
613
- ];
614
- var VAULT_ADDRESS = "0xD580071c47d4a667858B5FafAb85BC9C609beC5D";
615
-
616
- // src/core/ZyfaiSDK.ts
617
- var import_accounts2 = require("viem/accounts");
618
- var import_viem5 = require("viem");
619
-
620
- // src/config/chains.ts
621
- var import_viem2 = require("viem");
622
- var import_chains = require("viem/chains");
623
- var import_viem3 = require("viem");
624
- var plasma = (0, import_viem3.defineChain)({
625
- id: 9745,
626
- name: "Plasma",
627
- nativeCurrency: {
628
- decimals: 18,
629
- name: "Plasma",
630
- symbol: "PLSM"
631
- },
632
- rpcUrls: {
633
- default: {
634
- http: ["https://rpc.plasma.to"]
635
- }
636
- },
637
- blockExplorers: {
638
- default: {
639
- name: "Plasma Explorer",
640
- url: "https://explorer.plasma.io"
641
- }
642
- }
643
- });
644
- var ASSET_CONFIGS = {
645
- USDC: {
646
- symbol: "USDC",
647
- assetType: "usdc",
648
- displayName: "USDC",
649
- icon: "/ai-dashboard/usdc-token.png",
650
- decimals: 6,
651
- tokenSymbols: ["USDC", "USDC.e", "USDT", "USDT0"],
652
- tokenSymbolsByChainId: {
653
- 8453: "USDC",
654
- 42161: "USDC",
655
- 9745: "USDT0",
656
- 146: "USDC.e",
657
- 1: "USDC"
658
- },
659
- addresses: {
660
- 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
661
- // Base
662
- 42161: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
663
- // Arbitrum
664
- 9745: "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb",
665
- // Plasma
666
- 146: "0x29219dd400f2bf60e5a23d13be72b486d4038894",
667
- // Sonic
668
- 59144: "0x176211869ca2b568f2a7d4ee941e073a821ee1ff",
669
- // Linea
670
- 1: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
671
- // Ethereum
672
- },
673
- enabled: true
674
- },
675
- WETH: {
676
- symbol: "WETH",
677
- assetType: "eth",
678
- displayName: "WETH",
679
- icon: "/ai-dashboard/eth-token.png",
680
- decimals: 18,
681
- tokenSymbols: ["WETH", "ETH"],
682
- tokenSymbolsByChainId: {
683
- 8453: "WETH",
684
- 42161: "WETH",
685
- 9745: "WETH",
686
- 146: "WETH",
687
- 59144: "WETH",
688
- 1: "WETH"
689
- },
690
- addresses: {
691
- 8453: "0x4200000000000000000000000000000000000006",
692
- // Base
693
- 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
694
- // Arbitrum
695
- 9745: "0x4200000000000000000000000000000000000006",
696
- // Plasma
697
- 146: "0x039e64f90d4199560e7533692f69448878db85c7",
698
- // Sonic
699
- 59144: "0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f",
700
- // Linea
701
- 1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
702
- // Ethereum
703
- },
704
- enabled: true
705
- }
706
- };
707
- var getDefaultTokenAddress = (chainId, asset) => {
708
- const address = ASSET_CONFIGS[asset || "USDC"]?.addresses[chainId];
709
- if (!address || address === "0x0000000000000000000000000000000000000000") {
710
- throw new Error(
711
- `Default token address not configured for chain ${chainId}. Please provide tokenAddress explicitly.`
712
- );
713
- }
714
- return address;
715
- };
716
- var DEFAULT_RPC_URLS = {
717
- 8453: "https://mainnet.base.org",
718
- 42161: "https://arb1.arbitrum.io/rpc",
719
- 9745: "https://rpc.plasma.to"
720
- };
721
- var CHAINS = {
722
- 8453: import_chains.base,
723
- 42161: import_chains.arbitrum,
724
- 9745: plasma
725
- };
726
- var getChainConfig = (chainId, rpcUrls) => {
727
- const chain = CHAINS[chainId];
728
- if (!chain) {
729
- throw new Error(`Unsupported chain ID: ${chainId}`);
730
- }
731
- const rpcUrl = rpcUrls && rpcUrls[chainId] || DEFAULT_RPC_URLS[chainId];
732
- const publicClient = (0, import_viem2.createPublicClient)({
733
- chain,
734
- transport: (0, import_viem2.http)(rpcUrl)
735
- });
736
- return {
737
- chain,
738
- rpcUrl,
739
- publicClient
740
- };
741
- };
742
- var isSupportedChain = (chainId) => {
743
- return chainId in CHAINS;
744
- };
745
-
746
- // src/utils/safe-account.ts
747
- var import_module_sdk = require("@rhinestone/module-sdk");
748
- var import_accounts = require("permissionless/accounts");
749
- var import_viem4 = require("viem");
750
- var import_account_abstraction = require("viem/account-abstraction");
751
- var SAFE_7579_ADDRESS = "0x7579EE8307284F293B1927136486880611F20002";
752
- var ERC7579_LAUNCHPAD_ADDRESS = "0x7579011aB74c46090561ea277Ba79D510c6C00ff";
753
- var ACCOUNT_SALT = "zyfai";
754
- var getSafeAccount = async (config) => {
755
- const { safeOwnerAddress, publicClient } = config;
756
- if (!safeOwnerAddress) {
757
- throw new Error("Safe owner address is required");
758
- }
759
- const formattedOwnerAddress = (0, import_viem4.getAddress)(safeOwnerAddress);
760
- const ownableValidator = (0, import_module_sdk.getOwnableValidator)({
761
- owners: [formattedOwnerAddress],
762
- threshold: 1
763
- });
764
- const saltHex = (0, import_viem4.fromHex)((0, import_viem4.toHex)(ACCOUNT_SALT), "bigint");
765
- const tempOwner = {
766
- address: formattedOwnerAddress,
767
- type: "json-rpc"
768
- };
769
- const safeAccount = await (0, import_accounts.toSafeSmartAccount)({
770
- client: publicClient,
771
- owners: [tempOwner],
772
- version: "1.4.1",
773
- entryPoint: {
774
- address: import_account_abstraction.entryPoint07Address,
775
- version: "0.7"
776
- },
777
- safe4337ModuleAddress: SAFE_7579_ADDRESS,
778
- erc7579LaunchpadAddress: ERC7579_LAUNCHPAD_ADDRESS,
779
- attesters: [import_module_sdk.RHINESTONE_ATTESTER_ADDRESS],
780
- attestersThreshold: 1,
781
- validators: [
782
- {
783
- address: ownableValidator.address,
784
- context: ownableValidator.initData
785
- }
786
- ],
787
- saltNonce: saltHex
788
- });
789
- return safeAccount;
790
- };
791
- var getDeterministicSafeAddress = async (config) => {
792
- try {
793
- const safeAccount = await getSafeAccount(config);
794
- return await safeAccount.getAddress();
795
- } catch (error) {
796
- throw new Error(
797
- `Failed to get deterministic Safe address: ${error.message}`
798
- );
799
- }
800
- };
801
- var isSafeDeployed = async (address, publicClient) => {
802
- try {
803
- const code = await publicClient.getCode({ address });
804
- return !!code && code !== "0x";
805
- } catch (error) {
806
- console.error("Error checking if Safe is deployed:", error);
807
- return false;
808
- }
809
- };
810
- var getAccountType = async (address, publicClient) => {
811
- try {
812
- const code = await publicClient.getCode({ address });
813
- if (!code || code === "0x" || code.length === 2) {
814
- return "EOA";
815
- }
816
- if (code.startsWith("0xef01")) {
817
- return "EOA";
818
- }
819
- try {
820
- const threshold = await publicClient.readContract({
821
- address,
822
- abi: [
823
- {
824
- inputs: [],
825
- name: "getThreshold",
826
- outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
827
- stateMutability: "view",
828
- type: "function"
829
- }
830
- ],
831
- functionName: "getThreshold"
832
- });
833
- if (threshold !== void 0) {
834
- return "Safe";
835
- }
836
- } catch {
837
- }
838
- return "Unknown";
839
- } catch (error) {
840
- console.error("Error checking account type:", error);
841
- return "Unknown";
842
- }
843
- };
844
- var deploySafeAccount = async (config) => {
845
- try {
846
- const { owner, httpClient, chainId, strategy = "safe_strategy" } = config;
847
- if (!owner || !owner.account) {
848
- throw new Error(
849
- "Wallet not connected. Please connect your wallet first."
850
- );
851
- }
852
- const prepareResponse = await httpClient.post(
853
- `${ENDPOINTS.SAFE_DEPLOY}?chainId=${chainId}`,
854
- { strategy }
855
- );
856
- if (!prepareResponse.userOpHashToSign) {
857
- throw new Error(
858
- "Backend did not return userOpHashToSign. Response: " + JSON.stringify(prepareResponse)
859
- );
860
- }
861
- const userOpHashToSign = prepareResponse.userOpHashToSign;
862
- const userOpSignature = await owner.signMessage({
863
- account: owner.account,
864
- message: { raw: userOpHashToSign }
865
- });
866
- const deployResponse = await httpClient.post(
867
- `${ENDPOINTS.SAFE_DEPLOY}?chainId=${chainId}`,
868
- { userOpSignature, strategy }
869
- );
870
- if (!deployResponse.success) {
871
- throw new Error(
872
- `Safe deployment failed: ${JSON.stringify(deployResponse)}`
873
- );
874
- }
875
- return {
876
- safeAddress: deployResponse.safeAddress || "0x",
877
- txHash: deployResponse.txHash,
878
- isDeployed: true
879
- };
880
- } catch (error) {
881
- throw new Error(
882
- `Failed to deploy Safe account: ${error.message}`
883
- );
884
- }
885
- };
886
- var signSessionKey = async (config, sessions, allPublicClients, signingParams) => {
887
- const { owner, publicClient } = config;
888
- if (!owner || !owner.account) {
889
- throw new Error("Wallet not connected. Please connect your wallet first.");
890
- }
891
- const safeAccount = await getSafeAccount(config);
892
- const account = (0, import_module_sdk.getAccount)({
893
- address: safeAccount.address,
894
- type: "safe"
895
- });
896
- const clients = allPublicClients || [publicClient];
897
- const sessionNonces = await Promise.all(
898
- sessions.map((session) => {
899
- const sessionChainId = Number(session.chainId);
900
- const client = clients.find((c) => c.chain?.id === sessionChainId) || publicClient;
901
- return (0, import_module_sdk.getSessionNonce)({
902
- client,
903
- account,
904
- permissionId: (0, import_module_sdk.getPermissionId)({
905
- session
906
- })
907
- });
908
- })
909
- );
910
- const sessionDetails = await (0, import_module_sdk.getEnableSessionDetails)({
911
- sessions,
912
- account,
913
- clients,
914
- permitGenericPolicy: signingParams?.permitGenericPolicy ?? true,
915
- sessionNonces,
916
- ignoreSecurityAttestations: signingParams?.ignoreSecurityAttestations ?? false
917
- });
918
- const signature = await owner.signMessage({
919
- account: owner.account,
920
- message: {
921
- raw: sessionDetails.permissionEnableHash
922
- }
923
- });
924
- return {
925
- signature,
926
- sessionNonces
927
- };
928
- };
929
-
930
- // src/utils/strategy.ts
931
- function toInternalStrategy(publicStrategy) {
932
- switch (publicStrategy) {
933
- case "conservative":
934
- return "safe_strategy";
935
- case "aggressive":
936
- return "degen_strategy";
937
- default:
938
- throw new Error(
939
- `Invalid public strategy: ${publicStrategy}. Must be "conservative" or "aggressive".`
940
- );
941
- }
942
- }
943
- function toPublicStrategy(internalStrategy) {
944
- if (internalStrategy === "safe_strategy" || internalStrategy === "safe") {
945
- return "conservative";
946
- }
947
- if (internalStrategy === "degen_strategy" || internalStrategy === "degen") {
948
- return "aggressive";
949
- }
950
- throw new Error(
951
- `Invalid internal strategy: ${internalStrategy}. Must be "safe_strategy" or "degen_strategy".`
952
- );
953
- }
954
- function isValidPublicStrategy(strategy) {
955
- return strategy === "conservative" || strategy === "aggressive";
956
- }
957
- function convertStrategyToPublic(obj) {
958
- if (!obj.strategy) {
959
- const result = { ...obj };
960
- if ("hasStaleBalance" in result) {
961
- delete result.hasStaleBalance;
962
- }
963
- return result;
964
- }
965
- try {
966
- const result = {
967
- ...obj,
968
- strategy: toPublicStrategy(
969
- obj.strategy
970
- )
971
- };
972
- if ("hasStaleBalance" in result) {
973
- delete result.hasStaleBalance;
974
- }
975
- if ("staleBalances" in result) {
976
- result.staleBalances = result.staleBalances?.filter((staleBalance) => staleBalance.balance !== "0x0");
977
- }
978
- return result;
979
- } catch {
980
- const result = { ...obj };
981
- if ("hasStaleBalance" in result) {
982
- delete result.hasStaleBalance;
983
- }
984
- return result;
985
- }
986
- }
987
- function removeUnusedFields(obj) {
988
- const result = { ...obj };
989
- if ("hasStaleBalance" in result) {
990
- delete result.hasStaleBalance;
991
- }
992
- return result;
993
- }
994
- function convertAssetInternally(asset) {
995
- if (asset === "USDC") {
996
- return "usdc";
997
- }
998
- if (asset === "WETH") {
999
- return "eth";
1000
- }
1001
- throw new Error(`Invalid asset: ${asset}. Must be "USDC" or "WETH".`);
1002
- }
1003
- function convertStrategiesToPublic(array) {
1004
- return array.map((item) => convertStrategyToPublic(item));
1005
- }
1006
- function convertStrategyToPublicAndNaming(obj) {
1007
- const result = { ...obj };
1008
- if (result.strategy) {
1009
- try {
1010
- result.strategy = toPublicStrategy(result.strategy);
1011
- } catch {
1012
- }
1013
- }
1014
- if (result.average_apy_without_fee !== void 0) {
1015
- result.average_apy_with_fee = result.average_apy_without_fee;
1016
- delete result.average_apy_without_fee;
1017
- }
1018
- if (result.average_apy_with_rzfi_without_fee !== void 0) {
1019
- result.average_apy_with_rzfi_with_fee = result.average_apy_with_rzfi_without_fee;
1020
- delete result.average_apy_with_rzfi_without_fee;
1021
- }
1022
- if (result.events_average_apy_without_fee !== void 0) {
1023
- result.events_average_apy_with_fee = result.events_average_apy_without_fee;
1024
- delete result.events_average_apy_without_fee;
1025
- }
1026
- if (result.events_average_apy_with_rzfi_without_fee !== void 0) {
1027
- result.events_average_apy_with_rzfi_with_fee = result.events_average_apy_with_rzfi_without_fee;
1028
- delete result.events_average_apy_with_rzfi_without_fee;
1029
- }
1030
- return result;
1031
- }
1032
- function convertStrategiesToPublicAndNaming(array) {
1033
- return array.map((item) => convertStrategyToPublicAndNaming(item));
1034
- }
1035
-
1036
- // src/core/ZyfaiSDK.ts
1037
- var import_siwe = require("siwe");
1038
- var _ZyfaiSDK = class _ZyfaiSDK {
1039
- constructor(config) {
1040
- this.signer = null;
1041
- this.walletClient = null;
1042
- this.authenticatedUserId = null;
1043
- this.hasActiveSessionKey = false;
1044
- this.currentProvider = null;
1045
- this.currentChainId = null;
1046
- const sdkConfig = typeof config === "string" ? { apiKey: config } : config;
1047
- const { apiKey, rpcUrls, referralSource } = sdkConfig;
1048
- if (!apiKey) {
1049
- throw new Error("API key is required");
1050
- }
1051
- this.httpClient = new HttpClient(apiKey);
1052
- this.rpcUrls = rpcUrls;
1053
- this.referralSource = referralSource;
1054
- }
1055
- /**
1056
- * Authenticate user with SIWE (Sign-In with Ethereum) & JWT token
1057
- * This is required for accessing user-specific endpoints like session-keys/config
1058
- * Uses the connected wallet address for authentication
1059
- *
1060
- * @returns Promise that resolves when authentication is complete
1061
- */
1062
- async authenticateUser() {
1063
- try {
1064
- if (this.authenticatedUserId !== null) {
1065
- return;
1066
- }
1067
- const walletClient = this.getWalletClient();
1068
- const userAddress = (0, import_viem5.getAddress)(walletClient.account.address);
1069
- const chainId = this.currentChainId || walletClient.chain?.id || 8453;
1070
- const challengeResponse = await this.httpClient.post(ENDPOINTS.AUTH_CHALLENGE, {});
1071
- let uri;
1072
- let domain;
1073
- const globalWindow = typeof globalThis !== "undefined" ? globalThis.window : void 0;
1074
- const isNodeJs = !globalWindow?.location?.origin;
1075
- if (globalWindow?.location?.origin) {
1076
- uri = globalWindow.location.origin;
1077
- domain = globalWindow.location.host;
1078
- } else {
1079
- uri = API_ENDPOINT;
1080
- domain = API_ENDPOINT.split("//")[1];
1081
- }
1082
- const messageObj = new import_siwe.SiweMessage({
1083
- address: userAddress,
1084
- chainId,
1085
- domain,
1086
- nonce: challengeResponse.nonce,
1087
- statement: "Sign in with Ethereum",
1088
- uri,
1089
- version: "1",
1090
- issuedAt: (/* @__PURE__ */ new Date()).toISOString()
1091
- });
1092
- const messageString = messageObj.prepareMessage();
1093
- const signature = await walletClient.signMessage({
1094
- account: walletClient.account,
1095
- message: messageString
1096
- });
1097
- const loginResponse = await this.httpClient.post(
1098
- ENDPOINTS.AUTH_LOGIN,
1099
- {
1100
- message: messageObj,
1101
- signature,
1102
- referralSource: this.referralSource
1103
- },
1104
- // Set Origin header in Node.js to match message.uri (required by backend validation)
1105
- isNodeJs ? {
1106
- headers: {
1107
- Origin: uri
1108
- }
1109
- } : void 0
1110
- );
1111
- const authToken = loginResponse.accessToken;
1112
- if (!authToken) {
1113
- throw new Error("Authentication response missing access token");
1114
- }
1115
- this.httpClient.setAuthToken(authToken);
1116
- this.authenticatedUserId = loginResponse.userId || null;
1117
- this.hasActiveSessionKey = loginResponse.hasActiveSessionKey || false;
1118
- } catch (error) {
1119
- throw new Error(
1120
- `Failed to authenticate user: ${error.message}`
1121
- );
1122
- }
1123
- }
1124
- /**
1125
- * Update user profile with Smart Wallet address and chain configuration
1126
- * This method requires SIWE authentication and is automatically called after deploySafe
1127
- *
1128
- * @param request - User profile update data
1129
- * @returns Updated user profile information
1130
- *
1131
- * @example
1132
- * ```typescript
1133
- * await sdk.updateUserProfile({
1134
- * smartWallet: "0x1396730...",
1135
- * chains: [8453],
1136
- * });
1137
- * ```
1138
- */
1139
- async updateUserProfile(request) {
1140
- try {
1141
- await this.authenticateUser();
1142
- const asset = request.asset || "USDC";
1143
- const internalAsset = convertAssetInternally(asset);
1144
- let rebalanceStrategy;
1145
- if (request.strategy) {
1146
- if (!isValidPublicStrategy(request.strategy)) {
1147
- throw new Error(
1148
- `Invalid strategy: ${request.strategy}. Must be "conservative" or "aggressive".`
1149
- );
1150
- }
1151
- rebalanceStrategy = toInternalStrategy(
1152
- request.strategy
1153
- );
1154
- }
1155
- const assetSettings = {};
1156
- if (rebalanceStrategy !== void 0) assetSettings.rebalanceStrategy = rebalanceStrategy;
1157
- if (request.autocompounding !== void 0) assetSettings.autocompounding = request.autocompounding;
1158
- if (request.crosschainStrategy !== void 0) assetSettings.crosschainStrategy = request.crosschainStrategy;
1159
- if (request.splitting !== void 0) assetSettings.splitting = request.splitting;
1160
- if (request.minSplits !== void 0) assetSettings.minSplits = request.minSplits;
1161
- if (request.chains !== void 0) assetSettings.chains = request.chains;
1162
- if (request.autoSelectProtocols !== void 0) assetSettings.autoSelectProtocols = request.autoSelectProtocols;
1163
- if (request.protocols !== void 0) assetSettings.protocols = request.protocols;
1164
- const payload = {
1165
- assetTypeSettings: {
1166
- [internalAsset]: assetSettings
1167
- }
1168
- };
1169
- if (request.omniAccount !== void 0) payload.omniAccount = request.omniAccount;
1170
- if (request.agentName !== void 0) payload.agentName = request.agentName;
1171
- await this.httpClient.patch(
1172
- ENDPOINTS.USER_ME,
1173
- payload
1174
- );
1175
- return await this.getUserDetails(asset);
1176
- } catch (error) {
1177
- throw new Error(
1178
- `Failed to update user profile: ${error.message}`
1179
- );
1180
- }
1181
- }
1182
- /**
1183
- * Pause the agent by clearing all protocols
1184
- * Sets the user's protocols to an empty array, effectively pausing automated operations
1185
- *
1186
- * @returns Response indicating success and updated user details
1187
- *
1188
- * @example
1189
- * ```typescript
1190
- * const sdk = new ZyfaiSDK({ apiKey: 'your-api-key' });
1191
- *
1192
- * // Connect account first
1193
- * await sdk.connectAccount();
1194
- *
1195
- * // Pause the agent
1196
- * const result = await sdk.pauseAgent();
1197
- * console.log('Agent paused:', result.success);
1198
- * ```
1199
- */
1200
- async pauseAgent() {
1201
- try {
1202
- await this.updateUserProfile({
1203
- asset: "USDC",
1204
- protocols: []
1205
- });
1206
- const response = await this.updateUserProfile({
1207
- asset: "WETH",
1208
- protocols: []
1209
- });
1210
- return response;
1211
- } catch (error) {
1212
- throw new Error(`Failed to pause agent: ${error.message}`);
1213
- }
1214
- }
1215
- /**
1216
- * Resume the agent by restoring protocols based on user's strategy for each asset
1217
- * Fetches available protocols and assigns them based on each asset's strategy
1218
- *
1219
- * @returns Response indicating success and updated user details
1220
- *
1221
- * @example
1222
- * ```typescript
1223
- * const sdk = new ZyfaiSDK({ apiKey: 'your-api-key' });
1224
- *
1225
- * // Connect account first
1226
- * await sdk.connectAccount();
1227
- *
1228
- * // Resume the agent
1229
- * const result = await sdk.resumeAgent();
1230
- * console.log('Agent resumed:', result.success);
1231
- * ```
1232
- */
1233
- async resumeAgent() {
1234
- try {
1235
- const userDetailsUSDC = await this.getUserDetails("USDC");
1236
- const userDetailsETH = await this.getUserDetails("WETH");
1237
- const chains = userDetailsUSDC.chains && userDetailsUSDC.chains.length > 0 ? userDetailsUSDC.chains : [8453, 42161];
1238
- const allProtocols = await this.httpClient.get(
1239
- ENDPOINTS.PROTOCOLS()
1240
- );
1241
- const usdcStrategy = userDetailsUSDC.strategy || "safe_strategy";
1242
- const ethStrategy = userDetailsETH.strategy || "safe_strategy";
1243
- const filterProtocolsByStrategy = (strategy) => {
1244
- return allProtocols.filter((protocol) => {
1245
- const hasMatchingChain = protocol.chains.some(
1246
- (chain) => chains.includes(chain)
1247
- );
1248
- if (!hasMatchingChain) {
1249
- return false;
1250
- }
1251
- if (strategy === "degen_strategy") {
1252
- return protocol.strategies?.includes("safe_strategy") || protocol.strategies?.includes("degen_strategy");
1253
- }
1254
- return protocol.strategies?.includes("safe_strategy");
1255
- }).map((protocol) => protocol.id);
1256
- };
1257
- const usdcProtocols = filterProtocolsByStrategy(usdcStrategy);
1258
- const ethProtocols = filterProtocolsByStrategy(ethStrategy);
1259
- await this.updateUserProfile({
1260
- asset: "USDC",
1261
- protocols: usdcProtocols
1262
- });
1263
- const updatedUserDetailsETH = await this.updateUserProfile({
1264
- asset: "WETH",
1265
- protocols: ethProtocols
1266
- });
1267
- return updatedUserDetailsETH;
1268
- } catch (error) {
1269
- throw new Error(`Failed to resume agent: ${error.message}`);
1270
- }
1271
- }
1272
- /**
1273
- * Enable splitting for the user's account
1274
- * When enabled, deposits are split across multiple protocols based on minSplits setting
1275
- *
1276
- * @param minSplits - Optional minimum number of protocols to split across (default: 3)
1277
- * @returns Response indicating success and updated user details
1278
- *
1279
- * @example
1280
- * ```typescript
1281
- * const sdk = new ZyfaiSDK({ apiKey: 'your-api-key' });
1282
- *
1283
- * // Connect account first
1284
- * await sdk.connectAccount(privateKey, chainId);
1285
- *
1286
- * // Enable splitting with minimum 3 protocols
1287
- * const result = await sdk.enableSplitting(3);
1288
- * console.log('Splitting enabled:', result.success);
1289
- * ```
1290
- */
1291
- async enableSplitting(minSplits = 1) {
1292
- if (minSplits > 4) {
1293
- throw new Error("minSplits cannot exceed 4");
1294
- }
1295
- try {
1296
- const response = await this.updateUserProfile({
1297
- splitting: true,
1298
- minSplits
1299
- });
1300
- return response;
1301
- } catch (error) {
1302
- throw new Error(`Failed to enable splitting: ${error.message}`);
1303
- }
1304
- }
1305
- /**
1306
- * Disable splitting for the user's account
1307
- * When disabled, deposits will not be split across multiple protocols
1308
- *
1309
- * @returns Response indicating success and updated user details
1310
- *
1311
- * @example
1312
- * ```typescript
1313
- * const sdk = new ZyfaiSDK({ apiKey: 'your-api-key' });
1314
- *
1315
- * // Connect account first
1316
- * await sdk.connectAccount(privateKey, chainId);
1317
- *
1318
- * // Disable splitting
1319
- * const result = await sdk.disableSplitting();
1320
- * console.log('Splitting disabled:', result.success);
1321
- * ```
1322
- */
1323
- async disableSplitting() {
1324
- try {
1325
- const response = await this.updateUserProfile({
1326
- splitting: false
1327
- });
1328
- return response;
1329
- } catch (error) {
1330
- throw new Error(`Failed to disable splitting: ${error.message}`);
1331
- }
1332
- }
1333
- /**
1334
- * Update the minimum number of splits for the user's account
1335
- * This controls across how many protocols deposits should be distributed
1336
- *
1337
- * @param minSplits - Minimum number of protocols to split across
1338
- * @returns Response indicating success and updated user details
1339
- *
1340
- * @example
1341
- * ```typescript
1342
- * const sdk = new ZyfaiSDK({ apiKey: 'your-api-key' });
1343
- *
1344
- * // Connect account first
1345
- * await sdk.connectAccount(privateKey, chainId);
1346
- *
1347
- * // Update minimum splits to 5
1348
- * const result = await sdk.updateMinSplits(5);
1349
- * console.log('Min splits updated:', result.success);
1350
- * ```
1351
- */
1352
- async updateMinSplits(minSplits) {
1353
- try {
1354
- if (minSplits < 1) {
1355
- throw new Error("minSplits must be at least 1");
1356
- }
1357
- const response = await this.updateUserProfile({
1358
- minSplits
1359
- });
1360
- return response;
1361
- } catch (error) {
1362
- throw new Error(`Failed to update min splits: ${error.message}`);
1363
- }
1364
- }
1365
- /**
1366
- * Initialize user after Safe deployment
1367
- * This method is automatically called after deploySafe to initialize user state
1368
- *
1369
- * @param smartWallet - Safe smart wallet address
1370
- * @param chainId - Target chain ID
1371
- * @returns Initialization response
1372
- *
1373
- * @example
1374
- * ```typescript
1375
- * await sdk.initializeUser("0x1396730...", 8453);
1376
- * ```
1377
- * @internal
1378
- */
1379
- async initializeUser(smartWallet, chainId) {
1380
- try {
1381
- await this.authenticateUser();
1382
- const responseData = await this.httpClient.dataPostCustom(
1383
- DATA_ENDPOINTS.USER_INITIALIZE,
1384
- {
1385
- walletAddress: smartWallet
1386
- }
1387
- );
1388
- return {
1389
- success: responseData.status === "success" || true,
1390
- userId: responseData.userId || responseData.id,
1391
- smartWallet: responseData.smartWallet || smartWallet,
1392
- chainId: responseData.chainId || chainId,
1393
- message: responseData.message || responseData.status || "User initialized successfully"
1394
- };
1395
- } catch (error) {
1396
- throw new Error(`Failed to initialize user: ${error.message}`);
1397
- }
1398
- }
1399
- /**
1400
- * Handle account changes from wallet provider
1401
- * Resets authentication state when wallet is switched
1402
- * @private
1403
- */
1404
- async handleAccountsChanged(accounts) {
1405
- if (!accounts || accounts.length === 0) {
1406
- await this.disconnectAccount();
1407
- return;
1408
- }
1409
- const newAddress = accounts[0];
1410
- const currentAddress = this.walletClient?.account?.address;
1411
- if (currentAddress && newAddress.toLowerCase() === currentAddress.toLowerCase()) {
1412
- return;
1413
- }
1414
- this.authenticatedUserId = null;
1415
- this.hasActiveSessionKey = false;
1416
- this.httpClient.clearAuthToken();
1417
- if (this.walletClient && this.currentProvider) {
1418
- const chainConfig = getChainConfig(
1419
- this.walletClient.chain?.id || 8453,
1420
- this.rpcUrls
1421
- );
1422
- this.walletClient = (0, import_viem5.createWalletClient)({
1423
- account: newAddress,
1424
- chain: chainConfig.chain,
1425
- transport: (0, import_viem5.custom)(this.currentProvider)
1426
- });
1427
- this.currentChainId = this.walletClient.chain?.id || null;
1428
- try {
1429
- await this.authenticateUser();
1430
- } catch (error) {
1431
- console.warn(
1432
- "Failed to authenticate after wallet switch:",
1433
- error.message
1434
- );
1435
- }
1436
- }
1437
- }
1438
- /**
1439
- * Connect account for signing transactions
1440
- * Accepts either a private key string or a modern wallet provider
1441
- *
1442
- * @param account - Private key string or wallet provider object
1443
- * @param chainId - Target chain ID (default: 8453 - Base)
1444
- * @returns The connected EOA address
1445
- *
1446
- * @example
1447
- * // With private key
1448
- * await sdk.connectAccount('0x...');
1449
- *
1450
- * @example
1451
- * // With wallet provider (e.g., from wagmi, web3-react, etc.)
1452
- * const provider = await connector.getProvider();
1453
- * await sdk.connectAccount(provider);
1454
- */
1455
- async connectAccount(account, chainId = 8453) {
1456
- if (!isSupportedChain(chainId)) {
1457
- throw new Error(`Unsupported chain ID: ${chainId}`);
1458
- }
1459
- this.authenticatedUserId = null;
1460
- this.currentChainId = null;
1461
- this.httpClient.clearAuthToken();
1462
- if (this.currentProvider?.removeAllListeners) {
1463
- try {
1464
- this.currentProvider.removeAllListeners("accountsChanged");
1465
- } catch (error) {
1466
- }
1467
- }
1468
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
1469
- let connectedAddress;
1470
- if (typeof account === "string") {
1471
- let privateKey = account;
1472
- if (!privateKey.startsWith("0x")) {
1473
- privateKey = `0x${privateKey}`;
1474
- }
1475
- this.signer = (0, import_accounts2.privateKeyToAccount)(privateKey);
1476
- this.currentChainId = chainId;
1477
- this.walletClient = (0, import_viem5.createWalletClient)({
1478
- account: this.signer,
1479
- chain: chainConfig.chain,
1480
- transport: (0, import_viem5.http)(chainConfig.rpcUrl)
1481
- });
1482
- connectedAddress = this.signer.address;
1483
- this.currentProvider = null;
1484
- } else {
1485
- const provider = account;
1486
- if (!provider) {
1487
- throw new Error(
1488
- "Invalid account parameter. Expected private key string or wallet provider."
1489
- );
1490
- }
1491
- if (provider.request) {
1492
- const accounts = await provider.request({
1493
- method: "eth_requestAccounts"
1494
- });
1495
- if (!accounts || accounts.length === 0) {
1496
- throw new Error("No accounts found in wallet provider");
1497
- }
1498
- this.walletClient = (0, import_viem5.createWalletClient)({
1499
- account: accounts[0],
1500
- chain: chainConfig.chain,
1501
- transport: (0, import_viem5.custom)(provider)
1502
- });
1503
- connectedAddress = accounts[0];
1504
- this.currentProvider = provider;
1505
- this.currentChainId = chainId;
1506
- if (provider.on) {
1507
- provider.on("accountsChanged", this.handleAccountsChanged.bind(this));
1508
- }
1509
- } else if (provider.account && provider.transport) {
1510
- this.walletClient = (0, import_viem5.createWalletClient)({
1511
- account: provider.account,
1512
- chain: chainConfig.chain,
1513
- transport: provider.transport
1514
- });
1515
- connectedAddress = provider.account.address;
1516
- this.currentProvider = null;
1517
- this.currentChainId = chainId;
1518
- } else {
1519
- throw new Error(
1520
- "Invalid wallet provider. Expected EIP-1193 provider or viem WalletClient."
1521
- );
1522
- }
1523
- }
1524
- await this.authenticateUser();
1525
- return connectedAddress;
1526
- }
1527
- /**
1528
- * Disconnect account and clear authentication state
1529
- * Resets wallet connection, JWT token, and all authentication-related state
1530
- *
1531
- * @example
1532
- * ```typescript
1533
- * await sdk.disconnectAccount();
1534
- * console.log("Account disconnected");
1535
- * ```
1536
- */
1537
- async disconnectAccount() {
1538
- if (this.currentProvider?.removeAllListeners) {
1539
- try {
1540
- this.currentProvider.removeAllListeners("accountsChanged");
1541
- } catch (error) {
1542
- }
1543
- }
1544
- this.signer = null;
1545
- this.walletClient = null;
1546
- this.currentProvider = null;
1547
- this.currentChainId = null;
1548
- this.authenticatedUserId = null;
1549
- this.hasActiveSessionKey = false;
1550
- this.httpClient.clearAuthToken();
1551
- }
1552
- /**
1553
- * Get wallet client (throws if not connected)
1554
- * @private
1555
- */
1556
- getWalletClient(chainId) {
1557
- if (this.signer) {
1558
- const targetChainId = chainId || this.currentChainId || 8453;
1559
- const targetChainConfig = getChainConfig(
1560
- targetChainId,
1561
- this.rpcUrls
1562
- );
1563
- return (0, import_viem5.createWalletClient)({
1564
- account: this.signer,
1565
- chain: targetChainConfig.chain,
1566
- transport: (0, import_viem5.http)(targetChainConfig.rpcUrl)
1567
- });
1568
- } else {
1569
- if (!this.walletClient) {
1570
- throw new Error("No account connected. Call connectAccount() first");
1571
- }
1572
- return this.walletClient;
1573
- }
1574
- }
1575
- /**
1576
- * Get smart wallet address for a user
1577
- * Returns the deterministic Safe address for an EOA, or the address itself if already a Safe
1578
- *
1579
- * @param userAddress - User's EOA address
1580
- * @param chainId - Target chain ID
1581
- * @returns Smart wallet information including address and deployment status
1582
- */
1583
- async getSmartWalletAddress(userAddress, chainId) {
1584
- if (!userAddress) {
1585
- throw new Error("User address is required");
1586
- }
1587
- if (!isSupportedChain(chainId)) {
1588
- throw new Error(`Unsupported chain ID: ${chainId}`);
1589
- }
1590
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
1591
- try {
1592
- const smartWalletInfo = await this.getSmartWalletByEOA(userAddress);
1593
- if (smartWalletInfo.smartWallet) {
1594
- const isDeployed2 = await isSafeDeployed(
1595
- smartWalletInfo.smartWallet,
1596
- chainConfig.publicClient
1597
- );
1598
- return {
1599
- address: smartWalletInfo.smartWallet,
1600
- isDeployed: isDeployed2
1601
- };
1602
- }
1603
- } catch {
1604
- }
1605
- const safeAddress = await getDeterministicSafeAddress({
1606
- safeOwnerAddress: userAddress,
1607
- chain: chainConfig.chain,
1608
- publicClient: chainConfig.publicClient
1609
- });
1610
- const isDeployed = await isSafeDeployed(
1611
- safeAddress,
1612
- chainConfig.publicClient
1613
- );
1614
- return {
1615
- address: safeAddress,
1616
- isDeployed
1617
- };
1618
- }
1619
- /**
1620
- * Deploy Safe Smart Wallet for a user
1621
- *
1622
- * @param userAddress - User's EOA address (the connected EOA, not the smart wallet address)
1623
- * @param chainId - Target chain ID
1624
- * @param strategy - Optional strategy selection: "conservative" (default) or "aggressive"
1625
- * @param createSessionKey - If true, automatically creates a session key after deployment (default: false)
1626
- * @returns Deployment response with Safe address and transaction hash
1627
- *
1628
- * @example
1629
- * ```typescript
1630
- * // Deploy with default conservative strategy
1631
- * await sdk.deploySafe(userAddress, 8453);
1632
- *
1633
- * // Deploy with aggressive strategy
1634
- * await sdk.deploySafe(userAddress, 8453, "aggressive");
1635
- *
1636
- * // Deploy and automatically create session key
1637
- * await sdk.deploySafe(userAddress, 8453, "conservative", true);
1638
- * ```
1639
- */
1640
- async deploySafe(userAddress, chainId, strategy, createSessionKey) {
1641
- try {
1642
- if (!userAddress) {
1643
- throw new Error("User address is required");
1644
- }
1645
- if (!isSupportedChain(chainId)) {
1646
- throw new Error(`Unsupported chain ID: ${chainId}`);
1647
- }
1648
- await this.authenticateUser();
1649
- const walletClient = this.getWalletClient(chainId);
1650
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
1651
- const safeAddress = await getDeterministicSafeAddress({
1652
- safeOwnerAddress: userAddress,
1653
- chain: chainConfig.chain,
1654
- publicClient: chainConfig.publicClient
1655
- });
1656
- const alreadyDeployed = await isSafeDeployed(
1657
- safeAddress,
1658
- chainConfig.publicClient
1659
- );
1660
- if (!alreadyDeployed) {
1661
- const accountType = await getAccountType(
1662
- userAddress,
1663
- chainConfig.publicClient
1664
- );
1665
- if (accountType !== "EOA") {
1666
- throw new Error(
1667
- `Address ${userAddress} is not an EOA. Only EOA addresses can deploy Safe smart wallets.`
1668
- );
1669
- }
1670
- }
1671
- if (alreadyDeployed) {
1672
- let sessionKeyCreated2 = false;
1673
- if (createSessionKey) {
1674
- try {
1675
- await this.createSessionKey(userAddress, chainId);
1676
- sessionKeyCreated2 = true;
1677
- } catch (sessionKeyError) {
1678
- console.warn(
1679
- "Failed to create session key:",
1680
- sessionKeyError.message
1681
- );
1682
- }
1683
- }
1684
- return {
1685
- success: true,
1686
- safeAddress,
1687
- txHash: "0x0",
1688
- status: "deployed",
1689
- sessionKeyCreated: sessionKeyCreated2
1690
- };
1691
- }
1692
- const internalStrategy = strategy ? toInternalStrategy(strategy) : "safe_strategy";
1693
- const deploymentResult = await deploySafeAccount({
1694
- owner: walletClient,
1695
- safeOwnerAddress: userAddress,
1696
- chain: chainConfig.chain,
1697
- publicClient: chainConfig.publicClient,
1698
- chainId,
1699
- httpClient: this.httpClient,
1700
- strategy: internalStrategy
1701
- });
1702
- this.hasActiveSessionKey = false;
1703
- try {
1704
- await this.initializeUser(deploymentResult.safeAddress, chainId);
1705
- } catch (initError) {
1706
- console.warn(
1707
- "Failed to initialize user after Safe deployment:",
1708
- initError.message
1709
- );
1710
- }
1711
- let sessionKeyCreated = false;
1712
- if (createSessionKey) {
1713
- try {
1714
- await this.createSessionKey(userAddress, chainId);
1715
- sessionKeyCreated = true;
1716
- } catch (sessionKeyError) {
1717
- console.warn(
1718
- "Failed to create session key after Safe deployment:",
1719
- sessionKeyError.message
1720
- );
1721
- }
1722
- }
1723
- return {
1724
- success: true,
1725
- safeAddress: deploymentResult.safeAddress,
1726
- txHash: deploymentResult.txHash || "0x0",
1727
- status: "deployed",
1728
- sessionKeyCreated
1729
- };
1730
- } catch (error) {
1731
- console.error("Safe deployment failed:", error);
1732
- throw new Error(`Safe deployment failed: ${error.message}`);
1733
- }
1734
- }
1735
- /**
1736
- * Create session key with auto-fetched configuration from Zyfai API
1737
- * This is the simplified method that automatically fetches session configuration
1738
- *
1739
- * @param userAddress - User's EOA or Safe address
1740
- * @param chainId - Target chain ID
1741
- * @returns Session key response with signature and nonces
1742
- *
1743
- * @example
1744
- * ```typescript
1745
- * // Simple usage - no need to configure sessions manually
1746
- * const result = await sdk.createSessionKey(userAddress, 8453);
1747
- * console.log("Session created:", result.signature);
1748
- * ```
1749
- */
1750
- async createSessionKey(userAddress, chainId) {
1751
- try {
1752
- await this.authenticateUser();
1753
- if (!this.authenticatedUserId) {
1754
- throw new Error(
1755
- "User ID not available. Please ensure authentication completed successfully."
1756
- );
1757
- }
1758
- if (this.hasActiveSessionKey) {
1759
- return {
1760
- success: true,
1761
- userId: this.authenticatedUserId,
1762
- message: "Session key already exists and is active",
1763
- alreadyActive: true
1764
- };
1765
- }
1766
- const sessionConfigResponse = await this.httpClient.get(
1767
- ENDPOINTS.SESSION_KEYS_CONFIG
1768
- );
1769
- const sessionConfig = Array.isArray(sessionConfigResponse) ? sessionConfigResponse : sessionConfigResponse.sessions;
1770
- if (!sessionConfig || sessionConfig.length === 0) {
1771
- throw new Error("No session configuration available from API");
1772
- }
1773
- const sessions = sessionConfig.map((session) => ({
1774
- ...session,
1775
- chainId: BigInt(session.chainId)
1776
- }));
1777
- const DEFAULT_ACTION_TARGET = "0x0000000000000000000000000000000000000001";
1778
- const DEFAULT_ACTION_SELECTOR = "0x00000001";
1779
- const permitGenericPolicy = sessionConfig.some(
1780
- (session) => session.actions?.some(
1781
- (action) => action.actionTarget === DEFAULT_ACTION_TARGET && action.actionTargetSelector === DEFAULT_ACTION_SELECTOR
1782
- )
1783
- );
1784
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
1785
- const accountType = await getAccountType(
1786
- userAddress,
1787
- chainConfig.publicClient
1788
- );
1789
- const signingParams = {
1790
- permitGenericPolicy,
1791
- ignoreSecurityAttestations: accountType === "Safe"
1792
- };
1793
- const signatureResult = await this.signSessionKey(
1794
- userAddress,
1795
- chainId,
1796
- sessions,
1797
- signingParams
1798
- );
1799
- if (!signatureResult.signature) {
1800
- throw new Error("Failed to obtain session key signature");
1801
- }
1802
- await this.updateUserProtocols(chainId);
1803
- const signer = sessions[0].sessionValidator;
1804
- console.log("Session validator:", signer);
1805
- const activation = await this.activateSessionKey(
1806
- signer,
1807
- signatureResult.signature,
1808
- signatureResult.sessionNonces
1809
- );
1810
- this.hasActiveSessionKey = true;
1811
- return {
1812
- ...signatureResult,
1813
- userId: this.authenticatedUserId,
1814
- sessionActivation: activation
1815
- };
1816
- } catch (error) {
1817
- throw new Error(
1818
- `Failed to create session key: ${error.message}`
1819
- );
1820
- }
1821
- }
1822
- /**
1823
- * Internal method to sign session key
1824
- * @private
1825
- */
1826
- async signSessionKey(userAddress, chainId, sessions, signingParams) {
1827
- try {
1828
- if (!userAddress) {
1829
- throw new Error("User address is required");
1830
- }
1831
- if (!isSupportedChain(chainId)) {
1832
- throw new Error(`Unsupported chain ID: ${chainId}`);
1833
- }
1834
- if (!sessions || sessions.length === 0) {
1835
- throw new Error("At least one session configuration is required");
1836
- }
1837
- const walletClient = this.getWalletClient();
1838
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
1839
- const accountType = await getAccountType(
1840
- userAddress,
1841
- chainConfig.publicClient
1842
- );
1843
- if (accountType !== "EOA") {
1844
- throw new Error(
1845
- `Invalid account type for ${userAddress}. Must be an EOA.`
1846
- );
1847
- }
1848
- const sessionChainIds = [
1849
- ...new Set(sessions.map((s) => Number(s.chainId)))
1850
- ];
1851
- const allPublicClients = sessionChainIds.filter(isSupportedChain).map((id) => getChainConfig(id, this.rpcUrls).publicClient);
1852
- const { signature, sessionNonces } = await signSessionKey(
1853
- {
1854
- owner: walletClient,
1855
- safeOwnerAddress: userAddress,
1856
- chain: chainConfig.chain,
1857
- publicClient: chainConfig.publicClient
1858
- },
1859
- sessions,
1860
- allPublicClients,
1861
- signingParams
1862
- );
1863
- return {
1864
- success: true,
1865
- signature,
1866
- sessionNonces
1867
- };
1868
- } catch (error) {
1869
- throw new Error(
1870
- `Failed to sign session key: ${error.message}`
1871
- );
1872
- }
1873
- }
1874
- /**
1875
- * Update user protocols with available protocols from the chain
1876
- * This method is automatically called before activating session key
1877
- *
1878
- * @param chainId - Target chain ID
1879
- * @internal
1880
- */
1881
- async updateUserProtocols(chainId) {
1882
- try {
1883
- const protocolsResponse = await this.getAvailableProtocols(chainId);
1884
- const userDetails = await this.getUserDetails();
1885
- const filteredProtocols = userDetails.strategy ? protocolsResponse.protocols.filter((p) => p.strategies?.includes(userDetails.strategy)) : protocolsResponse.protocols;
1886
- if (!filteredProtocols || filteredProtocols.length === 0) {
1887
- console.warn(`No protocols available for chain ${chainId}`);
1888
- return;
1889
- }
1890
- const protocolIds = filteredProtocols.map((p) => p.id);
1891
- await this.updateUserProfile({
1892
- protocols: protocolIds
1893
- });
1894
- await this.updateUserProfile({
1895
- protocols: protocolIds,
1896
- asset: "WETH"
1897
- });
1898
- } catch (error) {
1899
- console.warn(
1900
- `Failed to update user protocols: ${error.message}`
1901
- );
1902
- }
1903
- }
1904
- /**
1905
- * Activate session key via Zyfai API
1906
- */
1907
- async activateSessionKey(signer, signature, sessionNonces) {
1908
- const nonces = this.normalizeSessionNonces(sessionNonces);
1909
- const payload = {
1910
- signer,
1911
- hash: signature,
1912
- nonces
1913
- };
1914
- return await this.httpClient.post(
1915
- ENDPOINTS.SESSION_KEYS_ADD,
1916
- payload
1917
- );
1918
- }
1919
- /**
1920
- * Convert session nonces from bigint[] to number[]
1921
- */
1922
- normalizeSessionNonces(sessionNonces) {
1923
- if (!sessionNonces || sessionNonces.length === 0) {
1924
- throw new Error(
1925
- "Session nonces missing from signature result. Cannot register session key."
1926
- );
1927
- }
1928
- return sessionNonces.map((nonce) => {
1929
- const value = Number(nonce);
1930
- if (!Number.isFinite(value) || value < 0) {
1931
- throw new Error(`Invalid session nonce value: ${nonce.toString()}`);
1932
- }
1933
- return value;
1934
- });
1935
- }
1936
- /**
1937
- * Deposit funds from EOA to Safe smart wallet
1938
- * Transfers tokens from the connected wallet to the user's Safe and logs the deposit
1939
- *
1940
- * Token is automatically selected based on chain:
1941
- * - Base (8453) and Arbitrum (42161): USDC
1942
- * - Plasma (9745): USDT
1943
- *
1944
- * @param userAddress - User's address (owner of the Safe)
1945
- * @param chainId - Target chain ID
1946
- * @param amount - Amount in least decimal units (e.g., "100000000" for 100 USDC with 6 decimals)
1947
- * @returns Deposit response with transaction hash
1948
- *
1949
- * @example
1950
- * ```typescript
1951
- * // Deposit 100 USDC (6 decimals) to Safe on Base
1952
- * const result = await sdk.depositFunds(
1953
- * "0xUser...",
1954
- * 8453,
1955
- * "100000000" // 100 USDC = 100 * 10^6
1956
- * );
1957
- * ```
1958
- */
1959
- async depositFunds(userAddress, chainId, amount, asset) {
1960
- try {
1961
- if (!userAddress) {
1962
- throw new Error("User address is required");
1963
- }
1964
- if (!isSupportedChain(chainId)) {
1965
- throw new Error(`Unsupported chain ID: ${chainId}`);
1966
- }
1967
- if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) {
1968
- throw new Error("Valid amount is required");
1969
- }
1970
- const token = getDefaultTokenAddress(chainId, asset);
1971
- const walletClient = this.getWalletClient();
1972
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
1973
- const safeAddress = await getDeterministicSafeAddress({
1974
- safeOwnerAddress: userAddress,
1975
- chain: chainConfig.chain,
1976
- publicClient: chainConfig.publicClient
1977
- });
1978
- const isDeployed = await isSafeDeployed(
1979
- safeAddress,
1980
- chainConfig.publicClient
1981
- );
1982
- if (!isDeployed) {
1983
- throw new Error(
1984
- `Safe not deployed for ${userAddress}. Please deploy the Safe first using deploySafe().`
1985
- );
1986
- }
1987
- const amountBigInt = BigInt(amount);
1988
- const txHash = await walletClient.writeContract({
1989
- address: token,
1990
- abi: ERC20_ABI,
1991
- functionName: "transfer",
1992
- args: [safeAddress, amountBigInt],
1993
- chain: chainConfig.chain,
1994
- account: walletClient.account
1995
- });
1996
- const receipt = await chainConfig.publicClient.waitForTransactionReceipt({
1997
- hash: txHash
1998
- });
1999
- try {
2000
- await this.httpClient.post(ENDPOINTS.LOG_DEPOSIT, {
2001
- chainId,
2002
- transaction: txHash,
2003
- token,
2004
- amount
2005
- });
2006
- } catch (logError) {
2007
- console.warn("Failed to log deposit:", logError.message);
2008
- }
2009
- if (receipt.status !== "success") {
2010
- throw new Error("Deposit transaction failed");
2011
- }
2012
- return {
2013
- success: true,
2014
- txHash,
2015
- smartWallet: safeAddress,
2016
- amount: amountBigInt.toString()
2017
- };
2018
- } catch (error) {
2019
- throw new Error(`Deposit failed: ${error.message}`);
2020
- }
2021
- }
2022
- /**
2023
- * Log a deposit that was executed client-side
2024
- *
2025
- * Use this method when you execute the deposit transaction yourself (e.g., with Privy,
2026
- * sponsored transactions, or any custom wallet implementation) and need to register
2027
- * the deposit with the Zyfai backend for tracking and yield optimization.
2028
- *
2029
- * This is useful for partners who:
2030
- * - Use sponsored/gasless transactions (Privy, Biconomy, etc.)
2031
- * - Have custom wallet implementations
2032
- * - Need more control over transaction execution
2033
- *
2034
- * Token is automatically selected based on chain if not provided:
2035
- * - Base (8453) and Arbitrum (42161): USDC
2036
- * - Plasma (9745): USDT
2037
- *
2038
- * @param chainId - Chain ID where the deposit was made
2039
- * @param txHash - Transaction hash of the deposit
2040
- * @param amount - Amount in least decimal units (e.g., "100000000" for 100 USDC with 6 decimals)
2041
- * @param tokenAddress - Optional: Token address (auto-selected based on chain if not provided)
2042
- * @returns Log deposit response with success status
2043
- *
2044
- * @example
2045
- * ```typescript
2046
- * // Execute deposit with Privy (sponsored transaction)
2047
- * const txHash = await privyWallet.sendTransaction({
2048
- * to: safeAddress,
2049
- * data: transferData,
2050
- * });
2051
- *
2052
- * // Log the deposit to Zyfai backend
2053
- * const result = await sdk.logDeposit(
2054
- * 8453, // chainId
2055
- * txHash, // transaction hash from Privy
2056
- * "100000000" // 100 USDC
2057
- * );
2058
- * console.log(result.success); // true
2059
- * ```
2060
- */
2061
- async logDeposit(chainId, txHash, amount, tokenAddress) {
2062
- try {
2063
- if (!isSupportedChain(chainId)) {
2064
- throw new Error(`Unsupported chain ID: ${chainId}`);
2065
- }
2066
- if (!txHash || !txHash.startsWith("0x")) {
2067
- throw new Error("Valid transaction hash is required");
2068
- }
2069
- if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) {
2070
- throw new Error("Valid amount is required");
2071
- }
2072
- const token = tokenAddress || getDefaultTokenAddress(chainId);
2073
- await this.httpClient.post(ENDPOINTS.LOG_DEPOSIT, {
2074
- chainId,
2075
- transaction: txHash,
2076
- token,
2077
- amount
2078
- });
2079
- return {
2080
- success: true,
2081
- message: "Deposit logged successfully"
2082
- };
2083
- } catch (error) {
2084
- throw new Error(`Log deposit failed: ${error.message}`);
2085
- }
2086
- }
2087
- /**
2088
- * Withdraw funds from Safe smart wallet
2089
- * Initiates a withdrawal request to the Zyfai API
2090
- * Note: The withdrawal is processed asynchronously, so txHash may not be immediately available
2091
- * Funds are always withdrawn to the Safe owner's address (userAddress)
2092
- *
2093
- * @param userAddress - User's address (owner of the Safe)
2094
- * @param chainId - Target chain ID
2095
- * @param amount - Optional: Amount in least decimal units to withdraw (partial withdrawal). If not specified, withdraws all funds
2096
- * @returns Withdraw response with message and optional transaction hash (available once processed)
2097
- *
2098
- * @example
2099
- * ```typescript
2100
- * // Full withdrawal
2101
- * const result = await sdk.withdrawFunds("0xUser...", 8453);
2102
- * console.log(result.message); // "Withdrawal request sent"
2103
- *
2104
- * // Partial withdrawal of 50 USDC (6 decimals)
2105
- * const result = await sdk.withdrawFunds(
2106
- * "0xUser...",
2107
- * 8453,
2108
- * "50000000" // 50 USDC = 50 * 10^6
2109
- * );
2110
- * ```
2111
- */
2112
- async withdrawFunds(userAddress, chainId, amount, tokenSymbol) {
2113
- try {
2114
- if (!userAddress) {
2115
- throw new Error("User address is required");
2116
- }
2117
- if (!isSupportedChain(chainId)) {
2118
- throw new Error(`Unsupported chain ID: ${chainId}`);
2119
- }
2120
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
2121
- let safeAddress;
2122
- try {
2123
- const smartWalletInfo = await this.getSmartWalletByEOA(userAddress);
2124
- if (smartWalletInfo.smartWallet) {
2125
- safeAddress = smartWalletInfo.smartWallet;
2126
- } else {
2127
- safeAddress = await getDeterministicSafeAddress({
2128
- safeOwnerAddress: userAddress,
2129
- chain: chainConfig.chain,
2130
- publicClient: chainConfig.publicClient
2131
- });
2132
- }
2133
- } catch {
2134
- safeAddress = await getDeterministicSafeAddress({
2135
- safeOwnerAddress: userAddress,
2136
- chain: chainConfig.chain,
2137
- publicClient: chainConfig.publicClient
2138
- });
2139
- }
2140
- const isDeployed = await isSafeDeployed(
2141
- safeAddress,
2142
- chainConfig.publicClient
2143
- );
2144
- if (!isDeployed) {
2145
- throw new Error(
2146
- `Safe not deployed for ${userAddress}. Please deploy the Safe first using deploySafe().`
2147
- );
2148
- }
2149
- await this.authenticateUser();
2150
- let response = {};
2151
- if (amount) {
2152
- response = await this.httpClient.post(ENDPOINTS.PARTIAL_WITHDRAW, {
2153
- chainId,
2154
- amount,
2155
- tokenSymbol
2156
- });
2157
- } else {
2158
- console.log("Full withdrawal", tokenSymbol);
2159
- response = await this.httpClient.get(ENDPOINTS.USER_WITHDRAW, {
2160
- params: { chainId, tokenSymbol }
2161
- });
2162
- console.log(JSON.stringify(response, null, 2));
2163
- }
2164
- const success = response?.success ?? true;
2165
- const message = response?.message || "Withdrawal request sent";
2166
- const txHash = response?.txHash || response?.transactionHash;
2167
- return {
2168
- success,
2169
- message,
2170
- txHash,
2171
- type: amount ? "partial" : "full",
2172
- amount: amount || "all"
2173
- };
2174
- } catch (error) {
2175
- throw new Error(`Withdrawal failed: ${error.message}`);
2176
- }
2177
- }
2178
- /**
2179
- * Get available DeFi protocols and pools for a specific chain
2180
- *
2181
- * @param chainId - Target chain ID
2182
- * @returns List of available protocols with their pools and APY data
2183
- *
2184
- * @example
2185
- * ```typescript
2186
- * const protocols = await sdk.getAvailableProtocols(8453);
2187
- * protocols.forEach(protocol => {
2188
- * console.log(`${protocol.name}: ${protocol.minApy}% - ${protocol.maxApy}% APY`);
2189
- * });
2190
- * ```
2191
- */
2192
- async getAvailableProtocols(chainId) {
2193
- try {
2194
- if (!isSupportedChain(chainId)) {
2195
- throw new Error(`Unsupported chain ID: ${chainId}`);
2196
- }
2197
- const response = await this.httpClient.get(
2198
- ENDPOINTS.PROTOCOLS(chainId)
2199
- );
2200
- return {
2201
- success: true,
2202
- chainId,
2203
- protocols: response
2204
- };
2205
- } catch (error) {
2206
- throw new Error(
2207
- `Failed to get available protocols: ${error.message}`
2208
- );
2209
- }
2210
- }
2211
- /**
2212
- * Get all active DeFi positions for a user
2213
- *
2214
- * @param userAddress - User's EOA address
2215
- * @param chainId - Optional: Filter by specific chain ID
2216
- * @returns User's positions across all protocols
2217
- *
2218
- * @example
2219
- * ```typescript
2220
- * // Get all positions across all chains
2221
- * const positions = await sdk.getPositions(userAddress);
2222
- *
2223
- * // Get positions on a specific chain
2224
- * const basePositions = await sdk.getPositions(userAddress, 8453);
2225
- * ```
2226
- */
2227
- async getPositions(userAddress, chainId) {
2228
- try {
2229
- if (!userAddress) {
2230
- throw new Error("User address is required");
2231
- }
2232
- if (chainId && !isSupportedChain(chainId)) {
2233
- throw new Error(`Unsupported chain ID: ${chainId}`);
2234
- }
2235
- const smartWalletInfo = await this.getSmartWalletByEOA(userAddress);
2236
- if (!smartWalletInfo.smartWallet) {
2237
- return {
2238
- success: true,
2239
- userAddress,
2240
- portfolio: {}
2241
- };
2242
- }
2243
- const response = await this.httpClient.get(
2244
- ENDPOINTS.DATA_POSITION(smartWalletInfo.smartWallet)
2245
- );
2246
- const convertedPosition = response ? convertStrategyToPublic(response) : void 0;
2247
- return {
2248
- success: true,
2249
- userAddress,
2250
- portfolio: convertedPosition
2251
- };
2252
- } catch (error) {
2253
- throw new Error(`Failed to get positions: ${error.message}`);
2254
- }
2255
- }
2256
- /**
2257
- * Get all active positions and portfolio for a user
2258
- *
2259
- * @param userAddress - User's EOA address
2260
- * @param chainId - Optional: Filter by specific chain ID
2261
- * @returns User's positions across all protocols
2262
- *
2263
- * @example
2264
- * ```typescript
2265
- * // Get all positions across all chains
2266
- * const positions = await sdk.getPositions(userAddress);
2267
- *
2268
- * // Get positions on a specific chain
2269
- * const basePositions = await sdk.getPositions(userAddress, 8453);
2270
- * ```
2271
- */
2272
- async getPortfolio(userAddress) {
2273
- try {
2274
- if (!userAddress) {
2275
- throw new Error("User address is required");
2276
- }
2277
- const smartWalletInfo = await this.getSmartWalletByEOA(userAddress);
2278
- if (!smartWalletInfo.smartWallet) {
2279
- return {
2280
- success: true,
2281
- userAddress,
2282
- portfolio: {}
2283
- };
2284
- }
2285
- console.log("Getting portfolio for", smartWalletInfo.smartWallet);
2286
- const response = await this.httpClient.get(
2287
- ENDPOINTS.DATA_PORTFOLIO(smartWalletInfo.smartWallet)
2288
- );
2289
- const convertedResponse = removeUnusedFields(response);
2290
- return {
2291
- success: true,
2292
- userAddress,
2293
- portfolio: convertedResponse
2294
- };
2295
- } catch (error) {
2296
- throw new Error(`Failed to get positions: ${error.message}`);
2297
- }
2298
- }
2299
- // ============================================================================
2300
- // User Details Methods
2301
- // ============================================================================
2302
- /**
2303
- * Get current authenticated user details
2304
- * Requires SIWE authentication
2305
- *
2306
- * @returns User details including smart wallet, chains, protocols, etc.
2307
- *
2308
- * @example
2309
- * ```typescript
2310
- * await sdk.connectAccount(privateKey, chainId);
2311
- * const user = await sdk.getUserDetails();
2312
- * console.log("Smart Wallet:", user.user.smartWallet);
2313
- * console.log("Chains:", user.user.chains);
2314
- * ```
2315
- */
2316
- async getUserDetails(asset = "USDC") {
2317
- try {
2318
- await this.authenticateUser();
2319
- const response = await this.httpClient.get(ENDPOINTS.USER_ME);
2320
- const internalAsset = convertAssetInternally(asset);
2321
- const convertedResponse = convertStrategyToPublic(response);
2322
- return {
2323
- success: true,
2324
- agentName: convertedResponse.agentName,
2325
- smartWallet: convertedResponse.smartWallet,
2326
- chains: convertedResponse.assetTypeSettings?.[internalAsset]?.chains || [],
2327
- hasActiveSessionKey: convertedResponse.hasActiveSessionKey || false,
2328
- omniAccount: convertedResponse.omniAccount,
2329
- asset,
2330
- autoSelectProtocols: convertedResponse.assetTypeSettings?.[internalAsset]?.autoSelectProtocols,
2331
- strategy: convertedResponse.assetTypeSettings?.[internalAsset]?.rebalanceStrategy,
2332
- autocompounding: convertedResponse.assetTypeSettings?.[internalAsset]?.autocompounding,
2333
- crosschainStrategy: convertedResponse.assetTypeSettings?.[internalAsset]?.crosschainStrategy,
2334
- splitting: convertedResponse.assetTypeSettings?.[internalAsset]?.splitting,
2335
- minSplits: convertedResponse.assetTypeSettings?.[internalAsset]?.minSplits || 0,
2336
- protocols: convertedResponse.assetTypeSettings?.[internalAsset]?.protocols || [],
2337
- customization: convertedResponse.customization
2338
- };
2339
- } catch (error) {
2340
- throw new Error(
2341
- `Failed to get user details: ${error.message}`
2342
- );
2343
- }
2344
- }
2345
- // ============================================================================
2346
- // TVL & Volume Methods
2347
- // ============================================================================
2348
- /**
2349
- * Get total value locked (TVL) across all Zyfai accounts
2350
- *
2351
- * @returns Total TVL in USD and breakdown by chain
2352
- *
2353
- * @example
2354
- * ```typescript
2355
- * const tvl = await sdk.getTVL();
2356
- * console.log("Total TVL:", tvl.totalTvl);
2357
- * ```
2358
- */
2359
- async getTVL() {
2360
- try {
2361
- const response = await this.httpClient.get(ENDPOINTS.DATA_TVL);
2362
- return {
2363
- success: true,
2364
- totalTvl: response.total || 0
2365
- };
2366
- } catch (error) {
2367
- throw new Error(`Failed to get TVL: ${error.message}`);
2368
- }
2369
- }
2370
- // ============================================================================
2371
- // APY Per Strategy Methods
2372
- // ============================================================================
2373
- /**
2374
- * Get APY per strategy for a specific chain
2375
- *
2376
- * @param crossChain - Whether to get cross-chain APY (true = omni account, false = simple account)
2377
- * @param days - Time period: 7, 14, or 30
2378
- * @param strategy - Strategy type: "conservative" (default) or "aggressive"
2379
- * @param chainId - Optional chain ID filter
2380
- * @param tokenSymbol - Optional token symbol filter (e.g. "USDC", "WETH", "WBTC")
2381
- * @returns APY per strategy for a specific chain
2382
- *
2383
- * @example
2384
- * ```typescript
2385
- * const apyPerStrategy = await sdk.getAPYPerStrategy(false, 7, "conservative", 8453, "USDC");
2386
- * console.log("APY per strategy per chain:", apyPerStrategy.data);
2387
- * ```
2388
- */
2389
- async getAPYPerStrategy(crossChain = false, days = 7, strategy = "conservative", chainId, tokenSymbol) {
2390
- try {
2391
- const internalStrategy = toInternalStrategy(strategy);
2392
- const internalStrategyShort = internalStrategy === "safe_strategy" ? "safe" : "degen";
2393
- const response = await this.httpClient.dataGet(
2394
- DATA_ENDPOINTS.APY_PER_STRATEGY({
2395
- isCrossChain: crossChain,
2396
- days,
2397
- strategy: internalStrategyShort,
2398
- chainId,
2399
- tokenSymbol
2400
- })
2401
- );
2402
- const convertedData = convertStrategiesToPublicAndNaming(
2403
- response.data || []
2404
- );
2405
- return {
2406
- success: true,
2407
- count: response.count || 0,
2408
- data: convertedData
2409
- };
2410
- } catch (error) {
2411
- throw new Error(
2412
- `Failed to get APY per strategy: ${error.message}`
2413
- );
2414
- }
2415
- }
2416
- /**
2417
- * Get total volume across all Zyfai accounts
2418
- *
2419
- * @returns Total volume in USD
2420
- *
2421
- * @example
2422
- * ```typescript
2423
- * const volume = await sdk.getVolume();
2424
- * console.log("Total Volume:", volume.volumeInUSD);
2425
- * ```
2426
- */
2427
- async getVolume(assetType = "usdc") {
2428
- try {
2429
- const response = await this.httpClient.get(ENDPOINTS.DATA_VOLUME(assetType));
2430
- return {
2431
- success: true,
2432
- volumeInUSD: response.volumeInUSD || "0"
2433
- };
2434
- } catch (error) {
2435
- throw new Error(`Failed to get volume: ${error.message}`);
2436
- }
2437
- }
2438
- // ============================================================================
2439
- // Active Wallets Methods
2440
- // ============================================================================
2441
- /**
2442
- * Get active wallets for a specific chain
2443
- *
2444
- * @param chainId - Chain ID to filter wallets
2445
- * @returns List of active wallets on the specified chain
2446
- *
2447
- * @example
2448
- * ```typescript
2449
- * const wallets = await sdk.getActiveWallets(8453); // Base
2450
- * console.log("Active wallets:", wallets.count);
2451
- * ```
2452
- */
2453
- async getActiveWallets(chainId) {
2454
- try {
2455
- if (!chainId) {
2456
- throw new Error("Chain ID is required");
2457
- }
2458
- const response = await this.httpClient.get(
2459
- ENDPOINTS.DATA_ACTIVE_WALLETS(chainId)
2460
- );
2461
- const wallets = Array.isArray(response) ? response : response.wallets || [];
2462
- return {
2463
- success: true,
2464
- chainId,
2465
- wallets: wallets.map((w) => ({
2466
- smartWallet: w.smartWallet || w,
2467
- chains: w.chains || [chainId],
2468
- hasBalance: w.hasBalance ?? true
2469
- })),
2470
- count: wallets.length
2471
- };
2472
- } catch (error) {
2473
- throw new Error(
2474
- `Failed to get active wallets: ${error.message}`
2475
- );
2476
- }
2477
- }
2478
- /**
2479
- * Get smart wallets associated with an EOA address
2480
- *
2481
- * @param eoaAddress - EOA (externally owned account) address
2482
- * @returns List of smart wallets owned by the EOA
2483
- *
2484
- * @example
2485
- * ```typescript
2486
- * const result = await sdk.getSmartWalletByEOA("0x...");
2487
- * console.log("Smart wallets:", result.smartWallets);
2488
- * ```
2489
- */
2490
- async getSmartWalletByEOA(eoaAddress) {
2491
- try {
2492
- if (!eoaAddress) {
2493
- throw new Error("EOA address is required");
2494
- }
2495
- const response = await this.httpClient.get(
2496
- ENDPOINTS.DATA_BY_EOA(eoaAddress)
2497
- );
2498
- const smartWallet = response.agent || null;
2499
- const chains = response.chains || [];
2500
- return {
2501
- success: true,
2502
- eoa: eoaAddress,
2503
- smartWallet,
2504
- chains
2505
- };
2506
- } catch (error) {
2507
- throw new Error(
2508
- `Failed to get smart wallets by EOA: ${error.message}`
2509
- );
2510
- }
2511
- }
2512
- // ============================================================================
2513
- // First Topup & History Methods
2514
- // ============================================================================
2515
- /**
2516
- * Get the first topup (deposit) information for a wallet
2517
- *
2518
- * @param walletAddress - Smart wallet address
2519
- * @param chainId - Chain ID
2520
- * @returns First topup date and details
2521
- *
2522
- * @example
2523
- * ```typescript
2524
- * const firstTopup = await sdk.getFirstTopup("0x...", 8453);
2525
- * console.log("First deposit date:", firstTopup.date);
2526
- * ```
2527
- */
2528
- async getFirstTopup(walletAddress, chainId) {
2529
- try {
2530
- if (!walletAddress) {
2531
- throw new Error("Wallet address is required");
2532
- }
2533
- if (!chainId) {
2534
- throw new Error("Chain ID is required");
2535
- }
2536
- const response = await this.httpClient.get(
2537
- ENDPOINTS.DATA_FIRST_TOPUP(walletAddress, chainId)
2538
- );
2539
- return {
2540
- success: true,
2541
- walletAddress,
2542
- date: response.date || response.firstTopup?.date || "",
2543
- amount: response.amount,
2544
- chainId: response.chainId || chainId
2545
- };
2546
- } catch (error) {
2547
- throw new Error(`Failed to get first topup: ${error.message}`);
2548
- }
2549
- }
2550
- /**
2551
- * Get transaction history for a wallet
2552
- *
2553
- * @param walletAddress - Smart wallet address
2554
- * @param chainId - Chain ID
2555
- * @param options - Optional pagination and date filters
2556
- * @returns Transaction history
2557
- *
2558
- * @example
2559
- * ```typescript
2560
- * const history = await sdk.getHistory("0x...", 8453, { limit: 50 });
2561
- * history.data.forEach(tx => console.log(tx.type, tx.amount));
2562
- * ```
2563
- */
2564
- async getHistory(walletAddress, chainId, options) {
2565
- try {
2566
- if (!walletAddress) {
2567
- throw new Error("Wallet address is required");
2568
- }
2569
- if (!chainId) {
2570
- throw new Error("Chain ID is required");
2571
- }
2572
- let endpoint = ENDPOINTS.DATA_HISTORY(walletAddress, chainId);
2573
- if (options?.limit) endpoint += `&limit=${options.limit}`;
2574
- if (options?.offset) endpoint += `&offset=${options.offset}`;
2575
- if (options?.fromDate) endpoint += `&fromDate=${options.fromDate}`;
2576
- if (options?.toDate) endpoint += `&toDate=${options.toDate}`;
2577
- const response = await this.httpClient.get(endpoint);
2578
- const convertedData = convertStrategiesToPublic(response.data || []);
2579
- return {
2580
- success: true,
2581
- walletAddress,
2582
- data: convertedData,
2583
- total: response.total || 0
2584
- };
2585
- } catch (error) {
2586
- throw new Error(`Failed to get history: ${error.message}`);
2587
- }
2588
- }
2589
- // ============================================================================
2590
- // Onchain Earnings Methods (Data API v2)
2591
- // ============================================================================
2592
- /**
2593
- * Get onchain earnings for a wallet
2594
- *
2595
- * @param walletAddress - Smart wallet address
2596
- * @returns Onchain earnings data with per-token breakdowns
2597
- *
2598
- * @example
2599
- * ```typescript
2600
- * const earnings = await sdk.getOnchainEarnings("0x...");
2601
- * console.log("Total USDC:", earnings.data.totalEarningsByToken["USDC"]);
2602
- * ```
2603
- */
2604
- async getOnchainEarnings(walletAddress) {
2605
- try {
2606
- if (!walletAddress) {
2607
- throw new Error("Wallet address is required");
2608
- }
2609
- const response = await this.httpClient.dataGet(
2610
- DATA_ENDPOINTS.ONCHAIN_EARNINGS(walletAddress)
2611
- );
2612
- return {
2613
- success: true,
2614
- data: {
2615
- walletAddress,
2616
- totalEarningsByToken: response.total_earnings_by_token || {},
2617
- lastCheckTimestamp: response.last_check_timestamp,
2618
- lastLogDate: response.last_log_date
2619
- }
2620
- };
2621
- } catch (error) {
2622
- throw new Error(
2623
- `Failed to get onchain earnings: ${error.message}`
2624
- );
2625
- }
2626
- }
2627
- /**
2628
- * Calculate/refresh onchain earnings for a wallet
2629
- * This triggers a recalculation of earnings on the backend
2630
- *
2631
- * @param walletAddress - Smart wallet address
2632
- * @returns Updated onchain earnings data with per-token breakdowns
2633
- *
2634
- * @example
2635
- * ```typescript
2636
- * const earnings = await sdk.calculateOnchainEarnings("0x...");
2637
- * console.log("Total USDC:", earnings.data.totalEarningsByToken["USDC"]);
2638
- * ```
2639
- */
2640
- async calculateOnchainEarnings(walletAddress) {
2641
- try {
2642
- if (!walletAddress) {
2643
- throw new Error("Wallet address is required");
2644
- }
2645
- const response = await this.httpClient.dataPost(
2646
- DATA_ENDPOINTS.CALCULATE_ONCHAIN_EARNINGS,
2647
- { walletAddress }
2648
- );
2649
- const data = response.data || response;
2650
- return {
2651
- success: true,
2652
- data: {
2653
- walletAddress,
2654
- totalEarningsByToken: data.total_earnings_by_token || {},
2655
- lastCheckTimestamp: data.last_check_timestamp,
2656
- lastLogDate: data.last_log_date
2657
- }
2658
- };
2659
- } catch (error) {
2660
- throw new Error(
2661
- `Failed to calculate onchain earnings: ${error.message}`
2662
- );
2663
- }
2664
- }
2665
- /**
2666
- * Get daily earnings for a wallet within a date range
2667
- *
2668
- * @param walletAddress - Smart wallet address
2669
- * @param startDate - Start date (YYYY-MM-DD format)
2670
- * @param endDate - End date (YYYY-MM-DD format)
2671
- * @returns Daily earnings breakdown
2672
- *
2673
- * @example
2674
- * ```typescript
2675
- * const daily = await sdk.getDailyEarnings("0x...", "2024-01-01", "2024-01-31");
2676
- * daily.data.forEach(d => console.log(d.snapshot_date, d.total_earnings_by_token));
2677
- * ```
2678
- */
2679
- async getDailyEarnings(walletAddress, startDate, endDate) {
2680
- try {
2681
- if (!walletAddress) {
2682
- throw new Error("Wallet address is required");
2683
- }
2684
- const response = await this.httpClient.dataGet(
2685
- DATA_ENDPOINTS.DAILY_EARNINGS(walletAddress, startDate, endDate)
2686
- );
2687
- const filteredData = (response.data || []).map((entry) => ({
2688
- wallet_address: entry.wallet_address,
2689
- snapshot_date: entry.snapshot_date,
2690
- total_earnings_by_token: entry.total_earnings_by_token,
2691
- daily_total_delta_by_token: entry.daily_total_delta_by_token,
2692
- created_at: entry.created_at
2693
- }));
2694
- return {
2695
- success: true,
2696
- walletAddress,
2697
- data: filteredData,
2698
- count: response.count || 0,
2699
- filters: {
2700
- startDate: startDate || null,
2701
- endDate: endDate || null
2702
- }
2703
- };
2704
- } catch (error) {
2705
- throw new Error(
2706
- `Failed to get daily earnings: ${error.message}`
2707
- );
2708
- }
2709
- }
2710
- // ============================================================================
2711
- // Opportunities Methods (Data API v2)
2712
- // ============================================================================
2713
- /**
2714
- * Get conservative (low-risk) yield opportunities
2715
- *
2716
- * @param chainId - Optional chain ID filter
2717
- * @param asset - Optional asset filter (e.g. "USDC", "WETH", "WBTC")
2718
- * @returns List of conservative yield opportunities
2719
- *
2720
- * @example
2721
- * ```typescript
2722
- * const opportunities = await sdk.getConservativeOpportunities(8453, "USDC");
2723
- * opportunities.data.forEach(o => console.log(o.protocolName, o.apy));
2724
- * ```
2725
- */
2726
- async getConservativeOpportunities(chainId, asset, status) {
2727
- try {
2728
- const response = await this.httpClient.dataGet(
2729
- DATA_ENDPOINTS.OPPORTUNITIES_SAFE(chainId, asset, status)
2730
- );
2731
- const data = response.data || response || [];
2732
- return {
2733
- success: true,
2734
- chainId,
2735
- strategyType: "conservative",
2736
- data: Array.isArray(data) ? data.map((o) => ({
2737
- id: o.id,
2738
- protocolId: o.protocol_id || o.protocolId,
2739
- protocolName: o.protocol_name || o.protocolName,
2740
- poolName: o.pool_name || o.poolName,
2741
- chainId: o.chain_id || o.chainId,
2742
- apy: o.combined_apy || 0,
2743
- tvl: o.tvl || o.zyfiTvl,
2744
- asset: o.asset || o.underlying_token,
2745
- risk: o.risk,
2746
- strategyType: "conservative",
2747
- status: o.status
2748
- })) : []
2749
- };
2750
- } catch (error) {
2751
- throw new Error(
2752
- `Failed to get safe opportunities: ${error.message}`
2753
- );
2754
- }
2755
- }
2756
- /**
2757
- * Get aggressive (high-risk, high-reward) yield opportunities
2758
- *
2759
- * @param chainId - Optional chain ID filter
2760
- * @param asset - Optional asset filter (e.g. "USDC", "WETH", "WBTC")
2761
- * @returns List of aggressive opportunities
2762
- *
2763
- * @example
2764
- * ```typescript
2765
- * const opportunities = await sdk.getAggressiveOpportunities(8453, "WETH");
2766
- * opportunities.data.forEach(o => console.log(o.protocolName, o.apy));
2767
- * ```
2768
- */
2769
- async getAggressiveOpportunities(chainId, asset, status) {
2770
- try {
2771
- const response = await this.httpClient.dataGet(
2772
- DATA_ENDPOINTS.OPPORTUNITIES_DEGEN(chainId, asset, status)
2773
- );
2774
- const data = response.data || response || [];
2775
- return {
2776
- success: true,
2777
- chainId,
2778
- strategyType: "aggressive",
2779
- data: Array.isArray(data) ? data.map((o) => ({
2780
- id: o.id,
2781
- protocolId: o.protocol_id || o.protocolId,
2782
- protocolName: o.protocol_name || o.protocolName,
2783
- poolName: o.pool_name || o.poolName,
2784
- chainId: o.chain_id || o.chainId,
2785
- apy: o.combined_apy || 0,
2786
- tvl: o.tvl || o.zyfiTvl,
2787
- asset: o.asset || o.underlying_token,
2788
- risk: o.risk,
2789
- strategyType: "aggressive",
2790
- status: o.status
2791
- })) : []
2792
- };
2793
- } catch (error) {
2794
- throw new Error(
2795
- `Failed to get aggressive opportunities: ${error.message}`
2796
- );
2797
- }
2798
- }
2799
- /**
2800
- * Get active conservative opportunities (status = "live") with risk and utilization data
2801
- * Returns pool info, liquidity depth (true if > 1M), utilization rate, stability metrics, avg APY, and collateral
2802
- *
2803
- * @param chainId - Optional chain ID filter
2804
- * @param asset - Optional asset filter (e.g. "USDC", "WETH", "WBTC")
2805
- * @returns Active conservative opportunities with risk data
2806
- *
2807
- * @example
2808
- * ```typescript
2809
- * const opps = await sdk.getActiveConservativeOppsRisk(8453);
2810
- * console.log(JSON.stringify(opps, null, 2));
2811
- * ```
2812
- */
2813
- async getActiveConservativeOppsRisk(chainId, asset) {
2814
- try {
2815
- const response = await this.httpClient.dataGet(
2816
- DATA_ENDPOINTS.OPPORTUNITIES_SAFE(chainId, asset)
2817
- );
2818
- const data = response.data || response || [];
2819
- const active = Array.isArray(data) ? data.filter((o) => o.status === "live").map((o) => {
2820
- const tvl = o.tvl || 0;
2821
- const liquidity = o.liquidity || 0;
2822
- const utilizationRate = tvl > 0 ? (tvl - liquidity) / tvl : 0;
2823
- return {
2824
- poolName: o.pool_name,
2825
- protocolName: o.protocol_name,
2826
- chainId: o.chain_id,
2827
- liquidityDepth: liquidity > 1e7 ? "deep" : liquidity > 1e6 ? "moderate" : "shallow",
2828
- utilizationRate: Math.round(utilizationRate * 1e4) / 100,
2829
- tvlStability: o.isTvlStable ?? null,
2830
- apyStability: o.isApyStable30Days ?? null,
2831
- tvlApyCombinedRisk: o.isApyTvlStable ?? null,
2832
- avgCombinedApy7d: o.averageCombinedApy7Days ?? null,
2833
- avgCombinedApy15d: o.averageCombinedApy15Days ?? null,
2834
- avgCombinedApy30d: o.averageCombinedApy30Days ?? null,
2835
- collateralSymbols: o.collateral_symbols || []
2836
- };
2837
- }) : [];
2838
- return active;
2839
- } catch (error) {
2840
- throw new Error(
2841
- `Failed to get active conservative opportunities risk: ${error.message}`
2842
- );
2843
- }
2844
- }
2845
- /**
2846
- * Get active aggressive opportunities (status = "live") with risk and utilization data
2847
- * Returns pool info, liquidity depth (true if > 1M), utilization rate, stability metrics, avg APY, and collateral
2848
- *
2849
- * @param chainId - Optional chain ID filter
2850
- * @param asset - Optional asset filter (e.g. "USDC", "WETH", "WBTC")
2851
- * @returns Active aggressive opportunities with risk data
2852
- *
2853
- * @example
2854
- * ```typescript
2855
- * const opps = await sdk.getActiveAggressiveOppsRisk(8453);
2856
- * console.log(JSON.stringify(opps, null, 2));
2857
- * ```
2858
- */
2859
- async getActiveAggressiveOppsRisk(chainId, asset) {
2860
- try {
2861
- const response = await this.httpClient.dataGet(
2862
- DATA_ENDPOINTS.OPPORTUNITIES_DEGEN(chainId, asset)
2863
- );
2864
- const data = response.data || response || [];
2865
- const active = Array.isArray(data) ? data.filter((o) => o.status === "live").map((o) => {
2866
- const tvl = o.tvl || 0;
2867
- const liquidity = o.liquidity || 0;
2868
- const utilizationRate = tvl > 0 ? (tvl - liquidity) / tvl : 0;
2869
- return {
2870
- poolName: o.pool_name,
2871
- protocolName: o.protocol_name,
2872
- chainId: o.chain_id,
2873
- liquidityDepth: liquidity > 1e7 ? "deep" : liquidity > 1e6 ? "moderate" : "shallow",
2874
- utilizationRate: Math.round(utilizationRate * 1e4) / 100,
2875
- tvlStability: o.isTvlStable ?? null,
2876
- apyStability: o.isApyStable30Days ?? null,
2877
- tvlApyCombinedRisk: o.isApyTvlStable ?? null,
2878
- avgCombinedApy7d: o.averageCombinedApy7Days ?? null,
2879
- avgCombinedApy15d: o.averageCombinedApy15Days ?? null,
2880
- avgCombinedApy30d: o.averageCombinedApy30Days ?? null,
2881
- collateralSymbols: o.collateral_symbols || []
2882
- };
2883
- }) : [];
2884
- return active;
2885
- } catch (error) {
2886
- throw new Error(
2887
- `Failed to get active aggressive opportunities risk: ${error.message}`
2888
- );
2889
- }
2890
- }
2891
- /**
2892
- * Get conservative pool status with derived health, risk, APY trend, and yield consistency
2893
- * Builds on getActiveConservativeOppsRisk and computes higher-level status indicators
2894
- *
2895
- * @param chainId - Optional chain ID filter
2896
- * @param asset - Optional asset filter (e.g. "USDC", "WETH", "WBTC")
2897
- * @returns Conservative pools with status data
2898
- */
2899
- async getConservativePoolStatus(chainId, asset) {
2900
- const pools = await this.getActiveConservativeOppsRisk(chainId, asset);
2901
- return pools.map((p) => this.derivePoolStatus(p));
2902
- }
2903
- /**
2904
- * Get aggressive pool status with derived health, risk, APY trend, and yield consistency
2905
- * Builds on getActiveAggressiveOppsRisk and computes higher-level status indicators
2906
- *
2907
- * @param chainId - Optional chain ID filter
2908
- * @param asset - Optional asset filter (e.g. "USDC", "WETH", "WBTC")
2909
- * @returns Aggressive pools with status data
2910
- */
2911
- async getAggressivePoolStatus(chainId, asset) {
2912
- const pools = await this.getActiveAggressiveOppsRisk(chainId, asset);
2913
- return pools.map((p) => this.derivePoolStatus(p));
2914
- }
2915
- derivePoolStatus(p) {
2916
- let riskSignals = 0;
2917
- if (p.tvlStability === false) riskSignals++;
2918
- if (p.apyStability === false) riskSignals++;
2919
- if (p.tvlApyCombinedRisk === false) riskSignals++;
2920
- if (p.liquidityDepth === "shallow") riskSignals++;
2921
- if (p.utilizationRate > 90) riskSignals++;
2922
- const riskLevel = riskSignals >= 3 ? "high" : riskSignals >= 1 ? "medium" : "low";
2923
- const stabilityScore = (p.tvlStability === true ? 1 : 0) + (p.apyStability === true ? 1 : 0) + (p.tvlApyCombinedRisk === true ? 1 : 0);
2924
- const liquidityBonus = p.liquidityDepth === "deep" ? 1 : p.liquidityDepth === "moderate" ? 0.5 : 0;
2925
- const healthTotal = stabilityScore + liquidityBonus;
2926
- const healthScore = healthTotal >= 3 ? "healthy" : healthTotal >= 1.5 ? "moderate" : "risky";
2927
- const apy7d = p.avgCombinedApy7d;
2928
- const apy30d = p.avgCombinedApy30d;
2929
- let apyTrend = "stable";
2930
- if (apy7d != null && apy30d != null && apy30d !== 0) {
2931
- const change = (apy7d - apy30d) / apy30d;
2932
- if (change > 0.1) apyTrend = "rising";
2933
- else if (change < -0.1) apyTrend = "falling";
2934
- }
2935
- let yieldConsistency = "consistent";
2936
- if (apy7d != null && apy30d != null && apy30d !== 0) {
2937
- const spread = Math.abs(apy7d - apy30d) / apy30d;
2938
- if (spread > 0.3) yieldConsistency = "volatile";
2939
- else if (spread > 0.1) yieldConsistency = "mixed";
2940
- }
2941
- return {
2942
- poolName: p.poolName,
2943
- protocolName: p.protocolName,
2944
- chainId: p.chainId,
2945
- healthScore,
2946
- riskLevel,
2947
- apyTrend,
2948
- yieldConsistency,
2949
- liquidityDepth: p.liquidityDepth,
2950
- avgCombinedApy7d: p.avgCombinedApy7d
2951
- };
2952
- }
2953
- // ============================================================================
2954
- // APY History Methods (Data API v2)
2955
- // ============================================================================
2956
- /**
2957
- * Get daily APY history with weighted average for a wallet
2958
- *
2959
- * @param walletAddress - Smart wallet address
2960
- * @param days - Period: "7D", "14D", or "30D" (default: "7D")
2961
- * @returns Daily APY history with per-position breakdowns and weighted averages
2962
- *
2963
- * @example
2964
- * ```typescript
2965
- * const apyHistory = await sdk.getDailyApyHistory("0x...", "30D");
2966
- * console.log("Weighted APY after fee:", apyHistory.weightedApyAfterFee); // { "USDC": 4.64, "WETH": 1.94 }
2967
- * ```
2968
- */
2969
- async getDailyApyHistory(walletAddress, days = "7D") {
2970
- try {
2971
- if (!walletAddress) {
2972
- throw new Error("Wallet address is required");
2973
- }
2974
- const response = await this.httpClient.dataGet(
2975
- DATA_ENDPOINTS.DAILY_APY_HISTORY_WEIGHTED(walletAddress, days)
2976
- );
2977
- const data = response.data || response;
2978
- return {
2979
- success: true,
2980
- walletAddress,
2981
- history: data.history || {},
2982
- totalDays: data.total_days || data.totalDays || 0,
2983
- requestedDays: data.requested_days || data.requestedDays,
2984
- weightedApyWithRzfiAfterFee: data.average_final_weighted_apy_after_fee_with_rzfi,
2985
- weightedApyAfterFee: data.average_final_weighted_apy_after_fee
2986
- };
2987
- } catch (error) {
2988
- throw new Error(
2989
- `Failed to get daily APY history: ${error.message}`
2990
- );
2991
- }
2992
- }
2993
- // ============================================================================
2994
- // Rebalance Methods
2995
- // ============================================================================
2996
- /**
2997
- * Get rebalance frequency/tier for a wallet
2998
- * Determines how often the wallet can be rebalanced based on tier
2999
- *
3000
- * @param walletAddress - Smart wallet address
3001
- * @returns Rebalance frequency tier and details
3002
- *
3003
- * @example
3004
- * ```typescript
3005
- * const frequency = await sdk.getRebalanceFrequency("0x...");
3006
- * console.log("Tier:", frequency.tier);
3007
- * console.log("Max rebalances/day:", frequency.frequency);
3008
- * ```
3009
- */
3010
- async getRebalanceFrequency(walletAddress) {
3011
- try {
3012
- if (!walletAddress) {
3013
- throw new Error("Wallet address is required");
3014
- }
3015
- const response = await this.httpClient.get(
3016
- ENDPOINTS.DATA_REBALANCE_FREQUENCY(walletAddress)
3017
- );
3018
- return {
3019
- success: true,
3020
- walletAddress,
3021
- tier: response.tier || "standard",
3022
- frequency: response.frequency || response.rebalanceFrequency || 1,
3023
- description: response.description
3024
- };
3025
- } catch (error) {
3026
- throw new Error(
3027
- `Failed to get rebalance frequency: ${error.message}`
3028
- );
3029
- }
3030
- }
3031
- // ============================================================================
3032
- // SDK Key Methods
3033
- // ============================================================================
3034
- /**
3035
- * Get allowed wallets for the current SDK API key
3036
- * Returns the list of smart wallet addresses created via this SDK key
3037
- *
3038
- * @returns List of allowed wallet addresses with metadata
3039
- *
3040
- * @example
3041
- * ```typescript
3042
- * const result = await sdk.getSdkAllowedWallets();
3043
- * console.log("Allowed wallets:", result.allowedWallets);
3044
- * console.log("Total count:", result.metadata.walletsCount);
3045
- * ```
3046
- */
3047
- async getSdkAllowedWallets() {
3048
- try {
3049
- const response = await this.httpClient.get(
3050
- ENDPOINTS.SDK_ALLOWED_WALLETS
3051
- );
3052
- return {
3053
- success: response.success || true,
3054
- allowedWallets: response.allowedWallets || [],
3055
- metadata: response.metadata || {
3056
- sdkKeyId: "",
3057
- clientName: "",
3058
- walletsCount: 0
3059
- }
3060
- };
3061
- } catch (error) {
3062
- throw new Error(
3063
- `Failed to get SDK allowed wallets: ${error.message}`
3064
- );
3065
- }
3066
- }
3067
- /**
3068
- * Get total TVL for all wallets under the current SDK API key
3069
- * This method calculates the total value locked across all wallets created via this SDK key
3070
- *
3071
- * @returns SDK key TVL information including allowed wallets and their individual/total TVL
3072
- *
3073
- * @example
3074
- * ```typescript
3075
- * const sdkTvl = await sdk.getSdkKeyTVL();
3076
- * console.log("Total TVL across all SDK wallets:", sdkTvl.totalTvl);
3077
- * console.log("Number of wallets:", sdkTvl.allowedWallets.length);
3078
- * console.log("TVL by wallet:", sdkTvl.tvlByWallet);
3079
- * ```
3080
- */
3081
- async getSdkKeyTVL() {
3082
- try {
3083
- const response = await this.httpClient.get(ENDPOINTS.SDK_TVL);
3084
- return {
3085
- success: response.success || true,
3086
- allowedWallets: response.allowedWallets || [],
3087
- totalTvl: response.totalTvl || 0,
3088
- tvlByWallet: response.tvlByWallet || [],
3089
- metadata: response.metadata || {
3090
- sdkKeyId: "",
3091
- clientName: "",
3092
- walletsCount: 0
3093
- }
3094
- };
3095
- } catch (error) {
3096
- throw new Error(
3097
- `Failed to get SDK key TVL: ${error.message}`
3098
- );
3099
- }
3100
- }
3101
- // ============================================================================
3102
- // Protocol/Pool Customization
3103
- // ============================================================================
3104
- /**
3105
- * Configure protocol and pool customizations in batch.
3106
- *
3107
- * Allows granular control over which pools to use for each protocol on each chain.
3108
- * This is useful for advanced users who want to target specific pools with desired APY/risk profiles.
3109
- *
3110
- * @param customizations - Array of customization configurations
3111
- * @returns Response indicating success
3112
- *
3113
- * @example
3114
- * ```typescript
3115
- * // Configure multiple protocols across different chains
3116
- * await sdk.customizeBatch([
3117
- * {
3118
- * protocolId: "protocol-uuid-1",
3119
- * pools: ["USDC Pool", "WETH Pool"],
3120
- * chainId: 8453, // Base
3121
- * autoselect: false
3122
- * },
3123
- * {
3124
- * protocolId: "protocol-uuid-1",
3125
- * pools: ["USDC Vault"],
3126
- * chainId: 42161, // Arbitrum
3127
- * autoselect: false
3128
- * },
3129
- * {
3130
- * protocolId: "protocol-uuid-2",
3131
- * pools: [], // Empty array when autoselect is true
3132
- * chainId: 8453,
3133
- * autoselect: true // Let engine auto-select best pools
3134
- * }
3135
- * ]);
3136
- * ```
3137
- */
3138
- async customizeBatch(customizations) {
3139
- try {
3140
- await this.authenticateUser();
3141
- const response = await this.httpClient.post(
3142
- ENDPOINTS.CUSTOMIZE_BATCH,
3143
- customizations
3144
- );
3145
- return response;
3146
- } catch (error) {
3147
- throw new Error(
3148
- `Failed to save customizations: ${error.message}`
3149
- );
3150
- }
3151
- }
3152
- /**
3153
- * Get available pools for a protocol.
3154
- *
3155
- * Returns the list of pools available for a given protocol, optionally filtered by strategy.
3156
- *
3157
- * @param protocolId - The protocol UUID
3158
- * @param strategy - Optional strategy filter ("conservative" or "aggressive")
3159
- * @returns List of available pool names
3160
- *
3161
- * @example
3162
- * ```typescript
3163
- * // Get all available pools for a protocol
3164
- * const pools = await sdk.getAvailablePools("protocol-uuid");
3165
- * console.log("Available pools:", pools.pools);
3166
- *
3167
- * // Get pools for conservative strategy only
3168
- * const conservativePools = await sdk.getAvailablePools(
3169
- * "protocol-uuid",
3170
- * "conservative"
3171
- * );
3172
- * ```
3173
- */
3174
- async getAvailablePools(protocolId, strategy) {
3175
- try {
3176
- let internalStrategy;
3177
- if (strategy) {
3178
- if (!isValidPublicStrategy(strategy)) {
3179
- throw new Error(
3180
- `Invalid strategy: ${strategy}. Must be "conservative" or "aggressive".`
3181
- );
3182
- }
3183
- internalStrategy = toInternalStrategy(strategy);
3184
- }
3185
- const response = await this.httpClient.get(
3186
- ENDPOINTS.CUSTOMIZATION_POOLS(protocolId, internalStrategy)
3187
- );
3188
- return response;
3189
- } catch (error) {
3190
- throw new Error(
3191
- `Failed to get available pools: ${error.message}`
3192
- );
3193
- }
3194
- }
3195
- /**
3196
- * Get currently selected pools for a protocol on a specific chain.
3197
- *
3198
- * Returns the pools that are currently configured for the authenticated user
3199
- * for a given protocol and chain combination.
3200
- *
3201
- * @param protocolId - The protocol UUID
3202
- * @param chainId - The chain ID
3203
- * @returns Currently selected pools and autoselect status
3204
- *
3205
- * @example
3206
- * ```typescript
3207
- * const selected = await sdk.getSelectedPools(
3208
- * "protocol-uuid",
3209
- * 8453 // Base
3210
- * );
3211
- *
3212
- * console.log("Selected pools:", selected.pools);
3213
- * console.log("Autoselect enabled:", selected.autoselect);
3214
- * ```
3215
- */
3216
- async getSelectedPools(protocolId, chainId) {
3217
- try {
3218
- await this.authenticateUser();
3219
- const response = await this.httpClient.get(
3220
- ENDPOINTS.CUSTOMIZATION_SELECTED_POOLS(protocolId, chainId)
3221
- );
3222
- return response;
3223
- } catch (error) {
3224
- throw new Error(
3225
- `Failed to get selected pools: ${error.message}`
3226
- );
3227
- }
3228
- }
3229
- /**
3230
- * Check if a chain ID supports the Identity Registry
3231
- */
3232
- isSupportedIdentityRegistryChain(chainId) {
3233
- return _ZyfaiSDK.IDENTITY_REGISTRY_CHAIN_IDS.includes(chainId);
3234
- }
3235
- /**
3236
- * Register an agent on the Identity Registry (ERC-8004)
3237
- *
3238
- * Fetches a tokenUri from the Zyfai API for the given smart wallet,
3239
- * then calls `register(tokenUri)` on the Identity Registry contract.
3240
- *
3241
- * @param smartWallet - The smart wallet address to register as an agent
3242
- * @param chainId - Chain ID to register on (only Base 8453 and Arbitrum 42161 supported)
3243
- * @returns Response with transaction hash and registration details
3244
- *
3245
- * @example
3246
- * ```typescript
3247
- * const sdk = new ZyfaiSDK({ apiKey: "your-api-key" });
3248
- * await sdk.connectAccount(privateKey, 8453);
3249
- *
3250
- * const result = await sdk.registerAgentOnIdentityRegistry("0xSmartWallet", 8453);
3251
- * console.log("Tx hash:", result.txHash);
3252
- * ```
3253
- */
3254
- async registerAgentOnIdentityRegistry(smartWallet, chainId) {
3255
- if (!smartWallet) {
3256
- throw new Error("Smart wallet address is required");
3257
- }
3258
- if (!this.isSupportedIdentityRegistryChain(chainId)) {
3259
- throw new Error(
3260
- `Chain ${chainId} is not supported for Identity Registry. Supported chains: Base (8453), Arbitrum (42161)`
3261
- );
3262
- }
3263
- try {
3264
- const tokenUriResponse = await this.httpClient.post(
3265
- ENDPOINTS.AGENT_TOKEN_URI,
3266
- { smartWallet }
3267
- );
3268
- if (!tokenUriResponse.tokenUri) {
3269
- throw new Error("API did not return a tokenUri");
3270
- }
3271
- const callData = (0, import_viem5.encodeFunctionData)({
3272
- abi: IDENTITY_REGISTRY_ABI,
3273
- functionName: "register",
3274
- args: [tokenUriResponse.tokenUri]
3275
- });
3276
- const walletClient = this.getWalletClient(chainId);
3277
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
3278
- const txHash = await walletClient.sendTransaction({
3279
- to: IDENTITY_REGISTRY_ADDRESS,
3280
- data: callData,
3281
- chain: chainConfig.chain,
3282
- account: walletClient.account
3283
- });
3284
- const receipt = await chainConfig.publicClient.waitForTransactionReceipt({
3285
- hash: txHash
3286
- });
3287
- if (receipt.status !== "success") {
3288
- throw new Error("Identity Registry registration transaction failed");
3289
- }
3290
- return {
3291
- success: true,
3292
- txHash,
3293
- chainId,
3294
- smartWallet
3295
- };
3296
- } catch (error) {
3297
- throw new Error(
3298
- `Failed to register agent on Identity Registry: ${error.message}`
3299
- );
3300
- }
3301
- }
3302
- // ============================================
3303
- // Vault Methods (Base only)
3304
- // ============================================
3305
- /**
3306
- * Deposit assets into the Zyfai Vault
3307
- * Currently only supports USDC on Base chain
3308
- *
3309
- * @param amount - Amount to deposit (in human readable format, e.g., "100" for 100 USDC)
3310
- * @param asset - Asset to deposit (default: "USDC")
3311
- * @returns Deposit transaction result
3312
- *
3313
- * @example
3314
- * ```typescript
3315
- * const result = await sdk.vaultDeposit("100", "USDC");
3316
- * console.log("Deposited:", result.txHash);
3317
- * ```
3318
- */
3319
- async vaultDeposit(amount, asset = "USDC", chainId = 8453) {
3320
- if (!this.walletClient?.account) {
3321
- throw new Error("Wallet not connected. Call connectAccount first.");
3322
- }
3323
- if (Number(amount) <= 2) {
3324
- throw new Error("Minimum deposit amount is 2.00 USDC");
3325
- }
3326
- const userAddress = this.walletClient.account.address;
3327
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
3328
- const tokenAddress = getDefaultTokenAddress(chainId, asset);
3329
- const decimals = 6;
3330
- const parsedAmount = BigInt(Math.floor(parseFloat(amount) * 10 ** decimals));
3331
- try {
3332
- const allowance = await chainConfig.publicClient.readContract({
3333
- address: tokenAddress,
3334
- abi: ERC20_ABI,
3335
- functionName: "allowance",
3336
- args: [userAddress, VAULT_ADDRESS]
3337
- });
3338
- if (allowance < parsedAmount) {
3339
- const approveTx = await this.walletClient.writeContract({
3340
- address: tokenAddress,
3341
- abi: ERC20_ABI,
3342
- functionName: "approve",
3343
- args: [VAULT_ADDRESS, parsedAmount],
3344
- chain: chainConfig.chain,
3345
- account: this.walletClient.account
3346
- });
3347
- await chainConfig.publicClient.waitForTransactionReceipt({
3348
- hash: approveTx
3349
- });
3350
- }
3351
- const depositTx = await this.walletClient.writeContract({
3352
- address: VAULT_ADDRESS,
3353
- abi: VAULT_ABI,
3354
- functionName: "deposit",
3355
- args: [parsedAmount, userAddress],
3356
- chain: chainConfig.chain,
3357
- account: this.walletClient.account
3358
- });
3359
- const receipt = await chainConfig.publicClient.waitForTransactionReceipt({
3360
- hash: depositTx
3361
- });
3362
- if (receipt.status !== "success") {
3363
- throw new Error("Vault deposit transaction failed");
3364
- }
3365
- return {
3366
- success: true,
3367
- txHash: depositTx,
3368
- amount,
3369
- asset,
3370
- vaultAddress: VAULT_ADDRESS
3371
- };
3372
- } catch (error) {
3373
- throw new Error(`Vault deposit failed: ${error.message}`);
3374
- }
3375
- }
3376
- /**
3377
- * Request withdrawal from the Zyfai Vault
3378
- * Withdrawals are async - use getVaultWithdrawStatus and vaultClaim after
3379
- *
3380
- * @param shares - Amount of shares to redeem (optional, defaults to all shares)
3381
- * @returns Withdraw request result with withdrawKey
3382
- *
3383
- * @example
3384
- * ```typescript
3385
- * const result = await sdk.vaultWithdraw();
3386
- * console.log("Withdraw key:", result.withdrawKey);
3387
- * // Later, check status and claim
3388
- * ```
3389
- */
3390
- async vaultWithdraw(shares, chainId = 8453) {
3391
- if (!this.walletClient?.account) {
3392
- throw new Error("Wallet not connected. Call connectAccount first.");
3393
- }
3394
- const userAddress = this.walletClient.account.address;
3395
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
3396
- try {
3397
- let sharesToRedeem;
3398
- if (shares) {
3399
- sharesToRedeem = BigInt(shares);
3400
- } else {
3401
- const maxShares = await chainConfig.publicClient.readContract({
3402
- address: VAULT_ADDRESS,
3403
- abi: VAULT_ABI,
3404
- functionName: "maxRequestRedeem",
3405
- args: [userAddress]
3406
- });
3407
- if (maxShares === 0n) {
3408
- throw new Error("No shares available to redeem or withdrawals are paused");
3409
- }
3410
- sharesToRedeem = maxShares;
3411
- }
3412
- const redeemTx = await this.walletClient.writeContract({
3413
- address: VAULT_ADDRESS,
3414
- abi: VAULT_ABI,
3415
- functionName: "requestRedeem",
3416
- args: [sharesToRedeem, userAddress, userAddress],
3417
- chain: chainConfig.chain,
3418
- account: this.walletClient.account
3419
- });
3420
- await chainConfig.publicClient.waitForTransactionReceipt({
3421
- hash: redeemTx
3422
- });
3423
- const nonceResult = await chainConfig.publicClient.readContract({
3424
- address: VAULT_ADDRESS,
3425
- abi: VAULT_ABI,
3426
- functionName: "nonces",
3427
- args: [userAddress]
3428
- });
3429
- const nonce = BigInt(nonceResult);
3430
- if (nonce === 0n) {
3431
- throw new Error("Nonce is 0 after requestRedeem - unexpected state");
3432
- }
3433
- const withdrawKey = await chainConfig.publicClient.readContract({
3434
- address: VAULT_ADDRESS,
3435
- abi: VAULT_ABI,
3436
- functionName: "getWithdrawKey",
3437
- args: [userAddress, nonce - 1n]
3438
- });
3439
- const isClaimable = await chainConfig.publicClient.readContract({
3440
- address: VAULT_ADDRESS,
3441
- abi: VAULT_ABI,
3442
- functionName: "isClaimable",
3443
- args: [withdrawKey]
3444
- });
3445
- return {
3446
- success: true,
3447
- txHash: redeemTx,
3448
- withdrawKey,
3449
- status: isClaimable ? "claimable" : "pending"
3450
- };
3451
- } catch (error) {
3452
- throw new Error(`Vault withdraw request failed: ${error.message}`);
3453
- }
3454
- }
3455
- /**
3456
- * Get the status of a pending withdrawal
3457
- *
3458
- * @param withdrawKey - The withdraw key to check (optional, gets latest if not provided)
3459
- * @returns Withdraw status
3460
- *
3461
- * @example
3462
- * ```typescript
3463
- * const status = await sdk.getVaultWithdrawStatus();
3464
- * if (status.isClaimable) {
3465
- * await sdk.vaultClaim(status.withdrawKey);
3466
- * }
3467
- * ```
3468
- */
3469
- async getVaultWithdrawStatus(withdrawKey, chainId = 8453) {
3470
- if (!this.walletClient?.account) {
3471
- throw new Error("Wallet not connected. Call connectAccount first.");
3472
- }
3473
- const userAddress = this.walletClient.account.address;
3474
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
3475
- try {
3476
- const nonceResult = await chainConfig.publicClient.readContract({
3477
- address: VAULT_ADDRESS,
3478
- abi: VAULT_ABI,
3479
- functionName: "nonces",
3480
- args: [userAddress]
3481
- });
3482
- const nonce = BigInt(nonceResult);
3483
- if (nonce === 0n) {
3484
- return {
3485
- success: true,
3486
- withdrawKey: null,
3487
- isClaimable: false,
3488
- isPending: false,
3489
- nonce: 0n
3490
- };
3491
- }
3492
- let keyToCheck = withdrawKey;
3493
- if (!keyToCheck) {
3494
- keyToCheck = await chainConfig.publicClient.readContract({
3495
- address: VAULT_ADDRESS,
3496
- abi: VAULT_ABI,
3497
- functionName: "getWithdrawKey",
3498
- args: [userAddress, nonce - 1n]
3499
- });
3500
- }
3501
- const [isClaimable, isClaimed] = await Promise.all([
3502
- chainConfig.publicClient.readContract({
3503
- address: VAULT_ADDRESS,
3504
- abi: VAULT_ABI,
3505
- functionName: "isClaimable",
3506
- args: [keyToCheck]
3507
- }),
3508
- chainConfig.publicClient.readContract({
3509
- address: VAULT_ADDRESS,
3510
- abi: VAULT_ABI,
3511
- functionName: "isClaimed",
3512
- args: [keyToCheck]
3513
- })
3514
- ]);
3515
- if (isClaimed) {
3516
- return {
3517
- success: true,
3518
- withdrawKey: null,
3519
- isClaimable: false,
3520
- isPending: false,
3521
- nonce
3522
- };
3523
- }
3524
- return {
3525
- success: true,
3526
- withdrawKey: keyToCheck,
3527
- isClaimable,
3528
- isPending: !isClaimable,
3529
- nonce
3530
- };
3531
- } catch (error) {
3532
- throw new Error(`Failed to get withdraw status: ${error.message}`);
3533
- }
3534
- }
3535
- /**
3536
- * Claim a completed withdrawal from the Zyfai Vault
3537
- *
3538
- * @param withdrawKey - The withdraw key to claim
3539
- * @returns Claim transaction result
3540
- *
3541
- * @example
3542
- * ```typescript
3543
- * const status = await sdk.getVaultWithdrawStatus();
3544
- * if (status.isClaimable) {
3545
- * const claim = await sdk.vaultClaim(status.withdrawKey);
3546
- * console.log("Claimed:", claim.txHash);
3547
- * }
3548
- * ```
3549
- */
3550
- async vaultClaim(withdrawKey, chainId = 8453) {
3551
- if (!this.walletClient?.account) {
3552
- throw new Error("Wallet not connected. Call connectAccount first.");
3553
- }
3554
- if (!withdrawKey) {
3555
- throw new Error("Withdraw key is required");
3556
- }
3557
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
3558
- try {
3559
- const isClaimable = await chainConfig.publicClient.readContract({
3560
- address: VAULT_ADDRESS,
3561
- abi: VAULT_ABI,
3562
- functionName: "isClaimable",
3563
- args: [withdrawKey]
3564
- });
3565
- if (!isClaimable) {
3566
- const isClaimed = await chainConfig.publicClient.readContract({
3567
- address: VAULT_ADDRESS,
3568
- abi: VAULT_ABI,
3569
- functionName: "isClaimed",
3570
- args: [withdrawKey]
3571
- });
3572
- if (isClaimed) {
3573
- throw new Error("This withdrawal has already been claimed");
3574
- }
3575
- throw new Error("Withdrawal is not yet claimable. Please wait for processing.");
3576
- }
3577
- const claimTx = await this.walletClient.writeContract({
3578
- address: VAULT_ADDRESS,
3579
- abi: VAULT_ABI,
3580
- functionName: "claim",
3581
- args: [withdrawKey],
3582
- chain: chainConfig.chain,
3583
- account: this.walletClient.account
3584
- });
3585
- const receipt = await chainConfig.publicClient.waitForTransactionReceipt({
3586
- hash: claimTx
3587
- });
3588
- if (receipt.status !== "success") {
3589
- throw new Error("Vault claim transaction failed");
3590
- }
3591
- return {
3592
- success: true,
3593
- txHash: claimTx,
3594
- claimed: true
3595
- };
3596
- } catch (error) {
3597
- throw new Error(`Vault claim failed: ${error.message}`);
3598
- }
3599
- }
3600
- /**
3601
- * Get vault shares info for the connected wallet
3602
- *
3603
- * @param userAddress - Optional user address (defaults to connected wallet)
3604
- * @returns Vault shares balance and token symbol
3605
- *
3606
- * @example
3607
- * ```typescript
3608
- * const { shares, symbol } = await sdk.getVaultShares();
3609
- * console.log(`Balance: ${shares} ${symbol}`);
3610
- * ```
3611
- */
3612
- async getVaultShares(userAddress, chainId = 8453) {
3613
- const address = userAddress || this.walletClient?.account?.address;
3614
- if (!address) {
3615
- throw new Error("User address required. Provide address or connect wallet first.");
3616
- }
3617
- const chainConfig = getChainConfig(chainId, this.rpcUrls);
3618
- const [shareBalance, tokenSymbol] = await Promise.all([
3619
- chainConfig.publicClient.readContract({
3620
- address: VAULT_ADDRESS,
3621
- abi: VAULT_ABI,
3622
- functionName: "balanceOf",
3623
- args: [address]
3624
- }),
3625
- chainConfig.publicClient.readContract({
3626
- address: VAULT_ADDRESS,
3627
- abi: VAULT_ABI,
3628
- functionName: "symbol"
3629
- })
3630
- ]);
3631
- return {
3632
- success: true,
3633
- shares: shareBalance,
3634
- symbol: tokenSymbol
3635
- };
3636
- }
3637
- };
3638
- // ============================================================================
3639
- // Agent Identity Registry
3640
- // ============================================================================
3641
- /**
3642
- * Supported chain IDs for the Identity Registry (ERC-8004)
3643
- */
3644
- _ZyfaiSDK.IDENTITY_REGISTRY_CHAIN_IDS = [8453, 42161];
3645
- var ZyfaiSDK = _ZyfaiSDK;
3646
-
3647
- // src/cli/utils/sdk-wrapper.ts
3648
- var import_viem6 = require("viem");
3649
- var import_chains3 = require("viem/chains");
3650
-
3651
- // src/cli/utils/moonpay.ts
3652
- var import_child_process = require("child_process");
3653
- function sanitizeShellArg(arg) {
3654
- if (!/^[a-zA-Z0-9._-]+$/.test(arg)) {
3655
- throw new Error(`Invalid argument: "${arg}". Only alphanumeric characters, dots, dashes, and underscores are allowed.`);
3656
- }
3657
- return arg;
3658
- }
3659
- function execMoonPay(args) {
3660
- try {
3661
- const command = ["mp", ...args, "--json"].join(" ");
3662
- const result = (0, import_child_process.execSync)(command, {
3663
- encoding: "utf-8",
3664
- stdio: ["pipe", "pipe", "pipe"]
3665
- });
3666
- return result.trim();
3667
- } catch (error) {
3668
- const execError = error;
3669
- const stderr = execError.stderr || execError.message || "Unknown error";
3670
- throw new Error(`MoonPay CLI error: ${stderr}`);
3671
- }
3672
- }
3673
- function checkMoonPayCli() {
3674
- try {
3675
- (0, import_child_process.execSync)("mp --version", { stdio: ["pipe", "pipe", "pipe"] });
3676
- return true;
3677
- } catch {
3678
- return false;
3679
- }
3680
- }
3681
- function getWallet(walletName) {
3682
- const safeName = sanitizeShellArg(walletName);
3683
- const result = execMoonPay(["wallet", "retrieve", "--wallet", safeName]);
3684
- return JSON.parse(result);
3685
- }
3686
- function getWalletAddress(walletName) {
3687
- const wallet = getWallet(walletName);
3688
- const ethAddress = wallet.addresses?.ethereum;
3689
- if (!ethAddress) {
3690
- throw new Error(`Wallet "${walletName}" does not have an Ethereum address`);
3691
- }
3692
- return ethAddress;
3693
- }
3694
- function signMessage(walletName, message, chain = "base") {
3695
- const safeName = sanitizeShellArg(walletName);
3696
- const safeChain = sanitizeShellArg(chain);
3697
- const fs2 = require("fs");
3698
- const os2 = require("os");
3699
- const path2 = require("path");
3700
- const tempFile = path2.join(os2.tmpdir(), `zyfai-siwe-${Date.now()}.txt`);
3701
- try {
3702
- fs2.writeFileSync(tempFile, message, { encoding: "utf-8" });
3703
- const result = (0, import_child_process.execSync)(
3704
- `mp message sign --wallet ${safeName} --chain ${safeChain} --message "$(cat ${tempFile})" --json`,
3705
- {
3706
- encoding: "utf-8",
3707
- stdio: ["pipe", "pipe", "pipe"],
3708
- shell: "/bin/bash"
3709
- }
3710
- );
3711
- const parsed = JSON.parse(result.trim());
3712
- return parsed.signature;
3713
- } catch (error) {
3714
- const execError = error;
3715
- throw new Error(`MoonPay sign error: ${execError.stderr || execError.message}`);
3716
- } finally {
3717
- try {
3718
- fs2.unlinkSync(tempFile);
3719
- } catch {
3720
- }
3721
- }
3722
- }
3723
- function signTypedData(walletName, typedData, chain = "base") {
3724
- const safeName = sanitizeShellArg(walletName);
3725
- const safeChain = sanitizeShellArg(chain);
3726
- const typedDataJson = JSON.stringify(typedData);
3727
- const fs2 = require("fs");
3728
- const os2 = require("os");
3729
- const path2 = require("path");
3730
- const tempFile = path2.join(os2.tmpdir(), `zyfai-typed-${Date.now()}.json`);
3731
- try {
3732
- fs2.writeFileSync(tempFile, typedDataJson, { encoding: "utf-8" });
3733
- const result = (0, import_child_process.execSync)(
3734
- `mp message sign --wallet ${safeName} --chain ${safeChain} --typedData "$(cat ${tempFile})" --json`,
3735
- {
3736
- encoding: "utf-8",
3737
- stdio: ["pipe", "pipe", "pipe"],
3738
- shell: "/bin/bash"
3739
- }
3740
- );
3741
- const parsed = JSON.parse(result.trim());
3742
- return parsed.signature;
3743
- } catch (error) {
3744
- const execError = error;
3745
- throw new Error(`MoonPay sign error: ${execError.stderr || execError.message}`);
3746
- } finally {
3747
- try {
3748
- fs2.unlinkSync(tempFile);
3749
- } catch {
3750
- }
3751
- }
3752
- }
3753
- function chainIdToMoonPayChain(chainId) {
3754
- switch (chainId) {
3755
- case 8453:
3756
- return "base";
3757
- case 42161:
3758
- return "arbitrum";
3759
- case 9745:
3760
- return "plasma";
3761
- default:
3762
- throw new Error(`Unsupported chain ID: ${chainId}`);
3763
- }
3764
- }
3765
- function moonPayChainToChainId(chain) {
3766
- switch (chain.toLowerCase()) {
3767
- case "base":
3768
- return 8453;
3769
- case "arbitrum":
3770
- return 42161;
3771
- case "plasma":
3772
- return 9745;
3773
- default:
3774
- throw new Error(`Unsupported chain: ${chain}`);
3775
- }
3776
- }
3777
-
3778
- // src/cli/utils/sdk-wrapper.ts
3779
- function createMoonPayAccount(walletName, chainId) {
3780
- const address = (0, import_viem6.getAddress)(getWalletAddress(walletName));
3781
- const moonPayChain = chainIdToMoonPayChain(chainId);
3782
- console.error("[DEBUG] Created MoonPay account:", address);
3783
- return {
3784
- address,
3785
- type: "local",
3786
- // Sign a message (EIP-191)
3787
- signMessage: async ({ message }) => {
3788
- console.error("[DEBUG] signMessage called with:", typeof message);
3789
- let messageStr;
3790
- if (typeof message === "string") {
3791
- console.error("[DEBUG] Message is string, length:", message.length);
3792
- messageStr = message;
3793
- } else if ("raw" in message) {
3794
- const raw = message.raw;
3795
- console.error("[DEBUG] Message is raw, type:", typeof raw, "isHex:", typeof raw === "string" && raw.startsWith("0x"));
3796
- if (typeof raw === "string") {
3797
- messageStr = raw;
3798
- } else {
3799
- messageStr = "0x" + Buffer.from(raw).toString("hex");
3800
- }
3801
- console.error("[DEBUG] Raw message to sign:", messageStr.substring(0, 50) + "...");
3802
- } else {
3803
- messageStr = message.toString();
3804
- }
3805
- console.error("[DEBUG] Calling MoonPay to sign message...");
3806
- const signature = signMessage(walletName, messageStr, moonPayChain);
3807
- console.error("[DEBUG] Got signature:", signature.substring(0, 20) + "...");
3808
- return signature;
3809
- },
3810
- // Sign typed data (EIP-712)
3811
- signTypedData: async (typedData) => {
3812
- console.error("[DEBUG] signTypedData called");
3813
- console.error("[DEBUG] TypedData:", JSON.stringify(typedData, null, 2).substring(0, 200) + "...");
3814
- const signature = signTypedData(walletName, typedData, moonPayChain);
3815
- console.error("[DEBUG] Got signature:", signature.substring(0, 20) + "...");
3816
- return signature;
3817
- },
3818
- // Sign a transaction - not implemented (we use backend for tx execution)
3819
- signTransaction: async () => {
3820
- console.error("[DEBUG] signTransaction called - NOT SUPPORTED");
3821
- throw new Error("Transaction signing not supported via MoonPay CLI. Use backend execution.");
3822
- }
3823
- };
3824
- }
3825
- async function createAuthenticatedSdk(walletName, chainId) {
3826
- console.error("[DEBUG] createAuthenticatedSdk starting...");
3827
- console.error("[DEBUG] Wallet:", walletName, "Chain:", chainId);
3828
- const apiKey = getApiKey();
3829
- console.error("[DEBUG] API Key:", apiKey.substring(0, 15) + "...");
3830
- const sdk = new ZyfaiSDK({ apiKey });
3831
- console.error("[DEBUG] Creating MoonPay account...");
3832
- const account = createMoonPayAccount(walletName, chainId);
3833
- const walletLike = {
3834
- account,
3835
- transport: (0, import_viem6.http)()
3836
- // http() returns a transport factory function
3837
- };
3838
- console.error("[DEBUG] Connecting account to SDK...");
3839
- await sdk.connectAccount(walletLike, chainId);
3840
- console.error("[DEBUG] Account connected successfully!");
3841
- return { sdk, userAddress: account.address };
3842
- }
3843
- function createReadOnlySdk() {
3844
- const apiKey = getApiKey();
3845
- return new ZyfaiSDK({ apiKey });
3846
- }
3847
- function parseChainArg(chain) {
3848
- if (/^\d+$/.test(chain)) {
3849
- const chainId = parseInt(chain, 10);
3850
- if (chainId === 8453 || chainId === 42161 || chainId === 9745) {
3851
- return chainId;
3852
- }
3853
- throw new Error(`Unsupported chain ID: ${chainId}. Supported: 8453 (base), 42161 (arbitrum), 9745 (plasma)`);
3854
- }
3855
- return moonPayChainToChainId(chain);
3856
- }
3857
-
3858
- // src/cli/commands/deploy.ts
3859
- function registerDeployCommand(program2) {
3860
- program2.command("deploy").description("Deploy a Safe smart wallet for yield optimization").requiredOption("--wallet <name>", "MoonPay wallet name to use for signing").option("--chain <chain>", "Chain to deploy on (base, arbitrum, plasma)", getDefaultChain()).option("--strategy <strategy>", "Yield strategy: conservative or aggressive", "conservative").action(async (options) => {
3861
- try {
3862
- const chainId = parseChainArg(options.chain);
3863
- const strategy = options.strategy;
3864
- if (!["conservative", "aggressive"].includes(strategy)) {
3865
- throw new Error("Invalid strategy. Must be 'conservative' or 'aggressive'");
3866
- }
3867
- const { sdk, userAddress } = await createAuthenticatedSdk(options.wallet, chainId);
3868
- const result = await sdk.deploySafe(
3869
- userAddress,
3870
- chainId,
3871
- strategy,
3872
- true
3873
- // createSessionKey
3874
- );
3875
- outputSuccess({
3876
- safeAddress: result.safeAddress,
3877
- status: result.status,
3878
- sessionKeyCreated: result.sessionKeyCreated,
3879
- txHash: result.txHash,
3880
- chain: options.chain,
3881
- chainId,
3882
- userAddress
3883
- });
3884
- } catch (error) {
3885
- outputError(error);
3886
- }
3887
- });
3888
- }
3889
-
3890
- // src/cli/commands/deposit.ts
3891
- function registerDepositCommand(program2) {
3892
- program2.command("deposit").description("Deposit tokens to Safe for yield optimization").requiredOption("--wallet <name>", "MoonPay wallet name").requiredOption("--amount <amount>", "Amount in token units (e.g., 100000000 for 100 USDC)").option("--chain <chain>", "Chain for deposit (base, arbitrum, plasma)", getDefaultChain()).option("--asset <asset>", "Asset to deposit: USDC or WETH", "USDC").action(async (options) => {
3893
- try {
3894
- const chainId = parseChainArg(options.chain);
3895
- const userAddress = getWalletAddress(options.wallet);
3896
- const { sdk } = await createAuthenticatedSdk(options.wallet, chainId);
3897
- const result = await sdk.depositFunds(
3898
- userAddress,
3899
- chainId,
3900
- options.amount,
3901
- options.asset
3902
- );
3903
- outputSuccess({
3904
- txHash: result.txHash,
3905
- smartWallet: result.smartWallet,
3906
- amount: result.amount,
3907
- asset: options.asset,
3908
- chain: options.chain,
3909
- chainId
3910
- });
3911
- } catch (error) {
3912
- outputError(error);
3913
- }
3914
- });
3915
- }
3916
-
3917
- // src/cli/commands/withdraw.ts
3918
- function registerWithdrawCommand(program2) {
3919
- program2.command("withdraw").description("Withdraw funds from Safe (full or partial)").requiredOption("--wallet <name>", "MoonPay wallet name").option("--chain <chain>", "Chain to withdraw from (base, arbitrum, plasma)", getDefaultChain()).option("--amount <amount>", "Amount to withdraw (omit for full withdrawal)").option("--asset <asset>", "Asset to withdraw: USDC or WETH", "USDC").action(async (options) => {
3920
- try {
3921
- const chainId = parseChainArg(options.chain);
3922
- const userAddress = getWalletAddress(options.wallet);
3923
- const { sdk } = await createAuthenticatedSdk(options.wallet, chainId);
3924
- const result = await sdk.withdrawFunds(
3925
- userAddress,
3926
- chainId,
3927
- options.amount,
3928
- options.asset
3929
- );
3930
- outputSuccess({
3931
- type: result.type,
3932
- amount: result.amount,
3933
- message: result.message,
3934
- txHash: result.txHash,
3935
- asset: options.asset,
3936
- chain: options.chain,
3937
- chainId
3938
- });
3939
- } catch (error) {
3940
- outputError(error);
3941
- }
3942
- });
3943
- }
3944
-
3945
- // src/cli/commands/earnings.ts
3946
- function registerEarningsCommand(program2) {
3947
- program2.command("earnings").description("Get onchain earnings for a Smart Wallet").requiredOption("--user <address>", "Smart Wallet address (not EOA)").action(async (options) => {
3948
- try {
3949
- const sdk = createReadOnlySdk();
3950
- const result = await sdk.getOnchainEarnings(options.user);
3951
- outputSuccess({
3952
- walletAddress: result.data.walletAddress,
3953
- totalEarningsByToken: result.data.totalEarningsByToken,
3954
- lastCheckTimestamp: result.data.lastCheckTimestamp,
3955
- lastLogDate: result.data.lastLogDate
3956
- });
3957
- } catch (error) {
3958
- outputError(error);
3959
- }
3960
- });
3961
- }
3962
-
3963
- // src/cli/index.ts
3964
- var program = new import_commander.Command();
3965
- program.name("zyfai").description(
3966
- "Zyfai CLI - DeFi yield optimization with Safe smart wallets\n\nCommands that modify state (deploy, deposit, withdraw) require a MoonPay wallet.\nRead-only commands (earnings) only need the API key."
3967
- ).version("0.2.31").hook("preAction", (thisCommand) => {
3968
- const commandName = thisCommand.args[0];
3969
- const needsMoonPay = ["deploy", "deposit", "withdraw"].includes(commandName);
3970
- if (needsMoonPay && !checkMoonPayCli()) {
3971
- outputError(
3972
- "MoonPay CLI not found.\n\nInstall with: npm install -g @moonpay/cli\nThen authenticate: mp login && mp verify\nCreate a wallet: mp wallet create --name zyfai"
3973
- );
3974
- }
3975
- });
3976
- registerConfigCommand(program);
3977
- registerDeployCommand(program);
3978
- registerDepositCommand(program);
3979
- registerWithdrawCommand(program);
3980
- registerEarningsCommand(program);
3981
- program.parse();