@zyfai/sdk 0.2.30 → 0.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli/index.js +3981 -0
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/index.mjs +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,3981 @@
|
|
|
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();
|