@owney/sdk 0.2.5
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 +584 -0
- package/dist/index.cjs +1465 -0
- package/dist/index.d.cts +322 -0
- package/dist/index.d.ts +322 -0
- package/dist/index.js +1435 -0
- package/package.json +30 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1435 @@
|
|
|
1
|
+
// src/agents/zyfai/zyfai.agent.ts
|
|
2
|
+
import { ZyfaiSDK } from "@zyfai/sdk";
|
|
3
|
+
|
|
4
|
+
// src/types/config.ts
|
|
5
|
+
var P_SUPPORTED_CHAINS = {
|
|
6
|
+
chainIds: [8453, 42161],
|
|
7
|
+
chains: ["BASE", "ARBITRUM"]
|
|
8
|
+
};
|
|
9
|
+
var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
|
|
10
|
+
var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
|
|
11
|
+
|
|
12
|
+
// src/lib/utils.ts
|
|
13
|
+
var isValidChainId = (chainId) => {
|
|
14
|
+
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Unsupported chain ID: ${chainId}. Supported chains: ${SUPPORTED_CHAIN_IDS.join(", ")}`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return chainId;
|
|
20
|
+
};
|
|
21
|
+
function hexToDecimal(hex, decimals = 6) {
|
|
22
|
+
const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
|
|
23
|
+
const parsed = BigInt(normalized);
|
|
24
|
+
if (decimals <= 0)
|
|
25
|
+
return Number(parsed);
|
|
26
|
+
const divisor = BigInt(10 ** decimals);
|
|
27
|
+
const whole = parsed / divisor;
|
|
28
|
+
const remainder = parsed % divisor;
|
|
29
|
+
return Number(whole) + Number(remainder) / Number(divisor);
|
|
30
|
+
}
|
|
31
|
+
var DayFilterMapping = {
|
|
32
|
+
"30D": 30,
|
|
33
|
+
"14D": 14,
|
|
34
|
+
"7D": 7
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/agents/zyfai/zyfai.mapper.ts
|
|
38
|
+
function mapDeposit(raw) {
|
|
39
|
+
return {
|
|
40
|
+
txHash: raw.txHash,
|
|
41
|
+
smartWallet: raw.smartWallet,
|
|
42
|
+
amount: raw.amount
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function mapWithdraw(raw) {
|
|
46
|
+
return {
|
|
47
|
+
txHash: raw.txHash,
|
|
48
|
+
type: raw.type,
|
|
49
|
+
amount: raw.amount
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
var CHAIN_ID_TO_NAME = {
|
|
53
|
+
8453: "BASE",
|
|
54
|
+
42161: "ARBITRUM"
|
|
55
|
+
};
|
|
56
|
+
var CHAIN_NAME_TO_ID = {
|
|
57
|
+
BASE: 8453,
|
|
58
|
+
ARBITRUM: 42161
|
|
59
|
+
};
|
|
60
|
+
function mapBalances(raw, chainId) {
|
|
61
|
+
const portfolio = raw.portfolio;
|
|
62
|
+
const tokenMap = /* @__PURE__ */ new Map();
|
|
63
|
+
for (const p of portfolio.positions || []) {
|
|
64
|
+
const symbol = p.token_symbol || "USDC";
|
|
65
|
+
const positionChainId = (p.chain ? CHAIN_NAME_TO_ID[p.chain.toUpperCase()] : void 0) ?? chainId;
|
|
66
|
+
const key = `${symbol}:${positionChainId}`;
|
|
67
|
+
const current = tokenMap.get(key);
|
|
68
|
+
tokenMap.set(key, {
|
|
69
|
+
symbol,
|
|
70
|
+
chainId: positionChainId,
|
|
71
|
+
amount: (current?.amount ?? 0) + hexToDecimal(p.amount || "0")
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
for (const s of portfolio.staleBalances || []) {
|
|
75
|
+
const symbol = s.tokenSymbol || "USDC";
|
|
76
|
+
const staleChainId = s.chainId ?? chainId;
|
|
77
|
+
const key = `${symbol}:${staleChainId}`;
|
|
78
|
+
const current = tokenMap.get(key);
|
|
79
|
+
tokenMap.set(key, {
|
|
80
|
+
symbol,
|
|
81
|
+
chainId: staleChainId,
|
|
82
|
+
amount: (current?.amount ?? 0) + hexToDecimal(s.balance || "0")
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
let totalBalance = 0;
|
|
86
|
+
const tokens = [];
|
|
87
|
+
for (const [, entry] of tokenMap) {
|
|
88
|
+
totalBalance += entry.amount;
|
|
89
|
+
tokens.push({
|
|
90
|
+
chain: CHAIN_ID_TO_NAME[entry.chainId] ?? "BASE",
|
|
91
|
+
chainId: entry.chainId,
|
|
92
|
+
asset: entry.symbol,
|
|
93
|
+
amount: String(entry.amount)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
smartWallet: portfolio.smartWallet,
|
|
98
|
+
totalBalance: String(totalBalance),
|
|
99
|
+
tokens
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function sumTokenValues(tokens) {
|
|
103
|
+
return Object.values(tokens).reduce((sum, val) => sum + val, 0);
|
|
104
|
+
}
|
|
105
|
+
function sumTokenEarnings(tokens) {
|
|
106
|
+
return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
|
|
107
|
+
}
|
|
108
|
+
function mapEarnings(raw, smartWallet) {
|
|
109
|
+
return {
|
|
110
|
+
smartWallet,
|
|
111
|
+
lifetimeEarnings: sumTokenEarnings(raw.data.totalEarningsByToken)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function mapApyHistory(raw) {
|
|
115
|
+
return {
|
|
116
|
+
walletAddress: raw.walletAddress,
|
|
117
|
+
weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function mapHistory(raw) {
|
|
121
|
+
const actionMap = {
|
|
122
|
+
"Deposit to protocol": "Deposit",
|
|
123
|
+
"Top up wallet": "Top up",
|
|
124
|
+
"Withdraw from protocol": "Withdraw",
|
|
125
|
+
Rebalance: "Rebalance"
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
total: raw.total,
|
|
129
|
+
data: raw.data.map((entry) => {
|
|
130
|
+
const rawLog = entry.rebalanceLog;
|
|
131
|
+
const positions = entry.positions ?? [];
|
|
132
|
+
let rebalanceLog = [];
|
|
133
|
+
let oldApy = null;
|
|
134
|
+
let newApy = null;
|
|
135
|
+
if (rawLog) {
|
|
136
|
+
const matched = positions.find(
|
|
137
|
+
(p) => p.protocol_name ? rawLog.newOpportunity.includes(`${p.protocol_name} (${p.pool})`) : false
|
|
138
|
+
);
|
|
139
|
+
oldApy = Number(rawLog.oldApy);
|
|
140
|
+
newApy = Number(rawLog.newApy);
|
|
141
|
+
rebalanceLog = [
|
|
142
|
+
{
|
|
143
|
+
fromProtocol: rawLog.oldOpportunity.replace(/\s*\(.*?\)/g, "").trim(),
|
|
144
|
+
toProtocol: rawLog.newOpportunity.replace(/\s*\(.*?\)/g, "").trim(),
|
|
145
|
+
tokenSymbol: matched?.token_symbol ?? "",
|
|
146
|
+
amount: matched ? String(hexToDecimal(matched.amount || "0x")) : "",
|
|
147
|
+
status: "success"
|
|
148
|
+
}
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
agent: "zyfai",
|
|
153
|
+
action: (entry.action ? actionMap[entry.action] : void 0) ?? entry.action,
|
|
154
|
+
date: entry.date || "",
|
|
155
|
+
newApy: String(newApy),
|
|
156
|
+
oldApy: String(oldApy),
|
|
157
|
+
transactions: [
|
|
158
|
+
{
|
|
159
|
+
txHashes: entry.transactionHash ? [entry.transactionHash] : [],
|
|
160
|
+
chainId: entry.chainId,
|
|
161
|
+
tokenSymbol: entry?.positions?.[0]?.token_symbol,
|
|
162
|
+
amount: String(hexToDecimal(entry?.positions?.[0]?.amount || "0x"))
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
rebalanceLog
|
|
166
|
+
};
|
|
167
|
+
})
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function mapAgentStatus(raw) {
|
|
171
|
+
return {
|
|
172
|
+
success: raw.success,
|
|
173
|
+
strategy: raw.strategy,
|
|
174
|
+
protocols: raw.protocols
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function mapUserProfile(raw, userAddress) {
|
|
178
|
+
return {
|
|
179
|
+
address: userAddress,
|
|
180
|
+
smartWallet: raw.smartWallet || "",
|
|
181
|
+
chains: raw.chains || [],
|
|
182
|
+
strategy: raw.strategy,
|
|
183
|
+
hasActiveSessionKey: raw.hasActiveSessionKey || false,
|
|
184
|
+
protocols: raw.protocols || []
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function mapApyByStrategy(raw) {
|
|
188
|
+
const first = raw.data[0];
|
|
189
|
+
return {
|
|
190
|
+
averageApy: first?.average_apy ?? 0
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/agents/zyfai/zyfai.agent.ts
|
|
195
|
+
var ZyfaiAgent = class {
|
|
196
|
+
id = "zyfai";
|
|
197
|
+
supportedChainIds = [8453, 42161];
|
|
198
|
+
supportedAssets = [
|
|
199
|
+
{
|
|
200
|
+
chainId: 8453,
|
|
201
|
+
chain: "BASE",
|
|
202
|
+
assets: ["USDC"]
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
chainId: 42161,
|
|
206
|
+
chain: "ARBITRUM",
|
|
207
|
+
assets: ["USDC"]
|
|
208
|
+
}
|
|
209
|
+
];
|
|
210
|
+
sdk;
|
|
211
|
+
connectedAddress = null;
|
|
212
|
+
connectedChainId = null;
|
|
213
|
+
constructor(apiKey) {
|
|
214
|
+
this.sdk = new ZyfaiSDK({ apiKey });
|
|
215
|
+
}
|
|
216
|
+
// --- Private helpers ---
|
|
217
|
+
getAddress() {
|
|
218
|
+
if (!this.connectedAddress) {
|
|
219
|
+
throw new Error("Wallet not connected. Call sdk.connect() first.");
|
|
220
|
+
}
|
|
221
|
+
return this.connectedAddress;
|
|
222
|
+
}
|
|
223
|
+
/** Light connect: SIWE auth only. Re-auths if chain changed. */
|
|
224
|
+
async connectAuth(state, chainId) {
|
|
225
|
+
const validChainId = isValidChainId(chainId);
|
|
226
|
+
if (!this.connectedAddress || this.connectedChainId !== validChainId) {
|
|
227
|
+
const address = await this.sdk.connectAccount(
|
|
228
|
+
state.provider,
|
|
229
|
+
validChainId
|
|
230
|
+
);
|
|
231
|
+
this.connectedAddress = address;
|
|
232
|
+
this.connectedChainId = validChainId;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** Full connect: SIWE auth + Safe deploy check + session key. */
|
|
236
|
+
async connectFull(state, chainId) {
|
|
237
|
+
await this.connectAuth(state, chainId);
|
|
238
|
+
const validChainId = isValidChainId(chainId);
|
|
239
|
+
const address = this.getAddress();
|
|
240
|
+
const wallet = await this.sdk.getSmartWalletAddress(address, validChainId);
|
|
241
|
+
if (!wallet.isDeployed) {
|
|
242
|
+
await this.sdk.deploySafe(address, validChainId, "aggressive", true);
|
|
243
|
+
}
|
|
244
|
+
const user = await this.sdk.getUserDetails();
|
|
245
|
+
if (!user.hasActiveSessionKey) {
|
|
246
|
+
await this.sdk.createSessionKey(address, validChainId);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** Connect + resolve smart wallet address for read operations. */
|
|
250
|
+
async resolveSmartWallet(state, chainId) {
|
|
251
|
+
await this.connectAuth(state, chainId);
|
|
252
|
+
const validChainId = isValidChainId(chainId);
|
|
253
|
+
const { address } = await this.sdk.getSmartWalletAddress(
|
|
254
|
+
this.getAddress(),
|
|
255
|
+
validChainId
|
|
256
|
+
);
|
|
257
|
+
return { smartWallet: address, chainId: validChainId };
|
|
258
|
+
}
|
|
259
|
+
// --- IAgent: Connection lifecycle ---
|
|
260
|
+
async disconnect() {
|
|
261
|
+
await this.sdk.disconnectAccount();
|
|
262
|
+
this.connectedAddress = null;
|
|
263
|
+
this.connectedChainId = null;
|
|
264
|
+
}
|
|
265
|
+
// --- IAgent: Account lifecycle ---
|
|
266
|
+
async activateAgent(state, chainId) {
|
|
267
|
+
try {
|
|
268
|
+
await this.connectFull(state, chainId);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
await this.disconnect();
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async deposit(state, chainId, amount, depositCallback) {
|
|
275
|
+
try {
|
|
276
|
+
await this.connectFull(state, chainId);
|
|
277
|
+
const validChainId = isValidChainId(chainId);
|
|
278
|
+
if (depositCallback) {
|
|
279
|
+
const wallet = await this.sdk.getSmartWalletAddress(
|
|
280
|
+
this.getAddress(),
|
|
281
|
+
validChainId
|
|
282
|
+
);
|
|
283
|
+
const txHash = await depositCallback(
|
|
284
|
+
wallet.address,
|
|
285
|
+
validChainId,
|
|
286
|
+
amount
|
|
287
|
+
);
|
|
288
|
+
await this.sdk.logDeposit(validChainId, txHash, amount);
|
|
289
|
+
return { txHash, smartWallet: wallet.address, amount };
|
|
290
|
+
}
|
|
291
|
+
const raw = await this.sdk.depositFunds(
|
|
292
|
+
this.getAddress(),
|
|
293
|
+
validChainId,
|
|
294
|
+
amount
|
|
295
|
+
);
|
|
296
|
+
return mapDeposit(raw);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
await this.disconnect();
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// --- IAgent: Fund operations ---
|
|
303
|
+
async withdraw(state, chainId, amount) {
|
|
304
|
+
await this.connectFull(state, chainId);
|
|
305
|
+
const validChainId = isValidChainId(chainId);
|
|
306
|
+
const raw = await this.sdk.withdrawFunds(
|
|
307
|
+
this.getAddress(),
|
|
308
|
+
validChainId,
|
|
309
|
+
amount
|
|
310
|
+
);
|
|
311
|
+
return mapWithdraw(raw);
|
|
312
|
+
}
|
|
313
|
+
// --- IAgent: Portfolio reads ---
|
|
314
|
+
async getBalances(state, chainId) {
|
|
315
|
+
await this.connectAuth(state, chainId);
|
|
316
|
+
const validChainId = isValidChainId(chainId);
|
|
317
|
+
const raw = await this.sdk.getPositions(this.getAddress(), validChainId);
|
|
318
|
+
return mapBalances(raw, validChainId);
|
|
319
|
+
}
|
|
320
|
+
async getEarnings(state, chainId) {
|
|
321
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
322
|
+
const raw = await this.sdk.calculateOnchainEarnings(smartWallet);
|
|
323
|
+
return mapEarnings(raw, smartWallet);
|
|
324
|
+
}
|
|
325
|
+
async getAccountApy(state, chainId, days) {
|
|
326
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
327
|
+
const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
|
|
328
|
+
return mapApyHistory(raw);
|
|
329
|
+
}
|
|
330
|
+
async getHistory(state, chainId, options) {
|
|
331
|
+
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
332
|
+
const raw = await this.sdk.getHistory(smartWallet, validChainId, options);
|
|
333
|
+
return mapHistory(raw);
|
|
334
|
+
}
|
|
335
|
+
// --- IAgent: Agent control ---
|
|
336
|
+
async pause(state, chainId) {
|
|
337
|
+
await this.connectAuth(state, chainId);
|
|
338
|
+
const raw = await this.sdk.pauseAgent();
|
|
339
|
+
return mapAgentStatus(raw);
|
|
340
|
+
}
|
|
341
|
+
async resume(state, chainId) {
|
|
342
|
+
await this.connectAuth(state, chainId);
|
|
343
|
+
const raw = await this.sdk.resumeAgent();
|
|
344
|
+
return mapAgentStatus(raw);
|
|
345
|
+
}
|
|
346
|
+
async getUserProfile(state, chainId) {
|
|
347
|
+
await this.connectAuth(state, chainId);
|
|
348
|
+
const raw = await this.sdk.getUserDetails();
|
|
349
|
+
return mapUserProfile(raw, this.connectedAddress);
|
|
350
|
+
}
|
|
351
|
+
// --- IAgent: Discovery (no wallet required) ---
|
|
352
|
+
async getAgentApy(days) {
|
|
353
|
+
const raw = await this.sdk.getAPYPerStrategy(
|
|
354
|
+
false,
|
|
355
|
+
DayFilterMapping[days],
|
|
356
|
+
"aggressive"
|
|
357
|
+
);
|
|
358
|
+
return mapApyByStrategy(raw);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// src/agents/sail/sail.mapper.ts
|
|
363
|
+
function mapDeposit2(raw) {
|
|
364
|
+
return {
|
|
365
|
+
txHash: raw.txHash,
|
|
366
|
+
smartWallet: raw.smartWallet,
|
|
367
|
+
amount: raw.amount
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function mapWithdraw2(raw) {
|
|
371
|
+
return {
|
|
372
|
+
txHash: raw.txHash,
|
|
373
|
+
type: raw.type,
|
|
374
|
+
amount: raw.amount
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function mapPortfolioBalances(raw, smartWallet) {
|
|
378
|
+
let total = 0;
|
|
379
|
+
const tokens = [];
|
|
380
|
+
for (const item of raw.result) {
|
|
381
|
+
total += item.usd_value;
|
|
382
|
+
tokens.push({
|
|
383
|
+
chain: item.chain.toUpperCase(),
|
|
384
|
+
chainId: item.chain_id,
|
|
385
|
+
asset: item.token === "USD\u20AE0" ? "USDT" : item.token,
|
|
386
|
+
amount: String(item.usd_value)
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
smartWallet,
|
|
391
|
+
totalBalance: String(total),
|
|
392
|
+
tokens
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function mapEarnings2(raw, smartWallet) {
|
|
396
|
+
return {
|
|
397
|
+
smartWallet,
|
|
398
|
+
lifetimeEarnings: raw.result
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function mapApyHistory2(raw, walletAddress) {
|
|
402
|
+
return {
|
|
403
|
+
walletAddress,
|
|
404
|
+
weightedApyAfterFee: raw.result
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function mapSailTransaction(tx) {
|
|
408
|
+
switch (tx.type) {
|
|
409
|
+
case "deposit":
|
|
410
|
+
case "redeem":
|
|
411
|
+
return {
|
|
412
|
+
txHashes: tx.tx_hashes,
|
|
413
|
+
chainId: tx.chain_id,
|
|
414
|
+
tokenSymbol: tx.asset,
|
|
415
|
+
amount: String(tx.amount)
|
|
416
|
+
};
|
|
417
|
+
case "swap":
|
|
418
|
+
return {
|
|
419
|
+
txHashes: tx.tx_hashes,
|
|
420
|
+
chainId: tx.chain_id,
|
|
421
|
+
tokenSymbol: tx.from_asset,
|
|
422
|
+
amount: String(tx.amount)
|
|
423
|
+
};
|
|
424
|
+
case "bridge":
|
|
425
|
+
return {
|
|
426
|
+
txHashes: tx.tx_hashes,
|
|
427
|
+
chainId: Number(tx.from_chain_id),
|
|
428
|
+
tokenSymbol: tx.asset,
|
|
429
|
+
amount: String(tx.amount)
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function buildRebalanceLogs(transactions, failedTransactions) {
|
|
434
|
+
const redeems = transactions.filter(
|
|
435
|
+
(tx) => tx.type === "redeem"
|
|
436
|
+
);
|
|
437
|
+
const deposits = transactions.filter(
|
|
438
|
+
(tx) => tx.type === "deposit"
|
|
439
|
+
);
|
|
440
|
+
const failedRedeems = failedTransactions.filter(
|
|
441
|
+
(tx) => tx.type === "redeem"
|
|
442
|
+
);
|
|
443
|
+
const failedDeposits = failedTransactions.filter(
|
|
444
|
+
(tx) => tx.type === "deposit"
|
|
445
|
+
);
|
|
446
|
+
const logs = [];
|
|
447
|
+
const matchedDeposits = /* @__PURE__ */ new Set();
|
|
448
|
+
const matchedRedeems = /* @__PURE__ */ new Set();
|
|
449
|
+
for (let r = 0; r < redeems.length; r++) {
|
|
450
|
+
const idx = deposits.findIndex(
|
|
451
|
+
(d, i) => !matchedDeposits.has(i) && d.amount === redeems[r].amount
|
|
452
|
+
);
|
|
453
|
+
if (idx !== -1) {
|
|
454
|
+
matchedDeposits.add(idx);
|
|
455
|
+
matchedRedeems.add(r);
|
|
456
|
+
logs.push({
|
|
457
|
+
fromProtocol: redeems[r].protocol_name,
|
|
458
|
+
toProtocol: deposits[idx].protocol_name,
|
|
459
|
+
tokenSymbol: redeems[r].asset,
|
|
460
|
+
amount: String(redeems[r].amount),
|
|
461
|
+
status: "success"
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
for (let i = 0; i < deposits.length; i++) {
|
|
466
|
+
if (matchedDeposits.has(i))
|
|
467
|
+
continue;
|
|
468
|
+
const failedRedeem = failedRedeems.find(
|
|
469
|
+
(fr) => fr.amount === deposits[i].amount
|
|
470
|
+
);
|
|
471
|
+
logs.push({
|
|
472
|
+
fromProtocol: failedRedeem ? failedRedeem.protocol_name : "Wallet",
|
|
473
|
+
toProtocol: deposits[i].protocol_name,
|
|
474
|
+
tokenSymbol: deposits[i].asset,
|
|
475
|
+
amount: String(deposits[i].amount),
|
|
476
|
+
status: failedRedeem ? "failed" : "success"
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
for (let i = 0; i < redeems.length; i++) {
|
|
480
|
+
if (matchedRedeems.has(i))
|
|
481
|
+
continue;
|
|
482
|
+
const failedDeposit = failedDeposits.find(
|
|
483
|
+
(fd) => fd.amount === redeems[i].amount
|
|
484
|
+
);
|
|
485
|
+
logs.push({
|
|
486
|
+
fromProtocol: redeems[i].protocol_name,
|
|
487
|
+
toProtocol: failedDeposit ? failedDeposit.protocol_name : "Wallet",
|
|
488
|
+
tokenSymbol: redeems[i].asset,
|
|
489
|
+
amount: String(redeems[i].amount),
|
|
490
|
+
status: failedDeposit ? "failed" : "success"
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
return logs;
|
|
494
|
+
}
|
|
495
|
+
function mapHistory2(raw) {
|
|
496
|
+
const actionMap = {
|
|
497
|
+
execution: "Rebalance",
|
|
498
|
+
deposit: "Top up",
|
|
499
|
+
withdraw: "Withdraw",
|
|
500
|
+
rewards: "Earned"
|
|
501
|
+
};
|
|
502
|
+
const filtered = raw.result.items.filter(
|
|
503
|
+
(i) => i.status === "success" && i.transactions.length
|
|
504
|
+
);
|
|
505
|
+
return {
|
|
506
|
+
total: filtered.length,
|
|
507
|
+
data: filtered.map((entry) => ({
|
|
508
|
+
agent: "sail",
|
|
509
|
+
action: actionMap[entry.type] ?? entry.type,
|
|
510
|
+
date: entry.time,
|
|
511
|
+
newApy: String(entry.new_apy),
|
|
512
|
+
oldApy: String(entry.previous_apy),
|
|
513
|
+
transactions: (entry.transactions ?? []).map(mapSailTransaction),
|
|
514
|
+
rebalanceLog: buildRebalanceLogs(
|
|
515
|
+
entry.transactions ?? [],
|
|
516
|
+
entry.failed_transactions ?? []
|
|
517
|
+
)
|
|
518
|
+
}))
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function mapAgentStatus2(raw) {
|
|
522
|
+
return {
|
|
523
|
+
success: raw.success,
|
|
524
|
+
strategy: raw.strategy,
|
|
525
|
+
protocols: raw.protocols
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function mapUserProfile2(raw) {
|
|
529
|
+
return {
|
|
530
|
+
address: raw.result.smart_account,
|
|
531
|
+
smartWallet: raw.result.smart_account,
|
|
532
|
+
chains: [],
|
|
533
|
+
hasActiveSessionKey: false,
|
|
534
|
+
protocols: []
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function mapApyByStrategy2(raw) {
|
|
538
|
+
return {
|
|
539
|
+
averageApy: raw.result
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/agents/sail/sail.agent.ts
|
|
544
|
+
var SAIL_API_BASE = "https://app.sail.money";
|
|
545
|
+
var PROJECT_ID = "sail";
|
|
546
|
+
var PAGE_ID = "home";
|
|
547
|
+
var SailAgent = class {
|
|
548
|
+
id = "sail";
|
|
549
|
+
supportedChainIds = [8453, 42161];
|
|
550
|
+
supportedAssets = [
|
|
551
|
+
{
|
|
552
|
+
chainId: 8453,
|
|
553
|
+
chain: "BASE",
|
|
554
|
+
assets: ["USDC"]
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
chainId: 42161,
|
|
558
|
+
chain: "ARBITRUM",
|
|
559
|
+
assets: ["USDC", "USDT"]
|
|
560
|
+
}
|
|
561
|
+
];
|
|
562
|
+
apiKey;
|
|
563
|
+
authToken = null;
|
|
564
|
+
accountToken = null;
|
|
565
|
+
connectedAddress = null;
|
|
566
|
+
smartWalletAddress = null;
|
|
567
|
+
connectedChainId = null;
|
|
568
|
+
constructor(apiKey) {
|
|
569
|
+
this.apiKey = apiKey;
|
|
570
|
+
}
|
|
571
|
+
// --- Private helpers ---
|
|
572
|
+
async request(method, path, body) {
|
|
573
|
+
const headers = {
|
|
574
|
+
"Content-Type": "application/json",
|
|
575
|
+
"X-Business-API-Key": this.apiKey
|
|
576
|
+
};
|
|
577
|
+
if (this.authToken) {
|
|
578
|
+
headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
579
|
+
}
|
|
580
|
+
if (this.accountToken) {
|
|
581
|
+
headers["X-Account-Token"] = this.accountToken;
|
|
582
|
+
}
|
|
583
|
+
const res = await fetch(`${SAIL_API_BASE}${path}`, {
|
|
584
|
+
method,
|
|
585
|
+
headers,
|
|
586
|
+
body: body ? JSON.stringify(body) : void 0
|
|
587
|
+
});
|
|
588
|
+
if (!res.ok) {
|
|
589
|
+
const text = await res.text().catch(() => "");
|
|
590
|
+
throw new Error(`Sail API error ${res.status}: ${text}`);
|
|
591
|
+
}
|
|
592
|
+
return res.json();
|
|
593
|
+
}
|
|
594
|
+
/** Initiate SIWE and sign with the user's wallet. */
|
|
595
|
+
async signSiwe(state, chainId) {
|
|
596
|
+
const initiate = await this.request(
|
|
597
|
+
"POST",
|
|
598
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/auth/initiate`,
|
|
599
|
+
{
|
|
600
|
+
method: "siwe",
|
|
601
|
+
address: state.walletAddress,
|
|
602
|
+
chainId
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
const signature = await state.provider.request({
|
|
606
|
+
method: "personal_sign",
|
|
607
|
+
params: [initiate.message, state.walletAddress]
|
|
608
|
+
});
|
|
609
|
+
return {
|
|
610
|
+
sessionId: initiate.sessionId,
|
|
611
|
+
message: initiate.message,
|
|
612
|
+
signature
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
/** Full connect: auth handles smart account creation via SIWE flow. */
|
|
616
|
+
async connectFull(state, chainId) {
|
|
617
|
+
if (this.connectedAddress && this.connectedChainId === chainId) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (!state.walletAddress) {
|
|
621
|
+
throw new Error("Wallet address is required");
|
|
622
|
+
}
|
|
623
|
+
const { sessionId, signature, message } = await this.signSiwe(
|
|
624
|
+
state,
|
|
625
|
+
chainId
|
|
626
|
+
);
|
|
627
|
+
const complete = await this.request(
|
|
628
|
+
"POST",
|
|
629
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/auth/complete`,
|
|
630
|
+
{
|
|
631
|
+
method: "custom",
|
|
632
|
+
sessionId,
|
|
633
|
+
signature,
|
|
634
|
+
walletAddress: state.walletAddress
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
this.authToken = complete.token;
|
|
638
|
+
this.accountToken = complete.accountToken;
|
|
639
|
+
this.connectedAddress = state.walletAddress;
|
|
640
|
+
this.smartWalletAddress = complete.walletAddress;
|
|
641
|
+
this.connectedChainId = chainId;
|
|
642
|
+
}
|
|
643
|
+
async resolveSmartWallet(state, chainId) {
|
|
644
|
+
await this.connectFull(state, chainId);
|
|
645
|
+
const validChainId = isValidChainId(chainId);
|
|
646
|
+
if (!this.smartWalletAddress) {
|
|
647
|
+
throw new Error("Wallet not connected. Call sdk.connect() first.");
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
smartWallet: this.smartWalletAddress,
|
|
651
|
+
chainId: validChainId
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
// --- IAgent: Connection lifecycle ---
|
|
655
|
+
async disconnect() {
|
|
656
|
+
this.authToken = null;
|
|
657
|
+
this.accountToken = null;
|
|
658
|
+
this.connectedAddress = null;
|
|
659
|
+
this.smartWalletAddress = null;
|
|
660
|
+
this.connectedChainId = null;
|
|
661
|
+
}
|
|
662
|
+
// --- IAgent: Account lifecycle ---
|
|
663
|
+
async activateAgent(state, chainId) {
|
|
664
|
+
try {
|
|
665
|
+
await this.connectFull(state, chainId);
|
|
666
|
+
} catch (error) {
|
|
667
|
+
await this.disconnect();
|
|
668
|
+
throw error;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async deposit(state, chainId, amount, depositCallback) {
|
|
672
|
+
if (!depositCallback) {
|
|
673
|
+
throw new Error(
|
|
674
|
+
"depositCallback is required for Sail. Provide a callback that transfers tokens and returns the transaction hash."
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
try {
|
|
678
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
679
|
+
const depositInfo = await this.getDepositInfo();
|
|
680
|
+
const resolvedTokenAddress = depositInfo.permittedTokens[0]?.address;
|
|
681
|
+
if (!resolvedTokenAddress) {
|
|
682
|
+
throw new Error("No permitted tokens available for deposit.");
|
|
683
|
+
}
|
|
684
|
+
await this.executePreHooks(resolvedTokenAddress, amount, chainId);
|
|
685
|
+
const txHash = await depositCallback(smartWallet, chainId, amount);
|
|
686
|
+
if (!txHash) {
|
|
687
|
+
throw new Error(
|
|
688
|
+
"depositCallback must return a transaction hash (txHash) for Sail deposits."
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
const postResult = await this.executePostHooks(
|
|
692
|
+
resolvedTokenAddress,
|
|
693
|
+
amount,
|
|
694
|
+
chainId,
|
|
695
|
+
txHash
|
|
696
|
+
);
|
|
697
|
+
return mapDeposit2({
|
|
698
|
+
txHash: postResult.transactionHash,
|
|
699
|
+
smartWallet,
|
|
700
|
+
amount
|
|
701
|
+
});
|
|
702
|
+
} catch (error) {
|
|
703
|
+
await this.disconnect();
|
|
704
|
+
throw error;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
// --- Private deposit flow helpers ---
|
|
708
|
+
async getDepositInfo() {
|
|
709
|
+
return this.request(
|
|
710
|
+
"GET",
|
|
711
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/deposit-info`
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
async executePreHooks(tokenAddress, amount, chainId) {
|
|
715
|
+
return this.request(
|
|
716
|
+
"POST",
|
|
717
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/deposit/pre-hooks`,
|
|
718
|
+
{ tokenAddress, amount, chainId }
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
async executePostHooks(tokenAddress, amount, chainId, txHash) {
|
|
722
|
+
return this.request(
|
|
723
|
+
"POST",
|
|
724
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/deposit/post-hooks`,
|
|
725
|
+
{ tokenAddress, amount, chainId, txHash }
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
// --- Private withdraw flow helpers ---
|
|
729
|
+
async getWithdrawInfo() {
|
|
730
|
+
return this.request(
|
|
731
|
+
"GET",
|
|
732
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/withdraw-info`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
// --- IAgent: Fund operations ---
|
|
736
|
+
async withdraw(state, chainId, amount) {
|
|
737
|
+
await this.connectFull(state, chainId);
|
|
738
|
+
const withdrawInfo = await this.getWithdrawInfo();
|
|
739
|
+
const tokenAddress = withdrawInfo.permittedTokens[0]?.address;
|
|
740
|
+
if (!tokenAddress) {
|
|
741
|
+
throw new Error("No permitted tokens available for withdrawal.");
|
|
742
|
+
}
|
|
743
|
+
const resolvedAmount = amount ?? withdrawInfo.currentBalance;
|
|
744
|
+
const { signature: adminWalletSignature, message: adminWalletPayload } = await this.signSiwe(state, chainId);
|
|
745
|
+
const payload = {
|
|
746
|
+
amount: String(resolvedAmount),
|
|
747
|
+
tokenAddress,
|
|
748
|
+
chainId,
|
|
749
|
+
adminWalletSignature,
|
|
750
|
+
adminWalletPayload
|
|
751
|
+
};
|
|
752
|
+
const raw = await this.request(
|
|
753
|
+
"POST",
|
|
754
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/withdraw`,
|
|
755
|
+
payload
|
|
756
|
+
);
|
|
757
|
+
return mapWithdraw2({
|
|
758
|
+
txHash: raw.transactionHash,
|
|
759
|
+
type: amount ? "partial" : "full",
|
|
760
|
+
amount: resolvedAmount
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
// --- IAgent: Portfolio reads ---
|
|
764
|
+
async getBalances(state, chainId) {
|
|
765
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
766
|
+
const raw = await this.getMetricsPortfolio(state, chainId);
|
|
767
|
+
return mapPortfolioBalances(raw, smartWallet);
|
|
768
|
+
}
|
|
769
|
+
async getEarnings(state, chainId) {
|
|
770
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
771
|
+
const raw = await this.getMetricsEarnings(state, chainId);
|
|
772
|
+
return mapEarnings2(raw, smartWallet);
|
|
773
|
+
}
|
|
774
|
+
async getAccountApy(state, chainId, days) {
|
|
775
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
776
|
+
const raw = await this.getMetricsYield(days, state, chainId);
|
|
777
|
+
return mapApyHistory2(raw, smartWallet);
|
|
778
|
+
}
|
|
779
|
+
async getHistory(state, chainId, options) {
|
|
780
|
+
const raw = await this.getMetricsHistory(state, chainId, options);
|
|
781
|
+
return mapHistory2(raw);
|
|
782
|
+
}
|
|
783
|
+
// -- Sail-specific: Metrics ---
|
|
784
|
+
async getMetricsPortfolio(state, chainId) {
|
|
785
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
786
|
+
return this.request(
|
|
787
|
+
"GET",
|
|
788
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/portfolio?${smartWallet ? `smart_account=${smartWallet}` : ""}`
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
async getMetricsEarnings(state, chainId) {
|
|
792
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
793
|
+
return this.request(
|
|
794
|
+
"GET",
|
|
795
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/earnings?smart_account=${smartWallet}`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
async getMetricsBalance(state, chainId, currency) {
|
|
799
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
800
|
+
return this.request(
|
|
801
|
+
"GET",
|
|
802
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/balance?smart_account=${smartWallet}¤cy=${currency}&limit=1000`
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
async getMetricsHistory(state, chainId, filters) {
|
|
806
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
807
|
+
const params = new URLSearchParams();
|
|
808
|
+
params.set("smart_account", smartWallet);
|
|
809
|
+
if (filters?.fromDate) {
|
|
810
|
+
params.set("start_date", filters.fromDate);
|
|
811
|
+
}
|
|
812
|
+
if (filters?.toDate) {
|
|
813
|
+
params.set("end_date", filters.toDate);
|
|
814
|
+
}
|
|
815
|
+
return this.request(
|
|
816
|
+
"GET",
|
|
817
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/history?${params.toString()}`
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
async getMetricsUser(state, chainId, currency) {
|
|
821
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
822
|
+
return this.request(
|
|
823
|
+
"GET",
|
|
824
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/user-metrics?smart_account=${smartWallet}¤cy=${currency}`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
async getMetricsYield(days, state, chainId) {
|
|
828
|
+
let walletAddress = "";
|
|
829
|
+
if (state && chainId) {
|
|
830
|
+
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
831
|
+
walletAddress = smartWallet;
|
|
832
|
+
}
|
|
833
|
+
const dayCount = DayFilterMapping[days];
|
|
834
|
+
if (dayCount === void 0) {
|
|
835
|
+
throw new Error(
|
|
836
|
+
`Invalid days value: "${days}". Expected one of: "7D", "14D", "30D".`
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
const now = /* @__PURE__ */ new Date();
|
|
840
|
+
const end = now.toISOString().split("T")[0];
|
|
841
|
+
const start = new Date(now.getTime() - dayCount * 864e5).toISOString().split("T")[0];
|
|
842
|
+
return this.request(
|
|
843
|
+
"GET",
|
|
844
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/metrics/yield?start=${start}&end=${end}${walletAddress ? `&smart_account=${walletAddress}` : ""}`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
// --- Sail-specific: Automation jobs ---
|
|
848
|
+
async getAutomationStatus(state, chainId) {
|
|
849
|
+
await this.connectFull(state, chainId);
|
|
850
|
+
return this.request(
|
|
851
|
+
"GET",
|
|
852
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/automation/status`
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
async startAutomation(state, chainId, graphId, prompt, options) {
|
|
856
|
+
await this.connectFull(state, chainId);
|
|
857
|
+
return this.request(
|
|
858
|
+
"POST",
|
|
859
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/automation/start`,
|
|
860
|
+
{
|
|
861
|
+
graphId,
|
|
862
|
+
prompt,
|
|
863
|
+
paramValues: options?.paramValues ?? {},
|
|
864
|
+
iterations: options?.iterations ?? 1,
|
|
865
|
+
branchId: options?.branchId
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
async pauseAutomation(state, chainId) {
|
|
870
|
+
await this.connectFull(state, chainId);
|
|
871
|
+
return this.request(
|
|
872
|
+
"POST",
|
|
873
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/automation/pause`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
async resumeAutomation(state, chainId) {
|
|
877
|
+
await this.connectFull(state, chainId);
|
|
878
|
+
return this.request(
|
|
879
|
+
"POST",
|
|
880
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/automation/resume`
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
async stopAutomation(state, chainId) {
|
|
884
|
+
await this.connectFull(state, chainId);
|
|
885
|
+
return this.request(
|
|
886
|
+
"POST",
|
|
887
|
+
`/api/v1/projects/${PROJECT_ID}/pages/${PAGE_ID}/automation/stop`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
// --- IAgent: Agent control ---
|
|
891
|
+
async pause(state, chainId) {
|
|
892
|
+
await this.connectFull(state, chainId);
|
|
893
|
+
await this.pauseAutomation(state, chainId);
|
|
894
|
+
return mapAgentStatus2({ success: true });
|
|
895
|
+
}
|
|
896
|
+
async resume(state, chainId) {
|
|
897
|
+
await this.connectFull(state, chainId);
|
|
898
|
+
await this.resumeAutomation(state, chainId);
|
|
899
|
+
return mapAgentStatus2({ success: true });
|
|
900
|
+
}
|
|
901
|
+
async getUserProfile(state, chainId) {
|
|
902
|
+
await this.connectFull(state, chainId);
|
|
903
|
+
const raw = await this.getMetricsUser(state, chainId, "usd");
|
|
904
|
+
return mapUserProfile2(raw);
|
|
905
|
+
}
|
|
906
|
+
// --- IAgent: Discovery (no wallet required) ---
|
|
907
|
+
async getAgentApy(days) {
|
|
908
|
+
const raw = await this.getMetricsYield(days);
|
|
909
|
+
return mapApyByStrategy2(raw);
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
// src/errors.ts
|
|
914
|
+
var AgentNotFoundError = class extends Error {
|
|
915
|
+
constructor(agentId, available) {
|
|
916
|
+
super(
|
|
917
|
+
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`
|
|
918
|
+
);
|
|
919
|
+
this.name = "AgentNotFoundError";
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
var NotConnectedError = class extends Error {
|
|
923
|
+
constructor() {
|
|
924
|
+
super("Not connected. Call sdk.connect(provider) first.");
|
|
925
|
+
this.name = "NotConnectedError";
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
var AgentChainIncompatibleError = class extends Error {
|
|
929
|
+
incompatibleAgents;
|
|
930
|
+
connectedChainId;
|
|
931
|
+
constructor(incompatibleAgents, connectedChainId) {
|
|
932
|
+
const details = incompatibleAgents.map(
|
|
933
|
+
({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
|
|
934
|
+
).join("; ");
|
|
935
|
+
super(
|
|
936
|
+
`Chain ${connectedChainId} is not supported by the following agents: ${details}`
|
|
937
|
+
);
|
|
938
|
+
this.name = "AgentChainIncompatibleError";
|
|
939
|
+
this.incompatibleAgents = incompatibleAgents;
|
|
940
|
+
this.connectedChainId = connectedChainId;
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
// src/lib/routing-api.ts
|
|
945
|
+
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
946
|
+
async function fetchAgentKeys(apiKey) {
|
|
947
|
+
const url = `${ROUTING_API_BASE_URL}/api/v1/agent/keys`;
|
|
948
|
+
const res = await fetch(url, {
|
|
949
|
+
method: "GET",
|
|
950
|
+
headers: {
|
|
951
|
+
"Content-Type": "application/json",
|
|
952
|
+
"x-owney-api-key": `${apiKey}`
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
if (!res.ok) {
|
|
956
|
+
const text = await res.text().catch(() => "");
|
|
957
|
+
throw new Error(`Routing API error ${res.status}: ${text}`);
|
|
958
|
+
}
|
|
959
|
+
const json = await res.json();
|
|
960
|
+
if (!json.success) {
|
|
961
|
+
throw new Error(`Routing API request failed: ${json.message}`);
|
|
962
|
+
}
|
|
963
|
+
return json.data;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// src/client.ts
|
|
967
|
+
var AGENT_CONSTRUCTORS = {
|
|
968
|
+
zyfai: ZyfaiAgent,
|
|
969
|
+
sail: SailAgent
|
|
970
|
+
};
|
|
971
|
+
var OwneySDK = class {
|
|
972
|
+
agents = /* @__PURE__ */ new Map();
|
|
973
|
+
activeAgents = /* @__PURE__ */ new Set();
|
|
974
|
+
state = null;
|
|
975
|
+
apiKey;
|
|
976
|
+
constructor(config) {
|
|
977
|
+
this.apiKey = config.apiKey;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Establish connection state for all subsequent SDK calls.
|
|
981
|
+
* Must be called once before any agent operation that requires a wallet.
|
|
982
|
+
* @param provider - EIP-1193 wallet provider
|
|
983
|
+
*/
|
|
984
|
+
// TODO: replace with a proper EIP-1193 Provider type
|
|
985
|
+
async connect(provider) {
|
|
986
|
+
const accounts = await provider.request({
|
|
987
|
+
method: "eth_accounts"
|
|
988
|
+
});
|
|
989
|
+
const walletAddress = accounts[0];
|
|
990
|
+
if (!walletAddress) {
|
|
991
|
+
throw new Error(
|
|
992
|
+
"No accounts found. Ensure the wallet is unlocked and connected."
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
this.state = { provider, walletAddress, chainId: null };
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Disconnect from all agents and clear connection state.
|
|
999
|
+
* After calling this, `connect()` must be called again before using agent methods.
|
|
1000
|
+
*/
|
|
1001
|
+
async disconnect() {
|
|
1002
|
+
for (const agent of this.agents.values()) {
|
|
1003
|
+
await agent.disconnect();
|
|
1004
|
+
}
|
|
1005
|
+
this.activeAgents.clear();
|
|
1006
|
+
this.state = null;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Check if the SDK has an active connection state.
|
|
1010
|
+
* @returns `true` if `connect()` has been called and `disconnect()` has not
|
|
1011
|
+
*/
|
|
1012
|
+
isConnected() {
|
|
1013
|
+
return this.state !== null;
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Get the currently active chain ID, set by `activateAgent`.
|
|
1017
|
+
* @returns The active chain ID, or `null` if `activateAgent` has not been called
|
|
1018
|
+
*/
|
|
1019
|
+
getActiveChainId() {
|
|
1020
|
+
return this.state?.chainId ?? null;
|
|
1021
|
+
}
|
|
1022
|
+
requireState() {
|
|
1023
|
+
if (!this.state)
|
|
1024
|
+
throw new NotConnectedError();
|
|
1025
|
+
return this.state;
|
|
1026
|
+
}
|
|
1027
|
+
requireChainId() {
|
|
1028
|
+
const chainId = this.state?.chainId ?? null;
|
|
1029
|
+
if (chainId === null) {
|
|
1030
|
+
throw new Error(
|
|
1031
|
+
"No active chain. Call sdk.activateAgent(chainId) first."
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
return chainId;
|
|
1035
|
+
}
|
|
1036
|
+
getAgent(agentId) {
|
|
1037
|
+
const agent = this.agents.get(agentId);
|
|
1038
|
+
if (!agent) {
|
|
1039
|
+
throw new AgentNotFoundError(agentId, [...this.agents.keys()]);
|
|
1040
|
+
}
|
|
1041
|
+
return agent;
|
|
1042
|
+
}
|
|
1043
|
+
getActiveAgents() {
|
|
1044
|
+
const active = /* @__PURE__ */ new Map();
|
|
1045
|
+
for (const id of this.activeAgents) {
|
|
1046
|
+
const agent = this.agents.get(id);
|
|
1047
|
+
if (agent)
|
|
1048
|
+
active.set(id, agent);
|
|
1049
|
+
}
|
|
1050
|
+
return active;
|
|
1051
|
+
}
|
|
1052
|
+
// --- Account lifecycle ---
|
|
1053
|
+
/**
|
|
1054
|
+
* Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
|
|
1055
|
+
* Deploys the Safe contract and creates a session key if not already set up.
|
|
1056
|
+
* Saves the chainId for use by all subsequent SDK calls.
|
|
1057
|
+
* @param chainId - Target chain ID (e.g. 8453 for Base, 42161 for Arbitrum)
|
|
1058
|
+
* @param agentId - Optional. One or more agent IDs to activate. Omit to activate all agents
|
|
1059
|
+
* that support the given chainId (incompatible agents are silently skipped).
|
|
1060
|
+
* If provided, ALL specified agents must support the chainId or the call
|
|
1061
|
+
* throws before activating any agent.
|
|
1062
|
+
*/
|
|
1063
|
+
async activateAgent(chainId, agentId) {
|
|
1064
|
+
const state = this.requireState();
|
|
1065
|
+
if (this.agents.size === 0) {
|
|
1066
|
+
const agentKeys = await fetchAgentKeys(this.apiKey);
|
|
1067
|
+
for (const { key, agent_type } of agentKeys) {
|
|
1068
|
+
const AgentClass = AGENT_CONSTRUCTORS[agent_type];
|
|
1069
|
+
if (!AgentClass)
|
|
1070
|
+
continue;
|
|
1071
|
+
const agent = new AgentClass(key);
|
|
1072
|
+
this.agents.set(agent_type, agent);
|
|
1073
|
+
}
|
|
1074
|
+
if (this.agents.size === 0) {
|
|
1075
|
+
throw new Error(
|
|
1076
|
+
"No supported agents returned by the routing API. Check your API key and routing configuration."
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
if (agentId !== void 0) {
|
|
1081
|
+
if (agentId.length === 0) {
|
|
1082
|
+
throw new Error(
|
|
1083
|
+
"agentId array must not be empty. Omit the argument to activate all compatible agents."
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
const uniqueIds = [...new Set(agentId)];
|
|
1087
|
+
const agents = uniqueIds.map((id) => this.getAgent(id));
|
|
1088
|
+
const incompatibleAgents = agents.filter(
|
|
1089
|
+
(agent) => !agent.supportedChainIds.includes(chainId)
|
|
1090
|
+
).map((agent) => ({
|
|
1091
|
+
agentId: agent.id,
|
|
1092
|
+
supportedChainIds: agent.supportedChainIds
|
|
1093
|
+
}));
|
|
1094
|
+
if (incompatibleAgents.length > 0) {
|
|
1095
|
+
throw new AgentChainIncompatibleError(incompatibleAgents, chainId);
|
|
1096
|
+
}
|
|
1097
|
+
for (const agent of agents) {
|
|
1098
|
+
await agent.activateAgent(state, chainId);
|
|
1099
|
+
}
|
|
1100
|
+
this.activeAgents.clear();
|
|
1101
|
+
for (const id of uniqueIds) {
|
|
1102
|
+
this.activeAgents.add(id);
|
|
1103
|
+
}
|
|
1104
|
+
state.chainId = chainId;
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
const compatible = [...this.agents.values()].filter(
|
|
1108
|
+
(agent) => agent.supportedChainIds.includes(chainId)
|
|
1109
|
+
);
|
|
1110
|
+
if (compatible.length === 0) {
|
|
1111
|
+
throw new Error(`No agents support chain ${chainId}.`);
|
|
1112
|
+
}
|
|
1113
|
+
for (const agent of compatible) {
|
|
1114
|
+
await agent.activateAgent(state, chainId);
|
|
1115
|
+
}
|
|
1116
|
+
this.activeAgents.clear();
|
|
1117
|
+
for (const agent of compatible) {
|
|
1118
|
+
this.activeAgents.add(agent.id);
|
|
1119
|
+
}
|
|
1120
|
+
state.chainId = chainId;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Deposit funds into a specific agent, or split equally across all agents if agentId is omitted.
|
|
1124
|
+
* Validates that the asset is supported by the target agent(s) on the active chain.
|
|
1125
|
+
* @param options - Deposit parameters
|
|
1126
|
+
* @param options.amount - Amount to deposit in smallest unit (e.g. "100000000" for 100 USDC)
|
|
1127
|
+
* @param options.asset - Asset symbol to deposit (e.g. "USDC")
|
|
1128
|
+
* @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
|
|
1129
|
+
* When agentId is omitted, this callback is invoked once per eligible agent with that agent's
|
|
1130
|
+
* split amount and smart wallet address — expect multiple wallet prompts.
|
|
1131
|
+
* @param options.agentId - Optional. Target agent. Omit to split equally across all agents.
|
|
1132
|
+
* @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
|
|
1133
|
+
*/
|
|
1134
|
+
async deposit(options) {
|
|
1135
|
+
const { amount, asset, depositCallback, agentId } = options;
|
|
1136
|
+
const state = this.requireState();
|
|
1137
|
+
const chainId = this.requireChainId();
|
|
1138
|
+
if (agentId) {
|
|
1139
|
+
const agent = this.getAgent(agentId);
|
|
1140
|
+
this.validateAssetSupport(agent, chainId, asset);
|
|
1141
|
+
return agent.deposit(state, chainId, amount, depositCallback);
|
|
1142
|
+
}
|
|
1143
|
+
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
1144
|
+
const totalAmount = BigInt(amount);
|
|
1145
|
+
if (totalAmount < BigInt(eligibleAgents.length)) {
|
|
1146
|
+
throw new Error(
|
|
1147
|
+
`Amount "${amount}" is too small to split across ${eligibleAgents.length} agents.`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
const perAgent = totalAmount / BigInt(eligibleAgents.length);
|
|
1151
|
+
const remainder = totalAmount % BigInt(eligibleAgents.length);
|
|
1152
|
+
const agentResults = {};
|
|
1153
|
+
for (let i = 0; i < eligibleAgents.length; i++) {
|
|
1154
|
+
const agent = eligibleAgents[i];
|
|
1155
|
+
const agentAmount = i === eligibleAgents.length - 1 ? String(perAgent + remainder) : String(perAgent);
|
|
1156
|
+
agentResults[agent.id] = await agent.deposit(
|
|
1157
|
+
state,
|
|
1158
|
+
chainId,
|
|
1159
|
+
agentAmount,
|
|
1160
|
+
depositCallback
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
return { agentResults };
|
|
1164
|
+
}
|
|
1165
|
+
validateAssetSupport(agent, chainId, asset) {
|
|
1166
|
+
const chainAssets = agent.supportedAssets.find(
|
|
1167
|
+
(sa) => sa.chainId === chainId
|
|
1168
|
+
);
|
|
1169
|
+
if (!chainAssets || !chainAssets.assets.includes(asset)) {
|
|
1170
|
+
throw new Error(
|
|
1171
|
+
`Agent "${agent.id}" does not support asset "${asset}" on chain ${chainId}.`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
getEligibleAgents(chainId, asset) {
|
|
1176
|
+
const eligible = [...this.agents.values()].filter((agent) => {
|
|
1177
|
+
const chainAssets = agent.supportedAssets.find(
|
|
1178
|
+
(sa) => sa.chainId === chainId
|
|
1179
|
+
);
|
|
1180
|
+
return chainAssets && chainAssets.assets.includes(asset);
|
|
1181
|
+
});
|
|
1182
|
+
if (eligible.length === 0) {
|
|
1183
|
+
throw new Error(
|
|
1184
|
+
`No agents support asset "${asset}" on chain ${chainId}.`
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
return eligible;
|
|
1188
|
+
}
|
|
1189
|
+
// --- Fund operations ---
|
|
1190
|
+
/**
|
|
1191
|
+
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
1192
|
+
* Validates that the asset is supported by the target agent(s) on the active chain.
|
|
1193
|
+
* @param options - Withdraw parameters
|
|
1194
|
+
* @param options.asset - Asset symbol (e.g. "USDC")
|
|
1195
|
+
* @param options.amount - Optional. Amount to withdraw. Omit for full withdrawal.
|
|
1196
|
+
* @param options.agentId - Optional. Target agent. Omit to withdraw from all agents.
|
|
1197
|
+
* @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
|
|
1198
|
+
*/
|
|
1199
|
+
async withdraw(options) {
|
|
1200
|
+
const { asset, amount, agentId } = options;
|
|
1201
|
+
const state = this.requireState();
|
|
1202
|
+
const chainId = this.requireChainId();
|
|
1203
|
+
if (agentId) {
|
|
1204
|
+
const agent = this.getAgent(agentId);
|
|
1205
|
+
this.validateAssetSupport(agent, chainId, asset);
|
|
1206
|
+
return agent.withdraw(state, chainId, amount);
|
|
1207
|
+
}
|
|
1208
|
+
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
1209
|
+
const results = {};
|
|
1210
|
+
for (const agent of eligibleAgents) {
|
|
1211
|
+
results[agent.id] = await agent.withdraw(state, chainId, amount);
|
|
1212
|
+
}
|
|
1213
|
+
return {
|
|
1214
|
+
agentResult: results
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
// --- Portfolio reads ---
|
|
1218
|
+
/**
|
|
1219
|
+
* Get the user's balances for a specific agent, or aggregated across all agents.
|
|
1220
|
+
* @param agentId - Optional. Agent to query. Omit for aggregated balances.
|
|
1221
|
+
* @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
|
|
1222
|
+
*/
|
|
1223
|
+
async getBalances(agentId) {
|
|
1224
|
+
const state = this.requireState();
|
|
1225
|
+
const chainId = this.requireChainId();
|
|
1226
|
+
if (agentId) {
|
|
1227
|
+
return this.getAgent(agentId).getBalances(state, chainId);
|
|
1228
|
+
}
|
|
1229
|
+
let totalBalance = 0;
|
|
1230
|
+
const results = {};
|
|
1231
|
+
for (const [id, agent] of this.getActiveAgents()) {
|
|
1232
|
+
results[id] = await agent.getBalances(state, chainId);
|
|
1233
|
+
totalBalance = totalBalance + Number(results[id].totalBalance);
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
totalBalance: String(totalBalance),
|
|
1237
|
+
agentBalances: results
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Get the user's on-chain earnings for a specific agent, or aggregated across all agents.
|
|
1242
|
+
* @param agentId - Optional. Agent to query. Omit for aggregated earnings.
|
|
1243
|
+
* @returns {AgentEarnings} for a single agent, or {OwneyEarnings} with totalEarnings and per-agent breakdown
|
|
1244
|
+
*/
|
|
1245
|
+
async getEarnings(agentId) {
|
|
1246
|
+
const state = this.requireState();
|
|
1247
|
+
const chainId = this.requireChainId();
|
|
1248
|
+
if (agentId) {
|
|
1249
|
+
return this.getAgent(agentId).getEarnings(state, chainId);
|
|
1250
|
+
}
|
|
1251
|
+
let totalEarnings = 0;
|
|
1252
|
+
const results = {};
|
|
1253
|
+
for (const [id, agent] of this.getActiveAgents()) {
|
|
1254
|
+
results[id] = await agent.getEarnings(state, chainId);
|
|
1255
|
+
totalEarnings = totalEarnings + Number(results[id].lifetimeEarnings);
|
|
1256
|
+
}
|
|
1257
|
+
return {
|
|
1258
|
+
totalEarnings: String(totalEarnings),
|
|
1259
|
+
agentEarnings: results
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Get the weighted APY for the user's account over a time period.
|
|
1264
|
+
* When querying all agents, the total APY is a balance-weighted average.
|
|
1265
|
+
* @param options - Contains agentId (optional) and days ("7D", "14D", or "30D")
|
|
1266
|
+
* @param options.agentId - Optional. Agent to query. Omit for balance-weighted total APY.
|
|
1267
|
+
* @param options.days - Lookback period: "7D", "14D", or "30D"
|
|
1268
|
+
* @returns {AccountAgentApy} for a single agent, or {OwneyAccountApy} with totalApy and per-agent breakdown
|
|
1269
|
+
*/
|
|
1270
|
+
async getAccountApy({
|
|
1271
|
+
agentId,
|
|
1272
|
+
days
|
|
1273
|
+
}) {
|
|
1274
|
+
const state = this.requireState();
|
|
1275
|
+
const chainId = this.requireChainId();
|
|
1276
|
+
if (agentId) {
|
|
1277
|
+
return this.getAgent(agentId).getAccountApy(state, chainId, days);
|
|
1278
|
+
}
|
|
1279
|
+
const activeAgents = this.getActiveAgents();
|
|
1280
|
+
const results = {};
|
|
1281
|
+
for (const [id, agent] of activeAgents) {
|
|
1282
|
+
results[id] = await agent.getAccountApy(state, chainId, days);
|
|
1283
|
+
}
|
|
1284
|
+
const balances = {};
|
|
1285
|
+
let totalBalance = 0;
|
|
1286
|
+
for (const [id, agent] of activeAgents) {
|
|
1287
|
+
const b = await agent.getBalances(state, chainId);
|
|
1288
|
+
balances[id] = Number(b.totalBalance);
|
|
1289
|
+
totalBalance += balances[id];
|
|
1290
|
+
}
|
|
1291
|
+
let totalApy = 0;
|
|
1292
|
+
if (totalBalance > 0) {
|
|
1293
|
+
for (const id of Object.keys(results)) {
|
|
1294
|
+
const weight = balances[id] / totalBalance;
|
|
1295
|
+
totalApy += Number(results[id].weightedApyAfterFee ?? 0) * weight;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return {
|
|
1299
|
+
totalApy: String(totalApy),
|
|
1300
|
+
agentApy: results
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Get transaction history for a specific agent, or all agents.
|
|
1305
|
+
* @param params - Optional. Contains agentId and filters.
|
|
1306
|
+
* @param params.agentId - Optional. Agent to query. Omit for all agents.
|
|
1307
|
+
* @param params.filters - Optional. Pagination and date filters (fromDate, toDate)
|
|
1308
|
+
* @returns {OwneyAgentHistory} with per-agent history
|
|
1309
|
+
*/
|
|
1310
|
+
async getHistory(params) {
|
|
1311
|
+
const state = this.requireState();
|
|
1312
|
+
const chainId = this.requireChainId();
|
|
1313
|
+
const { agentId, filters } = params ?? {};
|
|
1314
|
+
if (agentId) {
|
|
1315
|
+
return this.getAgent(agentId).getHistory(state, chainId, filters);
|
|
1316
|
+
}
|
|
1317
|
+
let results = [];
|
|
1318
|
+
for (const [, agent] of this.getActiveAgents()) {
|
|
1319
|
+
const { data } = await agent.getHistory(state, chainId, filters);
|
|
1320
|
+
results = [...results, ...data];
|
|
1321
|
+
}
|
|
1322
|
+
results.sort(
|
|
1323
|
+
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
|
|
1324
|
+
);
|
|
1325
|
+
return {
|
|
1326
|
+
total: results.length,
|
|
1327
|
+
data: results
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
// --- Agent control ---
|
|
1331
|
+
/**
|
|
1332
|
+
* Pause an agent's automated operations. The agent will stop rebalancing until resumed.
|
|
1333
|
+
* @param agentId - The agent to pause (e.g. "zyfai")
|
|
1334
|
+
* @returns OwneyAgentStatus with success flag
|
|
1335
|
+
*/
|
|
1336
|
+
async pauseAgent(agentId) {
|
|
1337
|
+
const state = this.requireState();
|
|
1338
|
+
const chainId = this.requireChainId();
|
|
1339
|
+
return this.getAgent(agentId).pause(state, chainId);
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Resume an agent's automated operations.
|
|
1343
|
+
* @param agentId - The agent to resume (e.g. "zyfai")
|
|
1344
|
+
* @returns OwneyAgentStatus with success flag
|
|
1345
|
+
*/
|
|
1346
|
+
async resumeAgent(agentId) {
|
|
1347
|
+
const state = this.requireState();
|
|
1348
|
+
const chainId = this.requireChainId();
|
|
1349
|
+
return this.getAgent(agentId).resume(state, chainId);
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Get the user's profile for a specific agent, or all agents.
|
|
1353
|
+
* @param agentId - Optional. Agent to query. Omit for all agents.
|
|
1354
|
+
* @returns {AgentUserProfile} for a single agent, or {OwneyUserProfile} with per-agent profiles
|
|
1355
|
+
*/
|
|
1356
|
+
async getUserProfile(agentId) {
|
|
1357
|
+
const state = this.requireState();
|
|
1358
|
+
const chainId = this.requireChainId();
|
|
1359
|
+
if (agentId) {
|
|
1360
|
+
return this.getAgent(agentId).getUserProfile(state, chainId);
|
|
1361
|
+
}
|
|
1362
|
+
const results = {};
|
|
1363
|
+
for (const [id, agent] of this.getActiveAgents()) {
|
|
1364
|
+
results[id] = await agent.getUserProfile(state, chainId);
|
|
1365
|
+
}
|
|
1366
|
+
return {
|
|
1367
|
+
agentUserProfile: results
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
// --- Discovery (no wallet required) ---
|
|
1371
|
+
/**
|
|
1372
|
+
* Get the agent's average APY performance over a time period. Does not require a wallet connection.
|
|
1373
|
+
* @param options - Contains agentId (optional) and days ("7D", "14D", or "30D")
|
|
1374
|
+
* @param options.agentId - Optional. Agent to query. Omit for all agents.
|
|
1375
|
+
* @param options.days - Lookback period: "7D", "14D", or "30D"
|
|
1376
|
+
* @returns {AgentApy} for a single agent, or {OwneyAgentApy} with per-agent APY
|
|
1377
|
+
*/
|
|
1378
|
+
async getAgentApy({
|
|
1379
|
+
agentId,
|
|
1380
|
+
days
|
|
1381
|
+
}) {
|
|
1382
|
+
if (agentId) {
|
|
1383
|
+
return this.getAgent(agentId).getAgentApy(days);
|
|
1384
|
+
}
|
|
1385
|
+
const results = {};
|
|
1386
|
+
for (const [id, agent] of this.agents) {
|
|
1387
|
+
results[id] = await agent.getAgentApy(days);
|
|
1388
|
+
}
|
|
1389
|
+
return {
|
|
1390
|
+
agentApy: results
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Get the current fund allocation percentages across agents.
|
|
1395
|
+
* @returns Object with agent names as keys and allocation percentages as values
|
|
1396
|
+
*/
|
|
1397
|
+
async getAgentAllocation() {
|
|
1398
|
+
const state = this.requireState();
|
|
1399
|
+
const chainId = this.requireChainId();
|
|
1400
|
+
const activeAgents = this.getActiveAgents();
|
|
1401
|
+
const balances = {};
|
|
1402
|
+
let totalBalance = 0;
|
|
1403
|
+
for (const [id, agent] of activeAgents) {
|
|
1404
|
+
const b = await agent.getBalances(state, chainId);
|
|
1405
|
+
balances[id] = Number(b.totalBalance);
|
|
1406
|
+
totalBalance += balances[id];
|
|
1407
|
+
}
|
|
1408
|
+
const allocation = {};
|
|
1409
|
+
if (totalBalance === 0) {
|
|
1410
|
+
for (const id of activeAgents.keys()) {
|
|
1411
|
+
allocation[id] = 0;
|
|
1412
|
+
}
|
|
1413
|
+
} else {
|
|
1414
|
+
for (const id of activeAgents.keys()) {
|
|
1415
|
+
allocation[id] = balances[id] > 0 ? Math.max(1, Math.floor(balances[id] / totalBalance * 100)) : 0;
|
|
1416
|
+
}
|
|
1417
|
+
const remainder = 100 - Object.values(allocation).reduce((a, b) => a + b, 0);
|
|
1418
|
+
if (remainder > 0) {
|
|
1419
|
+
const largestId = [...activeAgents.keys()].reduce(
|
|
1420
|
+
(a, b) => balances[a] >= balances[b] ? a : b
|
|
1421
|
+
);
|
|
1422
|
+
allocation[largestId] += remainder;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return {
|
|
1426
|
+
agentAllocation: allocation
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
export {
|
|
1431
|
+
AgentChainIncompatibleError,
|
|
1432
|
+
AgentNotFoundError,
|
|
1433
|
+
NotConnectedError,
|
|
1434
|
+
OwneySDK
|
|
1435
|
+
};
|