@owney/sdk 0.7.25-beta.1 → 0.7.25-beta.11
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 +79 -14
- package/dist/index.cjs +1789 -449
- package/dist/index.d.cts +284 -121
- package/dist/index.d.ts +284 -121
- package/dist/index.js +1753 -410
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,96 @@ __export(index_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(index_exports);
|
|
34
34
|
|
|
35
|
+
// src/lib/funding-evidence.ts
|
|
36
|
+
var import_viem = require("viem");
|
|
37
|
+
function notifyFunding(callback, evidence) {
|
|
38
|
+
try {
|
|
39
|
+
void Promise.resolve(callback?.({ ...evidence })).catch(() => {
|
|
40
|
+
});
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
var fullWithdrawalAbi = (0, import_viem.parseAbi)(["function withdrawAllAssetToUser(address recipient, address asset)"]);
|
|
45
|
+
var withdrawalEventAbi = (0, import_viem.parseAbi)(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
|
|
46
|
+
function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress) {
|
|
47
|
+
if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || transaction.to?.toLowerCase() !== evidence.agentWallet?.toLowerCase() || transaction.from.toLowerCase() !== evidence.walletAddress.toLowerCase()) return false;
|
|
48
|
+
try {
|
|
49
|
+
const decoded = (0, import_viem.decodeFunctionData)({ abi: fullWithdrawalAbi, data: transaction.input });
|
|
50
|
+
return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
|
|
56
|
+
if (receipt.status !== "success") return { ...evidence, transfer: "failed", delivery: "failed" };
|
|
57
|
+
let amount = 0n;
|
|
58
|
+
const seen = /* @__PURE__ */ new Set();
|
|
59
|
+
const attributed = /* @__PURE__ */ new Map();
|
|
60
|
+
const agent = evidence.agentWallet?.toLowerCase();
|
|
61
|
+
const owner = evidence.walletAddress.toLowerCase();
|
|
62
|
+
const logIndexes = [];
|
|
63
|
+
const transferLogs = [];
|
|
64
|
+
const zeroTransferLogs = [];
|
|
65
|
+
for (const log of [...receipt.logs].sort((a, b) => a.logIndex - b.logIndex)) {
|
|
66
|
+
if (log.address.toLowerCase() !== tokenAddress.toLowerCase()) continue;
|
|
67
|
+
if (seen.has(log.logIndex)) continue;
|
|
68
|
+
seen.add(log.logIndex);
|
|
69
|
+
try {
|
|
70
|
+
const event = (0, import_viem.decodeEventLog)({ abi: import_viem.erc20Abi, data: log.data, topics: log.topics });
|
|
71
|
+
if (event.eventName !== "Transfer") continue;
|
|
72
|
+
const { from, to, value } = event.args;
|
|
73
|
+
if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
|
|
74
|
+
zeroTransferLogs.push(log.logIndex);
|
|
75
|
+
let matched = 0n;
|
|
76
|
+
if (direction === "deposit") {
|
|
77
|
+
if (to.toLowerCase() === agent) matched = value;
|
|
78
|
+
} else {
|
|
79
|
+
const sender = from.toLowerCase();
|
|
80
|
+
const recipient = to.toLowerCase();
|
|
81
|
+
const available = sender === agent ? value : attributed.get(sender) ?? 0n;
|
|
82
|
+
const carried = available < value ? available : value;
|
|
83
|
+
if (sender !== agent) attributed.set(sender, available - carried);
|
|
84
|
+
if (recipient === owner) matched = carried;
|
|
85
|
+
else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
|
|
86
|
+
}
|
|
87
|
+
if (matched === 0n) continue;
|
|
88
|
+
amount += matched;
|
|
89
|
+
transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
|
|
90
|
+
logIndexes.push(log.logIndex);
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const expected = /^\d+$/.test(evidence.intendedAmount) ? BigInt(evidence.intendedAmount) : void 0;
|
|
95
|
+
const emptyPayout = direction === "withdraw" && evidence.agentId === "yieldseeker" && amount === 0n && zeroTransferLogs.length > 0 && receipt.logs.some((log) => {
|
|
96
|
+
if (log.address.toLowerCase() !== agent) return false;
|
|
97
|
+
try {
|
|
98
|
+
const { args } = (0, import_viem.decodeEventLog)({ abi: withdrawalEventAbi, data: log.data, topics: log.topics });
|
|
99
|
+
return args.owner.toLowerCase() === owner && args.recipient.toLowerCase() === owner && args.token.toLowerCase() === tokenAddress.toLowerCase() && args.amount === 0n;
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
if (emptyPayout) return {
|
|
105
|
+
...evidence,
|
|
106
|
+
blockNumber: receipt.blockNumber.toString(),
|
|
107
|
+
confirmedAmount: "0",
|
|
108
|
+
logIndexes: zeroTransferLogs,
|
|
109
|
+
transferLogs: zeroTransferLogs.map((logIndex) => ({ logIndex, amountRaw: "0" })),
|
|
110
|
+
transfer: "failed",
|
|
111
|
+
delivery: "failed"
|
|
112
|
+
};
|
|
113
|
+
const delivered = amount > 0n && (expected === void 0 || amount >= expected);
|
|
114
|
+
return {
|
|
115
|
+
...evidence,
|
|
116
|
+
blockNumber: receipt.blockNumber.toString(),
|
|
117
|
+
logIndexes,
|
|
118
|
+
transferLogs,
|
|
119
|
+
...amount > 0n ? { confirmedAmount: amount.toString() } : {},
|
|
120
|
+
transfer: delivered ? "confirmed" : "unknown",
|
|
121
|
+
delivery: direction === "withdraw" ? delivered ? "confirmed" : "unknown" : "not-applicable"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
35
125
|
// src/lib/deposit-batch-callback.ts
|
|
36
126
|
var batchCallbacks = /* @__PURE__ */ new WeakMap();
|
|
37
127
|
var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
|
|
@@ -159,6 +249,34 @@ function rateLimitDelay(error, now = Date.now()) {
|
|
|
159
249
|
}
|
|
160
250
|
|
|
161
251
|
// src/lib/agent-reads.ts
|
|
252
|
+
function rateLimitDiagnostics(error) {
|
|
253
|
+
const seen = /* @__PURE__ */ new Set();
|
|
254
|
+
let details = {};
|
|
255
|
+
function visit(value, depth = 0) {
|
|
256
|
+
if (depth > 6 || !value || typeof value !== "object" || seen.has(value))
|
|
257
|
+
return;
|
|
258
|
+
seen.add(value);
|
|
259
|
+
const record = value;
|
|
260
|
+
for (const key2 of [
|
|
261
|
+
"rpcSource",
|
|
262
|
+
"chainId",
|
|
263
|
+
"rpcMethod",
|
|
264
|
+
"providerRequestId",
|
|
265
|
+
"statusCode"
|
|
266
|
+
]) {
|
|
267
|
+
const candidate = record[key2];
|
|
268
|
+
if (candidate !== void 0 && details[key2] === void 0)
|
|
269
|
+
details = { ...details, [key2]: candidate };
|
|
270
|
+
}
|
|
271
|
+
for (const key2 of ["details", "cause", "response", "fields", "error"])
|
|
272
|
+
visit(record[key2], depth + 1);
|
|
273
|
+
}
|
|
274
|
+
visit(error);
|
|
275
|
+
return {
|
|
276
|
+
rpcSource: details.rpcSource ?? "agent-api",
|
|
277
|
+
...details
|
|
278
|
+
};
|
|
279
|
+
}
|
|
162
280
|
var AgentReads = class {
|
|
163
281
|
inFlight = /* @__PURE__ */ new Map();
|
|
164
282
|
cooldowns = /* @__PURE__ */ new Map();
|
|
@@ -166,14 +284,15 @@ var AgentReads = class {
|
|
|
166
284
|
clearInFlight() {
|
|
167
285
|
this.inFlight.clear();
|
|
168
286
|
}
|
|
169
|
-
limited(agentId, until) {
|
|
287
|
+
limited(agentId, until, diagnostics) {
|
|
170
288
|
return new OwneyError(
|
|
171
289
|
"AGENT_RATE_LIMITED",
|
|
172
290
|
"Too many requests. Please wait before trying again.",
|
|
173
291
|
{
|
|
174
292
|
statusCode: 429,
|
|
175
293
|
retryAt: until,
|
|
176
|
-
retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3))
|
|
294
|
+
retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3)),
|
|
295
|
+
...diagnostics
|
|
177
296
|
},
|
|
178
297
|
agentId
|
|
179
298
|
);
|
|
@@ -181,7 +300,9 @@ var AgentReads = class {
|
|
|
181
300
|
run(agentId, key2, fetch2) {
|
|
182
301
|
const cooldown = this.cooldowns.get(agentId);
|
|
183
302
|
if (cooldown && cooldown.until > Date.now()) {
|
|
184
|
-
return Promise.reject(
|
|
303
|
+
return Promise.reject(
|
|
304
|
+
this.limited(agentId, cooldown.until, cooldown.diagnostics)
|
|
305
|
+
);
|
|
185
306
|
}
|
|
186
307
|
const requestKey = JSON.stringify([agentId, key2]);
|
|
187
308
|
const existing = this.inFlight.get(requestKey);
|
|
@@ -202,8 +323,9 @@ var AgentReads = class {
|
|
|
202
323
|
Math.min(3e4 * 2 ** (failures - 1), 3e5)
|
|
203
324
|
);
|
|
204
325
|
const until = Math.max(previous?.until ?? 0, Date.now() + delay);
|
|
205
|
-
|
|
206
|
-
|
|
326
|
+
const diagnostics = rateLimitDiagnostics(error);
|
|
327
|
+
this.cooldowns.set(agentId, { until, failures, diagnostics });
|
|
328
|
+
throw this.limited(agentId, until, diagnostics);
|
|
207
329
|
}
|
|
208
330
|
).finally(() => {
|
|
209
331
|
if (this.inFlight.get(requestKey) === promise)
|
|
@@ -214,9 +336,72 @@ var AgentReads = class {
|
|
|
214
336
|
}
|
|
215
337
|
};
|
|
216
338
|
|
|
339
|
+
// src/agents/zyfai/zyfai.withdraw-submission.ts
|
|
340
|
+
var listenersByClient = /* @__PURE__ */ new WeakMap();
|
|
341
|
+
function observeWithdrawalSubmission(sdk, chainId, token) {
|
|
342
|
+
const client = sdk.httpClient;
|
|
343
|
+
if (!client || typeof client.get !== "function" || typeof client.post !== "function")
|
|
344
|
+
return { attempted: () => void 0, rejected: () => false, request: () => void 0, stop: () => {
|
|
345
|
+
} };
|
|
346
|
+
let listeners = listenersByClient.get(client);
|
|
347
|
+
if (!listeners) {
|
|
348
|
+
listeners = /* @__PURE__ */ new Set();
|
|
349
|
+
listenersByClient.set(client, listeners);
|
|
350
|
+
const observers = listeners;
|
|
351
|
+
for (const method of ["get", "post"]) {
|
|
352
|
+
const original = client[method];
|
|
353
|
+
client[method] = async function(...args) {
|
|
354
|
+
const [url, options] = args;
|
|
355
|
+
const withdrawal = method === "get" && url === "/users/withdraw" || method === "post" && url === "/users/partial-withdraw";
|
|
356
|
+
const payload = method === "get" ? options?.params : options;
|
|
357
|
+
const matching = withdrawal ? [...observers].filter((observer) => observer.chainId === payload?.chainId && observer.token === payload?.tokenSymbol) : [];
|
|
358
|
+
const startedAt = Date.now();
|
|
359
|
+
const request2 = {
|
|
360
|
+
attempted: true,
|
|
361
|
+
method: method === "get" ? "GET" : "POST",
|
|
362
|
+
path: method === "get" ? "/users/withdraw" : "/users/partial-withdraw"
|
|
363
|
+
};
|
|
364
|
+
const notify = (outcome) => {
|
|
365
|
+
if (outcome === "rejected" && matching.length !== 1) return;
|
|
366
|
+
for (const observer of matching) {
|
|
367
|
+
try {
|
|
368
|
+
observer.notify(outcome, { ...request2 });
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
notify("attempted");
|
|
374
|
+
try {
|
|
375
|
+
return await original.apply(this, args);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
const message = error instanceof Error ? error.message : "";
|
|
378
|
+
request2.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : message === "Network error: Unable to reach the server" ? "network" : "other";
|
|
379
|
+
if (["unauthorized", "forbidden", "rate-limited"].includes(request2.failure)) notify("rejected");
|
|
380
|
+
throw error;
|
|
381
|
+
} finally {
|
|
382
|
+
request2.elapsedMs = Math.max(0, Date.now() - startedAt);
|
|
383
|
+
notify("finished");
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
let submitted = false;
|
|
389
|
+
let rejected = false;
|
|
390
|
+
let request = { attempted: false };
|
|
391
|
+
const listener = { chainId, token, notify: (outcome, evidence) => {
|
|
392
|
+
request = evidence;
|
|
393
|
+
if (outcome === "attempted") submitted = true;
|
|
394
|
+
else if (outcome === "rejected") rejected = true;
|
|
395
|
+
} };
|
|
396
|
+
listeners.add(listener);
|
|
397
|
+
return { attempted: () => submitted, rejected: () => rejected, request: () => ({ ...request }), stop: () => {
|
|
398
|
+
listeners.delete(listener);
|
|
399
|
+
} };
|
|
400
|
+
}
|
|
401
|
+
|
|
217
402
|
// src/agents/zyfai/zyfai.agent.ts
|
|
218
403
|
var import_sdk = require("@zyfai/sdk");
|
|
219
|
-
var
|
|
404
|
+
var import_viem3 = require("viem");
|
|
220
405
|
var import_chains = require("viem/chains");
|
|
221
406
|
|
|
222
407
|
// src/types/config.ts
|
|
@@ -389,7 +574,11 @@ function mapPendingAllocations(raw2) {
|
|
|
389
574
|
}
|
|
390
575
|
function mapBalances(raw2, _chainId, smartWallet) {
|
|
391
576
|
const portfolio = raw2.portfolio;
|
|
392
|
-
|
|
577
|
+
if (!portfolio?.portfolioByChain || typeof portfolio.portfolioByChain !== "object")
|
|
578
|
+
throw new Error("Zyfai balance snapshot is unavailable.");
|
|
579
|
+
const portfolioByChain = portfolio.portfolioByChain;
|
|
580
|
+
const fetchedAt = Date.now();
|
|
581
|
+
const provenance = portfolio;
|
|
393
582
|
let totalBalance = 0;
|
|
394
583
|
const tokens = [];
|
|
395
584
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
|
|
@@ -398,7 +587,8 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
398
587
|
symbol,
|
|
399
588
|
{ balance, balanceWithFee, decimals }
|
|
400
589
|
] of Object.entries(tokensBySymbol ?? {})) {
|
|
401
|
-
|
|
590
|
+
if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
|
|
591
|
+
const amount = hexToDecimal(balanceWithFee ?? balance, decimals);
|
|
402
592
|
totalBalance += amount;
|
|
403
593
|
tokens.push({
|
|
404
594
|
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
@@ -448,6 +638,15 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
448
638
|
totalBalanceAsset: "usdc",
|
|
449
639
|
tokens,
|
|
450
640
|
positions,
|
|
641
|
+
readMetadata: tokens.map((token) => ({
|
|
642
|
+
chainId: token.chainId,
|
|
643
|
+
asset: token.asset,
|
|
644
|
+
fetchedAt,
|
|
645
|
+
providerUpdatedAt: typeof provenance.updatedAt === "string" ? provenance.updatedAt : void 0,
|
|
646
|
+
snapshotId: provenance.snapshotId === void 0 ? void 0 : String(provenance.snapshotId),
|
|
647
|
+
blockNumber: provenance.blockNumber === void 0 ? void 0 : String(provenance.blockNumber),
|
|
648
|
+
positionsStatus: Array.isArray(portfolio.positions) ? "available" : "unavailable"
|
|
649
|
+
})),
|
|
451
650
|
...pendingAllocations ? { pendingAllocations } : {}
|
|
452
651
|
};
|
|
453
652
|
}
|
|
@@ -788,28 +987,6 @@ function mapDailyEarnings(raw2, chainId, tokenSymbol) {
|
|
|
788
987
|
return { walletAddress: raw2.walletAddress, chainId, assets };
|
|
789
988
|
}
|
|
790
989
|
|
|
791
|
-
// src/agents/zyfai/zyfai.withdraw-amount.ts
|
|
792
|
-
var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
793
|
-
function topicToAddress(topic) {
|
|
794
|
-
return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
|
|
795
|
-
}
|
|
796
|
-
function extractWithdrawnAmount(logs, recipient, token, decimals) {
|
|
797
|
-
const wantRecipient = recipient.toLowerCase();
|
|
798
|
-
const wantToken = token.toLowerCase();
|
|
799
|
-
let total = 0n;
|
|
800
|
-
let matched = false;
|
|
801
|
-
for (const log of logs) {
|
|
802
|
-
if (log.address.toLowerCase() !== wantToken) continue;
|
|
803
|
-
if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC) continue;
|
|
804
|
-
if (topicToAddress(log.topics[2]) !== wantRecipient) continue;
|
|
805
|
-
if (!log.data || log.data === "0x") continue;
|
|
806
|
-
total += BigInt(log.data);
|
|
807
|
-
matched = true;
|
|
808
|
-
}
|
|
809
|
-
if (!matched) return void 0;
|
|
810
|
-
return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
|
|
811
|
-
}
|
|
812
|
-
|
|
813
990
|
// src/agents/zyfai/zyfai.history-cursor.ts
|
|
814
991
|
var InvalidHistoryCursorError = class extends Error {
|
|
815
992
|
code = "invalid_cursor";
|
|
@@ -1097,15 +1274,145 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
|
|
|
1097
1274
|
return !protocolListsEqual(current.protocols, desiredProtocols);
|
|
1098
1275
|
}
|
|
1099
1276
|
|
|
1277
|
+
// src/lib/paid-rpc.ts
|
|
1278
|
+
var import_viem2 = require("viem");
|
|
1279
|
+
var PAID_RPC_RETRY_COUNT = 3;
|
|
1280
|
+
var PAID_RPC_RETRY_DELAY_MS = 1e3;
|
|
1281
|
+
function configuredRpcProxyBaseUrl(baseUrl) {
|
|
1282
|
+
const configured = (baseUrl ?? "")?.trim();
|
|
1283
|
+
if (!configured) {
|
|
1284
|
+
throw new Error(
|
|
1285
|
+
"OWNEY_ROUTING_API_BASE_URL is required for RPC proxy requests unless routingApiBaseUrl or all rpcUrls are configured."
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
return configured;
|
|
1289
|
+
}
|
|
1290
|
+
function rpcProxyUrl(chainId, apiKey, baseUrl) {
|
|
1291
|
+
const url = new URL(
|
|
1292
|
+
`${configuredRpcProxyBaseUrl(baseUrl).replace(/\/$/, "")}/api/v1/rpc/${chainId}`
|
|
1293
|
+
);
|
|
1294
|
+
if (apiKey) url.searchParams.set("apiKey", apiKey);
|
|
1295
|
+
return url.toString();
|
|
1296
|
+
}
|
|
1297
|
+
function resolveRpcUrl(rpcUrls, chainId, apiKey = "", baseUrl) {
|
|
1298
|
+
const url = rpcUrls?.[chainId]?.trim();
|
|
1299
|
+
if (url) return url;
|
|
1300
|
+
return rpcProxyUrl(chainId, apiKey, baseUrl);
|
|
1301
|
+
}
|
|
1302
|
+
function resolveRpcUrls(rpcUrls, apiKey = "", baseUrl) {
|
|
1303
|
+
return {
|
|
1304
|
+
1: resolveRpcUrl(rpcUrls, 1, apiKey, baseUrl),
|
|
1305
|
+
8453: resolveRpcUrl(rpcUrls, 8453, apiKey, baseUrl),
|
|
1306
|
+
42161: resolveRpcUrl(rpcUrls, 42161, apiKey, baseUrl)
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
function createPaidRpcClient(chain, rpcUrls) {
|
|
1310
|
+
const url = resolveRpcUrl(
|
|
1311
|
+
rpcUrls,
|
|
1312
|
+
chain.id
|
|
1313
|
+
);
|
|
1314
|
+
return (0, import_viem2.createPublicClient)({
|
|
1315
|
+
chain,
|
|
1316
|
+
transport: (0, import_viem2.http)(url, {
|
|
1317
|
+
retryCount: PAID_RPC_RETRY_COUNT,
|
|
1318
|
+
retryDelay: PAID_RPC_RETRY_DELAY_MS
|
|
1319
|
+
})
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
function headerValue(error, name) {
|
|
1323
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1324
|
+
let value;
|
|
1325
|
+
function visit(candidate, depth = 0) {
|
|
1326
|
+
if (value || depth > 6 || !candidate || typeof candidate !== "object")
|
|
1327
|
+
return;
|
|
1328
|
+
if (seen.has(candidate)) return;
|
|
1329
|
+
seen.add(candidate);
|
|
1330
|
+
const record = candidate;
|
|
1331
|
+
const headers = record.headers;
|
|
1332
|
+
let found;
|
|
1333
|
+
if (typeof headers?.get === "function") {
|
|
1334
|
+
found = headers.get(name);
|
|
1335
|
+
} else {
|
|
1336
|
+
const headerRecord = headers;
|
|
1337
|
+
found = headerRecord?.[name] ?? headerRecord?.[name.toLowerCase()];
|
|
1338
|
+
}
|
|
1339
|
+
if (typeof found === "string" && found) {
|
|
1340
|
+
value = found;
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
for (const key2 of ["cause", "details", "response", "error"])
|
|
1344
|
+
visit(record[key2], depth + 1);
|
|
1345
|
+
}
|
|
1346
|
+
visit(error);
|
|
1347
|
+
return value;
|
|
1348
|
+
}
|
|
1349
|
+
function statusCode(error) {
|
|
1350
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1351
|
+
let status;
|
|
1352
|
+
function visit(candidate, depth = 0) {
|
|
1353
|
+
if (status || depth > 6 || !candidate || typeof candidate !== "object")
|
|
1354
|
+
return;
|
|
1355
|
+
if (seen.has(candidate)) return;
|
|
1356
|
+
seen.add(candidate);
|
|
1357
|
+
const record = candidate;
|
|
1358
|
+
for (const key2 of ["status", "statusCode"]) {
|
|
1359
|
+
const parsed = Number(record[key2]);
|
|
1360
|
+
if (Number.isInteger(parsed) && parsed >= 100 && parsed <= 599) {
|
|
1361
|
+
status = parsed;
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
for (const key2 of ["cause", "details", "response", "error"])
|
|
1366
|
+
visit(record[key2], depth + 1);
|
|
1367
|
+
}
|
|
1368
|
+
visit(error);
|
|
1369
|
+
return status;
|
|
1370
|
+
}
|
|
1371
|
+
function paidRpcError(error, chainId, rpcMethod, agentId) {
|
|
1372
|
+
const delay = rateLimitDelay(error);
|
|
1373
|
+
const providerRequestId = headerValue(error, "x-alchemy-request-id") ?? headerValue(error, "x-request-id");
|
|
1374
|
+
if (delay === void 0) {
|
|
1375
|
+
return new OwneyError(
|
|
1376
|
+
"AGENT_API_ERROR",
|
|
1377
|
+
"The blockchain RPC request failed.",
|
|
1378
|
+
{
|
|
1379
|
+
rpcSource: "paid-rpc",
|
|
1380
|
+
chainId,
|
|
1381
|
+
rpcMethod,
|
|
1382
|
+
...statusCode(error) ? { statusCode: statusCode(error) } : {},
|
|
1383
|
+
...providerRequestId ? { providerRequestId } : {}
|
|
1384
|
+
},
|
|
1385
|
+
agentId
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
const retryAt = Date.now() + delay;
|
|
1389
|
+
return new OwneyError(
|
|
1390
|
+
"AGENT_RATE_LIMITED",
|
|
1391
|
+
"The blockchain RPC is rate limited. Please wait before trying again.",
|
|
1392
|
+
{
|
|
1393
|
+
rpcSource: "paid-rpc",
|
|
1394
|
+
chainId,
|
|
1395
|
+
rpcMethod,
|
|
1396
|
+
statusCode: 429,
|
|
1397
|
+
retryAt,
|
|
1398
|
+
retryAfterSeconds: Math.max(0, Math.ceil(delay / 1e3)),
|
|
1399
|
+
...providerRequestId ? { providerRequestId } : {}
|
|
1400
|
+
},
|
|
1401
|
+
agentId
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
|
|
1405
|
+
try {
|
|
1406
|
+
return await operation();
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
throw paidRpcError(error, chainId, rpcMethod, agentId);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1100
1412
|
// src/agents/zyfai/zyfai.agent.ts
|
|
1101
|
-
var ERC7579_IS_MODULE_INSTALLED_ABI = (0,
|
|
1413
|
+
var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem3.parseAbi)([
|
|
1102
1414
|
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
1103
1415
|
]);
|
|
1104
|
-
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
1105
|
-
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
1106
|
-
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
1107
|
-
1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
1108
|
-
};
|
|
1109
1416
|
var WETH_ADDRESS_BY_CHAIN = {
|
|
1110
1417
|
8453: "0x4200000000000000000000000000000000000006",
|
|
1111
1418
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
@@ -1162,16 +1469,15 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1162
1469
|
connectAuthPromise = null;
|
|
1163
1470
|
rpcUrls;
|
|
1164
1471
|
publicClients = /* @__PURE__ */ new Map();
|
|
1165
|
-
//
|
|
1166
|
-
//
|
|
1167
|
-
|
|
1168
|
-
withdrawAmountCache = /* @__PURE__ */ new Map();
|
|
1472
|
+
// Receipt-derived amounts and log identities, scoped to the owner and asset.
|
|
1473
|
+
// Failures remain retryable; successful immutable evidence has a bounded cache.
|
|
1474
|
+
fundingHistoryCache = /* @__PURE__ */ new Map();
|
|
1169
1475
|
earningsReads = /* @__PURE__ */ new Map();
|
|
1170
1476
|
earningsRefreshes = /* @__PURE__ */ new Map();
|
|
1171
1477
|
earningsSnapshot = null;
|
|
1172
1478
|
earningsGeneration = 0;
|
|
1173
1479
|
constructor(apiKey, rpcUrls, referralSource) {
|
|
1174
|
-
this.rpcUrls = rpcUrls
|
|
1480
|
+
this.rpcUrls = resolveRpcUrls(rpcUrls);
|
|
1175
1481
|
this.sdk = new import_sdk.ZyfaiSDK({
|
|
1176
1482
|
apiKey,
|
|
1177
1483
|
rpcUrls: this.rpcUrls,
|
|
@@ -1185,16 +1491,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1185
1491
|
getPublicClient(chainId) {
|
|
1186
1492
|
const cached = this.publicClients.get(chainId);
|
|
1187
1493
|
if (cached) return cached;
|
|
1188
|
-
const client = (
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1494
|
+
const client = createPaidRpcClient(
|
|
1495
|
+
VIEM_CHAIN[chainId],
|
|
1496
|
+
this.rpcUrls
|
|
1497
|
+
);
|
|
1192
1498
|
this.publicClients.set(chainId, client);
|
|
1193
1499
|
return client;
|
|
1194
1500
|
}
|
|
1195
1501
|
/**
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
1502
|
+
* Resolves funding amounts and log identities from token receipts. Withdrawals
|
|
1503
|
+
* must travel from this agent wallet to its owner; top-ups must reach the agent.
|
|
1504
|
+
* The Zyfai history
|
|
1198
1505
|
* payload only reports the *remaining* position for a partial withdraw (see
|
|
1199
1506
|
* the note in `mapEntries`), so without this the UI shows the leftover
|
|
1200
1507
|
* balance instead of the amount withdrawn.
|
|
@@ -1206,48 +1513,62 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1206
1513
|
* Entries with no symbol (older payload shapes) fall back to USDC to
|
|
1207
1514
|
* preserve prior behavior.
|
|
1208
1515
|
*
|
|
1209
|
-
* Mutates `entries` in place
|
|
1210
|
-
*
|
|
1211
|
-
* `amount: undefined`, which consumers render as a hidden/retryable row
|
|
1212
|
-
* rather than a wrong number.
|
|
1516
|
+
* Mutates `entries` in place, sharing receipt requests within the page. RPC
|
|
1517
|
+
* failures leave provider top-ups unchanged and withdrawal amounts unknown.
|
|
1213
1518
|
*/
|
|
1214
|
-
async
|
|
1215
|
-
const
|
|
1216
|
-
(e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
|
|
1519
|
+
async resolveFundingTransfers(entries, chainId, recipient, smartWallet, tokenSymbol) {
|
|
1520
|
+
const movements = entries.filter(
|
|
1521
|
+
(e) => (e.action === "Withdraw" || e.action === "Top up") && e.transactions[0]?.txHashes[0]
|
|
1217
1522
|
);
|
|
1218
|
-
if (
|
|
1523
|
+
if (movements.length === 0) return;
|
|
1219
1524
|
const client = this.getPublicClient(chainId);
|
|
1525
|
+
const receipts = /* @__PURE__ */ new Map();
|
|
1220
1526
|
await Promise.all(
|
|
1221
|
-
|
|
1527
|
+
movements.map(async (entry) => {
|
|
1222
1528
|
const tx = entry.transactions[0];
|
|
1223
1529
|
const txHash = tx.txHashes[0];
|
|
1224
|
-
const rawSymbol = tx.tokenSymbol;
|
|
1530
|
+
const rawSymbol = tx.tokenSymbol ?? tokenSymbol;
|
|
1225
1531
|
const normalizedSymbol = rawSymbol ? rawSymbol.toLowerCase() === "eth" ? "WETH" : rawSymbol.toUpperCase() : "USDC";
|
|
1226
1532
|
const asset = SupportedAssets.find(
|
|
1227
1533
|
(a) => a.chainId === chainId && a.symbol === normalizedSymbol
|
|
1228
|
-
) ?? SupportedAssets.find(
|
|
1229
|
-
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
1230
1534
|
);
|
|
1231
1535
|
if (!asset) return;
|
|
1232
|
-
|
|
1233
|
-
|
|
1536
|
+
tx.tokenSymbol = asset.symbol;
|
|
1537
|
+
tx.tokenAddress = asset.address;
|
|
1538
|
+
tx.agentWallet = smartWallet;
|
|
1539
|
+
entry.id = `${chainId}:${txHash.toLowerCase()}:${entry.action}:${asset.address.toLowerCase()}:${smartWallet.toLowerCase()}`;
|
|
1540
|
+
if (!/^0x[0-9a-f]{64}$/i.test(txHash)) return;
|
|
1541
|
+
const cacheKey = `${entry.id}:${recipient.toLowerCase()}`;
|
|
1542
|
+
const cached = this.fundingHistoryCache.get(cacheKey);
|
|
1234
1543
|
if (cached !== void 0) {
|
|
1235
|
-
tx
|
|
1544
|
+
Object.assign(tx, cached);
|
|
1236
1545
|
return;
|
|
1237
1546
|
}
|
|
1238
1547
|
try {
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
asset.
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1548
|
+
if (!receipts.has(txHash)) receipts.set(txHash, client.getTransactionReceipt({ hash: txHash }));
|
|
1549
|
+
const receipt = await receipts.get(txHash);
|
|
1550
|
+
const evidence = inspectFundingReceipt(receipt, {
|
|
1551
|
+
agentId: "zyfai",
|
|
1552
|
+
walletAddress: recipient,
|
|
1553
|
+
chainId,
|
|
1554
|
+
token: asset.symbol,
|
|
1555
|
+
agentWallet: smartWallet,
|
|
1556
|
+
intendedAmount: "MAX",
|
|
1557
|
+
txHash,
|
|
1558
|
+
transfer: "unknown",
|
|
1559
|
+
registration: "not-applicable",
|
|
1560
|
+
delivery: "unknown"
|
|
1561
|
+
}, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
|
|
1562
|
+
if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
|
|
1563
|
+
Object.assign(tx, {
|
|
1564
|
+
amount: (0, import_viem3.formatUnits)(BigInt(evidence.confirmedAmount), asset.decimals),
|
|
1565
|
+
amountRaw: evidence.confirmedAmount,
|
|
1566
|
+
blockNumber: evidence.blockNumber,
|
|
1567
|
+
logIndexes: evidence.logIndexes,
|
|
1568
|
+
transferLogs: evidence.transferLogs
|
|
1569
|
+
});
|
|
1570
|
+
if (this.fundingHistoryCache.size >= 256) this.fundingHistoryCache.delete(this.fundingHistoryCache.keys().next().value);
|
|
1571
|
+
this.fundingHistoryCache.set(cacheKey, { ...tx });
|
|
1251
1572
|
}
|
|
1252
1573
|
} catch {
|
|
1253
1574
|
}
|
|
@@ -1336,7 +1657,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1336
1657
|
* Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
|
|
1337
1658
|
* cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
|
|
1338
1659
|
* we drop the cache and try again — the second attempt has no cache entry,
|
|
1339
|
-
* so it falls through to a real SIWE prompt. Zyfai
|
|
1660
|
+
* so it falls through to a real SIWE prompt. Zyfai labels every HTTP 401 with
|
|
1340
1661
|
* an `"Unauthorized: Invalid API key"` error so we can't distinguish revoked
|
|
1341
1662
|
* JWT from a misconfigured API key; we retry once either way, and if the
|
|
1342
1663
|
* second attempt still fails the original error propagates.
|
|
@@ -1369,6 +1690,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1369
1690
|
await this.connectAuth(state, chainId);
|
|
1370
1691
|
}
|
|
1371
1692
|
}
|
|
1693
|
+
/** A rejected execution session must not be reused by the next foreground attempt. */
|
|
1694
|
+
invalidateRejectedWithdrawalSession(error, state, chainId, authToken) {
|
|
1695
|
+
const message = error instanceof Error ? error.message : "";
|
|
1696
|
+
if (!/(?:^|: )Unauthorized: Invalid API key$/.test(message)) return;
|
|
1697
|
+
const internals = this.sdk;
|
|
1698
|
+
if (typeof authToken !== "string" || internals.httpClient.authToken !== authToken || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== chainId) return;
|
|
1699
|
+
clearSession(state.walletAddress, chainId);
|
|
1700
|
+
internals.httpClient.clearAuthToken();
|
|
1701
|
+
internals.authenticatedUserId = null;
|
|
1702
|
+
this.connectedChainId = null;
|
|
1703
|
+
}
|
|
1372
1704
|
/**
|
|
1373
1705
|
* Sync newly-available protocols onto an existing user's profile at wallet
|
|
1374
1706
|
* connection. Zyfai's own protocol sync (`updateUserProtocols`) runs only when
|
|
@@ -1914,7 +2246,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1914
2246
|
await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
|
|
1915
2247
|
return redeployed.address;
|
|
1916
2248
|
}
|
|
1917
|
-
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
2249
|
+
async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
|
|
1918
2250
|
try {
|
|
1919
2251
|
const validChainId = isValidChainId(chainId);
|
|
1920
2252
|
await this.ensureConnected(state, validChainId);
|
|
@@ -1923,17 +2255,48 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1923
2255
|
this.getAddress(),
|
|
1924
2256
|
validChainId
|
|
1925
2257
|
);
|
|
2258
|
+
notifyFunding(onProgress, {
|
|
2259
|
+
agentId: this.id,
|
|
2260
|
+
chainId,
|
|
2261
|
+
token: asset,
|
|
2262
|
+
walletAddress: state.walletAddress,
|
|
2263
|
+
agentWallet: smartWallet,
|
|
2264
|
+
intendedAmount: amount,
|
|
2265
|
+
transfer: "pending",
|
|
2266
|
+
registration: "pending",
|
|
2267
|
+
delivery: "not-applicable"
|
|
2268
|
+
});
|
|
1926
2269
|
const txHash = await depositCallback(smartWallet, validChainId, amount);
|
|
1927
|
-
|
|
2270
|
+
let evidence2 = {
|
|
2271
|
+
agentId: this.id,
|
|
2272
|
+
chainId,
|
|
2273
|
+
token: asset,
|
|
2274
|
+
walletAddress: state.walletAddress,
|
|
2275
|
+
agentWallet: smartWallet,
|
|
2276
|
+
intendedAmount: amount,
|
|
2277
|
+
txHash,
|
|
2278
|
+
transfer: "pending",
|
|
2279
|
+
registration: "pending",
|
|
2280
|
+
delivery: "not-applicable"
|
|
2281
|
+
};
|
|
2282
|
+
notifyFunding(onProgress, evidence2);
|
|
2283
|
+
try {
|
|
2284
|
+
const receipt = await this.getPublicClient(validChainId).getTransactionReceipt({ hash: txHash });
|
|
2285
|
+
const token = SupportedAssets.find((token2) => token2.chainId === chainId && token2.symbol === asset);
|
|
2286
|
+
evidence2 = inspectFundingReceipt(receipt, evidence2, token.address, "deposit");
|
|
2287
|
+
} catch {
|
|
2288
|
+
evidence2 = { ...evidence2, transfer: "unknown" };
|
|
2289
|
+
}
|
|
2290
|
+
notifyFunding(onProgress, evidence2);
|
|
1928
2291
|
try {
|
|
2292
|
+
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
1929
2293
|
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
logError
|
|
1934
|
-
);
|
|
2294
|
+
evidence2 = { ...evidence2, registration: "confirmed" };
|
|
2295
|
+
} catch {
|
|
2296
|
+
evidence2 = { ...evidence2, registration: "unknown" };
|
|
1935
2297
|
}
|
|
1936
|
-
|
|
2298
|
+
notifyFunding(onProgress, evidence2);
|
|
2299
|
+
return { txHash, smartWallet, amount, evidence: evidence2 };
|
|
1937
2300
|
}
|
|
1938
2301
|
await this.ensureWalletDeployed(this.getAddress(), validChainId);
|
|
1939
2302
|
const raw2 = await this.sdk.depositFunds(
|
|
@@ -1943,22 +2306,88 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1943
2306
|
asset,
|
|
1944
2307
|
"aggressive"
|
|
1945
2308
|
);
|
|
1946
|
-
|
|
2309
|
+
let evidence = {
|
|
2310
|
+
agentId: this.id,
|
|
2311
|
+
chainId,
|
|
2312
|
+
token: asset,
|
|
2313
|
+
walletAddress: state.walletAddress,
|
|
2314
|
+
agentWallet: raw2.smartWallet,
|
|
2315
|
+
intendedAmount: amount,
|
|
2316
|
+
txHash: raw2.txHash,
|
|
2317
|
+
transfer: "unknown",
|
|
2318
|
+
registration: "unknown",
|
|
2319
|
+
delivery: "not-applicable"
|
|
2320
|
+
};
|
|
2321
|
+
notifyFunding(onProgress, evidence);
|
|
2322
|
+
try {
|
|
2323
|
+
const receipt = await this.getPublicClient(validChainId).getTransactionReceipt({ hash: raw2.txHash });
|
|
2324
|
+
const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
|
|
2325
|
+
evidence = inspectFundingReceipt(receipt, evidence, token.address, "deposit");
|
|
2326
|
+
} catch {
|
|
2327
|
+
}
|
|
2328
|
+
notifyFunding(onProgress, evidence);
|
|
2329
|
+
return { ...mapDeposit(raw2), evidence };
|
|
1947
2330
|
} catch (error) {
|
|
1948
2331
|
throw error;
|
|
1949
2332
|
}
|
|
1950
2333
|
}
|
|
1951
2334
|
// --- IAgent: Fund operations ---
|
|
1952
|
-
async withdraw(state, chainId, token, amount) {
|
|
2335
|
+
async withdraw(state, chainId, token, amount, onProgress) {
|
|
1953
2336
|
const validChainId = isValidChainId(chainId);
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2337
|
+
try {
|
|
2338
|
+
await this.ensureConnected(state, validChainId);
|
|
2339
|
+
} catch (error) {
|
|
2340
|
+
notifyFunding(onProgress, {
|
|
2341
|
+
agentId: this.id,
|
|
2342
|
+
chainId,
|
|
2343
|
+
token,
|
|
2344
|
+
walletAddress: state.walletAddress,
|
|
2345
|
+
intendedAmount: amount ?? "MAX",
|
|
2346
|
+
transfer: "failed",
|
|
2347
|
+
registration: "not-applicable",
|
|
2348
|
+
delivery: "failed"
|
|
2349
|
+
});
|
|
2350
|
+
throw error;
|
|
2351
|
+
}
|
|
2352
|
+
const observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
|
|
2353
|
+
const authToken = this.sdk.httpClient?.authToken;
|
|
2354
|
+
let raw2;
|
|
2355
|
+
try {
|
|
2356
|
+
raw2 = await this.sdk.withdrawFunds(this.getAddress(), validChainId, amount, token);
|
|
2357
|
+
} catch (error) {
|
|
2358
|
+
this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
|
|
2359
|
+
if (observation.attempted() !== void 0) {
|
|
2360
|
+
const failed = observation.attempted() === false || observation.rejected();
|
|
2361
|
+
notifyFunding(onProgress, {
|
|
2362
|
+
agentId: this.id,
|
|
2363
|
+
chainId,
|
|
2364
|
+
token,
|
|
2365
|
+
walletAddress: state.walletAddress,
|
|
2366
|
+
intendedAmount: amount ?? "MAX",
|
|
2367
|
+
transfer: failed ? "failed" : "unknown",
|
|
2368
|
+
registration: "not-applicable",
|
|
2369
|
+
delivery: failed ? "failed" : "unknown",
|
|
2370
|
+
withdrawalRequest: observation.request()
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
throw error;
|
|
2374
|
+
} finally {
|
|
2375
|
+
observation.stop();
|
|
2376
|
+
}
|
|
1961
2377
|
if (!raw2.success) {
|
|
2378
|
+
const unresolved = !!raw2.txHash || /already|pending|queued|in.progress/i.test(raw2.message ?? "");
|
|
2379
|
+
notifyFunding(onProgress, {
|
|
2380
|
+
agentId: this.id,
|
|
2381
|
+
chainId,
|
|
2382
|
+
token,
|
|
2383
|
+
walletAddress: state.walletAddress,
|
|
2384
|
+
intendedAmount: amount ?? "MAX",
|
|
2385
|
+
txHash: raw2.txHash,
|
|
2386
|
+
transfer: unresolved ? "unknown" : "failed",
|
|
2387
|
+
registration: "not-applicable",
|
|
2388
|
+
delivery: unresolved ? "unknown" : "failed",
|
|
2389
|
+
withdrawalRequest: observation.request()
|
|
2390
|
+
});
|
|
1962
2391
|
throw new OwneyError(
|
|
1963
2392
|
"WITHDRAW_FAILED",
|
|
1964
2393
|
raw2.message || "Zyfai withdraw failed.",
|
|
@@ -1966,13 +2395,45 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1966
2395
|
this.id
|
|
1967
2396
|
);
|
|
1968
2397
|
}
|
|
1969
|
-
|
|
2398
|
+
const evidence = {
|
|
2399
|
+
agentId: this.id,
|
|
2400
|
+
chainId,
|
|
2401
|
+
token,
|
|
2402
|
+
walletAddress: state.walletAddress,
|
|
2403
|
+
intendedAmount: amount ?? "MAX",
|
|
2404
|
+
txHash: raw2.txHash,
|
|
2405
|
+
transfer: "pending",
|
|
2406
|
+
registration: "not-applicable",
|
|
2407
|
+
delivery: "pending",
|
|
2408
|
+
withdrawalRequest: observation.request()
|
|
2409
|
+
};
|
|
2410
|
+
notifyFunding(onProgress, evidence);
|
|
2411
|
+
return { ...mapWithdraw(raw2), evidence };
|
|
2412
|
+
}
|
|
2413
|
+
/** Registration recovery never signs, transfers, activates, or refreshes authentication. */
|
|
2414
|
+
async retryDepositRegistration(state, evidence) {
|
|
2415
|
+
const internals = this.sdk;
|
|
2416
|
+
if (!internals.httpClient.authToken || internals.authenticatedUserId === null || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== evidence.chainId) {
|
|
2417
|
+
throw new OwneyError("NOT_CONNECTED", "Reconnect the wallet before recovering deposit registration.");
|
|
2418
|
+
}
|
|
2419
|
+
if (evidence.transfer !== "confirmed" || !evidence.txHash) {
|
|
2420
|
+
throw new OwneyError("AGENT_API_ERROR", "Deposit transfer must be verified before registration recovery.");
|
|
2421
|
+
}
|
|
2422
|
+
const cid = isValidChainId(evidence.chainId);
|
|
2423
|
+
const tokenAddress = evidence.token === "WETH" ? WETH_ADDRESS_BY_CHAIN[cid] : void 0;
|
|
2424
|
+
await this.sdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
|
|
2425
|
+
return { ...evidence, registration: "confirmed" };
|
|
1970
2426
|
}
|
|
1971
2427
|
// --- IAgent: Portfolio reads ---
|
|
2428
|
+
async getFundingWallet(state, chainId) {
|
|
2429
|
+
return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
|
|
2430
|
+
}
|
|
1972
2431
|
async getBalances(state, chainId) {
|
|
2432
|
+
const requestedAt = Date.now();
|
|
1973
2433
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
1974
2434
|
const raw2 = await this.sdk.getPortfolio(this.getAddress());
|
|
1975
|
-
|
|
2435
|
+
const balance = mapBalances(raw2, validChainId, smartWallet);
|
|
2436
|
+
return { ...balance, readMetadata: balance.readMetadata?.map((read2) => ({ ...read2, requestedAt })) };
|
|
1976
2437
|
}
|
|
1977
2438
|
earningsKey(state, chainId, smartWallet) {
|
|
1978
2439
|
return JSON.stringify([
|
|
@@ -2045,7 +2506,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2045
2506
|
/**
|
|
2046
2507
|
* Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
|
|
2047
2508
|
* takes lowercase `assetType` and denominates WETH as "eth" (the same
|
|
2048
|
-
* spelling `
|
|
2509
|
+
* spelling `resolveFundingTransfers` normalizes on the way in). Anything
|
|
2049
2510
|
* unrecognised returns undefined so the request stays unfiltered rather
|
|
2050
2511
|
* than silently narrowing to nothing.
|
|
2051
2512
|
*/
|
|
@@ -2064,6 +2525,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2064
2525
|
}
|
|
2065
2526
|
async getHistory(state, chainId, options) {
|
|
2066
2527
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
2528
|
+
return this.readHistory(state, smartWallet, validChainId, options);
|
|
2529
|
+
}
|
|
2530
|
+
/** Recovery reads never enter connectAuth or wallet activation. */
|
|
2531
|
+
async getFundingHistory(state, chainId, options) {
|
|
2532
|
+
const smartWallet = await this.getFundingWallet(state, chainId);
|
|
2533
|
+
return this.readHistory(state, smartWallet, isValidChainId(chainId), options);
|
|
2534
|
+
}
|
|
2535
|
+
async readHistory(state, smartWallet, validChainId, options) {
|
|
2067
2536
|
const limit = options?.limit ?? 10;
|
|
2068
2537
|
const assetType = _ZyfaiAgent.toZyfaiAssetType(options?.tokenSymbol);
|
|
2069
2538
|
const cursor = options?.cursor ? decodeHistoryCursor(options.cursor) : null;
|
|
@@ -2105,9 +2574,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2105
2574
|
rawOffset: firstOvershoot ? firstOvershoot.rawIdx : offset
|
|
2106
2575
|
}) : void 0;
|
|
2107
2576
|
const data = mapEntries(pageData, validChainId);
|
|
2108
|
-
await this.
|
|
2577
|
+
await this.resolveFundingTransfers(data, validChainId, state.walletAddress, smartWallet, options?.tokenSymbol);
|
|
2578
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2579
|
+
const unique = data.filter((entry) => {
|
|
2580
|
+
if (!entry.id || !entry.transactions[0]?.transferLogs?.length) return true;
|
|
2581
|
+
if (seen.has(entry.id)) return false;
|
|
2582
|
+
seen.add(entry.id);
|
|
2583
|
+
return true;
|
|
2584
|
+
});
|
|
2109
2585
|
return {
|
|
2110
|
-
data,
|
|
2586
|
+
data: unique,
|
|
2111
2587
|
hasMore,
|
|
2112
2588
|
nextCursor
|
|
2113
2589
|
};
|
|
@@ -2156,7 +2632,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2156
2632
|
};
|
|
2157
2633
|
|
|
2158
2634
|
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
2159
|
-
var
|
|
2635
|
+
var import_viem8 = require("viem");
|
|
2160
2636
|
var import_chains3 = require("viem/chains");
|
|
2161
2637
|
|
|
2162
2638
|
// src/lib/chain-guard.ts
|
|
@@ -2195,7 +2671,7 @@ async function ensureWalletOnChain(pub, wallet, expected) {
|
|
|
2195
2671
|
}
|
|
2196
2672
|
|
|
2197
2673
|
// src/lib/transfer-auth.ts
|
|
2198
|
-
var
|
|
2674
|
+
var import_viem4 = require("viem");
|
|
2199
2675
|
|
|
2200
2676
|
// src/lib/sponsor-client.ts
|
|
2201
2677
|
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
@@ -2311,13 +2787,14 @@ async function postSponsorBatchTransfer(input) {
|
|
|
2311
2787
|
}
|
|
2312
2788
|
|
|
2313
2789
|
// src/lib/permit2.ts
|
|
2314
|
-
var
|
|
2790
|
+
var import_viem5 = require("viem");
|
|
2315
2791
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2792
|
+
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2316
2793
|
function permit2ApprovalAmount(requiredAmount) {
|
|
2317
2794
|
if (requiredAmount <= 0n) {
|
|
2318
2795
|
throw new Error("Permit2 approval requires a positive deposit amount");
|
|
2319
2796
|
}
|
|
2320
|
-
return
|
|
2797
|
+
return MAX_UINT256;
|
|
2321
2798
|
}
|
|
2322
2799
|
var ERC20_ALLOWANCE_ABI = [
|
|
2323
2800
|
{
|
|
@@ -2351,14 +2828,15 @@ var ERC20_ALLOWANCE_ABI = [
|
|
|
2351
2828
|
function randomPermit2Nonce() {
|
|
2352
2829
|
const bytes = new Uint8Array(32);
|
|
2353
2830
|
globalThis.crypto.getRandomValues(bytes);
|
|
2354
|
-
return BigInt((0,
|
|
2831
|
+
return BigInt((0, import_viem5.bytesToHex)(bytes));
|
|
2355
2832
|
}
|
|
2356
|
-
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2833
|
+
async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
|
|
2357
2834
|
return publicClient.readContract({
|
|
2358
2835
|
address: token,
|
|
2359
2836
|
abi: ERC20_ALLOWANCE_ABI,
|
|
2360
2837
|
functionName: "allowance",
|
|
2361
|
-
args: [owner, PERMIT2_ADDRESS]
|
|
2838
|
+
args: [owner, PERMIT2_ADDRESS],
|
|
2839
|
+
...blockNumber === void 0 ? {} : { blockNumber }
|
|
2362
2840
|
});
|
|
2363
2841
|
}
|
|
2364
2842
|
async function readErc20Balance(publicClient, token, owner) {
|
|
@@ -2393,7 +2871,7 @@ function makeVerificationAwareDepositCallback(implementation) {
|
|
|
2393
2871
|
|
|
2394
2872
|
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2395
2873
|
var import_siwe = require("siwe");
|
|
2396
|
-
var
|
|
2874
|
+
var import_viem6 = require("viem");
|
|
2397
2875
|
var import_chains2 = require("viem/chains");
|
|
2398
2876
|
|
|
2399
2877
|
// src/agents/yieldseeker/yieldseeker.auth-cache.ts
|
|
@@ -2506,7 +2984,7 @@ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
|
|
|
2506
2984
|
return new import_siwe.SiweMessage({
|
|
2507
2985
|
scheme: url.protocol.slice(0, -1),
|
|
2508
2986
|
domain: url.host,
|
|
2509
|
-
address: (0,
|
|
2987
|
+
address: (0, import_viem6.getAddress)(address),
|
|
2510
2988
|
uri: url.origin,
|
|
2511
2989
|
version: "1",
|
|
2512
2990
|
chainId,
|
|
@@ -2592,15 +3070,15 @@ var YieldseekerAuth = class {
|
|
|
2592
3070
|
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2593
3071
|
}
|
|
2594
3072
|
async sign(state, chainId) {
|
|
2595
|
-
const account = (0,
|
|
2596
|
-
const publicClient = (0,
|
|
3073
|
+
const account = (0, import_viem6.getAddress)(state.walletAddress);
|
|
3074
|
+
const publicClient = (0, import_viem6.createPublicClient)({
|
|
2597
3075
|
chain: import_chains2.base,
|
|
2598
|
-
transport: (0,
|
|
3076
|
+
transport: (0, import_viem6.custom)(state.provider)
|
|
2599
3077
|
});
|
|
2600
|
-
const walletClient = (0,
|
|
3078
|
+
const walletClient = (0, import_viem6.createWalletClient)({
|
|
2601
3079
|
account,
|
|
2602
3080
|
chain: import_chains2.base,
|
|
2603
|
-
transport: (0,
|
|
3081
|
+
transport: (0, import_viem6.custom)(state.provider)
|
|
2604
3082
|
});
|
|
2605
3083
|
await ensureWalletOnChain(
|
|
2606
3084
|
publicClient,
|
|
@@ -2741,10 +3219,11 @@ var YieldseekerApiClient = class {
|
|
|
2741
3219
|
const payload = await response.json().catch(() => null);
|
|
2742
3220
|
if (!response.ok) {
|
|
2743
3221
|
const error = providerError(payload, `HTTP_${response.status}`);
|
|
3222
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
2744
3223
|
throw new YieldseekerApiError(
|
|
2745
3224
|
response.status,
|
|
2746
3225
|
error.code,
|
|
2747
|
-
error.fields
|
|
3226
|
+
retryAfter ? { ...error.fields, headers: { "Retry-After": retryAfter } } : error.fields
|
|
2748
3227
|
);
|
|
2749
3228
|
}
|
|
2750
3229
|
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
@@ -2766,7 +3245,7 @@ var YieldseekerApiClient = class {
|
|
|
2766
3245
|
};
|
|
2767
3246
|
|
|
2768
3247
|
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2769
|
-
var
|
|
3248
|
+
var import_viem7 = require("viem");
|
|
2770
3249
|
|
|
2771
3250
|
// src/lib/helpers/snapshot-apy.ts
|
|
2772
3251
|
var DAY_MS = 864e5;
|
|
@@ -2833,10 +3312,10 @@ function raw(value, endpoint) {
|
|
|
2833
3312
|
return BigInt(value);
|
|
2834
3313
|
}
|
|
2835
3314
|
function decimal(value, decimals, endpoint) {
|
|
2836
|
-
return (0,
|
|
3315
|
+
return (0, import_viem7.formatUnits)(raw(value, endpoint), decimals);
|
|
2837
3316
|
}
|
|
2838
3317
|
function usd(rawAmount, decimals, price) {
|
|
2839
|
-
return Number((0,
|
|
3318
|
+
return Number((0, import_viem7.formatUnits)(rawAmount, decimals)) * price;
|
|
2840
3319
|
}
|
|
2841
3320
|
function percent(value) {
|
|
2842
3321
|
const result = Number(value);
|
|
@@ -2862,7 +3341,7 @@ function assetAddressValue(record, address) {
|
|
|
2862
3341
|
}
|
|
2863
3342
|
function position(value, asset, baseAssetDecimals) {
|
|
2864
3343
|
const option = value?.yieldOption;
|
|
2865
|
-
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0,
|
|
3344
|
+
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem7.isAddress)(option.address)) {
|
|
2866
3345
|
return invalid("yield positions", "missing vault metadata");
|
|
2867
3346
|
}
|
|
2868
3347
|
return {
|
|
@@ -2875,13 +3354,14 @@ function position(value, asset, baseAssetDecimals) {
|
|
|
2875
3354
|
// differ from the underlying asset. Yieldseeker already converts it to
|
|
2876
3355
|
// underlying base-asset units in `assetsBase`; pair that value with the
|
|
2877
3356
|
// snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
|
|
2878
|
-
// share quantity
|
|
3357
|
+
// share quantity separate from the withdrawable underlying asset amount.
|
|
2879
3358
|
amount: decimal(
|
|
2880
3359
|
value.assetsBase,
|
|
2881
3360
|
baseAssetDecimals,
|
|
2882
3361
|
"yield positions"
|
|
2883
3362
|
),
|
|
2884
3363
|
amountRaw: String(value.assetsRaw),
|
|
3364
|
+
withdrawableAmountRaw: String(value.withdrawableAssetsRaw),
|
|
2885
3365
|
apy: percent(option.riskAdjustedApy),
|
|
2886
3366
|
tvl: Number(option.totalDepositsUsd),
|
|
2887
3367
|
liquidity: Number(option.withdrawableDepositsUsd)
|
|
@@ -2931,7 +3411,17 @@ function mapYieldseekerBalances(contexts) {
|
|
|
2931
3411
|
totalBalanceAsset: "usdc",
|
|
2932
3412
|
assetBalances,
|
|
2933
3413
|
tokens,
|
|
2934
|
-
positions
|
|
3414
|
+
positions,
|
|
3415
|
+
readMetadata: contexts.map((context) => ({
|
|
3416
|
+
chainId: context.agent.chainId,
|
|
3417
|
+
asset: context.asset,
|
|
3418
|
+
fetchedAt: context.fetchedAt,
|
|
3419
|
+
readVersion: context.readVersion,
|
|
3420
|
+
providerUpdatedAt: context.snapshot.updatedDate,
|
|
3421
|
+
snapshotId: String(context.snapshot.agentWalletSnapshotId),
|
|
3422
|
+
netDepositsRaw: context.snapshot.netDepositsBase,
|
|
3423
|
+
positionsStatus: context.positionsUnavailable ? "unavailable" : "available"
|
|
3424
|
+
}))
|
|
2935
3425
|
};
|
|
2936
3426
|
}
|
|
2937
3427
|
function mapYieldseekerEarnings(contexts) {
|
|
@@ -2943,7 +3433,7 @@ function mapYieldseekerEarnings(contexts) {
|
|
|
2943
3433
|
chain: "BASE",
|
|
2944
3434
|
chainId: 8453,
|
|
2945
3435
|
asset: context.asset,
|
|
2946
|
-
amount: (0,
|
|
3436
|
+
amount: (0, import_viem7.formatUnits)(amount, context.snapshot.baseAssetDecimals)
|
|
2947
3437
|
});
|
|
2948
3438
|
lifetimeEarnings += usd(
|
|
2949
3439
|
amount,
|
|
@@ -3045,6 +3535,7 @@ function transactionHashes(details) {
|
|
|
3045
3535
|
function actionEntry(action) {
|
|
3046
3536
|
if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
|
|
3047
3537
|
return {
|
|
3538
|
+
id: `yieldseeker:action:${action.agentActionId}`,
|
|
3048
3539
|
agent: "yieldseeker",
|
|
3049
3540
|
action: actionType(action.actionType),
|
|
3050
3541
|
date: action.createdDate,
|
|
@@ -3074,7 +3565,7 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddres
|
|
|
3074
3565
|
const owner = ownerAddress.toLowerCase();
|
|
3075
3566
|
const agentWallet = wallet.walletAddress.toLowerCase();
|
|
3076
3567
|
const baseAsset = agent.assetAddress.toLowerCase();
|
|
3077
|
-
if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
|
|
3568
|
+
if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset || raw(movement.assetAmount, "historic position") <= 0n) {
|
|
3078
3569
|
return void 0;
|
|
3079
3570
|
}
|
|
3080
3571
|
let action;
|
|
@@ -3099,6 +3590,12 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddres
|
|
|
3099
3590
|
transactions: [
|
|
3100
3591
|
{
|
|
3101
3592
|
txHashes: [movement.transactionHash],
|
|
3593
|
+
tokenAddress: movement.assetAddress,
|
|
3594
|
+
agentWallet: wallet.walletAddress,
|
|
3595
|
+
logIndexes: [movement.logIndex],
|
|
3596
|
+
transferLogs: [{ logIndex: movement.logIndex, amountRaw: movement.assetAmount }],
|
|
3597
|
+
amountRaw: movement.assetAmount,
|
|
3598
|
+
blockNumber: String(movement.blockNumber),
|
|
3102
3599
|
chainId: agent.chainId,
|
|
3103
3600
|
tokenSymbol: asset,
|
|
3104
3601
|
amount: decimal(
|
|
@@ -3144,7 +3641,8 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3144
3641
|
ungrouped.push(entry);
|
|
3145
3642
|
continue;
|
|
3146
3643
|
}
|
|
3147
|
-
const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}`;
|
|
3644
|
+
const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}:${tx.tokenAddress?.toLowerCase() ?? ""}:${tx.agentWallet?.toLowerCase() ?? ""}`;
|
|
3645
|
+
entry.id = key2;
|
|
3148
3646
|
const previous = grouped.get(key2);
|
|
3149
3647
|
if (!previous) {
|
|
3150
3648
|
grouped.set(key2, entry);
|
|
@@ -3157,14 +3655,52 @@ function mapYieldseekerHistory(contexts, options) {
|
|
|
3157
3655
|
}
|
|
3158
3656
|
previous.positions.push(...entry.positions);
|
|
3159
3657
|
previous.transactions.push(...entry.transactions);
|
|
3658
|
+
} else if ((entry.action === "Top up" || entry.action === "Withdraw") && tx.amountRaw) {
|
|
3659
|
+
const target = previous.transactions[0];
|
|
3660
|
+
const known = new Set(target.logIndexes ?? []);
|
|
3661
|
+
if (tx.logIndexes?.some((index) => !known.has(index))) {
|
|
3662
|
+
target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
|
|
3663
|
+
target.amount = (0, import_viem7.formatUnits)(
|
|
3664
|
+
BigInt(target.amountRaw),
|
|
3665
|
+
YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
|
|
3666
|
+
);
|
|
3667
|
+
target.logIndexes = [...known, ...tx.logIndexes.filter((index) => !known.has(index))];
|
|
3668
|
+
target.transferLogs = [...target.transferLogs ?? [], ...(tx.transferLogs ?? []).filter((log) => !known.has(log.logIndex))];
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
const values = [...grouped.values(), ...ungrouped].filter((entry) => {
|
|
3673
|
+
const tx = entry.transactions[0];
|
|
3674
|
+
return tx?.amountRaw !== void 0 || ![...grouped.values()].some((other) => other !== entry && other.action === entry.action && other.transactions[0]?.amountRaw !== void 0 && other.transactions[0]?.txHashes[0]?.toLowerCase() === tx?.txHashes[0]?.toLowerCase());
|
|
3675
|
+
});
|
|
3676
|
+
let boundary;
|
|
3677
|
+
const scope = JSON.stringify([
|
|
3678
|
+
options.ownerAddress.toLowerCase(),
|
|
3679
|
+
contexts.map((context) => `${context.agent.chainId}:${context.wallet.walletAddress.toLowerCase()}:${context.asset}`).sort(),
|
|
3680
|
+
options.fromDate ?? null
|
|
3681
|
+
]);
|
|
3682
|
+
if (options.cursor) {
|
|
3683
|
+
try {
|
|
3684
|
+
boundary = JSON.parse(decodeURIComponent(atob(options.cursor)));
|
|
3685
|
+
if (!boundary || boundary.scope !== scope || typeof boundary.id !== "string" || !Number.isFinite(Date.parse(boundary.date)) || !Number.isFinite(Date.parse(boundary.toDate))) throw new Error();
|
|
3686
|
+
} catch {
|
|
3687
|
+
throw new InvalidHistoryCursorError("Invalid Yieldseeker history cursor.");
|
|
3160
3688
|
}
|
|
3161
3689
|
}
|
|
3162
|
-
const
|
|
3690
|
+
const toDate = boundary?.toDate ?? options.toDate ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3691
|
+
const filtered = values.filter((entry) => (!options.fromDate || entry.date >= options.fromDate) && entry.date <= toDate && (!boundary || entry.date < boundary.date || entry.date === boundary.date && (entry.id ?? "") > boundary.id)).sort((left, right) => right.date.localeCompare(left.date) || (left.id ?? "").localeCompare(right.id ?? ""));
|
|
3692
|
+
const data = filtered.slice(0, Math.max(1, options.limit));
|
|
3693
|
+
const hasMore = filtered.length > data.length;
|
|
3694
|
+
const last = data.at(-1);
|
|
3163
3695
|
return {
|
|
3164
|
-
data
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3696
|
+
data,
|
|
3697
|
+
hasMore,
|
|
3698
|
+
...hasMore && last ? { nextCursor: btoa(encodeURIComponent(JSON.stringify({
|
|
3699
|
+
date: last.date,
|
|
3700
|
+
id: last.id ?? "",
|
|
3701
|
+
toDate,
|
|
3702
|
+
scope
|
|
3703
|
+
}))) } : {}
|
|
3168
3704
|
};
|
|
3169
3705
|
}
|
|
3170
3706
|
function mapYieldseekerProfile(address, contexts) {
|
|
@@ -3206,10 +3742,18 @@ function mapYieldseekerAgentApy(options, days) {
|
|
|
3206
3742
|
|
|
3207
3743
|
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
3208
3744
|
var OWNEY_AGENT_NAME = "owney";
|
|
3745
|
+
var OWNEY_AGENT_RULE_PRESET = "explorative";
|
|
3209
3746
|
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
3210
3747
|
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3211
3748
|
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3212
3749
|
var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
|
|
3750
|
+
var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
|
|
3751
|
+
var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
|
|
3752
|
+
var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
|
|
3753
|
+
var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
|
|
3754
|
+
var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
3755
|
+
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
3756
|
+
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
3213
3757
|
function generateYieldseekerUsername() {
|
|
3214
3758
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
3215
3759
|
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
@@ -3253,6 +3797,7 @@ function query(params) {
|
|
|
3253
3797
|
var YieldseekerAgent = class {
|
|
3254
3798
|
id = "yieldseeker";
|
|
3255
3799
|
balanceComposition = "tokens-plus-positions";
|
|
3800
|
+
withdrawalRequiresWalletApproval = true;
|
|
3256
3801
|
supportedChainIds = [8453];
|
|
3257
3802
|
supportedAssets = [
|
|
3258
3803
|
{
|
|
@@ -3266,11 +3811,26 @@ var YieldseekerAgent = class {
|
|
|
3266
3811
|
];
|
|
3267
3812
|
api;
|
|
3268
3813
|
auth;
|
|
3814
|
+
rpcUrls;
|
|
3815
|
+
receiptClient;
|
|
3269
3816
|
transactionExecutor;
|
|
3270
3817
|
unwindReceiptWaiter;
|
|
3271
3818
|
agentContexts = /* @__PURE__ */ new Map();
|
|
3272
3819
|
users = /* @__PURE__ */ new Map();
|
|
3273
3820
|
pendingAgents = /* @__PURE__ */ new Map();
|
|
3821
|
+
pendingWalletContexts = /* @__PURE__ */ new Map();
|
|
3822
|
+
readCache = /* @__PURE__ */ new Map();
|
|
3823
|
+
pendingReads = /* @__PURE__ */ new Map();
|
|
3824
|
+
readGeneration = 0;
|
|
3825
|
+
portfolioVersions = /* @__PURE__ */ new Map();
|
|
3826
|
+
snapshotFailures = /* @__PURE__ */ new Map();
|
|
3827
|
+
standardReadFailures = /* @__PURE__ */ new Map();
|
|
3828
|
+
reconcileUntil = /* @__PURE__ */ new Map();
|
|
3829
|
+
activityRefreshUntil = /* @__PURE__ */ new Map();
|
|
3830
|
+
// null means a confirmed movement has no reliable baseline. Do not guess
|
|
3831
|
+
// settlement; keep slow reconciliation after the fast window expires.
|
|
3832
|
+
snapshotMovements = /* @__PURE__ */ new Map();
|
|
3833
|
+
movementEpochs = /* @__PURE__ */ new Map();
|
|
3274
3834
|
yieldOptions = /* @__PURE__ */ new Map();
|
|
3275
3835
|
pendingYieldOptions = /* @__PURE__ */ new Map();
|
|
3276
3836
|
constructor(owneyApiKey, options = {}) {
|
|
@@ -3280,10 +3840,16 @@ var YieldseekerAgent = class {
|
|
|
3280
3840
|
options.fetchFn
|
|
3281
3841
|
);
|
|
3282
3842
|
this.auth = new YieldseekerAuth(options.auth);
|
|
3843
|
+
this.rpcUrls = options.rpcUrls;
|
|
3283
3844
|
this.transactionExecutor = options.transactionExecutor;
|
|
3284
3845
|
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
3285
3846
|
}
|
|
3847
|
+
getReceiptClient() {
|
|
3848
|
+
this.receiptClient ??= createPaidRpcClient(import_chains3.base, this.rpcUrls);
|
|
3849
|
+
return this.receiptClient;
|
|
3850
|
+
}
|
|
3286
3851
|
async disconnect() {
|
|
3852
|
+
this.readGeneration += 1;
|
|
3287
3853
|
this.auth.clear();
|
|
3288
3854
|
for (const key2 of this.users.keys()) {
|
|
3289
3855
|
const [walletAddress, chainId] = key2.split(":");
|
|
@@ -3292,6 +3858,16 @@ var YieldseekerAgent = class {
|
|
|
3292
3858
|
this.users.clear();
|
|
3293
3859
|
this.agentContexts.clear();
|
|
3294
3860
|
this.pendingAgents.clear();
|
|
3861
|
+
this.pendingWalletContexts.clear();
|
|
3862
|
+
this.readCache.clear();
|
|
3863
|
+
this.pendingReads.clear();
|
|
3864
|
+
this.portfolioVersions.clear();
|
|
3865
|
+
this.snapshotFailures.clear();
|
|
3866
|
+
this.standardReadFailures.clear();
|
|
3867
|
+
this.reconcileUntil.clear();
|
|
3868
|
+
this.activityRefreshUntil.clear();
|
|
3869
|
+
this.snapshotMovements.clear();
|
|
3870
|
+
this.movementEpochs.clear();
|
|
3295
3871
|
}
|
|
3296
3872
|
async activateAgent(state, chainId, asset) {
|
|
3297
3873
|
this.assertChain(chainId);
|
|
@@ -3299,7 +3875,7 @@ var YieldseekerAgent = class {
|
|
|
3299
3875
|
this.assertAsset(targetAsset);
|
|
3300
3876
|
await this.ensureAgent(state, chainId, targetAsset);
|
|
3301
3877
|
}
|
|
3302
|
-
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
3878
|
+
async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
|
|
3303
3879
|
this.assertChain(chainId);
|
|
3304
3880
|
this.assertAsset(asset);
|
|
3305
3881
|
if (BigInt(amount) <= 0n) {
|
|
@@ -3311,49 +3887,85 @@ var YieldseekerAgent = class {
|
|
|
3311
3887
|
);
|
|
3312
3888
|
}
|
|
3313
3889
|
const context = await this.ensureAgent(state, chainId, asset);
|
|
3890
|
+
const generation = this.readGeneration;
|
|
3891
|
+
const movement = this.snapshotMovement(state, chainId, context, BigInt(amount));
|
|
3892
|
+
notifyFunding(onProgress, {
|
|
3893
|
+
agentId: this.id,
|
|
3894
|
+
chainId,
|
|
3895
|
+
token: asset,
|
|
3896
|
+
walletAddress: state.walletAddress,
|
|
3897
|
+
agentWallet: context.wallet.walletAddress,
|
|
3898
|
+
intendedAmount: amount,
|
|
3899
|
+
transfer: "pending",
|
|
3900
|
+
registration: "not-applicable",
|
|
3901
|
+
delivery: "not-applicable"
|
|
3902
|
+
});
|
|
3314
3903
|
let txHash;
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
amount
|
|
3327
|
-
);
|
|
3328
|
-
await this.waitForReceipt(state, chainId, txHash);
|
|
3329
|
-
} else {
|
|
3330
|
-
txHash = await this.submitTransaction(state, chainId, {
|
|
3331
|
-
from: (0, import_viem6.getAddress)(state.walletAddress),
|
|
3332
|
-
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3333
|
-
data: (0, import_viem6.encodeFunctionData)({
|
|
3334
|
-
abi: import_viem6.erc20Abi,
|
|
3335
|
-
functionName: "transfer",
|
|
3336
|
-
args: [(0, import_viem6.getAddress)(context.wallet.walletAddress), BigInt(amount)]
|
|
3337
|
-
}),
|
|
3338
|
-
value: "0",
|
|
3339
|
-
chainId
|
|
3340
|
-
});
|
|
3341
|
-
}
|
|
3342
|
-
} finally {
|
|
3343
|
-
await this.refreshSnapshotAfterMovement(
|
|
3344
|
-
state,
|
|
3904
|
+
let receipt;
|
|
3905
|
+
const submitted = (hash) => notifyFunding(onProgress, { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount, txHash: hash, transfer: "pending", registration: "not-applicable", delivery: "not-applicable" });
|
|
3906
|
+
if (depositCallback) {
|
|
3907
|
+
provideDepositVerificationContext(depositCallback, {
|
|
3908
|
+
agentId: "yieldseeker",
|
|
3909
|
+
signature: await this.auth.getToken(state, chainId),
|
|
3910
|
+
userId: context.user.userId,
|
|
3911
|
+
yieldseekerAgentId: context.agent.agentId
|
|
3912
|
+
});
|
|
3913
|
+
txHash = await depositCallback(
|
|
3914
|
+
context.wallet.walletAddress,
|
|
3345
3915
|
chainId,
|
|
3346
|
-
|
|
3347
|
-
"deposit"
|
|
3916
|
+
amount
|
|
3348
3917
|
);
|
|
3918
|
+
submitted(txHash);
|
|
3919
|
+
receipt = await this.waitForReceipt(state, chainId, txHash);
|
|
3920
|
+
} else {
|
|
3921
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
3922
|
+
from: (0, import_viem8.getAddress)(state.walletAddress),
|
|
3923
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3924
|
+
data: (0, import_viem8.encodeFunctionData)({
|
|
3925
|
+
abi: import_viem8.erc20Abi,
|
|
3926
|
+
functionName: "transfer",
|
|
3927
|
+
args: [(0, import_viem8.getAddress)(context.wallet.walletAddress), BigInt(amount)]
|
|
3928
|
+
}),
|
|
3929
|
+
value: "0",
|
|
3930
|
+
chainId
|
|
3931
|
+
}, submitted, (value) => {
|
|
3932
|
+
receipt = value;
|
|
3933
|
+
});
|
|
3349
3934
|
}
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3935
|
+
if (this.readGeneration === generation) {
|
|
3936
|
+
void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
3937
|
+
}
|
|
3938
|
+
const base5 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount, confirmedAmount: amount, txHash, transfer: "confirmed", registration: "not-applicable", delivery: "not-applicable" };
|
|
3939
|
+
const evidence = receipt ? inspectFundingReceipt(receipt, base5, YIELDSEEKER_ASSET_METADATA[asset].address, "deposit") : base5;
|
|
3940
|
+
notifyFunding(onProgress, evidence);
|
|
3941
|
+
return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
|
|
3942
|
+
}
|
|
3943
|
+
async withdraw(state, chainId, asset, amount, onProgress) {
|
|
3944
|
+
let ownerTransferAttempted = false;
|
|
3945
|
+
let evidence = {
|
|
3946
|
+
agentId: this.id,
|
|
3947
|
+
chainId,
|
|
3948
|
+
token: asset,
|
|
3949
|
+
walletAddress: state.walletAddress,
|
|
3950
|
+
intendedAmount: amount ?? "MAX",
|
|
3951
|
+
transfer: "pending",
|
|
3952
|
+
registration: "not-applicable",
|
|
3953
|
+
delivery: "pending"
|
|
3354
3954
|
};
|
|
3955
|
+
try {
|
|
3956
|
+
return await this.withdrawInternal(state, chainId, asset, amount, (next) => {
|
|
3957
|
+
evidence = { ...evidence, ...next };
|
|
3958
|
+
notifyFunding(onProgress, evidence);
|
|
3959
|
+
}, () => {
|
|
3960
|
+
ownerTransferAttempted = true;
|
|
3961
|
+
});
|
|
3962
|
+
} catch (error) {
|
|
3963
|
+
if (!ownerTransferAttempted || error instanceof OwneyError && error.code === "AGENT_TRANSACTION_REVERTED")
|
|
3964
|
+
notifyFunding(onProgress, { ...evidence, transfer: "failed", delivery: "failed" });
|
|
3965
|
+
throw error;
|
|
3966
|
+
}
|
|
3355
3967
|
}
|
|
3356
|
-
async
|
|
3968
|
+
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted) {
|
|
3357
3969
|
this.assertChain(chainId);
|
|
3358
3970
|
this.assertAsset(asset);
|
|
3359
3971
|
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
@@ -3373,6 +3985,9 @@ var YieldseekerAgent = class {
|
|
|
3373
3985
|
this.id
|
|
3374
3986
|
);
|
|
3375
3987
|
}
|
|
3988
|
+
const generation = this.readGeneration;
|
|
3989
|
+
let confirmedMovement = false;
|
|
3990
|
+
let settlement = null;
|
|
3376
3991
|
try {
|
|
3377
3992
|
const portfolio = await this.loadPortfolioContext(
|
|
3378
3993
|
state,
|
|
@@ -3380,16 +3995,32 @@ var YieldseekerAgent = class {
|
|
|
3380
3995
|
context
|
|
3381
3996
|
);
|
|
3382
3997
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3383
|
-
const
|
|
3384
|
-
(
|
|
3385
|
-
|
|
3386
|
-
|
|
3998
|
+
const readIdle = async () => {
|
|
3999
|
+
const balance = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
|
|
4000
|
+
address: metadata.address,
|
|
4001
|
+
abi: import_viem8.erc20Abi,
|
|
4002
|
+
functionName: "balanceOf",
|
|
4003
|
+
args: [(0, import_viem8.getAddress)(context.wallet.walletAddress)]
|
|
4004
|
+
}), chainId, "eth_call", this.id);
|
|
4005
|
+
this.assertSession(generation, chainId, asset);
|
|
4006
|
+
return balance;
|
|
4007
|
+
};
|
|
4008
|
+
const idle = await readIdle();
|
|
3387
4009
|
const deployed = portfolio.positions.reduce(
|
|
3388
4010
|
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
3389
4011
|
0n
|
|
3390
4012
|
);
|
|
3391
4013
|
const totalAvailable = idle + deployed;
|
|
3392
4014
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
4015
|
+
if (requested === 0n) {
|
|
4016
|
+
this.invalidateFundingRead(state, chainId, asset);
|
|
4017
|
+
throw new OwneyError(
|
|
4018
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4019
|
+
"No Yieldseeker funds are available to withdraw.",
|
|
4020
|
+
{ asset, available: "0" },
|
|
4021
|
+
this.id
|
|
4022
|
+
);
|
|
4023
|
+
}
|
|
3393
4024
|
if (requested > totalAvailable) {
|
|
3394
4025
|
throw new OwneyError(
|
|
3395
4026
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
@@ -3425,6 +4056,7 @@ var YieldseekerAgent = class {
|
|
|
3425
4056
|
throw this.invalidResponse("position withdrawal");
|
|
3426
4057
|
}
|
|
3427
4058
|
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
4059
|
+
confirmedMovement = true;
|
|
3428
4060
|
remaining -= assetsRaw;
|
|
3429
4061
|
}
|
|
3430
4062
|
if (remaining > 0n) {
|
|
@@ -3433,45 +4065,102 @@ var YieldseekerAgent = class {
|
|
|
3433
4065
|
remaining: remaining.toString()
|
|
3434
4066
|
});
|
|
3435
4067
|
}
|
|
3436
|
-
const
|
|
4068
|
+
const availableForOwner = confirmedMovement ? await readIdle() : idle;
|
|
4069
|
+
if (availableForOwner === 0n || amount !== void 0 && availableForOwner < requested) {
|
|
4070
|
+
this.invalidateFundingRead(state, chainId, asset);
|
|
4071
|
+
throw new OwneyError(
|
|
4072
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4073
|
+
"Yieldseeker funds are not available for the owner transfer.",
|
|
4074
|
+
{ asset, requested: amount ?? "MAX", available: availableForOwner.toString() },
|
|
4075
|
+
this.id
|
|
4076
|
+
);
|
|
4077
|
+
}
|
|
4078
|
+
const payout = amount === void 0 ? availableForOwner : requested;
|
|
4079
|
+
const plannedMovement = this.snapshotMovement(state, chainId, context, -payout);
|
|
4080
|
+
const account = (0, import_viem8.getAddress)(state.walletAddress);
|
|
4081
|
+
let receipt;
|
|
4082
|
+
const base5 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount === void 0 ? "MAX" : requested.toString(), transfer: "pending", registration: "not-applicable", delivery: "pending" };
|
|
4083
|
+
onOwnerTransferAttempted?.();
|
|
3437
4084
|
const txHash = await this.submitTransaction(state, chainId, {
|
|
3438
4085
|
from: account,
|
|
3439
|
-
to: (0,
|
|
3440
|
-
data: amount === void 0 ? (0,
|
|
4086
|
+
to: (0, import_viem8.getAddress)(context.wallet.walletAddress),
|
|
4087
|
+
data: amount === void 0 ? (0, import_viem8.encodeFunctionData)({
|
|
3441
4088
|
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3442
4089
|
functionName: "withdrawAllAssetToUser",
|
|
3443
4090
|
args: [account, metadata.address]
|
|
3444
|
-
}) : (0,
|
|
4091
|
+
}) : (0, import_viem8.encodeFunctionData)({
|
|
3445
4092
|
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3446
4093
|
functionName: "withdrawAssetToUser",
|
|
3447
4094
|
args: [account, metadata.address, requested]
|
|
3448
4095
|
}),
|
|
3449
4096
|
value: "0",
|
|
3450
4097
|
chainId
|
|
4098
|
+
}, (hash) => notifyFunding(onProgress, { ...base5, txHash: hash }), (value) => {
|
|
4099
|
+
receipt = value;
|
|
3451
4100
|
});
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
4101
|
+
confirmedMovement = true;
|
|
4102
|
+
settlement = plannedMovement;
|
|
4103
|
+
const evidence = receipt ? inspectFundingReceipt(receipt, { ...base5, txHash }, metadata.address, "withdraw") : { ...base5, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
|
|
4104
|
+
notifyFunding(onProgress, evidence);
|
|
4105
|
+
if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
|
|
4106
|
+
throw new OwneyError(
|
|
4107
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
4108
|
+
"The Yieldseeker withdrawal completed without transferring funds.",
|
|
4109
|
+
{ asset, available: "0", txHash },
|
|
4110
|
+
this.id
|
|
4111
|
+
);
|
|
4112
|
+
return { txHash, type: amount === void 0 ? "full" : "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
|
|
3457
4113
|
} finally {
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
4114
|
+
if (confirmedMovement && this.readGeneration === generation) {
|
|
4115
|
+
void this.refreshSnapshotAfterMovement(
|
|
4116
|
+
state,
|
|
4117
|
+
chainId,
|
|
4118
|
+
context,
|
|
4119
|
+
"withdrawal",
|
|
4120
|
+
settlement
|
|
4121
|
+
);
|
|
4122
|
+
}
|
|
3464
4123
|
}
|
|
3465
4124
|
}
|
|
3466
|
-
async getBalances(state, chainId) {
|
|
4125
|
+
async getBalances(state, chainId, asset) {
|
|
3467
4126
|
this.assertChain(chainId);
|
|
3468
|
-
|
|
4127
|
+
if (asset !== void 0) this.assertAsset(asset);
|
|
4128
|
+
return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, { asset, allowMissingPositions: true }));
|
|
3469
4129
|
}
|
|
3470
|
-
|
|
4130
|
+
invalidateFundingRead(state, chainId, asset, refreshUntil = Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS) {
|
|
4131
|
+
this.assertAsset(asset);
|
|
4132
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
4133
|
+
const failure = this.snapshotFailures.get(key2);
|
|
4134
|
+
this.advancePortfolioVersion(key2);
|
|
4135
|
+
if (failure && failure.retryAt > Date.now()) this.snapshotFailures.set(key2, failure);
|
|
4136
|
+
if (!this.snapshotMovements.has(key2)) this.snapshotMovements.set(key2, null);
|
|
4137
|
+
const until = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4138
|
+
this.reconcileUntil.set(key2, Math.max(this.reconcileUntil.get(key2) ?? 0, until));
|
|
4139
|
+
this.activityRefreshUntil.set(key2, Math.max(this.activityRefreshUntil.get(key2) ?? 0, until));
|
|
4140
|
+
}
|
|
4141
|
+
endFundingBalanceRefresh(state, chainId, asset, read2) {
|
|
3471
4142
|
this.assertChain(chainId);
|
|
3472
|
-
|
|
4143
|
+
this.assertAsset(asset);
|
|
4144
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
4145
|
+
const cached = this.readCache.get(`snapshot:${key2}`);
|
|
4146
|
+
const snapshot = cached?.value?.agentSnapshot;
|
|
4147
|
+
if (!cached || !snapshot || read2.chainId !== chainId || read2.asset.toUpperCase() !== asset || read2.readVersion !== this.portfolioVersion(key2) || read2.fetchedAt !== cached.fetchedAt || read2.snapshotId !== String(snapshot.agentWalletSnapshotId) || read2.providerUpdatedAt !== snapshot.updatedDate || read2.netDepositsRaw !== snapshot.netDepositsBase) return false;
|
|
4148
|
+
this.snapshotMovements.delete(key2);
|
|
4149
|
+
this.reconcileUntil.delete(key2);
|
|
4150
|
+
cached.expiresAt = cached.fetchedAt + YIELDSEEKER_PORTFOLIO_CACHE_MS;
|
|
4151
|
+
return true;
|
|
3473
4152
|
}
|
|
3474
|
-
async
|
|
4153
|
+
async getFundingWallet(state, chainId, asset) {
|
|
4154
|
+
this.assertAsset(asset);
|
|
4155
|
+
const context = this.agentContexts.get(this.contextKey(state, chainId, asset));
|
|
4156
|
+
if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
|
|
4157
|
+
return context.wallet.walletAddress;
|
|
4158
|
+
}
|
|
4159
|
+
async getEarnings(state, chainId) {
|
|
4160
|
+
this.assertChain(chainId);
|
|
4161
|
+
return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
|
|
4162
|
+
}
|
|
4163
|
+
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
3475
4164
|
this.assertChain(chainId);
|
|
3476
4165
|
const asset = tokenSymbol?.toUpperCase();
|
|
3477
4166
|
if (asset !== void 0) this.assertAsset(asset);
|
|
@@ -3485,7 +4174,7 @@ var YieldseekerAgent = class {
|
|
|
3485
4174
|
this.assertChain(chainId);
|
|
3486
4175
|
const asset = options?.tokenSymbol?.toUpperCase();
|
|
3487
4176
|
if (asset !== void 0) this.assertAsset(asset);
|
|
3488
|
-
const contexts = await this.
|
|
4177
|
+
const contexts = await this.loadActivityPortfolio(state, chainId, {
|
|
3489
4178
|
...asset ? { asset } : {},
|
|
3490
4179
|
historic: true,
|
|
3491
4180
|
actions: true
|
|
@@ -3497,11 +4186,12 @@ var YieldseekerAgent = class {
|
|
|
3497
4186
|
);
|
|
3498
4187
|
const vaultAddresses = new Set(
|
|
3499
4188
|
catalog.flat().filter(
|
|
3500
|
-
(yieldOption) => yieldOption.chainId === chainId && (0,
|
|
4189
|
+
(yieldOption) => yieldOption.chainId === chainId && (0, import_viem8.isAddress)(yieldOption.address)
|
|
3501
4190
|
).map((yieldOption) => yieldOption.address.toLowerCase())
|
|
3502
4191
|
);
|
|
3503
4192
|
return mapYieldseekerHistory(contexts, {
|
|
3504
4193
|
limit: options?.limit ?? 10,
|
|
4194
|
+
cursor: options?.cursor,
|
|
3505
4195
|
ownerAddress: state.walletAddress,
|
|
3506
4196
|
vaultAddresses,
|
|
3507
4197
|
...options?.fromDate ? { fromDate: options.fromDate } : {},
|
|
@@ -3512,7 +4202,7 @@ var YieldseekerAgent = class {
|
|
|
3512
4202
|
this.assertChain(chainId);
|
|
3513
4203
|
return mapYieldseekerProfile(
|
|
3514
4204
|
state.walletAddress,
|
|
3515
|
-
await this.
|
|
4205
|
+
await this.loadActivityPortfolio(state, chainId, {})
|
|
3516
4206
|
);
|
|
3517
4207
|
}
|
|
3518
4208
|
async getAgentApy(days, options) {
|
|
@@ -3554,6 +4244,96 @@ var YieldseekerAgent = class {
|
|
|
3554
4244
|
contextKey(state, chainId, asset) {
|
|
3555
4245
|
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3556
4246
|
}
|
|
4247
|
+
assertSession(generation, chainId, asset) {
|
|
4248
|
+
if (this.readGeneration !== generation) {
|
|
4249
|
+
throw new OwneyError(
|
|
4250
|
+
"NOT_CONNECTED",
|
|
4251
|
+
"Wallet session changed during the Yieldseeker request.",
|
|
4252
|
+
{ rpcSource: "agent-api", chainId, ...asset ? { asset } : {} },
|
|
4253
|
+
this.id
|
|
4254
|
+
);
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
cachedRead(key2, ttlMs, read2) {
|
|
4258
|
+
const cached = this.readCache.get(key2);
|
|
4259
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
4260
|
+
return Promise.resolve(cached.value);
|
|
4261
|
+
}
|
|
4262
|
+
const pending = this.pendingReads.get(key2);
|
|
4263
|
+
if (pending) return pending;
|
|
4264
|
+
const generation = this.readGeneration;
|
|
4265
|
+
const request = Promise.resolve().then(read2).then((value) => {
|
|
4266
|
+
if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
|
|
4267
|
+
this.readCache.set(key2, {
|
|
4268
|
+
expiresAt: Date.now() + ttlMs,
|
|
4269
|
+
fetchedAt: Date.now(),
|
|
4270
|
+
value
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
return value;
|
|
4274
|
+
}).finally(() => {
|
|
4275
|
+
if (this.pendingReads.get(key2) === request) {
|
|
4276
|
+
this.pendingReads.delete(key2);
|
|
4277
|
+
}
|
|
4278
|
+
});
|
|
4279
|
+
this.pendingReads.set(key2, request);
|
|
4280
|
+
return request;
|
|
4281
|
+
}
|
|
4282
|
+
agentListKey(state, chainId) {
|
|
4283
|
+
return `agents:${this.userKey(state, chainId)}`;
|
|
4284
|
+
}
|
|
4285
|
+
async listAgents(state, chainId, user) {
|
|
4286
|
+
const response = await this.cachedRead(
|
|
4287
|
+
this.agentListKey(state, chainId),
|
|
4288
|
+
YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
4289
|
+
async () => {
|
|
4290
|
+
const response2 = await this.walletRequest(
|
|
4291
|
+
state,
|
|
4292
|
+
chainId,
|
|
4293
|
+
`/users/${user.userId}/agents`
|
|
4294
|
+
);
|
|
4295
|
+
if (!Array.isArray(response2?.agents)) {
|
|
4296
|
+
throw this.invalidResponse("agent list");
|
|
4297
|
+
}
|
|
4298
|
+
return response2;
|
|
4299
|
+
}
|
|
4300
|
+
);
|
|
4301
|
+
return response.agents;
|
|
4302
|
+
}
|
|
4303
|
+
async contextForAgent(state, chainId, user, agent, asset) {
|
|
4304
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
4305
|
+
const cached = this.agentContexts.get(key2);
|
|
4306
|
+
if (cached?.agent.agentId === agent.agentId) return cached;
|
|
4307
|
+
const pending = this.pendingWalletContexts.get(key2);
|
|
4308
|
+
if (pending) return pending;
|
|
4309
|
+
const generation = this.readGeneration;
|
|
4310
|
+
const request = this.walletRequest(
|
|
4311
|
+
state,
|
|
4312
|
+
chainId,
|
|
4313
|
+
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
4314
|
+
).then((walletResponse) => {
|
|
4315
|
+
this.assertSession(generation, chainId, asset);
|
|
4316
|
+
if (!walletResponse?.agentWallet || !(0, import_viem8.isAddress)(walletResponse.agentWallet.walletAddress)) {
|
|
4317
|
+
throw this.invalidResponse("agent wallet");
|
|
4318
|
+
}
|
|
4319
|
+
const context = {
|
|
4320
|
+
user,
|
|
4321
|
+
agent,
|
|
4322
|
+
wallet: walletResponse.agentWallet,
|
|
4323
|
+
asset
|
|
4324
|
+
};
|
|
4325
|
+
this.agentContexts.set(key2, context);
|
|
4326
|
+
return context;
|
|
4327
|
+
});
|
|
4328
|
+
this.pendingWalletContexts.set(key2, request);
|
|
4329
|
+
try {
|
|
4330
|
+
return await request;
|
|
4331
|
+
} finally {
|
|
4332
|
+
if (this.pendingWalletContexts.get(key2) === request) {
|
|
4333
|
+
this.pendingWalletContexts.delete(key2);
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
3557
4337
|
async resolveUser(state, chainId) {
|
|
3558
4338
|
const key2 = this.userKey(state, chainId);
|
|
3559
4339
|
const inMemory = this.users.get(key2);
|
|
@@ -3563,7 +4343,8 @@ var YieldseekerAgent = class {
|
|
|
3563
4343
|
this.users.set(key2, persisted);
|
|
3564
4344
|
return persisted;
|
|
3565
4345
|
}
|
|
3566
|
-
const walletAddress = (0,
|
|
4346
|
+
const walletAddress = (0, import_viem8.getAddress)(state.walletAddress);
|
|
4347
|
+
const generation = this.readGeneration;
|
|
3567
4348
|
let user = null;
|
|
3568
4349
|
try {
|
|
3569
4350
|
const login = await this.providerRequest(
|
|
@@ -3610,6 +4391,7 @@ var YieldseekerAgent = class {
|
|
|
3610
4391
|
if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
|
|
3611
4392
|
throw this.invalidResponse("wallet identity");
|
|
3612
4393
|
}
|
|
4394
|
+
this.assertSession(generation, chainId);
|
|
3613
4395
|
const resolved = { userId: user.userId };
|
|
3614
4396
|
this.users.set(key2, resolved);
|
|
3615
4397
|
writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
|
|
@@ -3625,10 +4407,13 @@ var YieldseekerAgent = class {
|
|
|
3625
4407
|
if (cached) return cached;
|
|
3626
4408
|
const pending = this.pendingAgents.get(key2);
|
|
3627
4409
|
if (pending) return pending;
|
|
4410
|
+
const generation = this.readGeneration;
|
|
3628
4411
|
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3629
4412
|
async (context) => {
|
|
4413
|
+
this.assertSession(generation, chainId, asset);
|
|
3630
4414
|
if (!context) throw this.invalidResponse("agent creation");
|
|
3631
4415
|
await this.deployAgent(state, chainId, context);
|
|
4416
|
+
this.assertSession(generation, chainId, asset);
|
|
3632
4417
|
this.agentContexts.set(key2, context);
|
|
3633
4418
|
return context;
|
|
3634
4419
|
}
|
|
@@ -3637,29 +4422,27 @@ var YieldseekerAgent = class {
|
|
|
3637
4422
|
try {
|
|
3638
4423
|
return await request;
|
|
3639
4424
|
} finally {
|
|
3640
|
-
this.pendingAgents.delete(key2);
|
|
4425
|
+
if (this.pendingAgents.get(key2) === request) this.pendingAgents.delete(key2);
|
|
3641
4426
|
}
|
|
3642
4427
|
}
|
|
3643
4428
|
async findAgent(state, chainId, asset) {
|
|
3644
4429
|
const key2 = this.contextKey(state, chainId, asset);
|
|
3645
4430
|
const cached = this.agentContexts.get(key2);
|
|
3646
4431
|
if (cached) return cached;
|
|
4432
|
+
const generation = this.readGeneration;
|
|
3647
4433
|
const context = await this.resolveAgent(state, chainId, asset, false);
|
|
4434
|
+
this.assertSession(generation, chainId, asset);
|
|
3648
4435
|
if (context) this.agentContexts.set(key2, context);
|
|
3649
4436
|
return context;
|
|
3650
4437
|
}
|
|
3651
4438
|
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
4439
|
+
const generation = this.readGeneration;
|
|
3652
4440
|
const user = await this.resolveUser(state, chainId);
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
`/users/${user.userId}/agents`
|
|
3657
|
-
);
|
|
3658
|
-
if (!Array.isArray(response?.agents)) {
|
|
3659
|
-
throw this.invalidResponse("agent list");
|
|
3660
|
-
}
|
|
4441
|
+
this.assertSession(generation, chainId, asset);
|
|
4442
|
+
const agents = await this.listAgents(state, chainId, user);
|
|
4443
|
+
this.assertSession(generation, chainId, asset);
|
|
3661
4444
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3662
|
-
let agent =
|
|
4445
|
+
let agent = agents.find(
|
|
3663
4446
|
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
3664
4447
|
);
|
|
3665
4448
|
if (!agent && createIfMissing) {
|
|
@@ -3675,99 +4458,260 @@ var YieldseekerAgent = class {
|
|
|
3675
4458
|
chainId,
|
|
3676
4459
|
assetAddress: metadata.address,
|
|
3677
4460
|
type: "vault",
|
|
3678
|
-
rulePreset:
|
|
4461
|
+
rulePreset: OWNEY_AGENT_RULE_PRESET
|
|
3679
4462
|
}
|
|
3680
4463
|
}
|
|
3681
4464
|
);
|
|
4465
|
+
this.assertSession(generation, chainId, asset);
|
|
3682
4466
|
agent = created?.agent;
|
|
4467
|
+
if (agent) {
|
|
4468
|
+
this.readCache.set(this.agentListKey(state, chainId), {
|
|
4469
|
+
expiresAt: Date.now() + YIELDSEEKER_AGENT_LIST_CACHE_MS,
|
|
4470
|
+
fetchedAt: Date.now(),
|
|
4471
|
+
value: {
|
|
4472
|
+
agents: [...agents, agent]
|
|
4473
|
+
}
|
|
4474
|
+
});
|
|
4475
|
+
}
|
|
3683
4476
|
}
|
|
3684
4477
|
if (!agent) return null;
|
|
3685
4478
|
this.assertAgent(agent);
|
|
3686
|
-
|
|
3687
|
-
state,
|
|
3688
|
-
chainId,
|
|
3689
|
-
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3690
|
-
);
|
|
3691
|
-
if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
|
|
3692
|
-
throw this.invalidResponse("agent wallet");
|
|
3693
|
-
}
|
|
3694
|
-
return { user, agent, wallet: walletResponse.agentWallet, asset };
|
|
4479
|
+
return this.contextForAgent(state, chainId, user, agent, asset);
|
|
3695
4480
|
}
|
|
3696
4481
|
async loadPortfolio(state, chainId, options) {
|
|
3697
|
-
const
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
4482
|
+
const contexts = await this.resolvePortfolioContexts(state, chainId, options.asset);
|
|
4483
|
+
return Promise.all(
|
|
4484
|
+
contexts.map(
|
|
4485
|
+
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
4486
|
+
)
|
|
3702
4487
|
);
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
4488
|
+
}
|
|
4489
|
+
async loadActivityPortfolio(state, chainId, options) {
|
|
4490
|
+
const contexts = await this.resolvePortfolioContexts(state, chainId, options.asset);
|
|
4491
|
+
return Promise.all(
|
|
4492
|
+
contexts.map(
|
|
4493
|
+
(context) => this.loadActivityContext(state, chainId, context, options)
|
|
4494
|
+
)
|
|
4495
|
+
);
|
|
4496
|
+
}
|
|
4497
|
+
async resolvePortfolioContexts(state, chainId, assetFilter) {
|
|
4498
|
+
const generation = this.readGeneration;
|
|
4499
|
+
const user = await this.resolveUser(state, chainId);
|
|
4500
|
+
this.assertSession(generation, chainId);
|
|
4501
|
+
const agents = await this.listAgents(state, chainId, user);
|
|
4502
|
+
this.assertSession(generation, chainId);
|
|
3706
4503
|
const contexts = [];
|
|
3707
|
-
for (const agent of
|
|
4504
|
+
for (const agent of agents) {
|
|
3708
4505
|
const asset = this.assetForAgent(agent);
|
|
3709
|
-
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" ||
|
|
4506
|
+
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || assetFilter && assetFilter !== asset) {
|
|
3710
4507
|
continue;
|
|
3711
4508
|
}
|
|
3712
4509
|
this.assertAgent(agent);
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
4510
|
+
contexts.push(this.contextForAgent(state, chainId, user, agent, asset));
|
|
4511
|
+
}
|
|
4512
|
+
const resolvedContexts = await Promise.all(contexts);
|
|
4513
|
+
this.assertSession(generation, chainId);
|
|
4514
|
+
return resolvedContexts;
|
|
4515
|
+
}
|
|
4516
|
+
portfolioVersion(contextKey) {
|
|
4517
|
+
return this.portfolioVersions.get(contextKey) ?? 0;
|
|
4518
|
+
}
|
|
4519
|
+
portfolioCacheMs(contextKey) {
|
|
4520
|
+
return (this.reconcileUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_CACHE_MS : YIELDSEEKER_PORTFOLIO_CACHE_MS;
|
|
4521
|
+
}
|
|
4522
|
+
snapshotMovement(state, chainId, context, delta) {
|
|
4523
|
+
const key2 = this.contextKey(state, chainId, context.asset);
|
|
4524
|
+
const previous = this.snapshotMovements.get(key2);
|
|
4525
|
+
if (this.snapshotMovements.has(key2)) {
|
|
4526
|
+
if (!previous || previous.delta > 0n !== delta > 0n) return null;
|
|
4527
|
+
return {
|
|
4528
|
+
...previous,
|
|
4529
|
+
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4530
|
+
delta: previous.delta + delta,
|
|
4531
|
+
expectedNetDeposits: previous.expectedNetDeposits + delta
|
|
3726
4532
|
};
|
|
3727
|
-
this.agentContexts.set(this.contextKey(state, chainId, asset), context);
|
|
3728
|
-
contexts.push(context);
|
|
3729
4533
|
}
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
);
|
|
4534
|
+
const cached = this.readCache.get(`snapshot:${key2}`);
|
|
4535
|
+
if (!cached || cached.expiresAt <= Date.now()) return null;
|
|
4536
|
+
const snapshot = cached.value.agentSnapshot;
|
|
4537
|
+
if (snapshot.baseAssetDecimals !== YIELDSEEKER_ASSET_METADATA[context.asset].decimals) return null;
|
|
4538
|
+
if (!/^-?\d+$/.test(snapshot.netDepositsBase) || !/^\d+$/.test(snapshot.totalValueBase)) return null;
|
|
4539
|
+
return {
|
|
4540
|
+
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4541
|
+
delta,
|
|
4542
|
+
decimals: snapshot.baseAssetDecimals,
|
|
4543
|
+
expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
|
|
4544
|
+
};
|
|
4545
|
+
}
|
|
4546
|
+
advancePortfolioVersion(contextKey) {
|
|
4547
|
+
const version = this.portfolioVersion(contextKey) + 1;
|
|
4548
|
+
this.portfolioVersions.set(contextKey, version);
|
|
4549
|
+
this.snapshotFailures.delete(contextKey);
|
|
4550
|
+
for (const kind of ["snapshot", "positions", "historic", "actions"]) {
|
|
4551
|
+
const key2 = `${kind}:${contextKey}`;
|
|
4552
|
+
this.readCache.delete(key2);
|
|
4553
|
+
this.pendingReads.delete(key2);
|
|
4554
|
+
}
|
|
4555
|
+
return version;
|
|
4556
|
+
}
|
|
4557
|
+
requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, forceRefresh = false) {
|
|
4558
|
+
return this.walletRequest(
|
|
4559
|
+
state,
|
|
4560
|
+
chainId,
|
|
4561
|
+
`${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`
|
|
4562
|
+
).then((response) => {
|
|
4563
|
+
if (!response?.agentSnapshot) {
|
|
4564
|
+
throw this.invalidResponse("agent snapshot");
|
|
4565
|
+
}
|
|
4566
|
+
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4567
|
+
this.snapshotFailures.delete(contextKey);
|
|
4568
|
+
const movement = this.snapshotMovements.get(contextKey);
|
|
4569
|
+
const snapshot = response.agentSnapshot;
|
|
4570
|
+
if (movement && snapshot.baseAssetDecimals === movement.decimals && /^-?\d+$/.test(snapshot.netDepositsBase) && /^\d+$/.test(snapshot.totalValueBase)) {
|
|
4571
|
+
const netDeposits = BigInt(snapshot.netDepositsBase);
|
|
4572
|
+
const reconciled = movement.delta > 0n ? netDeposits >= movement.expectedNetDeposits : netDeposits <= movement.expectedNetDeposits;
|
|
4573
|
+
if (reconciled) {
|
|
4574
|
+
this.snapshotMovements.delete(contextKey);
|
|
4575
|
+
this.reconcileUntil.delete(contextKey);
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
return response;
|
|
4580
|
+
}).catch((error) => {
|
|
4581
|
+
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4582
|
+
this.snapshotFailures.set(contextKey, {
|
|
4583
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, rateLimitDelay(error) ?? 0),
|
|
4584
|
+
error
|
|
4585
|
+
});
|
|
4586
|
+
}
|
|
4587
|
+
throw error;
|
|
4588
|
+
});
|
|
4589
|
+
}
|
|
4590
|
+
portfolioSnapshot(state, chainId, context, contextKey) {
|
|
4591
|
+
const key2 = `snapshot:${contextKey}`;
|
|
4592
|
+
const cached = this.readCache.get(key2);
|
|
4593
|
+
if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
|
|
4594
|
+
const failure = this.snapshotFailures.get(contextKey);
|
|
4595
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4596
|
+
this.advancePortfolioVersion(contextKey);
|
|
4597
|
+
}
|
|
4598
|
+
const version = this.portfolioVersion(contextKey);
|
|
4599
|
+
const generation = this.readGeneration;
|
|
4600
|
+
return {
|
|
4601
|
+
version,
|
|
4602
|
+
read: this.cachedRead(key2, this.portfolioCacheMs(contextKey), () => (
|
|
4603
|
+
// Force only during the bounded post-transaction window. Keep unknown
|
|
4604
|
+
// or unreconciled movements for verification, but do not let them turn
|
|
4605
|
+
// routine polling into forced refreshes indefinitely.
|
|
4606
|
+
this.requestPortfolioSnapshot(
|
|
4607
|
+
state,
|
|
4608
|
+
chainId,
|
|
4609
|
+
context,
|
|
4610
|
+
contextKey,
|
|
4611
|
+
version,
|
|
4612
|
+
generation,
|
|
4613
|
+
this.snapshotMovements.has(contextKey) && (this.reconcileUntil.get(contextKey) ?? 0) > Date.now()
|
|
4614
|
+
)
|
|
4615
|
+
))
|
|
4616
|
+
};
|
|
3735
4617
|
}
|
|
3736
4618
|
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
3737
|
-
const
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
4619
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4620
|
+
const generation = this.readGeneration;
|
|
4621
|
+
const assertSession = () => this.assertSession(generation, chainId, context.asset);
|
|
4622
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4623
|
+
const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
|
|
4624
|
+
const parts = this.loadPortfolioParts(state, chainId, context, options, contextKey);
|
|
4625
|
+
try {
|
|
4626
|
+
let positionsUnavailable = false;
|
|
4627
|
+
const safeParts = options.allowMissingPositions ? parts.catch(() => {
|
|
4628
|
+
positionsUnavailable = true;
|
|
4629
|
+
return { positions: [] };
|
|
4630
|
+
}) : parts;
|
|
4631
|
+
const [snapshot, portfolioParts] = await Promise.all([read2, safeParts]);
|
|
4632
|
+
assertSession();
|
|
4633
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4634
|
+
return {
|
|
4635
|
+
...context,
|
|
4636
|
+
snapshot: snapshot.agentSnapshot,
|
|
4637
|
+
fetchedAt: this.readCache.get(`snapshot:${contextKey}`)?.fetchedAt,
|
|
4638
|
+
readVersion: version,
|
|
4639
|
+
positionsUnavailable,
|
|
4640
|
+
...portfolioParts
|
|
4641
|
+
};
|
|
4642
|
+
} catch (error) {
|
|
4643
|
+
assertSession();
|
|
4644
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4645
|
+
throw error;
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
throw new OwneyError(
|
|
4649
|
+
"AGENT_API_ERROR",
|
|
4650
|
+
"Yieldseeker portfolio changed during the read. Please retry.",
|
|
4651
|
+
{ rpcSource: "agent-api", chainId, asset: context.asset },
|
|
4652
|
+
this.id
|
|
4653
|
+
);
|
|
4654
|
+
}
|
|
4655
|
+
async loadActivityContext(state, chainId, context, options) {
|
|
4656
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4657
|
+
const generation = this.readGeneration;
|
|
4658
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4659
|
+
const version = this.portfolioVersion(contextKey);
|
|
4660
|
+
try {
|
|
4661
|
+
const parts = await this.loadPortfolioParts(state, chainId, context, options, contextKey);
|
|
4662
|
+
this.assertSession(generation, chainId, context.asset);
|
|
4663
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4664
|
+
return { ...context, ...parts };
|
|
4665
|
+
} catch (error) {
|
|
4666
|
+
this.assertSession(generation, chainId, context.asset);
|
|
4667
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4668
|
+
throw error;
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
throw new OwneyError(
|
|
4672
|
+
"AGENT_API_ERROR",
|
|
4673
|
+
"Yieldseeker activity changed during the read. Please retry.",
|
|
4674
|
+
{ rpcSource: "agent-api", chainId, asset: context.asset },
|
|
4675
|
+
this.id
|
|
4676
|
+
);
|
|
4677
|
+
}
|
|
4678
|
+
async loadPortfolioParts(state, chainId, context, options, contextKey) {
|
|
4679
|
+
const [positions, historic, actions] = await Promise.all([
|
|
4680
|
+
this.cachedRead(
|
|
4681
|
+
`positions:${contextKey}`,
|
|
4682
|
+
this.portfolioCacheMs(contextKey),
|
|
4683
|
+
async () => {
|
|
4684
|
+
const response = await this.walletRequest(
|
|
4685
|
+
state,
|
|
4686
|
+
chainId,
|
|
4687
|
+
this.agentPath(context, "yield-positions")
|
|
4688
|
+
);
|
|
4689
|
+
if (!Array.isArray(response?.yieldPositions)) {
|
|
4690
|
+
throw this.invalidResponse("yield positions");
|
|
4691
|
+
}
|
|
4692
|
+
return response;
|
|
4693
|
+
}
|
|
3750
4694
|
),
|
|
3751
|
-
options.historic ? this.
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
this.
|
|
4695
|
+
options.historic ? this.cachedRead(
|
|
4696
|
+
`historic:${contextKey}`,
|
|
4697
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4698
|
+
() => this.walletRequest(
|
|
4699
|
+
state,
|
|
4700
|
+
chainId,
|
|
4701
|
+
this.agentPath(context, "wallet/historic-position")
|
|
4702
|
+
)
|
|
3755
4703
|
) : Promise.resolve(void 0),
|
|
3756
|
-
options.actions ? this.
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
this.
|
|
4704
|
+
options.actions ? this.cachedRead(
|
|
4705
|
+
`actions:${contextKey}`,
|
|
4706
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4707
|
+
() => this.walletRequest(
|
|
4708
|
+
state,
|
|
4709
|
+
chainId,
|
|
4710
|
+
this.agentPath(context, "actions")
|
|
4711
|
+
)
|
|
3760
4712
|
) : Promise.resolve(void 0)
|
|
3761
4713
|
]);
|
|
3762
|
-
if (!snapshot?.agentSnapshot) {
|
|
3763
|
-
throw this.invalidResponse("agent snapshot");
|
|
3764
|
-
}
|
|
3765
|
-
if (!Array.isArray(positions?.yieldPositions)) {
|
|
3766
|
-
throw this.invalidResponse("yield positions");
|
|
3767
|
-
}
|
|
3768
4714
|
return {
|
|
3769
|
-
...context,
|
|
3770
|
-
snapshot: snapshot.agentSnapshot,
|
|
3771
4715
|
positions: positions.yieldPositions,
|
|
3772
4716
|
...historic?.position ? { historic: historic.position } : {},
|
|
3773
4717
|
...actions?.actions ? { actions: actions.actions } : {}
|
|
@@ -3782,25 +4726,28 @@ var YieldseekerAgent = class {
|
|
|
3782
4726
|
this.agentPath(context, "deploy"),
|
|
3783
4727
|
{ method: "POST", body: {} }
|
|
3784
4728
|
);
|
|
3785
|
-
if (!deployed?.agentWallet || !(0,
|
|
4729
|
+
if (!deployed?.agentWallet || !(0, import_viem8.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
|
|
3786
4730
|
throw this.invalidResponse("agent deployment", {
|
|
3787
4731
|
reason: "Deploy did not return the expected Agent Wallet."
|
|
3788
4732
|
});
|
|
3789
4733
|
}
|
|
3790
4734
|
context.wallet = deployed.agentWallet;
|
|
3791
4735
|
}
|
|
3792
|
-
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
4736
|
+
async refreshSnapshotAfterMovement(state, chainId, context, movement, settlement) {
|
|
4737
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4738
|
+
this.reconcileUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4739
|
+
this.activityRefreshUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4740
|
+
const epoch = this.movementEpochs.get(contextKey) ?? 0;
|
|
4741
|
+
this.snapshotMovements.set(contextKey, settlement?.epoch === epoch ? settlement : null);
|
|
4742
|
+
this.movementEpochs.set(contextKey, epoch + 1);
|
|
4743
|
+
const version = this.advancePortfolioVersion(contextKey);
|
|
4744
|
+
const generation = this.readGeneration;
|
|
3793
4745
|
try {
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
shouldForceRefresh: true
|
|
3799
|
-
})}`
|
|
4746
|
+
await this.cachedRead(
|
|
4747
|
+
`snapshot:${contextKey}`,
|
|
4748
|
+
YIELDSEEKER_SETTLEMENT_CACHE_MS,
|
|
4749
|
+
() => this.requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, true)
|
|
3800
4750
|
);
|
|
3801
|
-
if (!response?.agentSnapshot) {
|
|
3802
|
-
throw this.invalidResponse("agent snapshot refresh");
|
|
3803
|
-
}
|
|
3804
4751
|
} catch (error) {
|
|
3805
4752
|
console.warn(
|
|
3806
4753
|
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
@@ -3812,10 +4759,26 @@ var YieldseekerAgent = class {
|
|
|
3812
4759
|
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
3813
4760
|
}
|
|
3814
4761
|
async walletRequest(state, chainId, path, options = {}) {
|
|
4762
|
+
const read2 = (options.method ?? "GET") === "GET";
|
|
4763
|
+
const userKey = this.userKey(state, chainId);
|
|
4764
|
+
if (read2) {
|
|
4765
|
+
const failure = this.standardReadFailures.get(userKey);
|
|
4766
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4767
|
+
if (failure) this.standardReadFailures.delete(userKey);
|
|
4768
|
+
}
|
|
4769
|
+
const generation = this.readGeneration;
|
|
3815
4770
|
try {
|
|
3816
4771
|
return await this.providerRequest(state, chainId, path, options);
|
|
3817
4772
|
} catch (error) {
|
|
3818
|
-
|
|
4773
|
+
const mapped = this.mapApiError(error);
|
|
4774
|
+
const delay = read2 ? rateLimitDelay(mapped) : void 0;
|
|
4775
|
+
if (delay !== void 0 && this.readGeneration === generation) {
|
|
4776
|
+
this.standardReadFailures.set(userKey, {
|
|
4777
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, delay),
|
|
4778
|
+
error: mapped
|
|
4779
|
+
});
|
|
4780
|
+
}
|
|
4781
|
+
throw mapped;
|
|
3819
4782
|
}
|
|
3820
4783
|
}
|
|
3821
4784
|
async providerRequest(state, chainId, path, options = {}) {
|
|
@@ -3856,6 +4819,7 @@ var YieldseekerAgent = class {
|
|
|
3856
4819
|
code,
|
|
3857
4820
|
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
3858
4821
|
{
|
|
4822
|
+
rpcSource: "agent-api",
|
|
3859
4823
|
statusCode: error.status,
|
|
3860
4824
|
providerCode: error.providerCode,
|
|
3861
4825
|
...error.responseFields ? { fields: error.responseFields } : {}
|
|
@@ -3863,37 +4827,45 @@ var YieldseekerAgent = class {
|
|
|
3863
4827
|
this.id
|
|
3864
4828
|
);
|
|
3865
4829
|
}
|
|
3866
|
-
async submitTransaction(state, chainId, transaction) {
|
|
4830
|
+
async submitTransaction(state, chainId, transaction, onSubmitted, onReceipt) {
|
|
3867
4831
|
if (this.transactionExecutor) {
|
|
3868
|
-
|
|
4832
|
+
const hash2 = await this.transactionExecutor(state, chainId, transaction);
|
|
4833
|
+
onSubmitted?.(hash2);
|
|
4834
|
+
return hash2;
|
|
3869
4835
|
}
|
|
3870
4836
|
this.assertTransaction(transaction, state, chainId);
|
|
3871
|
-
const account = (0,
|
|
3872
|
-
const walletClient = (0,
|
|
4837
|
+
const account = (0, import_viem8.getAddress)(state.walletAddress);
|
|
4838
|
+
const walletClient = (0, import_viem8.createWalletClient)({
|
|
3873
4839
|
account,
|
|
3874
4840
|
chain: import_chains3.base,
|
|
3875
|
-
transport: (0,
|
|
4841
|
+
transport: (0, import_viem8.custom)(state.provider)
|
|
3876
4842
|
});
|
|
3877
|
-
const
|
|
4843
|
+
const walletChainClient = (0, import_viem8.createPublicClient)({
|
|
3878
4844
|
chain: import_chains3.base,
|
|
3879
|
-
transport: (0,
|
|
4845
|
+
transport: (0, import_viem8.custom)(state.provider)
|
|
3880
4846
|
});
|
|
3881
4847
|
await ensureWalletOnChain(
|
|
3882
|
-
|
|
4848
|
+
walletChainClient,
|
|
3883
4849
|
walletClient,
|
|
3884
4850
|
8453
|
|
3885
4851
|
);
|
|
3886
4852
|
const hash = await walletClient.sendTransaction({
|
|
3887
4853
|
account,
|
|
3888
4854
|
chain: import_chains3.base,
|
|
3889
|
-
to: (0,
|
|
4855
|
+
to: (0, import_viem8.getAddress)(transaction.to),
|
|
3890
4856
|
data: transaction.data,
|
|
3891
4857
|
value: BigInt(transaction.value)
|
|
3892
4858
|
});
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
4859
|
+
onSubmitted?.(hash);
|
|
4860
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
4861
|
+
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4862
|
+
hash,
|
|
4863
|
+
confirmations: 1
|
|
4864
|
+
}),
|
|
4865
|
+
8453,
|
|
4866
|
+
"eth_getTransactionReceipt",
|
|
4867
|
+
this.id
|
|
4868
|
+
);
|
|
3897
4869
|
if (receipt.status !== "success") {
|
|
3898
4870
|
throw new OwneyError(
|
|
3899
4871
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -3902,6 +4874,7 @@ var YieldseekerAgent = class {
|
|
|
3902
4874
|
this.id
|
|
3903
4875
|
);
|
|
3904
4876
|
}
|
|
4877
|
+
onReceipt?.(receipt);
|
|
3905
4878
|
return hash;
|
|
3906
4879
|
}
|
|
3907
4880
|
async waitForReceipt(state, chainId, transactionHash) {
|
|
@@ -3909,14 +4882,15 @@ var YieldseekerAgent = class {
|
|
|
3909
4882
|
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
3910
4883
|
return;
|
|
3911
4884
|
}
|
|
3912
|
-
const
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
4885
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
4886
|
+
() => this.getReceiptClient().waitForTransactionReceipt({
|
|
4887
|
+
hash: transactionHash,
|
|
4888
|
+
confirmations: 1
|
|
4889
|
+
}),
|
|
4890
|
+
8453,
|
|
4891
|
+
"eth_getTransactionReceipt",
|
|
4892
|
+
this.id
|
|
4893
|
+
);
|
|
3920
4894
|
if (receipt.status !== "success") {
|
|
3921
4895
|
throw new OwneyError(
|
|
3922
4896
|
"AGENT_TRANSACTION_REVERTED",
|
|
@@ -3925,14 +4899,15 @@ var YieldseekerAgent = class {
|
|
|
3925
4899
|
this.id
|
|
3926
4900
|
);
|
|
3927
4901
|
}
|
|
4902
|
+
return receipt;
|
|
3928
4903
|
}
|
|
3929
4904
|
assertTransaction(transaction, state, chainId) {
|
|
3930
|
-
if (!transaction || typeof transaction.from !== "string" || !(0,
|
|
4905
|
+
if (!transaction || typeof transaction.from !== "string" || !(0, import_viem8.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem8.isAddress)(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || (0, import_viem8.getAddress)(transaction.from) !== (0, import_viem8.getAddress)(state.walletAddress)) {
|
|
3931
4906
|
throw this.invalidResponse("transaction");
|
|
3932
4907
|
}
|
|
3933
4908
|
}
|
|
3934
4909
|
assertAgent(agent) {
|
|
3935
|
-
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0,
|
|
4910
|
+
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem8.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
|
|
3936
4911
|
throw this.invalidResponse("agent");
|
|
3937
4912
|
}
|
|
3938
4913
|
}
|
|
@@ -4085,7 +5060,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
|
4085
5060
|
}
|
|
4086
5061
|
|
|
4087
5062
|
// src/lib/helpers/withdraw-helper.ts
|
|
4088
|
-
var
|
|
5063
|
+
var import_viem9 = require("viem");
|
|
4089
5064
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
4090
5065
|
const target = asset.toUpperCase();
|
|
4091
5066
|
return agents.map((agent) => {
|
|
@@ -4093,7 +5068,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
4093
5068
|
const tokenBalance = agentBalance?.tokens.find(
|
|
4094
5069
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4095
5070
|
);
|
|
4096
|
-
let balance = tokenBalance ? (0,
|
|
5071
|
+
let balance = tokenBalance ? (0, import_viem9.parseUnits)(tokenBalance.amount, decimals) : 0n;
|
|
4097
5072
|
if (agent.balanceComposition === "tokens-plus-positions") {
|
|
4098
5073
|
const chainNameById = {
|
|
4099
5074
|
1: "ETHEREUM",
|
|
@@ -4105,16 +5080,16 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
4105
5080
|
const positionChain = position2.chain.trim().toUpperCase();
|
|
4106
5081
|
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4107
5082
|
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
4108
|
-
|
|
4109
|
-
try {
|
|
4110
|
-
balance += BigInt(position2.amountRaw);
|
|
4111
|
-
continue;
|
|
4112
|
-
} catch {
|
|
4113
|
-
}
|
|
4114
|
-
}
|
|
4115
|
-
balance += (0, import_viem7.parseUnits)(position2.amount, decimals);
|
|
5083
|
+
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : (0, import_viem9.parseUnits)(position2.amount, decimals);
|
|
4116
5084
|
}
|
|
4117
5085
|
}
|
|
5086
|
+
const assetTotal = agentBalance?.assetBalances?.find(
|
|
5087
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
5088
|
+
);
|
|
5089
|
+
if (assetTotal) {
|
|
5090
|
+
const total = (0, import_viem9.parseUnits)(assetTotal.amount, decimals);
|
|
5091
|
+
if (total < balance) balance = total > 0n ? total : 0n;
|
|
5092
|
+
}
|
|
4118
5093
|
return { agent, balance };
|
|
4119
5094
|
});
|
|
4120
5095
|
}
|
|
@@ -4271,15 +5246,50 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
4271
5246
|
}
|
|
4272
5247
|
|
|
4273
5248
|
// src/client.ts
|
|
4274
|
-
var
|
|
5249
|
+
var import_viem13 = require("viem");
|
|
4275
5250
|
var import_chains4 = require("viem/chains");
|
|
4276
5251
|
|
|
4277
5252
|
// src/lib/sponsored-token-batch.ts
|
|
4278
|
-
var
|
|
5253
|
+
var import_viem11 = require("viem");
|
|
4279
5254
|
|
|
4280
5255
|
// src/lib/permit2-batch.ts
|
|
4281
|
-
var
|
|
5256
|
+
var import_viem10 = require("viem");
|
|
4282
5257
|
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
5258
|
+
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
5259
|
+
var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
5260
|
+
var ERC2612_PERMIT_TYPES = {
|
|
5261
|
+
Permit: [
|
|
5262
|
+
{ name: "owner", type: "address" },
|
|
5263
|
+
{ name: "spender", type: "address" },
|
|
5264
|
+
{ name: "value", type: "uint256" },
|
|
5265
|
+
{ name: "nonce", type: "uint256" },
|
|
5266
|
+
{ name: "deadline", type: "uint256" }
|
|
5267
|
+
]
|
|
5268
|
+
};
|
|
5269
|
+
var ERC2612_READ_ABI = (0, import_viem10.parseAbi)([
|
|
5270
|
+
"function name() view returns (string)",
|
|
5271
|
+
"function version() view returns (string)",
|
|
5272
|
+
"function nonces(address owner) view returns (uint256)"
|
|
5273
|
+
]);
|
|
5274
|
+
function erc2612TypedData(input) {
|
|
5275
|
+
return {
|
|
5276
|
+
domain: {
|
|
5277
|
+
name: input.permit.tokenName,
|
|
5278
|
+
version: input.permit.tokenVersion,
|
|
5279
|
+
chainId: input.chainId,
|
|
5280
|
+
verifyingContract: input.token
|
|
5281
|
+
},
|
|
5282
|
+
types: ERC2612_PERMIT_TYPES,
|
|
5283
|
+
primaryType: "Permit",
|
|
5284
|
+
message: {
|
|
5285
|
+
owner: input.owner,
|
|
5286
|
+
spender: BATCH_PERMIT2_ADDRESS,
|
|
5287
|
+
value: BigInt(input.permit.value),
|
|
5288
|
+
nonce: BigInt(input.permit.nonce),
|
|
5289
|
+
deadline: BigInt(input.permit.deadline)
|
|
5290
|
+
}
|
|
5291
|
+
};
|
|
5292
|
+
}
|
|
4283
5293
|
var PERMIT_BATCH_TYPES = {
|
|
4284
5294
|
PermitBatchWitnessTransferFrom: [
|
|
4285
5295
|
{ name: "permitted", type: "TokenPermissions[]" },
|
|
@@ -4294,7 +5304,7 @@ var PERMIT_BATCH_TYPES = {
|
|
|
4294
5304
|
{ name: "amount", type: "uint256" }
|
|
4295
5305
|
]
|
|
4296
5306
|
};
|
|
4297
|
-
var PERMIT2_BATCH_ABI = (0,
|
|
5307
|
+
var PERMIT2_BATCH_ABI = (0, import_viem10.parseAbi)([
|
|
4298
5308
|
"struct TokenPermissions { address token; uint256 amount; }",
|
|
4299
5309
|
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
4300
5310
|
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
@@ -4353,7 +5363,7 @@ function clear(key2) {
|
|
|
4353
5363
|
else window.localStorage.removeItem(key2);
|
|
4354
5364
|
}
|
|
4355
5365
|
function sponsorTokenBatch(i) {
|
|
4356
|
-
const key2 = `owney.token-batch.v1:${(0,
|
|
5366
|
+
const key2 = `owney.token-batch.v1:${(0, import_viem11.keccak256)((0, import_viem11.toBytes)(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
|
|
4357
5367
|
const plan = planOf(i.transfers);
|
|
4358
5368
|
const active = inflight.get(key2);
|
|
4359
5369
|
if (active) {
|
|
@@ -4384,7 +5394,7 @@ async function execute(i, key2, plan) {
|
|
|
4384
5394
|
baseUrl: i.baseUrl,
|
|
4385
5395
|
body
|
|
4386
5396
|
});
|
|
4387
|
-
if (!prepared.serializedTransaction || (0,
|
|
5397
|
+
if (!prepared.serializedTransaction || (0, import_viem11.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
|
|
4388
5398
|
throw new Error(
|
|
4389
5399
|
"Sponsorship API did not return a valid prepared transaction."
|
|
4390
5400
|
);
|
|
@@ -4394,12 +5404,16 @@ async function execute(i, key2, plan) {
|
|
|
4394
5404
|
};
|
|
4395
5405
|
save(key2, body);
|
|
4396
5406
|
}
|
|
5407
|
+
try {
|
|
5408
|
+
i.onSubmitted?.((0, import_viem11.keccak256)(body.serializedTransaction));
|
|
5409
|
+
} catch {
|
|
5410
|
+
}
|
|
4397
5411
|
const result = await postSponsorBatchTransfer({
|
|
4398
5412
|
apiKey: i.apiKey,
|
|
4399
5413
|
baseUrl: i.baseUrl,
|
|
4400
5414
|
body
|
|
4401
5415
|
});
|
|
4402
|
-
if (result.txHash !== (0,
|
|
5416
|
+
if (result.txHash !== (0, import_viem11.keccak256)(body.serializedTransaction))
|
|
4403
5417
|
throw new Error(
|
|
4404
5418
|
"Sponsorship receipt does not match the pending transaction."
|
|
4405
5419
|
);
|
|
@@ -4414,7 +5428,7 @@ async function execute(i, key2, plan) {
|
|
|
4414
5428
|
const saved = read(key2);
|
|
4415
5429
|
if (saved) {
|
|
4416
5430
|
const previous = JSON.parse(saved);
|
|
4417
|
-
if (previous.chainId !== i.chainId || !(0,
|
|
5431
|
+
if (previous.chainId !== i.chainId || !(0, import_viem11.isAddressEqual)(previous.from, i.owner) || !(0, import_viem11.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
|
|
4418
5432
|
throw new Error(
|
|
4419
5433
|
"Retry the previous token deposit and agent split first to reconcile its status."
|
|
4420
5434
|
);
|
|
@@ -4431,28 +5445,67 @@ async function execute(i, key2, plan) {
|
|
|
4431
5445
|
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
4432
5446
|
"Insufficient token balance for this deposit."
|
|
4433
5447
|
);
|
|
4434
|
-
if (allowance < total)
|
|
5448
|
+
if (allowance < total && (i.chainId !== 8453 || !(0, import_viem11.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
|
|
4435
5449
|
throw new OwneyError(
|
|
4436
5450
|
"PERMIT2_APPROVAL_REQUIRED",
|
|
4437
5451
|
"token deposits need a one-time Permit2 approval."
|
|
4438
5452
|
);
|
|
4439
|
-
const
|
|
5453
|
+
const now = (await i.pub.getBlock()).timestamp;
|
|
5454
|
+
let erc2612Permit;
|
|
5455
|
+
if (allowance < total) {
|
|
5456
|
+
const [tokenName, tokenVersion, permitNonce] = await Promise.all([
|
|
5457
|
+
i.pub.readContract({
|
|
5458
|
+
address: i.token,
|
|
5459
|
+
abi: ERC2612_READ_ABI,
|
|
5460
|
+
functionName: "name"
|
|
5461
|
+
}),
|
|
5462
|
+
i.pub.readContract({
|
|
5463
|
+
address: i.token,
|
|
5464
|
+
abi: ERC2612_READ_ABI,
|
|
5465
|
+
functionName: "version"
|
|
5466
|
+
}),
|
|
5467
|
+
i.pub.readContract({
|
|
5468
|
+
address: i.token,
|
|
5469
|
+
abi: ERC2612_READ_ABI,
|
|
5470
|
+
functionName: "nonces",
|
|
5471
|
+
args: [i.owner]
|
|
5472
|
+
})
|
|
5473
|
+
]);
|
|
5474
|
+
const unsignedPermit = {
|
|
5475
|
+
value: MAX_UINT256.toString(),
|
|
5476
|
+
nonce: permitNonce.toString(),
|
|
5477
|
+
deadline: (now + 900n).toString(),
|
|
5478
|
+
tokenName,
|
|
5479
|
+
tokenVersion
|
|
5480
|
+
};
|
|
5481
|
+
const signature2 = await i.wallet.signTypedData({
|
|
5482
|
+
account: i.owner,
|
|
5483
|
+
...erc2612TypedData({
|
|
5484
|
+
chainId: 8453,
|
|
5485
|
+
token: i.token,
|
|
5486
|
+
owner: i.owner,
|
|
5487
|
+
permit: unsignedPermit
|
|
5488
|
+
})
|
|
5489
|
+
});
|
|
5490
|
+
erc2612Permit = { ...unsignedPermit, signature: signature2 };
|
|
5491
|
+
}
|
|
5492
|
+
const spender = erc2612Permit ? MULTICALL3_ADDRESS : await getSponsorRelayerAddress({
|
|
4440
5493
|
apiKey: i.apiKey,
|
|
4441
5494
|
baseUrl: i.baseUrl,
|
|
4442
5495
|
chainId: i.chainId
|
|
4443
5496
|
});
|
|
4444
|
-
const now = (await i.pub.getBlock()).timestamp;
|
|
4445
5497
|
const unsigned = {
|
|
4446
5498
|
chainId: i.chainId,
|
|
4447
5499
|
token: i.token,
|
|
4448
5500
|
from: i.owner,
|
|
4449
5501
|
transfers: i.transfers,
|
|
4450
5502
|
nonce: randomPermit2Nonce().toString(),
|
|
4451
|
-
deadline: (now + 900n).toString()
|
|
5503
|
+
deadline: (now + 900n).toString(),
|
|
5504
|
+
...erc2612Permit ? { erc2612Permit } : {}
|
|
4452
5505
|
};
|
|
4453
5506
|
const signature = await i.wallet.signTypedData({
|
|
4454
5507
|
account: i.owner,
|
|
4455
|
-
...batchTypedData(unsigned,
|
|
5508
|
+
...batchTypedData(unsigned, spender)
|
|
4456
5509
|
});
|
|
4457
5510
|
i.onApproved?.();
|
|
4458
5511
|
return send({ ...unsigned, signature });
|
|
@@ -4472,8 +5525,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
4472
5525
|
"CHAIN_UNSUPPORTED",
|
|
4473
5526
|
`No sponsored token configured for chain ${chainId}`
|
|
4474
5527
|
);
|
|
4475
|
-
const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
|
|
4476
|
-
await ensureWalletOnChain(
|
|
5528
|
+
const pub = deps.getPublicClient(chainId), walletChain = deps.getWalletChainClient?.(chainId) ?? pub, wallet = deps.getWalletClient(chainId);
|
|
5529
|
+
await ensureWalletOnChain(walletChain, wallet, chainId);
|
|
4477
5530
|
return sponsorTokenBatch({
|
|
4478
5531
|
apiKey: deps.apiKey,
|
|
4479
5532
|
baseUrl: deps.baseUrl,
|
|
@@ -4483,7 +5536,8 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
4483
5536
|
transfers,
|
|
4484
5537
|
pub,
|
|
4485
5538
|
wallet,
|
|
4486
|
-
onApproved: deps.onApproved
|
|
5539
|
+
onApproved: deps.onApproved,
|
|
5540
|
+
onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
|
|
4487
5541
|
});
|
|
4488
5542
|
};
|
|
4489
5543
|
const callback = makeVerificationAwareDepositCallback(
|
|
@@ -4569,7 +5623,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
|
4569
5623
|
}
|
|
4570
5624
|
|
|
4571
5625
|
// src/lib/sponsored-calls-deposit.ts
|
|
4572
|
-
var
|
|
5626
|
+
var import_viem12 = require("viem");
|
|
4573
5627
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
4574
5628
|
var DEFAULT_MAX_POLLS = 30;
|
|
4575
5629
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -4577,7 +5631,7 @@ async function paymasterSupported(provider, owner, chainId) {
|
|
|
4577
5631
|
method: "wallet_getCapabilities",
|
|
4578
5632
|
params: [owner]
|
|
4579
5633
|
});
|
|
4580
|
-
const forChain = caps?.[(0,
|
|
5634
|
+
const forChain = caps?.[(0, import_viem12.toHex)(chainId)] ?? caps?.[String(chainId)];
|
|
4581
5635
|
return Boolean(forChain?.paymasterService?.supported);
|
|
4582
5636
|
}
|
|
4583
5637
|
function makeSponsoredCallsCallback(deps) {
|
|
@@ -4614,8 +5668,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4614
5668
|
const calls = transfers.map((transfer) => ({
|
|
4615
5669
|
to: token,
|
|
4616
5670
|
value: "0x0",
|
|
4617
|
-
data: (0,
|
|
4618
|
-
abi:
|
|
5671
|
+
data: (0, import_viem12.encodeFunctionData)({
|
|
5672
|
+
abi: import_viem12.erc20Abi,
|
|
4619
5673
|
functionName: "transfer",
|
|
4620
5674
|
args: [transfer.to, BigInt(transfer.amount)]
|
|
4621
5675
|
})
|
|
@@ -4653,7 +5707,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4653
5707
|
{
|
|
4654
5708
|
version: "2.0.0",
|
|
4655
5709
|
from: deps.ownerAddress,
|
|
4656
|
-
chainId: (0,
|
|
5710
|
+
chainId: (0, import_viem12.toHex)(chainId),
|
|
4657
5711
|
atomicRequired: transfers.length > 1,
|
|
4658
5712
|
calls,
|
|
4659
5713
|
capabilities: {
|
|
@@ -4677,6 +5731,12 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4677
5731
|
params: [callsId]
|
|
4678
5732
|
});
|
|
4679
5733
|
const txHash = status?.receipts?.[0]?.transactionHash;
|
|
5734
|
+
if (txHash && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
|
|
5735
|
+
try {
|
|
5736
|
+
deps.onSubmitted?.(txHash, transfers);
|
|
5737
|
+
} catch {
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
4680
5740
|
if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
|
|
4681
5741
|
throw new OwneyError(
|
|
4682
5742
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -4713,6 +5773,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4713
5773
|
}
|
|
4714
5774
|
|
|
4715
5775
|
// src/client.ts
|
|
5776
|
+
var PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS = 6;
|
|
5777
|
+
var PERMIT2_ALLOWANCE_VERIFY_DELAY_MS = 250;
|
|
4716
5778
|
function encodeMultiAgentCursor(map) {
|
|
4717
5779
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
4718
5780
|
}
|
|
@@ -4773,6 +5835,7 @@ var OwneySDK = class {
|
|
|
4773
5835
|
// leave every user's agent profile alone.
|
|
4774
5836
|
orgAgentConfig;
|
|
4775
5837
|
orgAgentConfigPromise = null;
|
|
5838
|
+
rpcUrls;
|
|
4776
5839
|
zyfaiRpcUrls;
|
|
4777
5840
|
yieldseekerApiBaseUrl;
|
|
4778
5841
|
yieldseekerSiweOrigin;
|
|
@@ -4783,6 +5846,7 @@ var OwneySDK = class {
|
|
|
4783
5846
|
paymasterServiceUrl;
|
|
4784
5847
|
cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
|
|
4785
5848
|
agentReads = new AgentReads();
|
|
5849
|
+
recoveredFundingReads = /* @__PURE__ */ new Set();
|
|
4786
5850
|
activationChecks = /* @__PURE__ */ new Map();
|
|
4787
5851
|
readAgent(agent, method, fetch2, params = null) {
|
|
4788
5852
|
const key2 = JSON.stringify([
|
|
@@ -4797,7 +5861,12 @@ var OwneySDK = class {
|
|
|
4797
5861
|
constructor(config) {
|
|
4798
5862
|
this.apiKey = config.apiKey;
|
|
4799
5863
|
if (config.debug) setOwneyDebug(true);
|
|
4800
|
-
this.
|
|
5864
|
+
this.rpcUrls = resolveRpcUrls(
|
|
5865
|
+
config.rpcUrls,
|
|
5866
|
+
config.apiKey,
|
|
5867
|
+
config.routingApiBaseUrl
|
|
5868
|
+
);
|
|
5869
|
+
this.zyfaiRpcUrls = config.rpcUrls ? void 0 : config.zyfaiRpcUrls;
|
|
4801
5870
|
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
4802
5871
|
this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
|
|
4803
5872
|
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
@@ -4887,9 +5956,12 @@ var OwneySDK = class {
|
|
|
4887
5956
|
}
|
|
4888
5957
|
return this.state.provider;
|
|
4889
5958
|
}
|
|
5959
|
+
getPaidRpcClient(chainId) {
|
|
5960
|
+
return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
|
|
5961
|
+
}
|
|
4890
5962
|
/** Builds the default USDC batch callback for the connected wallet. */
|
|
4891
|
-
getDefaultSponsoredCallback(onApproved) {
|
|
4892
|
-
if (!onApproved && this.cachedSponsoredCallback)
|
|
5963
|
+
getDefaultSponsoredCallback(onApproved, onSubmitted) {
|
|
5964
|
+
if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
|
|
4893
5965
|
return this.cachedSponsoredCallback;
|
|
4894
5966
|
const provider = this.requireConnectedProvider();
|
|
4895
5967
|
const owner = this.state.walletAddress;
|
|
@@ -4898,27 +5970,29 @@ var OwneySDK = class {
|
|
|
4898
5970
|
baseUrl: this.routingApiBaseUrl,
|
|
4899
5971
|
ownerAddress: owner,
|
|
4900
5972
|
onApproved,
|
|
5973
|
+
onSubmitted,
|
|
4901
5974
|
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
4902
5975
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
4903
5976
|
// PublicClient/WalletClient param types — structurally identical at
|
|
4904
5977
|
// runtime, but the two share a name TS treats as unrelated.
|
|
4905
|
-
getPublicClient: (cid) => (
|
|
5978
|
+
getPublicClient: (cid) => this.getPaidRpcClient(cid),
|
|
5979
|
+
getWalletChainClient: (cid) => (0, import_viem13.createPublicClient)({
|
|
4906
5980
|
chain: VIEM_CHAIN2[cid],
|
|
4907
|
-
transport: (0,
|
|
5981
|
+
transport: (0, import_viem13.custom)(provider)
|
|
4908
5982
|
}),
|
|
4909
|
-
getWalletClient: (cid) => (0,
|
|
5983
|
+
getWalletClient: (cid) => (0, import_viem13.createWalletClient)({
|
|
4910
5984
|
account: owner,
|
|
4911
5985
|
chain: VIEM_CHAIN2[cid],
|
|
4912
|
-
transport: (0,
|
|
5986
|
+
transport: (0, import_viem13.custom)(provider)
|
|
4913
5987
|
})
|
|
4914
5988
|
});
|
|
4915
|
-
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
5989
|
+
if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
|
|
4916
5990
|
return callback;
|
|
4917
5991
|
}
|
|
4918
5992
|
/** Builds the wallet-native sponsored calls callback for compatible paymasters. */
|
|
4919
|
-
getDefaultSponsoredCallsCallback(asset, onApproved) {
|
|
5993
|
+
getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
|
|
4920
5994
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
4921
|
-
if (!onApproved && cached) return cached;
|
|
5995
|
+
if (!onApproved && !onSubmitted && cached) return cached;
|
|
4922
5996
|
const provider = this.requireConnectedProvider();
|
|
4923
5997
|
const callback = makeSponsoredCallsCallback({
|
|
4924
5998
|
apiKey: this.apiKey,
|
|
@@ -4927,9 +6001,10 @@ var OwneySDK = class {
|
|
|
4927
6001
|
ownerAddress: this.state.walletAddress,
|
|
4928
6002
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
4929
6003
|
onApproved,
|
|
6004
|
+
onSubmitted,
|
|
4930
6005
|
tokenAddressByChain: sponsoredTokensFor(asset)
|
|
4931
6006
|
});
|
|
4932
|
-
if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
6007
|
+
if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
4933
6008
|
return callback;
|
|
4934
6009
|
}
|
|
4935
6010
|
/**
|
|
@@ -4938,8 +6013,8 @@ var OwneySDK = class {
|
|
|
4938
6013
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
4939
6014
|
* single-use batch authorization instead of an EIP-3009 authorization.
|
|
4940
6015
|
*/
|
|
4941
|
-
getDefaultWethSponsoredCallback(onApproved) {
|
|
4942
|
-
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
6016
|
+
getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
|
|
6017
|
+
if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
|
|
4943
6018
|
return this.cachedWethSponsoredCallback;
|
|
4944
6019
|
const provider = this.requireConnectedProvider();
|
|
4945
6020
|
const owner = this.state.walletAddress;
|
|
@@ -4948,21 +6023,23 @@ var OwneySDK = class {
|
|
|
4948
6023
|
baseUrl: this.routingApiBaseUrl,
|
|
4949
6024
|
ownerAddress: owner,
|
|
4950
6025
|
onApproved,
|
|
6026
|
+
onSubmitted,
|
|
4951
6027
|
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
4952
6028
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
4953
6029
|
// PublicClient/WalletClient param types — structurally identical at
|
|
4954
6030
|
// runtime, but the two share a name TS treats as unrelated.
|
|
4955
|
-
getPublicClient: (cid) => (
|
|
6031
|
+
getPublicClient: (cid) => this.getPaidRpcClient(cid),
|
|
6032
|
+
getWalletChainClient: (cid) => (0, import_viem13.createPublicClient)({
|
|
4956
6033
|
chain: VIEM_CHAIN2[cid],
|
|
4957
|
-
transport: (0,
|
|
6034
|
+
transport: (0, import_viem13.custom)(provider)
|
|
4958
6035
|
}),
|
|
4959
|
-
getWalletClient: (cid) => (0,
|
|
6036
|
+
getWalletClient: (cid) => (0, import_viem13.createWalletClient)({
|
|
4960
6037
|
account: owner,
|
|
4961
6038
|
chain: VIEM_CHAIN2[cid],
|
|
4962
|
-
transport: (0,
|
|
6039
|
+
transport: (0, import_viem13.custom)(provider)
|
|
4963
6040
|
})
|
|
4964
6041
|
});
|
|
4965
|
-
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
6042
|
+
if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
|
|
4966
6043
|
return callback;
|
|
4967
6044
|
}
|
|
4968
6045
|
getAgent(agentId) {
|
|
@@ -4972,6 +6049,9 @@ var OwneySDK = class {
|
|
|
4972
6049
|
}
|
|
4973
6050
|
return agent;
|
|
4974
6051
|
}
|
|
6052
|
+
getActiveAgentIds() {
|
|
6053
|
+
return [...this.getActiveAgents().keys()].sort();
|
|
6054
|
+
}
|
|
4975
6055
|
getActiveAgents() {
|
|
4976
6056
|
const active = /* @__PURE__ */ new Map();
|
|
4977
6057
|
for (const id of this.activeAgents) {
|
|
@@ -5065,12 +6145,21 @@ var OwneySDK = class {
|
|
|
5065
6145
|
createAgent(agentId, key2) {
|
|
5066
6146
|
if (agentId === "zyfai") {
|
|
5067
6147
|
if (!key2) return null;
|
|
5068
|
-
return new ZyfaiAgent(
|
|
6148
|
+
return new ZyfaiAgent(
|
|
6149
|
+
key2,
|
|
6150
|
+
this.zyfaiRpcUrls ? resolveRpcUrls(
|
|
6151
|
+
this.zyfaiRpcUrls,
|
|
6152
|
+
this.apiKey,
|
|
6153
|
+
this.routingApiBaseUrl
|
|
6154
|
+
) : this.rpcUrls,
|
|
6155
|
+
this.referralSource
|
|
6156
|
+
);
|
|
5069
6157
|
}
|
|
5070
6158
|
if (agentId === "yieldseeker") {
|
|
5071
6159
|
return new YieldseekerAgent(this.apiKey, {
|
|
5072
6160
|
auth: { origin: this.yieldseekerSiweOrigin },
|
|
5073
|
-
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
|
|
6161
|
+
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl),
|
|
6162
|
+
rpcUrls: this.rpcUrls
|
|
5074
6163
|
});
|
|
5075
6164
|
}
|
|
5076
6165
|
return null;
|
|
@@ -5257,12 +6346,23 @@ var OwneySDK = class {
|
|
|
5257
6346
|
*/
|
|
5258
6347
|
async deposit(options) {
|
|
5259
6348
|
const { amount, asset, depositCallback, agentId, onApproved } = options;
|
|
6349
|
+
const prepared = /* @__PURE__ */ new Map();
|
|
6350
|
+
const onProgress = options.onProgress ? (evidence) => {
|
|
6351
|
+
if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
|
|
6352
|
+
notifyFunding(options.onProgress, evidence);
|
|
6353
|
+
} : void 0;
|
|
6354
|
+
const onSubmitted = onProgress ? (hash, destinations) => {
|
|
6355
|
+
for (const destination of destinations) {
|
|
6356
|
+
const evidence = prepared.get(destination.to.toLowerCase());
|
|
6357
|
+
if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
|
|
6358
|
+
}
|
|
6359
|
+
} : void 0;
|
|
5260
6360
|
if (BigInt(amount) === 0n) {
|
|
5261
6361
|
return { agentResults: {} };
|
|
5262
6362
|
}
|
|
5263
6363
|
const state = this.requireState();
|
|
5264
6364
|
const chainId = this.requireChainId();
|
|
5265
|
-
const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
|
|
6365
|
+
const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted) : this.getDefaultSponsoredCallback(onApproved, onSubmitted));
|
|
5266
6366
|
if (agentId) {
|
|
5267
6367
|
if (this.isAgentDisabled(agentId)) {
|
|
5268
6368
|
throw new OwneyError(
|
|
@@ -5281,7 +6381,8 @@ var OwneySDK = class {
|
|
|
5281
6381
|
amount,
|
|
5282
6382
|
asset,
|
|
5283
6383
|
effectiveCallback,
|
|
5284
|
-
depositCallback
|
|
6384
|
+
depositCallback,
|
|
6385
|
+
onProgress
|
|
5285
6386
|
);
|
|
5286
6387
|
}
|
|
5287
6388
|
const totalAmount = BigInt(amount);
|
|
@@ -5345,7 +6446,7 @@ var OwneySDK = class {
|
|
|
5345
6446
|
run: (callback) => withFailureReporting(
|
|
5346
6447
|
this.apiKey,
|
|
5347
6448
|
agent.id,
|
|
5348
|
-
() => agent.deposit(state, chainId, amount2, asset, callback),
|
|
6449
|
+
() => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
|
|
5349
6450
|
this.routingApiBaseUrl
|
|
5350
6451
|
)
|
|
5351
6452
|
})),
|
|
@@ -5361,7 +6462,8 @@ var OwneySDK = class {
|
|
|
5361
6462
|
);
|
|
5362
6463
|
await this.approvePermit2(
|
|
5363
6464
|
asset,
|
|
5364
|
-
requiredAmount
|
|
6465
|
+
requiredAmount,
|
|
6466
|
+
cid
|
|
5365
6467
|
);
|
|
5366
6468
|
return batchTransfer(cid, transfers);
|
|
5367
6469
|
}
|
|
@@ -5381,7 +6483,8 @@ var OwneySDK = class {
|
|
|
5381
6483
|
agentAmount,
|
|
5382
6484
|
asset,
|
|
5383
6485
|
effectiveCallback,
|
|
5384
|
-
depositCallback
|
|
6486
|
+
depositCallback,
|
|
6487
|
+
onProgress
|
|
5385
6488
|
);
|
|
5386
6489
|
} catch (error) {
|
|
5387
6490
|
if (Object.keys(agentResults).length === 0) throw error;
|
|
@@ -5405,10 +6508,12 @@ var OwneySDK = class {
|
|
|
5405
6508
|
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
5406
6509
|
* two independent auto-recovery mechanisms:
|
|
5407
6510
|
*
|
|
5408
|
-
* 1. Missing Permit2 allowance
|
|
5409
|
-
* callback and the attempt fails with
|
|
5410
|
-
*
|
|
5411
|
-
*
|
|
6511
|
+
* 1. Missing Permit2 allowance outside the atomic Base-USDC path: when the
|
|
6512
|
+
* app did not supply its own callback and the attempt fails with
|
|
6513
|
+
* `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
|
|
6514
|
+
* for that token. Base USDC bundles a gasless ERC-2612 approval inside
|
|
6515
|
+
* its sponsored deposit and never reaches this branch. Other tokens send
|
|
6516
|
+
* the one-time user-paid Permit2 approval via `approvePermit2()` and
|
|
5412
6517
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
5413
6518
|
* per call so a wallet/agent that keeps reporting the allowance as
|
|
5414
6519
|
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
@@ -5426,17 +6531,11 @@ var OwneySDK = class {
|
|
|
5426
6531
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
5427
6532
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
5428
6533
|
*/
|
|
5429
|
-
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
|
|
6534
|
+
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
|
|
5430
6535
|
const attempt = (callback) => withFailureReporting(
|
|
5431
6536
|
this.apiKey,
|
|
5432
6537
|
agent.id,
|
|
5433
|
-
() => agent.deposit(
|
|
5434
|
-
state,
|
|
5435
|
-
chainId,
|
|
5436
|
-
amount,
|
|
5437
|
-
asset,
|
|
5438
|
-
callback
|
|
5439
|
-
),
|
|
6538
|
+
() => onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
|
|
5440
6539
|
this.routingApiBaseUrl
|
|
5441
6540
|
);
|
|
5442
6541
|
let approvalAttempted = false;
|
|
@@ -5451,7 +6550,8 @@ var OwneySDK = class {
|
|
|
5451
6550
|
);
|
|
5452
6551
|
await this.approvePermit2(
|
|
5453
6552
|
asset,
|
|
5454
|
-
BigInt(amount)
|
|
6553
|
+
BigInt(amount),
|
|
6554
|
+
chainId
|
|
5455
6555
|
);
|
|
5456
6556
|
continue;
|
|
5457
6557
|
}
|
|
@@ -5598,6 +6698,98 @@ var OwneySDK = class {
|
|
|
5598
6698
|
}
|
|
5599
6699
|
return eligible;
|
|
5600
6700
|
}
|
|
6701
|
+
/**
|
|
6702
|
+
* Run owner-approved withdrawals before relayer-only withdrawals. Wallet
|
|
6703
|
+
* approval is the only point at which the user can cancel the aggregate
|
|
6704
|
+
* operation, so no relayer leg should commit before it has completed.
|
|
6705
|
+
*/
|
|
6706
|
+
orderAgentsForWithdrawal(agents) {
|
|
6707
|
+
return agents.map((agent, index) => ({ agent, index })).sort((left, right) => {
|
|
6708
|
+
const approvalOrder = Number(Boolean(right.agent.withdrawalRequiresWalletApproval)) - Number(Boolean(left.agent.withdrawalRequiresWalletApproval));
|
|
6709
|
+
return approvalOrder || left.index - right.index;
|
|
6710
|
+
}).map(({ agent }) => agent);
|
|
6711
|
+
}
|
|
6712
|
+
isUserRejectedWithdrawal(error) {
|
|
6713
|
+
let current = error;
|
|
6714
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6715
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
6716
|
+
seen.add(current);
|
|
6717
|
+
const candidate = current;
|
|
6718
|
+
if (candidate.code === 4001 || candidate.code === "4001" || candidate.code === "ACTION_REJECTED") return true;
|
|
6719
|
+
const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
|
|
6720
|
+
if (/user (?:rejected|denied)|rejected by user|request rejected/i.test(message)) {
|
|
6721
|
+
return true;
|
|
6722
|
+
}
|
|
6723
|
+
current = candidate.cause;
|
|
6724
|
+
}
|
|
6725
|
+
return false;
|
|
6726
|
+
}
|
|
6727
|
+
captureReadScope(requestedChain) {
|
|
6728
|
+
const original = this.requireState();
|
|
6729
|
+
const chainId = this.requireChainId();
|
|
6730
|
+
if (requestedChain !== void 0 && requestedChain !== chainId) {
|
|
6731
|
+
throw new OwneyError("NO_ACTIVE_CHAIN", "Account read scope changed. Please retry.");
|
|
6732
|
+
}
|
|
6733
|
+
const wallet = original.walletAddress;
|
|
6734
|
+
return { state: { ...original }, chainId, assertCurrent: () => {
|
|
6735
|
+
if (this.state !== original || this.state.walletAddress !== wallet || this.state.chainId !== chainId)
|
|
6736
|
+
throw new OwneyError("NOT_CONNECTED", "Account read scope changed. Please retry.");
|
|
6737
|
+
} };
|
|
6738
|
+
}
|
|
6739
|
+
/** Revalidate a saved transfer against the authenticated account and actual receipt. */
|
|
6740
|
+
async inspectFunding(evidence, direction, options = {}) {
|
|
6741
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
6742
|
+
if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase() || !evidence.txHash)
|
|
6743
|
+
throw new OwneyError("AGENT_API_ERROR", "Funding evidence does not match the connected account.");
|
|
6744
|
+
const agent = this.getAgent(evidence.agentId);
|
|
6745
|
+
const verifiedWallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
|
|
6746
|
+
const agentWallet = evidence.agentWallet ?? verifiedWallet;
|
|
6747
|
+
if (!agentWallet || agentWallet.toLowerCase() !== verifiedWallet.toLowerCase())
|
|
6748
|
+
throw new OwneyError("AGENT_API_ERROR", "Funding wallet does not match this account.");
|
|
6749
|
+
const token = SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token);
|
|
6750
|
+
if (!token) throw new OwneyError("CHAIN_UNSUPPORTED", "Unsupported funding asset.");
|
|
6751
|
+
const rpc = this.getPaidRpcClient(chainId);
|
|
6752
|
+
const receipt = await rpc.getTransactionReceipt({ hash: evidence.txHash });
|
|
6753
|
+
assertCurrent();
|
|
6754
|
+
let verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet }, token.address, direction);
|
|
6755
|
+
if (direction === "withdraw" && evidence.agentId === "yieldseeker" && verified.transfer === "unknown" && verified.confirmedAmount && BigInt(verified.confirmedAmount) > 0n) {
|
|
6756
|
+
const transaction = await rpc.getTransaction({ hash: evidence.txHash });
|
|
6757
|
+
assertCurrent();
|
|
6758
|
+
if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address))
|
|
6759
|
+
verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
|
|
6760
|
+
}
|
|
6761
|
+
const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
|
|
6762
|
+
if ((verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
|
|
6763
|
+
if (options.refreshBalance !== false)
|
|
6764
|
+
agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil);
|
|
6765
|
+
this.recoveredFundingReads.add(key2);
|
|
6766
|
+
if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.values().next().value);
|
|
6767
|
+
}
|
|
6768
|
+
return verified;
|
|
6769
|
+
}
|
|
6770
|
+
/** A display acknowledgement controls polling only; it never confirms a financial outcome. */
|
|
6771
|
+
endFundingBalanceRefresh(evidence, read2) {
|
|
6772
|
+
const { state, chainId } = this.captureReadScope(evidence.chainId);
|
|
6773
|
+
if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase() || read2.chainId !== chainId || read2.asset.toUpperCase() !== evidence.token) return false;
|
|
6774
|
+
return this.getAgent(evidence.agentId).endFundingBalanceRefresh?.(state, chainId, evidence.token, read2) ?? false;
|
|
6775
|
+
}
|
|
6776
|
+
/** History lookup for receipt recovery; adapters may not authenticate or sign here. */
|
|
6777
|
+
async getFundingHistory(evidence, fromDate, options) {
|
|
6778
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
|
|
6779
|
+
if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase())
|
|
6780
|
+
throw new OwneyError("NOT_CONNECTED", "Funding account changed.");
|
|
6781
|
+
const agent = this.getAgent(evidence.agentId);
|
|
6782
|
+
if (!agent.getFundingHistory) return { data: [], hasMore: false };
|
|
6783
|
+
const filters = { tokenSymbol: evidence.token, fromDate, limit: 50, ...options };
|
|
6784
|
+
const page = await this.readAgent(agent, "history", () => agent.getFundingHistory(state, chainId, filters), filters);
|
|
6785
|
+
assertCurrent();
|
|
6786
|
+
return page;
|
|
6787
|
+
}
|
|
6788
|
+
async recoverDepositRegistration(evidence) {
|
|
6789
|
+
const verified = await this.inspectFunding(evidence, "deposit");
|
|
6790
|
+
if (verified.agentId !== "zyfai") return verified;
|
|
6791
|
+
return this.getAgent("zyfai").retryDepositRegistration(this.requireState(), verified);
|
|
6792
|
+
}
|
|
5601
6793
|
// --- Fund operations ---
|
|
5602
6794
|
/**
|
|
5603
6795
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
@@ -5609,10 +6801,66 @@ var OwneySDK = class {
|
|
|
5609
6801
|
* @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
|
|
5610
6802
|
*/
|
|
5611
6803
|
async withdraw(options) {
|
|
5612
|
-
const { asset, amount, agentId } = options;
|
|
5613
6804
|
const state = this.requireState();
|
|
5614
6805
|
const chainId = this.requireChainId();
|
|
6806
|
+
const ids = options.agentId ? [options.agentId] : this.getActiveAgentIds().filter((id) => !options.excludeAgentIds?.includes(id));
|
|
6807
|
+
const attempted = /* @__PURE__ */ new Set();
|
|
6808
|
+
try {
|
|
6809
|
+
return await this.withdrawFromAgents(options, state, chainId, attempted);
|
|
6810
|
+
} catch (error) {
|
|
6811
|
+
for (const id of ids) if (!attempted.has(id)) {
|
|
6812
|
+
notifyFunding(options.onProgress, {
|
|
6813
|
+
agentId: id,
|
|
6814
|
+
chainId,
|
|
6815
|
+
token: options.asset,
|
|
6816
|
+
walletAddress: state.walletAddress,
|
|
6817
|
+
intendedAmount: options.amount ?? "MAX",
|
|
6818
|
+
transfer: "failed",
|
|
6819
|
+
registration: "not-applicable",
|
|
6820
|
+
delivery: "failed"
|
|
6821
|
+
});
|
|
6822
|
+
}
|
|
6823
|
+
throw error;
|
|
6824
|
+
}
|
|
6825
|
+
}
|
|
6826
|
+
async withdrawFromAgents(options, state, chainId, attempted) {
|
|
6827
|
+
const { asset, amount, agentId, onAgentResult } = options;
|
|
6828
|
+
const notifyAgentResult = (id, result) => {
|
|
6829
|
+
try {
|
|
6830
|
+
if (result.evidence) notifyFunding(options.onProgress, result.evidence);
|
|
6831
|
+
onAgentResult?.(id, result);
|
|
6832
|
+
} catch (error) {
|
|
6833
|
+
console.warn("Withdrawal result observer failed:", error);
|
|
6834
|
+
}
|
|
6835
|
+
};
|
|
5615
6836
|
const token = asset;
|
|
6837
|
+
const submissionEvidence = /* @__PURE__ */ new Map();
|
|
6838
|
+
const submit = async (agent, requested2) => {
|
|
6839
|
+
attempted.add(agent.id);
|
|
6840
|
+
let evidence = {
|
|
6841
|
+
agentId: agent.id,
|
|
6842
|
+
chainId,
|
|
6843
|
+
token,
|
|
6844
|
+
walletAddress: state.walletAddress,
|
|
6845
|
+
intendedAmount: requested2 ?? "MAX",
|
|
6846
|
+
transfer: "pending",
|
|
6847
|
+
registration: "not-applicable",
|
|
6848
|
+
delivery: "pending"
|
|
6849
|
+
};
|
|
6850
|
+
const progress = (next) => {
|
|
6851
|
+
evidence = { ...evidence, ...next };
|
|
6852
|
+
submissionEvidence.set(agent.id, evidence);
|
|
6853
|
+
notifyFunding(options.onProgress, evidence);
|
|
6854
|
+
};
|
|
6855
|
+
progress(evidence);
|
|
6856
|
+
try {
|
|
6857
|
+
return await agent.withdraw(state, chainId, token, requested2, progress);
|
|
6858
|
+
} catch (error) {
|
|
6859
|
+
if (!evidence.txHash && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
|
|
6860
|
+
progress({ ...evidence, transfer: "failed", delivery: "failed" });
|
|
6861
|
+
throw error;
|
|
6862
|
+
}
|
|
6863
|
+
};
|
|
5616
6864
|
const assetInfo = SupportedAssets.find(
|
|
5617
6865
|
(a) => a.chainId === chainId && a.symbol === token
|
|
5618
6866
|
);
|
|
@@ -5625,21 +6873,27 @@ var OwneySDK = class {
|
|
|
5625
6873
|
if (agentId) {
|
|
5626
6874
|
const agent = this.getAgent(agentId);
|
|
5627
6875
|
this.validateAssetSupport(agent, chainId, asset);
|
|
5628
|
-
|
|
6876
|
+
const result = await withFailureReporting(
|
|
5629
6877
|
this.apiKey,
|
|
5630
6878
|
agent.id,
|
|
5631
|
-
() => agent
|
|
6879
|
+
() => submit(agent, amount),
|
|
5632
6880
|
this.routingApiBaseUrl
|
|
5633
6881
|
);
|
|
6882
|
+
notifyAgentResult(agent.id, result);
|
|
6883
|
+
return result;
|
|
5634
6884
|
}
|
|
5635
|
-
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
6885
|
+
const eligibleAgents = this.getEligibleAgents(chainId, asset).filter((agent) => !options.excludeAgentIds?.includes(agent.id));
|
|
6886
|
+
const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
|
|
5636
6887
|
if (!amount) {
|
|
5637
6888
|
const results2 = {};
|
|
5638
6889
|
const agentErrors2 = {};
|
|
5639
|
-
for (const agent of
|
|
6890
|
+
for (const agent of withdrawalAgents) {
|
|
5640
6891
|
try {
|
|
5641
|
-
results2[agent.id] = await agent
|
|
6892
|
+
results2[agent.id] = await submit(agent);
|
|
6893
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
5642
6894
|
} catch (err) {
|
|
6895
|
+
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
6896
|
+
if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
|
|
5643
6897
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
5644
6898
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
5645
6899
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -5658,6 +6912,13 @@ var OwneySDK = class {
|
|
|
5658
6912
|
{ asset, agentErrors: agentErrors2 }
|
|
5659
6913
|
);
|
|
5660
6914
|
}
|
|
6915
|
+
if (Object.keys(agentErrors2).length > 0) {
|
|
6916
|
+
throw new OwneyError(
|
|
6917
|
+
"WITHDRAW_PARTIAL_FAILURE",
|
|
6918
|
+
"Some agents could not complete the withdrawal.",
|
|
6919
|
+
{ asset, partialResults: results2, agentErrors: agentErrors2 }
|
|
6920
|
+
);
|
|
6921
|
+
}
|
|
5661
6922
|
const amountsKnown = Object.values(results2).every(
|
|
5662
6923
|
(result) => typeof result.amount === "string" && /^\d+$/.test(result.amount)
|
|
5663
6924
|
);
|
|
@@ -5667,7 +6928,7 @@ var OwneySDK = class {
|
|
|
5667
6928
|
};
|
|
5668
6929
|
}
|
|
5669
6930
|
const requested = BigInt(amount);
|
|
5670
|
-
const aggregated = await this.getBalances();
|
|
6931
|
+
const aggregated = await this.getBalances(void 0, { chainId, asset: token });
|
|
5671
6932
|
const unavailableAgents = eligibleAgents.filter(
|
|
5672
6933
|
(agent) => !(agent.id in aggregated.agentBalances)
|
|
5673
6934
|
);
|
|
@@ -5720,21 +6981,19 @@ var OwneySDK = class {
|
|
|
5720
6981
|
planned: 0n
|
|
5721
6982
|
}));
|
|
5722
6983
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
6984
|
+
const orderedPlans = this.orderAgentsForWithdrawal(plans.map((p) => p.agent)).map((agent) => plans.find((plan) => plan.agent === agent));
|
|
5723
6985
|
const results = {};
|
|
5724
6986
|
const agentErrors = {
|
|
5725
6987
|
...aggregated.agentErrors ?? {}
|
|
5726
6988
|
};
|
|
5727
|
-
for (let i = 0; i <
|
|
5728
|
-
const p =
|
|
6989
|
+
for (let i = 0; i < orderedPlans.length; i++) {
|
|
6990
|
+
const p = orderedPlans[i];
|
|
5729
6991
|
if (p.planned === 0n) continue;
|
|
5730
6992
|
try {
|
|
5731
|
-
results[p.agent.id] = await p.agent.
|
|
5732
|
-
|
|
5733
|
-
chainId,
|
|
5734
|
-
token,
|
|
5735
|
-
p.planned.toString()
|
|
5736
|
-
);
|
|
6993
|
+
results[p.agent.id] = await submit(p.agent, p.planned.toString());
|
|
6994
|
+
notifyAgentResult(p.agent.id, results[p.agent.id]);
|
|
5737
6995
|
} catch (err) {
|
|
6996
|
+
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
5738
6997
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
5739
6998
|
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
5740
6999
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
@@ -5746,7 +7005,8 @@ var OwneySDK = class {
|
|
|
5746
7005
|
);
|
|
5747
7006
|
const failedAmount = p.planned;
|
|
5748
7007
|
p.planned = 0n;
|
|
5749
|
-
|
|
7008
|
+
if (submissionEvidence.get(p.agent.id)?.transfer === "failed")
|
|
7009
|
+
redistributeShare(orderedPlans, i, failedAmount);
|
|
5750
7010
|
}
|
|
5751
7011
|
}
|
|
5752
7012
|
if (Object.keys(results).length === 0) {
|
|
@@ -5783,12 +7043,19 @@ var OwneySDK = class {
|
|
|
5783
7043
|
* @param agentId - Optional. Agent to query. Omit for aggregated balances.
|
|
5784
7044
|
* @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
|
|
5785
7045
|
*/
|
|
5786
|
-
async getBalances(agentId) {
|
|
5787
|
-
const state = this.
|
|
5788
|
-
const
|
|
7046
|
+
async getBalances(agentId, options) {
|
|
7047
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
7048
|
+
const asset = options?.asset;
|
|
7049
|
+
const readBalance = (agent) => this.readAgent(
|
|
7050
|
+
agent,
|
|
7051
|
+
"balances",
|
|
7052
|
+
() => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
|
|
7053
|
+
asset ? { asset } : null
|
|
7054
|
+
);
|
|
5789
7055
|
if (agentId) {
|
|
5790
7056
|
const agent = this.getAgent(agentId);
|
|
5791
|
-
const result = await
|
|
7057
|
+
const result = await readBalance(agent);
|
|
7058
|
+
assertCurrent();
|
|
5792
7059
|
return {
|
|
5793
7060
|
...result,
|
|
5794
7061
|
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
@@ -5799,7 +7066,7 @@ var OwneySDK = class {
|
|
|
5799
7066
|
const entries = [...this.getActiveAgents().entries()];
|
|
5800
7067
|
const balanceResults = await Promise.allSettled(
|
|
5801
7068
|
entries.map(async ([id, agent]) => {
|
|
5802
|
-
const b = await
|
|
7069
|
+
const b = await readBalance(agent);
|
|
5803
7070
|
return [
|
|
5804
7071
|
id,
|
|
5805
7072
|
{
|
|
@@ -5809,6 +7076,7 @@ var OwneySDK = class {
|
|
|
5809
7076
|
];
|
|
5810
7077
|
})
|
|
5811
7078
|
);
|
|
7079
|
+
assertCurrent();
|
|
5812
7080
|
let successCount = 0;
|
|
5813
7081
|
const agentErrors = {};
|
|
5814
7082
|
const agentRetryAt = {};
|
|
@@ -5856,23 +7124,11 @@ var OwneySDK = class {
|
|
|
5856
7124
|
const agent = this.getAgent(agentId);
|
|
5857
7125
|
return this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
|
|
5858
7126
|
}
|
|
5859
|
-
let totalEarnings = 0;
|
|
5860
|
-
const results = {};
|
|
5861
7127
|
const entries = [...this.getActiveAgents().entries()];
|
|
5862
|
-
|
|
5863
|
-
entries
|
|
5864
|
-
|
|
5865
|
-
return [id, e];
|
|
5866
|
-
})
|
|
7128
|
+
return this.aggregateEarnings(
|
|
7129
|
+
entries,
|
|
7130
|
+
(agent) => this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId))
|
|
5867
7131
|
);
|
|
5868
|
-
for (const [id, e] of earningsResults) {
|
|
5869
|
-
results[id] = e;
|
|
5870
|
-
totalEarnings += Number(e.lifetimeEarnings);
|
|
5871
|
-
}
|
|
5872
|
-
return {
|
|
5873
|
-
totalEarnings: String(totalEarnings),
|
|
5874
|
-
agentEarnings: results
|
|
5875
|
-
};
|
|
5876
7132
|
}
|
|
5877
7133
|
/**
|
|
5878
7134
|
* Return refreshed earnings while letting each agent own its refresh policy.
|
|
@@ -5887,18 +7143,42 @@ var OwneySDK = class {
|
|
|
5887
7143
|
() => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId)
|
|
5888
7144
|
);
|
|
5889
7145
|
if (agentId) return refreshAgent(this.getAgent(agentId));
|
|
5890
|
-
|
|
7146
|
+
return this.aggregateEarnings([...this.getActiveAgents().entries()], refreshAgent);
|
|
7147
|
+
}
|
|
7148
|
+
async aggregateEarnings(entries, read2) {
|
|
7149
|
+
const settled = await Promise.allSettled(entries.map(([, agent]) => read2(agent)));
|
|
5891
7150
|
const agentEarnings = {};
|
|
5892
|
-
const
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
)
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
7151
|
+
const agentErrors = {};
|
|
7152
|
+
const agentRetryAt = {};
|
|
7153
|
+
const failures = [];
|
|
7154
|
+
let totalEarnings = 0;
|
|
7155
|
+
for (let i = 0; i < settled.length; i++) {
|
|
7156
|
+
const [id] = entries[i];
|
|
7157
|
+
const result = settled[i];
|
|
7158
|
+
if (result.status === "fulfilled") {
|
|
7159
|
+
agentEarnings[id] = result.value;
|
|
7160
|
+
totalEarnings += Number(result.value.lifetimeEarnings);
|
|
7161
|
+
continue;
|
|
7162
|
+
}
|
|
7163
|
+
const reason = result.reason;
|
|
7164
|
+
failures.push(reason);
|
|
7165
|
+
const retryDelay = rateLimitDelay(reason);
|
|
7166
|
+
if (retryDelay !== void 0) agentRetryAt[id] = Date.now() + retryDelay;
|
|
7167
|
+
agentErrors[id] = reason instanceof Error ? reason.message : String(reason);
|
|
7168
|
+
console.error(`[owney-sdk] Earnings fetch failed for agent "${id}":`, reason);
|
|
7169
|
+
}
|
|
7170
|
+
if (entries.length > 0 && Object.keys(agentEarnings).length === 0) {
|
|
7171
|
+
throw new OwneyError(
|
|
7172
|
+
"EARNINGS_ALL_FAILED",
|
|
7173
|
+
"Failed to fetch earnings for all active agents.",
|
|
7174
|
+
{ agentErrors, failures }
|
|
7175
|
+
);
|
|
5900
7176
|
}
|
|
5901
|
-
return {
|
|
7177
|
+
return {
|
|
7178
|
+
totalEarnings: String(totalEarnings),
|
|
7179
|
+
agentEarnings,
|
|
7180
|
+
...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
|
|
7181
|
+
};
|
|
5902
7182
|
}
|
|
5903
7183
|
/**
|
|
5904
7184
|
* Get the weighted APY for the user's account over a time period.
|
|
@@ -6064,12 +7344,13 @@ var OwneySDK = class {
|
|
|
6064
7344
|
* @returns {OwneyAgentHistory} `data` (up to `limit` entries), `hasMore`, `nextCursor`.
|
|
6065
7345
|
*/
|
|
6066
7346
|
async getHistory(options) {
|
|
6067
|
-
const state = this.
|
|
6068
|
-
const chainId = this.requireChainId();
|
|
7347
|
+
const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
|
|
6069
7348
|
const { agentId, filters } = options ?? {};
|
|
6070
7349
|
if (agentId) {
|
|
6071
7350
|
const agent = this.getAgent(agentId);
|
|
6072
|
-
|
|
7351
|
+
const page = await this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
|
|
7352
|
+
assertCurrent();
|
|
7353
|
+
return page;
|
|
6073
7354
|
}
|
|
6074
7355
|
const activeAgents = [...this.getActiveAgents().values()];
|
|
6075
7356
|
const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
|
|
@@ -6079,20 +7360,35 @@ var OwneySDK = class {
|
|
|
6079
7360
|
if (filters?.cursor && agentCursor === void 0) {
|
|
6080
7361
|
return { agentId: agent.id, page: null };
|
|
6081
7362
|
}
|
|
6082
|
-
const agentFilters = { ...filters, cursor: agentCursor };
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
7363
|
+
const agentFilters = { ...filters, cursor: agentCursor || void 0 };
|
|
7364
|
+
try {
|
|
7365
|
+
const page = await this.readAgent(
|
|
7366
|
+
agent,
|
|
7367
|
+
"history",
|
|
7368
|
+
() => agent.getHistory(state, chainId, agentFilters),
|
|
7369
|
+
agentFilters
|
|
7370
|
+
);
|
|
7371
|
+
return { agentId: agent.id, page };
|
|
7372
|
+
} catch (error) {
|
|
7373
|
+
return { agentId: agent.id, page: null, error };
|
|
7374
|
+
}
|
|
6090
7375
|
})
|
|
6091
7376
|
);
|
|
7377
|
+
assertCurrent();
|
|
7378
|
+
const agentErrors = {};
|
|
7379
|
+
const agentRetryAt = {};
|
|
6092
7380
|
const merged = [];
|
|
6093
7381
|
const nextCursorMap = {};
|
|
6094
7382
|
let hasMore = false;
|
|
6095
|
-
for (const
|
|
7383
|
+
for (const result of perAgent) {
|
|
7384
|
+
const { agentId: id, page } = result;
|
|
7385
|
+
if ("error" in result) {
|
|
7386
|
+
agentErrors[id] = result.error instanceof Error ? result.error.message : String(result.error);
|
|
7387
|
+
const delay = rateLimitDelay(result.error);
|
|
7388
|
+
if (delay !== void 0) agentRetryAt[id] = Date.now() + delay;
|
|
7389
|
+
nextCursorMap[id] = cursorMap[id] ?? "";
|
|
7390
|
+
hasMore = true;
|
|
7391
|
+
}
|
|
6096
7392
|
if (!page) continue;
|
|
6097
7393
|
merged.push(...page.data);
|
|
6098
7394
|
if (page.hasMore && page.nextCursor) {
|
|
@@ -6112,6 +7408,7 @@ var OwneySDK = class {
|
|
|
6112
7408
|
});
|
|
6113
7409
|
return {
|
|
6114
7410
|
data: merged,
|
|
7411
|
+
...Object.keys(agentErrors).length ? { agentErrors, agentRetryAt } : {},
|
|
6115
7412
|
hasMore,
|
|
6116
7413
|
nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
|
|
6117
7414
|
};
|
|
@@ -6172,16 +7469,18 @@ var OwneySDK = class {
|
|
|
6172
7469
|
}
|
|
6173
7470
|
/**
|
|
6174
7471
|
* User-paid approval of Permit2 on the selected token for the active chain.
|
|
6175
|
-
*
|
|
6176
|
-
*
|
|
6177
|
-
*
|
|
7472
|
+
* Grants the maximum ERC20 allowance so later deposits do not require another
|
|
7473
|
+
* approval. Resolves after one confirmation so the subsequent deposit attempt
|
|
7474
|
+
* sees the new allowance. Deposit retries pass their captured chain id so a
|
|
7475
|
+
* concurrent activation cannot redirect the approval to another network.
|
|
6178
7476
|
*
|
|
6179
7477
|
* @param requiredAmount Raw base-unit amount the pending deposit must cover.
|
|
7478
|
+
* @param expectedChainId Chain captured by the deposit that requested approval.
|
|
6180
7479
|
* @returns the approval transaction hash.
|
|
6181
7480
|
*/
|
|
6182
|
-
async approvePermit2(asset = "WETH", requiredAmount = 0n) {
|
|
7481
|
+
async approvePermit2(asset = "WETH", requiredAmount = 0n, expectedChainId) {
|
|
6183
7482
|
const state = this.requireState();
|
|
6184
|
-
const chainId = this.requireChainId();
|
|
7483
|
+
const chainId = expectedChainId ?? this.requireChainId();
|
|
6185
7484
|
this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
|
|
6186
7485
|
const token = sponsoredTokensFor(asset)[chainId];
|
|
6187
7486
|
if (!token) {
|
|
@@ -6191,16 +7490,18 @@ var OwneySDK = class {
|
|
|
6191
7490
|
);
|
|
6192
7491
|
}
|
|
6193
7492
|
const provider = this.requireConnectedProvider();
|
|
6194
|
-
const
|
|
7493
|
+
const walletChainClient = (0, import_viem13.createPublicClient)({
|
|
6195
7494
|
chain: VIEM_CHAIN2[chainId],
|
|
6196
|
-
transport: (0,
|
|
7495
|
+
transport: (0, import_viem13.custom)(provider)
|
|
6197
7496
|
});
|
|
7497
|
+
const publicClient = this.getPaidRpcClient(chainId);
|
|
6198
7498
|
const approvalAmount = permit2ApprovalAmount(requiredAmount);
|
|
6199
|
-
const wallet = (0,
|
|
7499
|
+
const wallet = (0, import_viem13.createWalletClient)({
|
|
6200
7500
|
account: state.walletAddress,
|
|
6201
7501
|
chain: VIEM_CHAIN2[chainId],
|
|
6202
|
-
transport: (0,
|
|
7502
|
+
transport: (0, import_viem13.custom)(provider)
|
|
6203
7503
|
});
|
|
7504
|
+
await ensureWalletOnChain(walletChainClient, wallet, chainId);
|
|
6204
7505
|
const hash = await wallet.writeContract({
|
|
6205
7506
|
address: token,
|
|
6206
7507
|
abi: ERC20_ALLOWANCE_ABI,
|
|
@@ -6209,14 +7510,53 @@ var OwneySDK = class {
|
|
|
6209
7510
|
account: state.walletAddress,
|
|
6210
7511
|
chain: VIEM_CHAIN2[chainId]
|
|
6211
7512
|
});
|
|
6212
|
-
const receipt = await
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
7513
|
+
const receipt = await withPaidRpcDiagnostics(
|
|
7514
|
+
() => publicClient.waitForTransactionReceipt({
|
|
7515
|
+
hash,
|
|
7516
|
+
confirmations: 1
|
|
7517
|
+
}),
|
|
7518
|
+
chainId,
|
|
7519
|
+
"eth_getTransactionReceipt"
|
|
7520
|
+
);
|
|
6216
7521
|
if (receipt.status !== "success") {
|
|
6217
7522
|
throw new Error(`Permit2 approval reverted (tx ${hash})`);
|
|
6218
7523
|
}
|
|
6219
|
-
|
|
7524
|
+
let observedAllowance = 0n;
|
|
7525
|
+
let verificationError;
|
|
7526
|
+
for (let attempt = 0; attempt < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS; attempt += 1) {
|
|
7527
|
+
try {
|
|
7528
|
+
observedAllowance = await readPermit2Allowance(
|
|
7529
|
+
publicClient,
|
|
7530
|
+
token,
|
|
7531
|
+
state.walletAddress,
|
|
7532
|
+
attempt === 0 ? receipt.blockNumber : void 0
|
|
7533
|
+
);
|
|
7534
|
+
verificationError = void 0;
|
|
7535
|
+
if (observedAllowance >= requiredAmount) return hash;
|
|
7536
|
+
} catch (error) {
|
|
7537
|
+
verificationError = error;
|
|
7538
|
+
}
|
|
7539
|
+
if (attempt + 1 < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS) {
|
|
7540
|
+
await new Promise(
|
|
7541
|
+
(resolve) => setTimeout(resolve, PERMIT2_ALLOWANCE_VERIFY_DELAY_MS)
|
|
7542
|
+
);
|
|
7543
|
+
}
|
|
7544
|
+
}
|
|
7545
|
+
throw new OwneyError(
|
|
7546
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
7547
|
+
"Permit2 approval was confirmed, but the required token allowance was not observable.",
|
|
7548
|
+
{
|
|
7549
|
+
approvalConfirmed: true,
|
|
7550
|
+
approvalTxHash: hash,
|
|
7551
|
+
owner: state.walletAddress,
|
|
7552
|
+
token,
|
|
7553
|
+
spender: PERMIT2_ADDRESS,
|
|
7554
|
+
chainId,
|
|
7555
|
+
requiredAmount: requiredAmount.toString(),
|
|
7556
|
+
observedAllowance: observedAllowance.toString(),
|
|
7557
|
+
...verificationError instanceof Error ? { verificationError: verificationError.message } : {}
|
|
7558
|
+
}
|
|
7559
|
+
);
|
|
6220
7560
|
}
|
|
6221
7561
|
// --- Discovery (no wallet required) ---
|
|
6222
7562
|
/**
|
|
@@ -6321,7 +7661,7 @@ var OwneySDK = class {
|
|
|
6321
7661
|
};
|
|
6322
7662
|
|
|
6323
7663
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
6324
|
-
var
|
|
7664
|
+
var import_viem14 = require("viem");
|
|
6325
7665
|
var import_siwe2 = require("siwe");
|
|
6326
7666
|
var import_sdk2 = require("@zyfai/sdk");
|
|
6327
7667
|
|
|
@@ -6448,7 +7788,7 @@ function buildSIWXConfig(deps) {
|
|
|
6448
7788
|
issuedAt,
|
|
6449
7789
|
toString() {
|
|
6450
7790
|
return new import_siwe2.SiweMessage({
|
|
6451
|
-
address: (0,
|
|
7791
|
+
address: (0, import_viem14.getAddress)(accountAddress),
|
|
6452
7792
|
chainId: numericChainId(chainId),
|
|
6453
7793
|
domain,
|
|
6454
7794
|
uri,
|