@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/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(this.limited(agentId, cooldown.until));
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
- this.cooldowns.set(agentId, { until, failures });
206
- throw this.limited(agentId, until);
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 import_viem = require("viem");
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
- const portfolioByChain = portfolio.portfolioByChain ?? {};
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
- const amount = hexToDecimal(balanceWithFee ?? balance ?? "0", decimals);
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, import_viem.parseAbi)([
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
- // txHash -> resolved withdrawn amount. Receipts are immutable, so a
1166
- // successful decode is cached forever; failures are NOT cached so a transient
1167
- // RPC error retries on the next fetch.
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 ?? DEFAULT_ZYFAI_RPC_URLS;
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 = (0, import_viem.createPublicClient)({
1189
- chain: VIEM_CHAIN[chainId],
1190
- transport: (0, import_viem.http)(this.rpcUrls[chainId])
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
- * Fills in the true withdrawn amount for `Withdraw` entries by decoding the
1197
- * on-chain `Transfer` that lands in the user's wallet. The Zyfai history
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. Runs all lookups concurrently and is tolerant
1210
- * of RPC failures: an entry whose amount can't be resolved keeps
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 resolveWithdrawnAmounts(entries, chainId, recipient) {
1215
- const withdrawals = entries.filter(
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 (withdrawals.length === 0) return;
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
- withdrawals.map(async (entry) => {
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
- const cacheKey = `${txHash}:${asset.address}`;
1233
- const cached = this.withdrawAmountCache.get(cacheKey);
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.amount = cached;
1544
+ Object.assign(tx, cached);
1236
1545
  return;
1237
1546
  }
1238
1547
  try {
1239
- const receipt = await client.getTransactionReceipt({
1240
- hash: txHash
1241
- });
1242
- const amount = extractWithdrawnAmount(
1243
- receipt.logs,
1244
- recipient,
1245
- asset.address,
1246
- asset.decimals
1247
- );
1248
- if (amount !== void 0) {
1249
- this.withdrawAmountCache.set(cacheKey, amount);
1250
- tx.amount = amount;
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 collapses every 4xx into
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
- const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
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
- } catch (logError) {
1931
- console.warn(
1932
- "[owney-sdk] Deposit landed on-chain but logDeposit failed (non-fatal):",
1933
- logError
1934
- );
2294
+ evidence2 = { ...evidence2, registration: "confirmed" };
2295
+ } catch {
2296
+ evidence2 = { ...evidence2, registration: "unknown" };
1935
2297
  }
1936
- return { txHash, smartWallet, amount };
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
- return mapDeposit(raw2);
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
- await this.ensureConnected(state, validChainId);
1955
- const raw2 = await this.sdk.withdrawFunds(
1956
- this.getAddress(),
1957
- validChainId,
1958
- amount,
1959
- token
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
- return mapWithdraw(raw2);
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
- return mapBalances(raw2, validChainId, smartWallet);
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 `resolveWithdrawnAmounts` normalizes on the way in). Anything
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.resolveWithdrawnAmounts(data, validChainId, state.walletAddress);
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 import_viem6 = require("viem");
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 import_viem2 = require("viem");
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 import_viem3 = require("viem");
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 requiredAmount;
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, import_viem3.bytesToHex)(bytes));
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 import_viem4 = require("viem");
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, import_viem4.getAddress)(address),
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, import_viem4.getAddress)(state.walletAddress);
2596
- const publicClient = (0, import_viem4.createPublicClient)({
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, import_viem4.custom)(state.provider)
3076
+ transport: (0, import_viem6.custom)(state.provider)
2599
3077
  });
2600
- const walletClient = (0, import_viem4.createWalletClient)({
3078
+ const walletClient = (0, import_viem6.createWalletClient)({
2601
3079
  account,
2602
3080
  chain: import_chains2.base,
2603
- transport: (0, import_viem4.custom)(state.provider)
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 import_viem5 = require("viem");
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, import_viem5.formatUnits)(raw(value, endpoint), decimals);
3315
+ return (0, import_viem7.formatUnits)(raw(value, endpoint), decimals);
2837
3316
  }
2838
3317
  function usd(rawAmount, decimals, price) {
2839
- return Number((0, import_viem5.formatUnits)(rawAmount, decimals)) * price;
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, import_viem5.isAddress)(option.address)) {
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 separately because withdraw-from-position expects it.
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, import_viem5.formatUnits)(amount, context.snapshot.baseAssetDecimals)
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 filtered = [...grouped.values(), ...ungrouped].filter((entry) => (!options.fromDate || entry.date >= options.fromDate) && (!options.toDate || entry.date <= options.toDate)).sort((left, right) => right.date.localeCompare(left.date));
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: filtered.slice(0, options.limit),
3165
- // v1 returns the whole action/movement collection and defines no cursor.
3166
- // Report a terminal page so callers never loop over the same prefix.
3167
- hasMore: false
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
- try {
3316
- if (depositCallback) {
3317
- provideDepositVerificationContext(depositCallback, {
3318
- agentId: "yieldseeker",
3319
- signature: await this.auth.getToken(state, chainId),
3320
- userId: context.user.userId,
3321
- yieldseekerAgentId: context.agent.agentId
3322
- });
3323
- txHash = await depositCallback(
3324
- context.wallet.walletAddress,
3325
- chainId,
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
- context,
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
- return {
3351
- txHash,
3352
- smartWallet: context.wallet.walletAddress,
3353
- amount
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 withdraw(state, chainId, asset, amount) {
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 idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
3384
- ([address]) => address.toLowerCase() === metadata.address.toLowerCase()
3385
- );
3386
- const idle = BigInt(idleEntry?.[1] ?? "0");
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 account = (0, import_viem6.getAddress)(state.walletAddress);
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, import_viem6.getAddress)(context.wallet.walletAddress),
3440
- data: amount === void 0 ? (0, import_viem6.encodeFunctionData)({
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, import_viem6.encodeFunctionData)({
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
- return {
3453
- txHash,
3454
- type: amount === void 0 ? "full" : "partial",
3455
- amount: requested.toString()
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
- await this.refreshSnapshotAfterMovement(
3459
- state,
3460
- chainId,
3461
- context,
3462
- "withdrawal"
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
- return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
4127
+ if (asset !== void 0) this.assertAsset(asset);
4128
+ return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, { asset, allowMissingPositions: true }));
3469
4129
  }
3470
- async getEarnings(state, chainId) {
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
- return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
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 getAccountApy(state, chainId, days, tokenSymbol) {
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.loadPortfolio(state, chainId, {
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, import_viem6.isAddress)(yieldOption.address)
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.loadPortfolio(state, chainId, {})
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, import_viem6.getAddress)(state.walletAddress);
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
- const response = await this.walletRequest(
3654
- state,
3655
- chainId,
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 = response.agents.find(
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: null
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
- const walletResponse = await this.walletRequest(
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 user = await this.resolveUser(state, chainId);
3698
- const response = await this.walletRequest(
3699
- state,
3700
- chainId,
3701
- `/users/${user.userId}/agents`
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
- if (!Array.isArray(response?.agents)) {
3704
- throw this.invalidResponse("agent list");
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 response.agents) {
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" || options.asset && options.asset !== asset) {
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
- const walletResponse = await this.walletRequest(
3714
- state,
3715
- chainId,
3716
- `/users/${user.userId}/agents/${agent.agentId}/wallet`
3717
- );
3718
- if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
3719
- throw this.invalidResponse("agent wallet");
3720
- }
3721
- const context = {
3722
- user,
3723
- agent,
3724
- wallet: walletResponse.agentWallet,
3725
- asset
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
- return Promise.all(
3731
- contexts.map(
3732
- (context) => this.loadPortfolioContext(state, chainId, context, options)
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 [snapshot, positions, historic, actions] = await Promise.all([
3738
- this.walletRequest(
3739
- state,
3740
- chainId,
3741
- `${this.agentPath(context, "snapshot")}${query({
3742
- shouldOnlyUseRecentValue: true,
3743
- shouldAllowStaleOnError: true
3744
- })}`
3745
- ),
3746
- this.walletRequest(
3747
- state,
3748
- chainId,
3749
- this.agentPath(context, "yield-positions")
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.walletRequest(
3752
- state,
3753
- chainId,
3754
- this.agentPath(context, "wallet/historic-position")
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.walletRequest(
3757
- state,
3758
- chainId,
3759
- this.agentPath(context, "actions")
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, import_viem6.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
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
- const response = await this.walletRequest(
3795
- state,
3796
- chainId,
3797
- `${this.agentPath(context, "snapshot")}${query({
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
- throw this.mapApiError(error);
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
- return this.transactionExecutor(state, chainId, transaction);
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, import_viem6.getAddress)(state.walletAddress);
3872
- const walletClient = (0, import_viem6.createWalletClient)({
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, import_viem6.custom)(state.provider)
4841
+ transport: (0, import_viem8.custom)(state.provider)
3876
4842
  });
3877
- const publicClient = (0, import_viem6.createPublicClient)({
4843
+ const walletChainClient = (0, import_viem8.createPublicClient)({
3878
4844
  chain: import_chains3.base,
3879
- transport: (0, import_viem6.custom)(state.provider)
4845
+ transport: (0, import_viem8.custom)(state.provider)
3880
4846
  });
3881
4847
  await ensureWalletOnChain(
3882
- publicClient,
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, import_viem6.getAddress)(transaction.to),
4855
+ to: (0, import_viem8.getAddress)(transaction.to),
3890
4856
  data: transaction.data,
3891
4857
  value: BigInt(transaction.value)
3892
4858
  });
3893
- const receipt = await publicClient.waitForTransactionReceipt({
3894
- hash,
3895
- confirmations: 1
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 publicClient = (0, import_viem6.createPublicClient)({
3913
- chain: import_chains3.base,
3914
- transport: (0, import_viem6.custom)(state.provider)
3915
- });
3916
- const receipt = await publicClient.waitForTransactionReceipt({
3917
- hash: transactionHash,
3918
- confirmations: 1
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, import_viem6.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem6.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_viem6.getAddress)(transaction.from) !== (0, import_viem6.getAddress)(state.walletAddress)) {
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, import_viem6.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
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 import_viem7 = require("viem");
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, import_viem7.parseUnits)(tokenBalance.amount, decimals) : 0n;
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
- if (position2.amountRaw !== void 0) {
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 import_viem11 = require("viem");
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 import_viem9 = require("viem");
5253
+ var import_viem11 = require("viem");
4279
5254
 
4280
5255
  // src/lib/permit2-batch.ts
4281
- var import_viem8 = require("viem");
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, import_viem8.parseAbi)([
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, import_viem9.keccak256)((0, import_viem9.toBytes)(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
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, import_viem9.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
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, import_viem9.keccak256)(body.serializedTransaction))
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, import_viem9.isAddressEqual)(previous.from, i.owner) || !(0, import_viem9.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
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 relayer = await getSponsorRelayerAddress({
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, relayer)
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(pub, wallet, chainId);
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 import_viem10 = require("viem");
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, import_viem10.toHex)(chainId)] ?? caps?.[String(chainId)];
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, import_viem10.encodeFunctionData)({
4618
- abi: import_viem10.erc20Abi,
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, import_viem10.toHex)(chainId),
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.zyfaiRpcUrls = config.zyfaiRpcUrls;
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) => (0, import_viem11.createPublicClient)({
5978
+ getPublicClient: (cid) => this.getPaidRpcClient(cid),
5979
+ getWalletChainClient: (cid) => (0, import_viem13.createPublicClient)({
4906
5980
  chain: VIEM_CHAIN2[cid],
4907
- transport: (0, import_viem11.custom)(provider)
5981
+ transport: (0, import_viem13.custom)(provider)
4908
5982
  }),
4909
- getWalletClient: (cid) => (0, import_viem11.createWalletClient)({
5983
+ getWalletClient: (cid) => (0, import_viem13.createWalletClient)({
4910
5984
  account: owner,
4911
5985
  chain: VIEM_CHAIN2[cid],
4912
- transport: (0, import_viem11.custom)(provider)
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) => (0, import_viem11.createPublicClient)({
6031
+ getPublicClient: (cid) => this.getPaidRpcClient(cid),
6032
+ getWalletChainClient: (cid) => (0, import_viem13.createPublicClient)({
4956
6033
  chain: VIEM_CHAIN2[cid],
4957
- transport: (0, import_viem11.custom)(provider)
6034
+ transport: (0, import_viem13.custom)(provider)
4958
6035
  }),
4959
- getWalletClient: (cid) => (0, import_viem11.createWalletClient)({
6036
+ getWalletClient: (cid) => (0, import_viem13.createWalletClient)({
4960
6037
  account: owner,
4961
6038
  chain: VIEM_CHAIN2[cid],
4962
- transport: (0, import_viem11.custom)(provider)
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(key2, this.zyfaiRpcUrls, this.referralSource);
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: when the app did not supply its own
5409
- * callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
5410
- * token deposit, this is the wallet's first Permit2 deposit for that token. We send
5411
- * the one-time (user-paid) Permit2 approval via `approvePermit2()` and
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
- return withFailureReporting(
6876
+ const result = await withFailureReporting(
5629
6877
  this.apiKey,
5630
6878
  agent.id,
5631
- () => agent.withdraw(state, chainId, token, amount),
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 eligibleAgents) {
6890
+ for (const agent of withdrawalAgents) {
5640
6891
  try {
5641
- results2[agent.id] = await agent.withdraw(state, chainId, token);
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 < plans.length; i++) {
5728
- const p = plans[i];
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.withdraw(
5732
- state,
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
- redistributeShare(plans, i, failedAmount);
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.requireState();
5788
- const chainId = this.requireChainId();
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 this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
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 this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
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
- const earningsResults = await Promise.all(
5863
- entries.map(async ([id, agent]) => {
5864
- const e = await this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
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
- let totalEarnings = 0;
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 refreshed = await Promise.all(
5893
- [...this.getActiveAgents().entries()].map(
5894
- async ([id, agent]) => [id, await refreshAgent(agent)]
5895
- )
5896
- );
5897
- for (const [id, earnings] of refreshed) {
5898
- agentEarnings[id] = earnings;
5899
- totalEarnings += Number(earnings.lifetimeEarnings);
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 { totalEarnings: String(totalEarnings), agentEarnings };
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.requireState();
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
- return this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
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
- const page = await this.readAgent(
6084
- agent,
6085
- "history",
6086
- () => agent.getHistory(state, chainId, agentFilters),
6087
- agentFilters
6088
- );
6089
- return { agentId: agent.id, page };
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 { agentId: id, page } of perAgent) {
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
- * Approves exactly the pending deposit amount. Another approval is required
6176
- * for a later deposit once this allowance has been consumed. Resolves after
6177
- * one confirmation so the subsequent deposit attempt sees the new allowance.
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 publicClient = (0, import_viem11.createPublicClient)({
7493
+ const walletChainClient = (0, import_viem13.createPublicClient)({
6195
7494
  chain: VIEM_CHAIN2[chainId],
6196
- transport: (0, import_viem11.custom)(provider)
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, import_viem11.createWalletClient)({
7499
+ const wallet = (0, import_viem13.createWalletClient)({
6200
7500
  account: state.walletAddress,
6201
7501
  chain: VIEM_CHAIN2[chainId],
6202
- transport: (0, import_viem11.custom)(provider)
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 publicClient.waitForTransactionReceipt({
6213
- hash,
6214
- confirmations: 1
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
- return hash;
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 import_viem12 = require("viem");
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, import_viem12.getAddress)(accountAddress),
7791
+ address: (0, import_viem14.getAddress)(accountAddress),
6452
7792
  chainId: numericChainId(chainId),
6453
7793
  domain,
6454
7794
  uri,