@owney/sdk 0.7.25-beta.10 → 0.7.25-beta.12

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.js CHANGED
@@ -1,3 +1,93 @@
1
+ // src/lib/funding-evidence.ts
2
+ import { decodeEventLog, decodeFunctionData, erc20Abi, parseAbi } from "viem";
3
+ function notifyFunding(callback, evidence) {
4
+ try {
5
+ void Promise.resolve(callback?.({ ...evidence })).catch(() => {
6
+ });
7
+ } catch {
8
+ }
9
+ }
10
+ var fullWithdrawalAbi = parseAbi(["function withdrawAllAssetToUser(address recipient, address asset)"]);
11
+ var withdrawalEventAbi = parseAbi(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
12
+ function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress) {
13
+ 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;
14
+ try {
15
+ const decoded = decodeFunctionData({ abi: fullWithdrawalAbi, data: transaction.input });
16
+ return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
22
+ if (receipt.status !== "success") return { ...evidence, transfer: "failed", delivery: "failed" };
23
+ let amount = 0n;
24
+ const seen = /* @__PURE__ */ new Set();
25
+ const attributed = /* @__PURE__ */ new Map();
26
+ const agent = evidence.agentWallet?.toLowerCase();
27
+ const owner = evidence.walletAddress.toLowerCase();
28
+ const logIndexes = [];
29
+ const transferLogs = [];
30
+ const zeroTransferLogs = [];
31
+ for (const log of [...receipt.logs].sort((a, b) => a.logIndex - b.logIndex)) {
32
+ if (log.address.toLowerCase() !== tokenAddress.toLowerCase()) continue;
33
+ if (seen.has(log.logIndex)) continue;
34
+ seen.add(log.logIndex);
35
+ try {
36
+ const event = decodeEventLog({ abi: erc20Abi, data: log.data, topics: log.topics });
37
+ if (event.eventName !== "Transfer") continue;
38
+ const { from, to, value } = event.args;
39
+ if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
40
+ zeroTransferLogs.push(log.logIndex);
41
+ let matched = 0n;
42
+ if (direction === "deposit") {
43
+ if (to.toLowerCase() === agent) matched = value;
44
+ } else {
45
+ const sender = from.toLowerCase();
46
+ const recipient = to.toLowerCase();
47
+ const available = sender === agent ? value : attributed.get(sender) ?? 0n;
48
+ const carried = available < value ? available : value;
49
+ if (sender !== agent) attributed.set(sender, available - carried);
50
+ if (recipient === owner) matched = carried;
51
+ else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
52
+ }
53
+ if (matched === 0n) continue;
54
+ amount += matched;
55
+ transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
56
+ logIndexes.push(log.logIndex);
57
+ } catch {
58
+ }
59
+ }
60
+ const expected = /^\d+$/.test(evidence.intendedAmount) ? BigInt(evidence.intendedAmount) : void 0;
61
+ const emptyPayout = direction === "withdraw" && evidence.agentId === "yieldseeker" && amount === 0n && zeroTransferLogs.length > 0 && receipt.logs.some((log) => {
62
+ if (log.address.toLowerCase() !== agent) return false;
63
+ try {
64
+ const { args } = decodeEventLog({ abi: withdrawalEventAbi, data: log.data, topics: log.topics });
65
+ return args.owner.toLowerCase() === owner && args.recipient.toLowerCase() === owner && args.token.toLowerCase() === tokenAddress.toLowerCase() && args.amount === 0n;
66
+ } catch {
67
+ return false;
68
+ }
69
+ });
70
+ if (emptyPayout) return {
71
+ ...evidence,
72
+ blockNumber: receipt.blockNumber.toString(),
73
+ confirmedAmount: "0",
74
+ logIndexes: zeroTransferLogs,
75
+ transferLogs: zeroTransferLogs.map((logIndex) => ({ logIndex, amountRaw: "0" })),
76
+ transfer: "failed",
77
+ delivery: "failed"
78
+ };
79
+ const delivered = amount > 0n && (expected === void 0 || amount >= expected);
80
+ return {
81
+ ...evidence,
82
+ blockNumber: receipt.blockNumber.toString(),
83
+ logIndexes,
84
+ transferLogs,
85
+ ...amount > 0n ? { confirmedAmount: amount.toString() } : {},
86
+ transfer: delivered ? "confirmed" : "unknown",
87
+ delivery: direction === "withdraw" ? delivered ? "confirmed" : "unknown" : "not-applicable"
88
+ };
89
+ }
90
+
1
91
  // src/lib/deposit-batch-callback.ts
2
92
  var batchCallbacks = /* @__PURE__ */ new WeakMap();
3
93
  var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
@@ -212,9 +302,72 @@ var AgentReads = class {
212
302
  }
213
303
  };
214
304
 
305
+ // src/agents/zyfai/zyfai.withdraw-submission.ts
306
+ var listenersByClient = /* @__PURE__ */ new WeakMap();
307
+ function observeWithdrawalSubmission(sdk, chainId, token) {
308
+ const client = sdk.httpClient;
309
+ if (!client || typeof client.get !== "function" || typeof client.post !== "function")
310
+ return { attempted: () => void 0, rejected: () => false, request: () => void 0, stop: () => {
311
+ } };
312
+ let listeners = listenersByClient.get(client);
313
+ if (!listeners) {
314
+ listeners = /* @__PURE__ */ new Set();
315
+ listenersByClient.set(client, listeners);
316
+ const observers = listeners;
317
+ for (const method of ["get", "post"]) {
318
+ const original = client[method];
319
+ client[method] = async function(...args) {
320
+ const [url, options] = args;
321
+ const withdrawal = method === "get" && url === "/users/withdraw" || method === "post" && url === "/users/partial-withdraw";
322
+ const payload = method === "get" ? options?.params : options;
323
+ const matching = withdrawal ? [...observers].filter((observer) => observer.chainId === payload?.chainId && observer.token === payload?.tokenSymbol) : [];
324
+ const startedAt = Date.now();
325
+ const request2 = {
326
+ attempted: true,
327
+ method: method === "get" ? "GET" : "POST",
328
+ path: method === "get" ? "/users/withdraw" : "/users/partial-withdraw"
329
+ };
330
+ const notify = (outcome) => {
331
+ if (outcome === "rejected" && matching.length !== 1) return;
332
+ for (const observer of matching) {
333
+ try {
334
+ observer.notify(outcome, { ...request2 });
335
+ } catch {
336
+ }
337
+ }
338
+ };
339
+ notify("attempted");
340
+ try {
341
+ return await original.apply(this, args);
342
+ } catch (error) {
343
+ const message = error instanceof Error ? error.message : "";
344
+ 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";
345
+ if (["unauthorized", "forbidden", "rate-limited"].includes(request2.failure)) notify("rejected");
346
+ throw error;
347
+ } finally {
348
+ request2.elapsedMs = Math.max(0, Date.now() - startedAt);
349
+ notify("finished");
350
+ }
351
+ };
352
+ }
353
+ }
354
+ let submitted = false;
355
+ let rejected = false;
356
+ let request = { attempted: false };
357
+ const listener = { chainId, token, notify: (outcome, evidence) => {
358
+ request = evidence;
359
+ if (outcome === "attempted") submitted = true;
360
+ else if (outcome === "rejected") rejected = true;
361
+ } };
362
+ listeners.add(listener);
363
+ return { attempted: () => submitted, rejected: () => rejected, request: () => ({ ...request }), stop: () => {
364
+ listeners.delete(listener);
365
+ } };
366
+ }
367
+
215
368
  // src/agents/zyfai/zyfai.agent.ts
216
369
  import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
217
- import { parseAbi } from "viem";
370
+ import { formatUnits, parseAbi as parseAbi2 } from "viem";
218
371
  import { base, arbitrum, mainnet } from "viem/chains";
219
372
 
220
373
  // src/types/config.ts
@@ -387,7 +540,11 @@ function mapPendingAllocations(raw2) {
387
540
  }
388
541
  function mapBalances(raw2, _chainId, smartWallet) {
389
542
  const portfolio = raw2.portfolio;
390
- const portfolioByChain = portfolio.portfolioByChain ?? {};
543
+ if (!portfolio?.portfolioByChain || typeof portfolio.portfolioByChain !== "object")
544
+ throw new Error("Zyfai balance snapshot is unavailable.");
545
+ const portfolioByChain = portfolio.portfolioByChain;
546
+ const fetchedAt = Date.now();
547
+ const provenance = portfolio;
391
548
  let totalBalance = 0;
392
549
  const tokens = [];
393
550
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
@@ -396,7 +553,8 @@ function mapBalances(raw2, _chainId, smartWallet) {
396
553
  symbol,
397
554
  { balance, balanceWithFee, decimals }
398
555
  ] of Object.entries(tokensBySymbol ?? {})) {
399
- const amount = hexToDecimal(balanceWithFee ?? balance ?? "0", decimals);
556
+ if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
557
+ const amount = hexToDecimal(balanceWithFee ?? balance, decimals);
400
558
  totalBalance += amount;
401
559
  tokens.push({
402
560
  chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
@@ -446,6 +604,15 @@ function mapBalances(raw2, _chainId, smartWallet) {
446
604
  totalBalanceAsset: "usdc",
447
605
  tokens,
448
606
  positions,
607
+ readMetadata: tokens.map((token) => ({
608
+ chainId: token.chainId,
609
+ asset: token.asset,
610
+ fetchedAt,
611
+ providerUpdatedAt: typeof provenance.updatedAt === "string" ? provenance.updatedAt : void 0,
612
+ snapshotId: provenance.snapshotId === void 0 ? void 0 : String(provenance.snapshotId),
613
+ blockNumber: provenance.blockNumber === void 0 ? void 0 : String(provenance.blockNumber),
614
+ positionsStatus: Array.isArray(portfolio.positions) ? "available" : "unavailable"
615
+ })),
449
616
  ...pendingAllocations ? { pendingAllocations } : {}
450
617
  };
451
618
  }
@@ -786,28 +953,6 @@ function mapDailyEarnings(raw2, chainId, tokenSymbol) {
786
953
  return { walletAddress: raw2.walletAddress, chainId, assets };
787
954
  }
788
955
 
789
- // src/agents/zyfai/zyfai.withdraw-amount.ts
790
- var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
791
- function topicToAddress(topic) {
792
- return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
793
- }
794
- function extractWithdrawnAmount(logs, recipient, token, decimals) {
795
- const wantRecipient = recipient.toLowerCase();
796
- const wantToken = token.toLowerCase();
797
- let total = 0n;
798
- let matched = false;
799
- for (const log of logs) {
800
- if (log.address.toLowerCase() !== wantToken) continue;
801
- if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC) continue;
802
- if (topicToAddress(log.topics[2]) !== wantRecipient) continue;
803
- if (!log.data || log.data === "0x") continue;
804
- total += BigInt(log.data);
805
- matched = true;
806
- }
807
- if (!matched) return void 0;
808
- return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
809
- }
810
-
811
956
  // src/agents/zyfai/zyfai.history-cursor.ts
812
957
  var InvalidHistoryCursorError = class extends Error {
813
958
  code = "invalid_cursor";
@@ -1234,7 +1379,7 @@ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
1234
1379
  }
1235
1380
 
1236
1381
  // src/agents/zyfai/zyfai.agent.ts
1237
- var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi([
1382
+ var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi2([
1238
1383
  "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
1239
1384
  ]);
1240
1385
  var WETH_ADDRESS_BY_CHAIN = {
@@ -1293,10 +1438,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
1293
1438
  connectAuthPromise = null;
1294
1439
  rpcUrls;
1295
1440
  publicClients = /* @__PURE__ */ new Map();
1296
- // txHash -> resolved withdrawn amount. Receipts are immutable, so a
1297
- // successful decode is cached forever; failures are NOT cached so a transient
1298
- // RPC error retries on the next fetch.
1299
- withdrawAmountCache = /* @__PURE__ */ new Map();
1441
+ // Receipt-derived amounts and log identities, scoped to the owner and asset.
1442
+ // Failures remain retryable; successful immutable evidence has a bounded cache.
1443
+ fundingHistoryCache = /* @__PURE__ */ new Map();
1300
1444
  earningsReads = /* @__PURE__ */ new Map();
1301
1445
  earningsRefreshes = /* @__PURE__ */ new Map();
1302
1446
  earningsSnapshot = null;
@@ -1324,8 +1468,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
1324
1468
  return client;
1325
1469
  }
1326
1470
  /**
1327
- * Fills in the true withdrawn amount for `Withdraw` entries by decoding the
1328
- * on-chain `Transfer` that lands in the user's wallet. The Zyfai history
1471
+ * Resolves funding amounts and log identities from token receipts. Withdrawals
1472
+ * must travel from this agent wallet to its owner; top-ups must reach the agent.
1473
+ * The Zyfai history
1329
1474
  * payload only reports the *remaining* position for a partial withdraw (see
1330
1475
  * the note in `mapEntries`), so without this the UI shows the leftover
1331
1476
  * balance instead of the amount withdrawn.
@@ -1337,48 +1482,62 @@ var ZyfaiAgent = class _ZyfaiAgent {
1337
1482
  * Entries with no symbol (older payload shapes) fall back to USDC to
1338
1483
  * preserve prior behavior.
1339
1484
  *
1340
- * Mutates `entries` in place. Runs all lookups concurrently and is tolerant
1341
- * of RPC failures: an entry whose amount can't be resolved keeps
1342
- * `amount: undefined`, which consumers render as a hidden/retryable row
1343
- * rather than a wrong number.
1485
+ * Mutates `entries` in place, sharing receipt requests within the page. RPC
1486
+ * failures leave provider top-ups unchanged and withdrawal amounts unknown.
1344
1487
  */
1345
- async resolveWithdrawnAmounts(entries, chainId, recipient) {
1346
- const withdrawals = entries.filter(
1347
- (e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
1488
+ async resolveFundingTransfers(entries, chainId, recipient, smartWallet, tokenSymbol) {
1489
+ const movements = entries.filter(
1490
+ (e) => (e.action === "Withdraw" || e.action === "Top up") && e.transactions[0]?.txHashes[0]
1348
1491
  );
1349
- if (withdrawals.length === 0) return;
1492
+ if (movements.length === 0) return;
1350
1493
  const client = this.getPublicClient(chainId);
1494
+ const receipts = /* @__PURE__ */ new Map();
1351
1495
  await Promise.all(
1352
- withdrawals.map(async (entry) => {
1496
+ movements.map(async (entry) => {
1353
1497
  const tx = entry.transactions[0];
1354
1498
  const txHash = tx.txHashes[0];
1355
- const rawSymbol = tx.tokenSymbol;
1499
+ const rawSymbol = tx.tokenSymbol ?? tokenSymbol;
1356
1500
  const normalizedSymbol = rawSymbol ? rawSymbol.toLowerCase() === "eth" ? "WETH" : rawSymbol.toUpperCase() : "USDC";
1357
1501
  const asset = SupportedAssets.find(
1358
1502
  (a) => a.chainId === chainId && a.symbol === normalizedSymbol
1359
- ) ?? SupportedAssets.find(
1360
- (a) => a.chainId === chainId && a.symbol === "USDC"
1361
1503
  );
1362
1504
  if (!asset) return;
1363
- const cacheKey = `${txHash}:${asset.address}`;
1364
- const cached = this.withdrawAmountCache.get(cacheKey);
1505
+ tx.tokenSymbol = asset.symbol;
1506
+ tx.tokenAddress = asset.address;
1507
+ tx.agentWallet = smartWallet;
1508
+ entry.id = `${chainId}:${txHash.toLowerCase()}:${entry.action}:${asset.address.toLowerCase()}:${smartWallet.toLowerCase()}`;
1509
+ if (!/^0x[0-9a-f]{64}$/i.test(txHash)) return;
1510
+ const cacheKey = `${entry.id}:${recipient.toLowerCase()}`;
1511
+ const cached = this.fundingHistoryCache.get(cacheKey);
1365
1512
  if (cached !== void 0) {
1366
- tx.amount = cached;
1513
+ Object.assign(tx, cached);
1367
1514
  return;
1368
1515
  }
1369
1516
  try {
1370
- const receipt = await client.getTransactionReceipt({
1371
- hash: txHash
1372
- });
1373
- const amount = extractWithdrawnAmount(
1374
- receipt.logs,
1375
- recipient,
1376
- asset.address,
1377
- asset.decimals
1378
- );
1379
- if (amount !== void 0) {
1380
- this.withdrawAmountCache.set(cacheKey, amount);
1381
- tx.amount = amount;
1517
+ if (!receipts.has(txHash)) receipts.set(txHash, client.getTransactionReceipt({ hash: txHash }));
1518
+ const receipt = await receipts.get(txHash);
1519
+ const evidence = inspectFundingReceipt(receipt, {
1520
+ agentId: "zyfai",
1521
+ walletAddress: recipient,
1522
+ chainId,
1523
+ token: asset.symbol,
1524
+ agentWallet: smartWallet,
1525
+ intendedAmount: "MAX",
1526
+ txHash,
1527
+ transfer: "unknown",
1528
+ registration: "not-applicable",
1529
+ delivery: "unknown"
1530
+ }, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
1531
+ if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
1532
+ Object.assign(tx, {
1533
+ amount: formatUnits(BigInt(evidence.confirmedAmount), asset.decimals),
1534
+ amountRaw: evidence.confirmedAmount,
1535
+ blockNumber: evidence.blockNumber,
1536
+ logIndexes: evidence.logIndexes,
1537
+ transferLogs: evidence.transferLogs
1538
+ });
1539
+ if (this.fundingHistoryCache.size >= 256) this.fundingHistoryCache.delete(this.fundingHistoryCache.keys().next().value);
1540
+ this.fundingHistoryCache.set(cacheKey, { ...tx });
1382
1541
  }
1383
1542
  } catch {
1384
1543
  }
@@ -1467,7 +1626,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1467
1626
  * Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
1468
1627
  * cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
1469
1628
  * we drop the cache and try again — the second attempt has no cache entry,
1470
- * so it falls through to a real SIWE prompt. Zyfai collapses every 4xx into
1629
+ * so it falls through to a real SIWE prompt. Zyfai labels every HTTP 401 with
1471
1630
  * an `"Unauthorized: Invalid API key"` error so we can't distinguish revoked
1472
1631
  * JWT from a misconfigured API key; we retry once either way, and if the
1473
1632
  * second attempt still fails the original error propagates.
@@ -1500,6 +1659,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
1500
1659
  await this.connectAuth(state, chainId);
1501
1660
  }
1502
1661
  }
1662
+ /** A rejected execution session must not be reused by the next foreground attempt. */
1663
+ invalidateRejectedWithdrawalSession(error, state, chainId, authToken) {
1664
+ const message = error instanceof Error ? error.message : "";
1665
+ if (!/(?:^|: )Unauthorized: Invalid API key$/.test(message)) return;
1666
+ const internals = this.sdk;
1667
+ if (typeof authToken !== "string" || internals.httpClient.authToken !== authToken || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== chainId) return;
1668
+ clearSession(state.walletAddress, chainId);
1669
+ internals.httpClient.clearAuthToken();
1670
+ internals.authenticatedUserId = null;
1671
+ this.connectedChainId = null;
1672
+ }
1503
1673
  /**
1504
1674
  * Sync newly-available protocols onto an existing user's profile at wallet
1505
1675
  * connection. Zyfai's own protocol sync (`updateUserProtocols`) runs only when
@@ -2045,7 +2215,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2045
2215
  await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
2046
2216
  return redeployed.address;
2047
2217
  }
2048
- async deposit(state, chainId, amount, asset, depositCallback) {
2218
+ async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
2049
2219
  try {
2050
2220
  const validChainId = isValidChainId(chainId);
2051
2221
  await this.ensureConnected(state, validChainId);
@@ -2054,17 +2224,48 @@ var ZyfaiAgent = class _ZyfaiAgent {
2054
2224
  this.getAddress(),
2055
2225
  validChainId
2056
2226
  );
2227
+ notifyFunding(onProgress, {
2228
+ agentId: this.id,
2229
+ chainId,
2230
+ token: asset,
2231
+ walletAddress: state.walletAddress,
2232
+ agentWallet: smartWallet,
2233
+ intendedAmount: amount,
2234
+ transfer: "pending",
2235
+ registration: "pending",
2236
+ delivery: "not-applicable"
2237
+ });
2057
2238
  const txHash = await depositCallback(smartWallet, validChainId, amount);
2058
- const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
2239
+ let evidence2 = {
2240
+ agentId: this.id,
2241
+ chainId,
2242
+ token: asset,
2243
+ walletAddress: state.walletAddress,
2244
+ agentWallet: smartWallet,
2245
+ intendedAmount: amount,
2246
+ txHash,
2247
+ transfer: "pending",
2248
+ registration: "pending",
2249
+ delivery: "not-applicable"
2250
+ };
2251
+ notifyFunding(onProgress, evidence2);
2059
2252
  try {
2253
+ const receipt = await this.getPublicClient(validChainId).getTransactionReceipt({ hash: txHash });
2254
+ const token = SupportedAssets.find((token2) => token2.chainId === chainId && token2.symbol === asset);
2255
+ evidence2 = inspectFundingReceipt(receipt, evidence2, token.address, "deposit");
2256
+ } catch {
2257
+ evidence2 = { ...evidence2, transfer: "unknown" };
2258
+ }
2259
+ notifyFunding(onProgress, evidence2);
2260
+ try {
2261
+ const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
2060
2262
  await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
2061
- } catch (logError) {
2062
- console.warn(
2063
- "[owney-sdk] Deposit landed on-chain but logDeposit failed (non-fatal):",
2064
- logError
2065
- );
2263
+ evidence2 = { ...evidence2, registration: "confirmed" };
2264
+ } catch {
2265
+ evidence2 = { ...evidence2, registration: "unknown" };
2066
2266
  }
2067
- return { txHash, smartWallet, amount };
2267
+ notifyFunding(onProgress, evidence2);
2268
+ return { txHash, smartWallet, amount, evidence: evidence2 };
2068
2269
  }
2069
2270
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
2070
2271
  const raw2 = await this.sdk.depositFunds(
@@ -2074,22 +2275,88 @@ var ZyfaiAgent = class _ZyfaiAgent {
2074
2275
  asset,
2075
2276
  "aggressive"
2076
2277
  );
2077
- return mapDeposit(raw2);
2278
+ let evidence = {
2279
+ agentId: this.id,
2280
+ chainId,
2281
+ token: asset,
2282
+ walletAddress: state.walletAddress,
2283
+ agentWallet: raw2.smartWallet,
2284
+ intendedAmount: amount,
2285
+ txHash: raw2.txHash,
2286
+ transfer: "unknown",
2287
+ registration: "unknown",
2288
+ delivery: "not-applicable"
2289
+ };
2290
+ notifyFunding(onProgress, evidence);
2291
+ try {
2292
+ const receipt = await this.getPublicClient(validChainId).getTransactionReceipt({ hash: raw2.txHash });
2293
+ const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
2294
+ evidence = inspectFundingReceipt(receipt, evidence, token.address, "deposit");
2295
+ } catch {
2296
+ }
2297
+ notifyFunding(onProgress, evidence);
2298
+ return { ...mapDeposit(raw2), evidence };
2078
2299
  } catch (error) {
2079
2300
  throw error;
2080
2301
  }
2081
2302
  }
2082
2303
  // --- IAgent: Fund operations ---
2083
- async withdraw(state, chainId, token, amount) {
2304
+ async withdraw(state, chainId, token, amount, onProgress) {
2084
2305
  const validChainId = isValidChainId(chainId);
2085
- await this.ensureConnected(state, validChainId);
2086
- const raw2 = await this.sdk.withdrawFunds(
2087
- this.getAddress(),
2088
- validChainId,
2089
- amount,
2090
- token
2091
- );
2306
+ try {
2307
+ await this.ensureConnected(state, validChainId);
2308
+ } catch (error) {
2309
+ notifyFunding(onProgress, {
2310
+ agentId: this.id,
2311
+ chainId,
2312
+ token,
2313
+ walletAddress: state.walletAddress,
2314
+ intendedAmount: amount ?? "MAX",
2315
+ transfer: "failed",
2316
+ registration: "not-applicable",
2317
+ delivery: "failed"
2318
+ });
2319
+ throw error;
2320
+ }
2321
+ const observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2322
+ const authToken = this.sdk.httpClient?.authToken;
2323
+ let raw2;
2324
+ try {
2325
+ raw2 = await this.sdk.withdrawFunds(this.getAddress(), validChainId, amount, token);
2326
+ } catch (error) {
2327
+ this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
2328
+ if (observation.attempted() !== void 0) {
2329
+ const failed = observation.attempted() === false || observation.rejected();
2330
+ notifyFunding(onProgress, {
2331
+ agentId: this.id,
2332
+ chainId,
2333
+ token,
2334
+ walletAddress: state.walletAddress,
2335
+ intendedAmount: amount ?? "MAX",
2336
+ transfer: failed ? "failed" : "unknown",
2337
+ registration: "not-applicable",
2338
+ delivery: failed ? "failed" : "unknown",
2339
+ withdrawalRequest: observation.request()
2340
+ });
2341
+ }
2342
+ throw error;
2343
+ } finally {
2344
+ observation.stop();
2345
+ }
2092
2346
  if (!raw2.success) {
2347
+ const unresolved = !!raw2.txHash || /already|pending|queued|in.progress/i.test(raw2.message ?? "");
2348
+ notifyFunding(onProgress, {
2349
+ agentId: this.id,
2350
+ chainId,
2351
+ token,
2352
+ walletAddress: state.walletAddress,
2353
+ intendedAmount: amount ?? "MAX",
2354
+ txHash: raw2.txHash,
2355
+ transfer: unresolved ? "unknown" : "failed",
2356
+ registration: "not-applicable",
2357
+ delivery: unresolved ? "unknown" : "failed",
2358
+ withdrawalRequest: observation.request()
2359
+ });
2093
2360
  throw new OwneyError(
2094
2361
  "WITHDRAW_FAILED",
2095
2362
  raw2.message || "Zyfai withdraw failed.",
@@ -2097,13 +2364,45 @@ var ZyfaiAgent = class _ZyfaiAgent {
2097
2364
  this.id
2098
2365
  );
2099
2366
  }
2100
- return mapWithdraw(raw2);
2367
+ const evidence = {
2368
+ agentId: this.id,
2369
+ chainId,
2370
+ token,
2371
+ walletAddress: state.walletAddress,
2372
+ intendedAmount: amount ?? "MAX",
2373
+ txHash: raw2.txHash,
2374
+ transfer: "pending",
2375
+ registration: "not-applicable",
2376
+ delivery: "pending",
2377
+ withdrawalRequest: observation.request()
2378
+ };
2379
+ notifyFunding(onProgress, evidence);
2380
+ return { ...mapWithdraw(raw2), evidence };
2381
+ }
2382
+ /** Registration recovery never signs, transfers, activates, or refreshes authentication. */
2383
+ async retryDepositRegistration(state, evidence) {
2384
+ const internals = this.sdk;
2385
+ if (!internals.httpClient.authToken || internals.authenticatedUserId === null || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== evidence.chainId) {
2386
+ throw new OwneyError("NOT_CONNECTED", "Reconnect the wallet before recovering deposit registration.");
2387
+ }
2388
+ if (evidence.transfer !== "confirmed" || !evidence.txHash) {
2389
+ throw new OwneyError("AGENT_API_ERROR", "Deposit transfer must be verified before registration recovery.");
2390
+ }
2391
+ const cid = isValidChainId(evidence.chainId);
2392
+ const tokenAddress = evidence.token === "WETH" ? WETH_ADDRESS_BY_CHAIN[cid] : void 0;
2393
+ await this.sdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
2394
+ return { ...evidence, registration: "confirmed" };
2101
2395
  }
2102
2396
  // --- IAgent: Portfolio reads ---
2397
+ async getFundingWallet(state, chainId) {
2398
+ return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
2399
+ }
2103
2400
  async getBalances(state, chainId) {
2401
+ const requestedAt = Date.now();
2104
2402
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
2105
2403
  const raw2 = await this.sdk.getPortfolio(this.getAddress());
2106
- return mapBalances(raw2, validChainId, smartWallet);
2404
+ const balance = mapBalances(raw2, validChainId, smartWallet);
2405
+ return { ...balance, readMetadata: balance.readMetadata?.map((read2) => ({ ...read2, requestedAt })) };
2107
2406
  }
2108
2407
  earningsKey(state, chainId, smartWallet) {
2109
2408
  return JSON.stringify([
@@ -2176,7 +2475,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2176
2475
  /**
2177
2476
  * Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
2178
2477
  * takes lowercase `assetType` and denominates WETH as "eth" (the same
2179
- * spelling `resolveWithdrawnAmounts` normalizes on the way in). Anything
2478
+ * spelling `resolveFundingTransfers` normalizes on the way in). Anything
2180
2479
  * unrecognised returns undefined so the request stays unfiltered rather
2181
2480
  * than silently narrowing to nothing.
2182
2481
  */
@@ -2195,6 +2494,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
2195
2494
  }
2196
2495
  async getHistory(state, chainId, options) {
2197
2496
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
2497
+ return this.readHistory(state, smartWallet, validChainId, options);
2498
+ }
2499
+ /** Recovery reads never enter connectAuth or wallet activation. */
2500
+ async getFundingHistory(state, chainId, options) {
2501
+ const smartWallet = await this.getFundingWallet(state, chainId);
2502
+ return this.readHistory(state, smartWallet, isValidChainId(chainId), options);
2503
+ }
2504
+ async readHistory(state, smartWallet, validChainId, options) {
2198
2505
  const limit = options?.limit ?? 10;
2199
2506
  const assetType = _ZyfaiAgent.toZyfaiAssetType(options?.tokenSymbol);
2200
2507
  const cursor = options?.cursor ? decodeHistoryCursor(options.cursor) : null;
@@ -2236,9 +2543,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
2236
2543
  rawOffset: firstOvershoot ? firstOvershoot.rawIdx : offset
2237
2544
  }) : void 0;
2238
2545
  const data = mapEntries(pageData, validChainId);
2239
- await this.resolveWithdrawnAmounts(data, validChainId, state.walletAddress);
2546
+ await this.resolveFundingTransfers(data, validChainId, state.walletAddress, smartWallet, options?.tokenSymbol);
2547
+ const seen = /* @__PURE__ */ new Set();
2548
+ const unique = data.filter((entry) => {
2549
+ if (!entry.id || !entry.transactions[0]?.transferLogs?.length) return true;
2550
+ if (seen.has(entry.id)) return false;
2551
+ seen.add(entry.id);
2552
+ return true;
2553
+ });
2240
2554
  return {
2241
- data,
2555
+ data: unique,
2242
2556
  hasMore,
2243
2557
  nextCursor
2244
2558
  };
@@ -2292,7 +2606,7 @@ import {
2292
2606
  createWalletClient as createWalletClient2,
2293
2607
  custom as custom2,
2294
2608
  encodeFunctionData,
2295
- erc20Abi,
2609
+ erc20Abi as erc20Abi2,
2296
2610
  getAddress as getAddress2,
2297
2611
  isAddress as isAddress2
2298
2612
  } from "viem";
@@ -2913,7 +3227,7 @@ var YieldseekerApiClient = class {
2913
3227
  };
2914
3228
 
2915
3229
  // src/agents/yieldseeker/yieldseeker.mapper.ts
2916
- import { formatUnits, isAddress } from "viem";
3230
+ import { formatUnits as formatUnits2, isAddress } from "viem";
2917
3231
 
2918
3232
  // src/lib/helpers/snapshot-apy.ts
2919
3233
  var DAY_MS = 864e5;
@@ -2980,10 +3294,10 @@ function raw(value, endpoint) {
2980
3294
  return BigInt(value);
2981
3295
  }
2982
3296
  function decimal(value, decimals, endpoint) {
2983
- return formatUnits(raw(value, endpoint), decimals);
3297
+ return formatUnits2(raw(value, endpoint), decimals);
2984
3298
  }
2985
3299
  function usd(rawAmount, decimals, price) {
2986
- return Number(formatUnits(rawAmount, decimals)) * price;
3300
+ return Number(formatUnits2(rawAmount, decimals)) * price;
2987
3301
  }
2988
3302
  function percent(value) {
2989
3303
  const result = Number(value);
@@ -3079,7 +3393,17 @@ function mapYieldseekerBalances(contexts) {
3079
3393
  totalBalanceAsset: "usdc",
3080
3394
  assetBalances,
3081
3395
  tokens,
3082
- positions
3396
+ positions,
3397
+ readMetadata: contexts.map((context) => ({
3398
+ chainId: context.agent.chainId,
3399
+ asset: context.asset,
3400
+ fetchedAt: context.fetchedAt,
3401
+ readVersion: context.readVersion,
3402
+ providerUpdatedAt: context.snapshot.updatedDate,
3403
+ snapshotId: String(context.snapshot.agentWalletSnapshotId),
3404
+ netDepositsRaw: context.snapshot.netDepositsBase,
3405
+ positionsStatus: context.positionsUnavailable ? "unavailable" : "available"
3406
+ }))
3083
3407
  };
3084
3408
  }
3085
3409
  function mapYieldseekerEarnings(contexts) {
@@ -3091,7 +3415,7 @@ function mapYieldseekerEarnings(contexts) {
3091
3415
  chain: "BASE",
3092
3416
  chainId: 8453,
3093
3417
  asset: context.asset,
3094
- amount: formatUnits(amount, context.snapshot.baseAssetDecimals)
3418
+ amount: formatUnits2(amount, context.snapshot.baseAssetDecimals)
3095
3419
  });
3096
3420
  lifetimeEarnings += usd(
3097
3421
  amount,
@@ -3193,6 +3517,7 @@ function transactionHashes(details) {
3193
3517
  function actionEntry(action) {
3194
3518
  if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
3195
3519
  return {
3520
+ id: `yieldseeker:action:${action.agentActionId}`,
3196
3521
  agent: "yieldseeker",
3197
3522
  action: actionType(action.actionType),
3198
3523
  date: action.createdDate,
@@ -3222,7 +3547,7 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddres
3222
3547
  const owner = ownerAddress.toLowerCase();
3223
3548
  const agentWallet = wallet.walletAddress.toLowerCase();
3224
3549
  const baseAsset = agent.assetAddress.toLowerCase();
3225
- if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
3550
+ if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset || raw(movement.assetAmount, "historic position") <= 0n) {
3226
3551
  return void 0;
3227
3552
  }
3228
3553
  let action;
@@ -3247,6 +3572,12 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddres
3247
3572
  transactions: [
3248
3573
  {
3249
3574
  txHashes: [movement.transactionHash],
3575
+ tokenAddress: movement.assetAddress,
3576
+ agentWallet: wallet.walletAddress,
3577
+ logIndexes: [movement.logIndex],
3578
+ transferLogs: [{ logIndex: movement.logIndex, amountRaw: movement.assetAmount }],
3579
+ amountRaw: movement.assetAmount,
3580
+ blockNumber: String(movement.blockNumber),
3250
3581
  chainId: agent.chainId,
3251
3582
  tokenSymbol: asset,
3252
3583
  amount: decimal(
@@ -3292,7 +3623,8 @@ function mapYieldseekerHistory(contexts, options) {
3292
3623
  ungrouped.push(entry);
3293
3624
  continue;
3294
3625
  }
3295
- const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}`;
3626
+ const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}:${tx.tokenAddress?.toLowerCase() ?? ""}:${tx.agentWallet?.toLowerCase() ?? ""}`;
3627
+ entry.id = key2;
3296
3628
  const previous = grouped.get(key2);
3297
3629
  if (!previous) {
3298
3630
  grouped.set(key2, entry);
@@ -3305,14 +3637,52 @@ function mapYieldseekerHistory(contexts, options) {
3305
3637
  }
3306
3638
  previous.positions.push(...entry.positions);
3307
3639
  previous.transactions.push(...entry.transactions);
3640
+ } else if ((entry.action === "Top up" || entry.action === "Withdraw") && tx.amountRaw) {
3641
+ const target = previous.transactions[0];
3642
+ const known = new Set(target.logIndexes ?? []);
3643
+ if (tx.logIndexes?.some((index) => !known.has(index))) {
3644
+ target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
3645
+ target.amount = formatUnits2(
3646
+ BigInt(target.amountRaw),
3647
+ YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
3648
+ );
3649
+ target.logIndexes = [...known, ...tx.logIndexes.filter((index) => !known.has(index))];
3650
+ target.transferLogs = [...target.transferLogs ?? [], ...(tx.transferLogs ?? []).filter((log) => !known.has(log.logIndex))];
3651
+ }
3652
+ }
3653
+ }
3654
+ const values = [...grouped.values(), ...ungrouped].filter((entry) => {
3655
+ const tx = entry.transactions[0];
3656
+ 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());
3657
+ });
3658
+ let boundary;
3659
+ const scope = JSON.stringify([
3660
+ options.ownerAddress.toLowerCase(),
3661
+ contexts.map((context) => `${context.agent.chainId}:${context.wallet.walletAddress.toLowerCase()}:${context.asset}`).sort(),
3662
+ options.fromDate ?? null
3663
+ ]);
3664
+ if (options.cursor) {
3665
+ try {
3666
+ boundary = JSON.parse(decodeURIComponent(atob(options.cursor)));
3667
+ if (!boundary || boundary.scope !== scope || typeof boundary.id !== "string" || !Number.isFinite(Date.parse(boundary.date)) || !Number.isFinite(Date.parse(boundary.toDate))) throw new Error();
3668
+ } catch {
3669
+ throw new InvalidHistoryCursorError("Invalid Yieldseeker history cursor.");
3308
3670
  }
3309
3671
  }
3310
- 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));
3672
+ const toDate = boundary?.toDate ?? options.toDate ?? (/* @__PURE__ */ new Date()).toISOString();
3673
+ 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 ?? ""));
3674
+ const data = filtered.slice(0, Math.max(1, options.limit));
3675
+ const hasMore = filtered.length > data.length;
3676
+ const last = data.at(-1);
3311
3677
  return {
3312
- data: filtered.slice(0, options.limit),
3313
- // v1 returns the whole action/movement collection and defines no cursor.
3314
- // Report a terminal page so callers never loop over the same prefix.
3315
- hasMore: false
3678
+ data,
3679
+ hasMore,
3680
+ ...hasMore && last ? { nextCursor: btoa(encodeURIComponent(JSON.stringify({
3681
+ date: last.date,
3682
+ id: last.id ?? "",
3683
+ toDate,
3684
+ scope
3685
+ }))) } : {}
3316
3686
  };
3317
3687
  }
3318
3688
  function mapYieldseekerProfile(address, contexts) {
@@ -3487,7 +3857,7 @@ var YieldseekerAgent = class {
3487
3857
  this.assertAsset(targetAsset);
3488
3858
  await this.ensureAgent(state, chainId, targetAsset);
3489
3859
  }
3490
- async deposit(state, chainId, amount, asset, depositCallback) {
3860
+ async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
3491
3861
  this.assertChain(chainId);
3492
3862
  this.assertAsset(asset);
3493
3863
  if (BigInt(amount) <= 0n) {
@@ -3501,7 +3871,20 @@ var YieldseekerAgent = class {
3501
3871
  const context = await this.ensureAgent(state, chainId, asset);
3502
3872
  const generation = this.readGeneration;
3503
3873
  const movement = this.snapshotMovement(state, chainId, context, BigInt(amount));
3874
+ notifyFunding(onProgress, {
3875
+ agentId: this.id,
3876
+ chainId,
3877
+ token: asset,
3878
+ walletAddress: state.walletAddress,
3879
+ agentWallet: context.wallet.walletAddress,
3880
+ intendedAmount: amount,
3881
+ transfer: "pending",
3882
+ registration: "not-applicable",
3883
+ delivery: "not-applicable"
3884
+ });
3504
3885
  let txHash;
3886
+ let receipt;
3887
+ 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" });
3505
3888
  if (depositCallback) {
3506
3889
  provideDepositVerificationContext(depositCallback, {
3507
3890
  agentId: "yieldseeker",
@@ -3514,30 +3897,57 @@ var YieldseekerAgent = class {
3514
3897
  chainId,
3515
3898
  amount
3516
3899
  );
3517
- await this.waitForReceipt(state, chainId, txHash);
3900
+ submitted(txHash);
3901
+ receipt = await this.waitForReceipt(state, chainId, txHash);
3518
3902
  } else {
3519
3903
  txHash = await this.submitTransaction(state, chainId, {
3520
3904
  from: getAddress2(state.walletAddress),
3521
3905
  to: YIELDSEEKER_ASSET_METADATA[asset].address,
3522
3906
  data: encodeFunctionData({
3523
- abi: erc20Abi,
3907
+ abi: erc20Abi2,
3524
3908
  functionName: "transfer",
3525
3909
  args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
3526
3910
  }),
3527
3911
  value: "0",
3528
3912
  chainId
3913
+ }, submitted, (value) => {
3914
+ receipt = value;
3529
3915
  });
3530
3916
  }
3531
3917
  if (this.readGeneration === generation) {
3532
3918
  void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
3533
3919
  }
3534
- return {
3535
- txHash,
3536
- smartWallet: context.wallet.walletAddress,
3537
- amount
3920
+ 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" };
3921
+ const evidence = receipt ? inspectFundingReceipt(receipt, base5, YIELDSEEKER_ASSET_METADATA[asset].address, "deposit") : base5;
3922
+ notifyFunding(onProgress, evidence);
3923
+ return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
3924
+ }
3925
+ async withdraw(state, chainId, asset, amount, onProgress) {
3926
+ let ownerTransferAttempted = false;
3927
+ let evidence = {
3928
+ agentId: this.id,
3929
+ chainId,
3930
+ token: asset,
3931
+ walletAddress: state.walletAddress,
3932
+ intendedAmount: amount ?? "MAX",
3933
+ transfer: "pending",
3934
+ registration: "not-applicable",
3935
+ delivery: "pending"
3538
3936
  };
3937
+ try {
3938
+ return await this.withdrawInternal(state, chainId, asset, amount, (next) => {
3939
+ evidence = { ...evidence, ...next };
3940
+ notifyFunding(onProgress, evidence);
3941
+ }, () => {
3942
+ ownerTransferAttempted = true;
3943
+ });
3944
+ } catch (error) {
3945
+ if (!ownerTransferAttempted || error instanceof OwneyError && error.code === "AGENT_TRANSACTION_REVERTED")
3946
+ notifyFunding(onProgress, { ...evidence, transfer: "failed", delivery: "failed" });
3947
+ throw error;
3948
+ }
3539
3949
  }
3540
- async withdraw(state, chainId, asset, amount) {
3950
+ async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted) {
3541
3951
  this.assertChain(chainId);
3542
3952
  this.assertAsset(asset);
3543
3953
  if (amount !== void 0 && BigInt(amount) <= 0n) {
@@ -3567,17 +3977,32 @@ var YieldseekerAgent = class {
3567
3977
  context
3568
3978
  );
3569
3979
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3570
- const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
3571
- ([address]) => address.toLowerCase() === metadata.address.toLowerCase()
3572
- );
3573
- const idle = BigInt(idleEntry?.[1] ?? "0");
3980
+ const readIdle = async () => {
3981
+ const balance = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
3982
+ address: metadata.address,
3983
+ abi: erc20Abi2,
3984
+ functionName: "balanceOf",
3985
+ args: [getAddress2(context.wallet.walletAddress)]
3986
+ }), chainId, "eth_call", this.id);
3987
+ this.assertSession(generation, chainId, asset);
3988
+ return balance;
3989
+ };
3990
+ const idle = await readIdle();
3574
3991
  const deployed = portfolio.positions.reduce(
3575
3992
  (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
3576
3993
  0n
3577
3994
  );
3578
3995
  const totalAvailable = idle + deployed;
3579
3996
  const requested = amount === void 0 ? totalAvailable : BigInt(amount);
3580
- const plannedMovement = this.snapshotMovement(state, chainId, context, -requested);
3997
+ if (requested === 0n) {
3998
+ this.invalidateFundingRead(state, chainId, asset);
3999
+ throw new OwneyError(
4000
+ "WITHDRAW_INSUFFICIENT_BALANCE",
4001
+ "No Yieldseeker funds are available to withdraw.",
4002
+ { asset, available: "0" },
4003
+ this.id
4004
+ );
4005
+ }
3581
4006
  if (requested > totalAvailable) {
3582
4007
  throw new OwneyError(
3583
4008
  "WITHDRAW_INSUFFICIENT_BALANCE",
@@ -3622,7 +4047,22 @@ var YieldseekerAgent = class {
3622
4047
  remaining: remaining.toString()
3623
4048
  });
3624
4049
  }
4050
+ const availableForOwner = confirmedMovement ? await readIdle() : idle;
4051
+ if (availableForOwner === 0n || amount !== void 0 && availableForOwner < requested) {
4052
+ this.invalidateFundingRead(state, chainId, asset);
4053
+ throw new OwneyError(
4054
+ "WITHDRAW_INSUFFICIENT_BALANCE",
4055
+ "Yieldseeker funds are not available for the owner transfer.",
4056
+ { asset, requested: amount ?? "MAX", available: availableForOwner.toString() },
4057
+ this.id
4058
+ );
4059
+ }
4060
+ const payout = amount === void 0 ? availableForOwner : requested;
4061
+ const plannedMovement = this.snapshotMovement(state, chainId, context, -payout);
3625
4062
  const account = getAddress2(state.walletAddress);
4063
+ let receipt;
4064
+ 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" };
4065
+ onOwnerTransferAttempted?.();
3626
4066
  const txHash = await this.submitTransaction(state, chainId, {
3627
4067
  from: account,
3628
4068
  to: getAddress2(context.wallet.walletAddress),
@@ -3637,14 +4077,21 @@ var YieldseekerAgent = class {
3637
4077
  }),
3638
4078
  value: "0",
3639
4079
  chainId
4080
+ }, (hash) => notifyFunding(onProgress, { ...base5, txHash: hash }), (value) => {
4081
+ receipt = value;
3640
4082
  });
3641
4083
  confirmedMovement = true;
3642
4084
  settlement = plannedMovement;
3643
- return {
3644
- txHash,
3645
- type: amount === void 0 ? "full" : "partial",
3646
- amount: requested.toString()
3647
- };
4085
+ const evidence = receipt ? inspectFundingReceipt(receipt, { ...base5, txHash }, metadata.address, "withdraw") : { ...base5, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
4086
+ notifyFunding(onProgress, evidence);
4087
+ if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
4088
+ throw new OwneyError(
4089
+ "WITHDRAW_INSUFFICIENT_BALANCE",
4090
+ "The Yieldseeker withdrawal completed without transferring funds.",
4091
+ { asset, available: "0", txHash },
4092
+ this.id
4093
+ );
4094
+ return { txHash, type: amount === void 0 ? "full" : "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
3648
4095
  } finally {
3649
4096
  if (confirmedMovement && this.readGeneration === generation) {
3650
4097
  void this.refreshSnapshotAfterMovement(
@@ -3657,9 +4104,39 @@ var YieldseekerAgent = class {
3657
4104
  }
3658
4105
  }
3659
4106
  }
3660
- async getBalances(state, chainId) {
4107
+ async getBalances(state, chainId, asset) {
4108
+ this.assertChain(chainId);
4109
+ if (asset !== void 0) this.assertAsset(asset);
4110
+ return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, { asset, allowMissingPositions: true }));
4111
+ }
4112
+ invalidateFundingRead(state, chainId, asset, refreshUntil = Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS) {
4113
+ this.assertAsset(asset);
4114
+ const key2 = this.contextKey(state, chainId, asset);
4115
+ const failure = this.snapshotFailures.get(key2);
4116
+ this.advancePortfolioVersion(key2);
4117
+ if (failure && failure.retryAt > Date.now()) this.snapshotFailures.set(key2, failure);
4118
+ if (!this.snapshotMovements.has(key2)) this.snapshotMovements.set(key2, null);
4119
+ const until = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
4120
+ this.reconcileUntil.set(key2, Math.max(this.reconcileUntil.get(key2) ?? 0, until));
4121
+ this.activityRefreshUntil.set(key2, Math.max(this.activityRefreshUntil.get(key2) ?? 0, until));
4122
+ }
4123
+ endFundingBalanceRefresh(state, chainId, asset, read2) {
3661
4124
  this.assertChain(chainId);
3662
- return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
4125
+ this.assertAsset(asset);
4126
+ const key2 = this.contextKey(state, chainId, asset);
4127
+ const cached = this.readCache.get(`snapshot:${key2}`);
4128
+ const snapshot = cached?.value?.agentSnapshot;
4129
+ 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;
4130
+ this.snapshotMovements.delete(key2);
4131
+ this.reconcileUntil.delete(key2);
4132
+ cached.expiresAt = cached.fetchedAt + YIELDSEEKER_PORTFOLIO_CACHE_MS;
4133
+ return true;
4134
+ }
4135
+ async getFundingWallet(state, chainId, asset) {
4136
+ this.assertAsset(asset);
4137
+ const context = this.agentContexts.get(this.contextKey(state, chainId, asset));
4138
+ if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
4139
+ return context.wallet.walletAddress;
3663
4140
  }
3664
4141
  async getEarnings(state, chainId) {
3665
4142
  this.assertChain(chainId);
@@ -3696,6 +4173,7 @@ var YieldseekerAgent = class {
3696
4173
  );
3697
4174
  return mapYieldseekerHistory(contexts, {
3698
4175
  limit: options?.limit ?? 10,
4176
+ cursor: options?.cursor,
3699
4177
  ownerAddress: state.walletAddress,
3700
4178
  vaultAddresses,
3701
4179
  ...options?.fromDate ? { fromDate: options.fromDate } : {},
@@ -3770,6 +4248,7 @@ var YieldseekerAgent = class {
3770
4248
  if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
3771
4249
  this.readCache.set(key2, {
3772
4250
  expiresAt: Date.now() + ttlMs,
4251
+ fetchedAt: Date.now(),
3773
4252
  value
3774
4253
  });
3775
4254
  }
@@ -3970,6 +4449,7 @@ var YieldseekerAgent = class {
3970
4449
  if (agent) {
3971
4450
  this.readCache.set(this.agentListKey(state, chainId), {
3972
4451
  expiresAt: Date.now() + YIELDSEEKER_AGENT_LIST_CACHE_MS,
4452
+ fetchedAt: Date.now(),
3973
4453
  value: {
3974
4454
  agents: [...agents, agent]
3975
4455
  }
@@ -4030,7 +4510,6 @@ var YieldseekerAgent = class {
4030
4510
  ...previous,
4031
4511
  epoch: this.movementEpochs.get(key2) ?? 0,
4032
4512
  delta: previous.delta + delta,
4033
- expectedBalance: previous.expectedBalance + delta,
4034
4513
  expectedNetDeposits: previous.expectedNetDeposits + delta
4035
4514
  };
4036
4515
  }
@@ -4043,7 +4522,6 @@ var YieldseekerAgent = class {
4043
4522
  epoch: this.movementEpochs.get(key2) ?? 0,
4044
4523
  delta,
4045
4524
  decimals: snapshot.baseAssetDecimals,
4046
- expectedBalance: BigInt(snapshot.totalValueBase) + delta,
4047
4525
  expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
4048
4526
  };
4049
4527
  }
@@ -4072,9 +4550,8 @@ var YieldseekerAgent = class {
4072
4550
  const movement = this.snapshotMovements.get(contextKey);
4073
4551
  const snapshot = response.agentSnapshot;
4074
4552
  if (movement && snapshot.baseAssetDecimals === movement.decimals && /^-?\d+$/.test(snapshot.netDepositsBase) && /^\d+$/.test(snapshot.totalValueBase)) {
4075
- const balance = BigInt(snapshot.totalValueBase);
4076
4553
  const netDeposits = BigInt(snapshot.netDepositsBase);
4077
- const reconciled = movement.delta > 0n ? balance >= movement.expectedBalance && netDeposits >= movement.expectedNetDeposits : balance <= movement.expectedBalance && netDeposits <= movement.expectedNetDeposits;
4554
+ const reconciled = movement.delta > 0n ? netDeposits >= movement.expectedNetDeposits : netDeposits <= movement.expectedNetDeposits;
4078
4555
  if (reconciled) {
4079
4556
  this.snapshotMovements.delete(contextKey);
4080
4557
  this.reconcileUntil.delete(contextKey);
@@ -4128,12 +4605,20 @@ var YieldseekerAgent = class {
4128
4605
  const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
4129
4606
  const parts = this.loadPortfolioParts(state, chainId, context, options, contextKey);
4130
4607
  try {
4131
- const [snapshot, portfolioParts] = await Promise.all([read2, parts]);
4608
+ let positionsUnavailable = false;
4609
+ const safeParts = options.allowMissingPositions ? parts.catch(() => {
4610
+ positionsUnavailable = true;
4611
+ return { positions: [] };
4612
+ }) : parts;
4613
+ const [snapshot, portfolioParts] = await Promise.all([read2, safeParts]);
4132
4614
  assertSession();
4133
4615
  if (this.portfolioVersion(contextKey) !== version) continue;
4134
4616
  return {
4135
4617
  ...context,
4136
4618
  snapshot: snapshot.agentSnapshot,
4619
+ fetchedAt: this.readCache.get(`snapshot:${contextKey}`)?.fetchedAt,
4620
+ readVersion: version,
4621
+ positionsUnavailable,
4137
4622
  ...portfolioParts
4138
4623
  };
4139
4624
  } catch (error) {
@@ -4324,9 +4809,11 @@ var YieldseekerAgent = class {
4324
4809
  this.id
4325
4810
  );
4326
4811
  }
4327
- async submitTransaction(state, chainId, transaction) {
4812
+ async submitTransaction(state, chainId, transaction, onSubmitted, onReceipt) {
4328
4813
  if (this.transactionExecutor) {
4329
- return this.transactionExecutor(state, chainId, transaction);
4814
+ const hash2 = await this.transactionExecutor(state, chainId, transaction);
4815
+ onSubmitted?.(hash2);
4816
+ return hash2;
4330
4817
  }
4331
4818
  this.assertTransaction(transaction, state, chainId);
4332
4819
  const account = getAddress2(state.walletAddress);
@@ -4351,6 +4838,7 @@ var YieldseekerAgent = class {
4351
4838
  data: transaction.data,
4352
4839
  value: BigInt(transaction.value)
4353
4840
  });
4841
+ onSubmitted?.(hash);
4354
4842
  const receipt = await withPaidRpcDiagnostics(
4355
4843
  () => this.getReceiptClient().waitForTransactionReceipt({
4356
4844
  hash,
@@ -4368,6 +4856,7 @@ var YieldseekerAgent = class {
4368
4856
  this.id
4369
4857
  );
4370
4858
  }
4859
+ onReceipt?.(receipt);
4371
4860
  return hash;
4372
4861
  }
4373
4862
  async waitForReceipt(state, chainId, transactionHash) {
@@ -4392,6 +4881,7 @@ var YieldseekerAgent = class {
4392
4881
  this.id
4393
4882
  );
4394
4883
  }
4884
+ return receipt;
4395
4885
  }
4396
4886
  assertTransaction(transaction, state, chainId) {
4397
4887
  if (!transaction || typeof transaction.from !== "string" || !isAddress2(transaction.from) || typeof transaction.to !== "string" || !isAddress2(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 || getAddress2(transaction.from) !== getAddress2(state.walletAddress)) {
@@ -4753,10 +5243,9 @@ import {
4753
5243
  } from "viem";
4754
5244
 
4755
5245
  // src/lib/permit2-batch.ts
4756
- import { parseAbi as parseAbi2, hashStruct } from "viem";
5246
+ import { parseAbi as parseAbi3, hashStruct } from "viem";
4757
5247
  var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
4758
5248
  var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
4759
- var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
4760
5249
  var ERC2612_PERMIT_TYPES = {
4761
5250
  Permit: [
4762
5251
  { name: "owner", type: "address" },
@@ -4766,7 +5255,7 @@ var ERC2612_PERMIT_TYPES = {
4766
5255
  { name: "deadline", type: "uint256" }
4767
5256
  ]
4768
5257
  };
4769
- var ERC2612_READ_ABI = parseAbi2([
5258
+ var ERC2612_READ_ABI = parseAbi3([
4770
5259
  "function name() view returns (string)",
4771
5260
  "function version() view returns (string)",
4772
5261
  "function nonces(address owner) view returns (uint256)"
@@ -4804,7 +5293,7 @@ var PERMIT_BATCH_TYPES = {
4804
5293
  { name: "amount", type: "uint256" }
4805
5294
  ]
4806
5295
  };
4807
- var PERMIT2_BATCH_ABI = parseAbi2([
5296
+ var PERMIT2_BATCH_ABI = parseAbi3([
4808
5297
  "struct TokenPermissions { address token; uint256 amount; }",
4809
5298
  "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
4810
5299
  "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
@@ -4888,6 +5377,22 @@ async function execute(i, key2, plan) {
4888
5377
  let body = initial;
4889
5378
  save(key2, body);
4890
5379
  try {
5380
+ if (body.erc2612Permit && !body.serializedPermitTransaction) {
5381
+ const preparedPermit = await postSponsorBatchTransfer({
5382
+ apiKey: i.apiKey,
5383
+ baseUrl: i.baseUrl,
5384
+ body
5385
+ });
5386
+ if (!preparedPermit.serializedPermitTransaction || !preparedPermit.permitTxHash || keccak256(preparedPermit.serializedPermitTransaction) !== preparedPermit.permitTxHash)
5387
+ throw new Error(
5388
+ "Sponsorship API did not return a valid prepared permit transaction."
5389
+ );
5390
+ body = {
5391
+ ...body,
5392
+ serializedPermitTransaction: preparedPermit.serializedPermitTransaction
5393
+ };
5394
+ save(key2, body);
5395
+ }
4891
5396
  if (!body.serializedTransaction) {
4892
5397
  const prepared = await postSponsorBatchTransfer({
4893
5398
  apiKey: i.apiKey,
@@ -4904,6 +5409,10 @@ async function execute(i, key2, plan) {
4904
5409
  };
4905
5410
  save(key2, body);
4906
5411
  }
5412
+ try {
5413
+ i.onSubmitted?.(keccak256(body.serializedTransaction));
5414
+ } catch {
5415
+ }
4907
5416
  const result = await postSponsorBatchTransfer({
4908
5417
  apiKey: i.apiKey,
4909
5418
  baseUrl: i.baseUrl,
@@ -4985,7 +5494,7 @@ async function execute(i, key2, plan) {
4985
5494
  });
4986
5495
  erc2612Permit = { ...unsignedPermit, signature: signature2 };
4987
5496
  }
4988
- const spender = erc2612Permit ? MULTICALL3_ADDRESS : await getSponsorRelayerAddress({
5497
+ const spender = await getSponsorRelayerAddress({
4989
5498
  apiKey: i.apiKey,
4990
5499
  baseUrl: i.baseUrl,
4991
5500
  chainId: i.chainId
@@ -5032,7 +5541,8 @@ function makeSponsoredTokenCallback(deps) {
5032
5541
  transfers,
5033
5542
  pub,
5034
5543
  wallet,
5035
- onApproved: deps.onApproved
5544
+ onApproved: deps.onApproved,
5545
+ onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
5036
5546
  });
5037
5547
  };
5038
5548
  const callback = makeVerificationAwareDepositCallback(
@@ -5118,7 +5628,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
5118
5628
  }
5119
5629
 
5120
5630
  // src/lib/sponsored-calls-deposit.ts
5121
- import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi2, toHex } from "viem";
5631
+ import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi3, toHex } from "viem";
5122
5632
  var DEFAULT_POLL_INTERVAL_MS = 1500;
5123
5633
  var DEFAULT_MAX_POLLS = 30;
5124
5634
  async function paymasterSupported(provider, owner, chainId) {
@@ -5164,7 +5674,7 @@ function makeSponsoredCallsCallback(deps) {
5164
5674
  to: token,
5165
5675
  value: "0x0",
5166
5676
  data: encodeFunctionData2({
5167
- abi: erc20Abi2,
5677
+ abi: erc20Abi3,
5168
5678
  functionName: "transfer",
5169
5679
  args: [transfer.to, BigInt(transfer.amount)]
5170
5680
  })
@@ -5226,6 +5736,12 @@ function makeSponsoredCallsCallback(deps) {
5226
5736
  params: [callsId]
5227
5737
  });
5228
5738
  const txHash = status?.receipts?.[0]?.transactionHash;
5739
+ if (txHash && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
5740
+ try {
5741
+ deps.onSubmitted?.(txHash, transfers);
5742
+ } catch {
5743
+ }
5744
+ }
5229
5745
  if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
5230
5746
  throw new OwneyError(
5231
5747
  "SPONSOR_REQUEST_FAILED",
@@ -5335,6 +5851,7 @@ var OwneySDK = class {
5335
5851
  paymasterServiceUrl;
5336
5852
  cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
5337
5853
  agentReads = new AgentReads();
5854
+ recoveredFundingReads = /* @__PURE__ */ new Set();
5338
5855
  activationChecks = /* @__PURE__ */ new Map();
5339
5856
  readAgent(agent, method, fetch2, params = null) {
5340
5857
  const key2 = JSON.stringify([
@@ -5448,8 +5965,8 @@ var OwneySDK = class {
5448
5965
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
5449
5966
  }
5450
5967
  /** Builds the default USDC batch callback for the connected wallet. */
5451
- getDefaultSponsoredCallback(onApproved) {
5452
- if (!onApproved && this.cachedSponsoredCallback)
5968
+ getDefaultSponsoredCallback(onApproved, onSubmitted) {
5969
+ if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
5453
5970
  return this.cachedSponsoredCallback;
5454
5971
  const provider = this.requireConnectedProvider();
5455
5972
  const owner = this.state.walletAddress;
@@ -5458,6 +5975,7 @@ var OwneySDK = class {
5458
5975
  baseUrl: this.routingApiBaseUrl,
5459
5976
  ownerAddress: owner,
5460
5977
  onApproved,
5978
+ onSubmitted,
5461
5979
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
5462
5980
  // Casts work around viem's chain-narrowed Client vs the generic
5463
5981
  // PublicClient/WalletClient param types — structurally identical at
@@ -5473,13 +5991,13 @@ var OwneySDK = class {
5473
5991
  transport: custom3(provider)
5474
5992
  })
5475
5993
  });
5476
- if (!onApproved) this.cachedSponsoredCallback = callback;
5994
+ if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
5477
5995
  return callback;
5478
5996
  }
5479
5997
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
5480
- getDefaultSponsoredCallsCallback(asset, onApproved) {
5998
+ getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
5481
5999
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
5482
- if (!onApproved && cached) return cached;
6000
+ if (!onApproved && !onSubmitted && cached) return cached;
5483
6001
  const provider = this.requireConnectedProvider();
5484
6002
  const callback = makeSponsoredCallsCallback({
5485
6003
  apiKey: this.apiKey,
@@ -5488,9 +6006,10 @@ var OwneySDK = class {
5488
6006
  ownerAddress: this.state.walletAddress,
5489
6007
  paymasterServiceUrl: this.paymasterServiceUrl,
5490
6008
  onApproved,
6009
+ onSubmitted,
5491
6010
  tokenAddressByChain: sponsoredTokensFor(asset)
5492
6011
  });
5493
- if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
6012
+ if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
5494
6013
  return callback;
5495
6014
  }
5496
6015
  /**
@@ -5499,8 +6018,8 @@ var OwneySDK = class {
5499
6018
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
5500
6019
  * single-use batch authorization instead of an EIP-3009 authorization.
5501
6020
  */
5502
- getDefaultWethSponsoredCallback(onApproved) {
5503
- if (!onApproved && this.cachedWethSponsoredCallback)
6021
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
6022
+ if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
5504
6023
  return this.cachedWethSponsoredCallback;
5505
6024
  const provider = this.requireConnectedProvider();
5506
6025
  const owner = this.state.walletAddress;
@@ -5509,6 +6028,7 @@ var OwneySDK = class {
5509
6028
  baseUrl: this.routingApiBaseUrl,
5510
6029
  ownerAddress: owner,
5511
6030
  onApproved,
6031
+ onSubmitted,
5512
6032
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
5513
6033
  // Casts work around viem's chain-narrowed Client vs the generic
5514
6034
  // PublicClient/WalletClient param types — structurally identical at
@@ -5524,7 +6044,7 @@ var OwneySDK = class {
5524
6044
  transport: custom3(provider)
5525
6045
  })
5526
6046
  });
5527
- if (!onApproved) this.cachedWethSponsoredCallback = callback;
6047
+ if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
5528
6048
  return callback;
5529
6049
  }
5530
6050
  getAgent(agentId) {
@@ -5534,6 +6054,9 @@ var OwneySDK = class {
5534
6054
  }
5535
6055
  return agent;
5536
6056
  }
6057
+ getActiveAgentIds() {
6058
+ return [...this.getActiveAgents().keys()].sort();
6059
+ }
5537
6060
  getActiveAgents() {
5538
6061
  const active = /* @__PURE__ */ new Map();
5539
6062
  for (const id of this.activeAgents) {
@@ -5828,12 +6351,23 @@ var OwneySDK = class {
5828
6351
  */
5829
6352
  async deposit(options) {
5830
6353
  const { amount, asset, depositCallback, agentId, onApproved } = options;
6354
+ const prepared = /* @__PURE__ */ new Map();
6355
+ const onProgress = options.onProgress ? (evidence) => {
6356
+ if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
6357
+ notifyFunding(options.onProgress, evidence);
6358
+ } : void 0;
6359
+ const onSubmitted = onProgress ? (hash, destinations) => {
6360
+ for (const destination of destinations) {
6361
+ const evidence = prepared.get(destination.to.toLowerCase());
6362
+ if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
6363
+ }
6364
+ } : void 0;
5831
6365
  if (BigInt(amount) === 0n) {
5832
6366
  return { agentResults: {} };
5833
6367
  }
5834
6368
  const state = this.requireState();
5835
6369
  const chainId = this.requireChainId();
5836
- const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
6370
+ const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted) : this.getDefaultSponsoredCallback(onApproved, onSubmitted));
5837
6371
  if (agentId) {
5838
6372
  if (this.isAgentDisabled(agentId)) {
5839
6373
  throw new OwneyError(
@@ -5852,7 +6386,8 @@ var OwneySDK = class {
5852
6386
  amount,
5853
6387
  asset,
5854
6388
  effectiveCallback,
5855
- depositCallback
6389
+ depositCallback,
6390
+ onProgress
5856
6391
  );
5857
6392
  }
5858
6393
  const totalAmount = BigInt(amount);
@@ -5916,7 +6451,7 @@ var OwneySDK = class {
5916
6451
  run: (callback) => withFailureReporting(
5917
6452
  this.apiKey,
5918
6453
  agent.id,
5919
- () => agent.deposit(state, chainId, amount2, asset, callback),
6454
+ () => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
5920
6455
  this.routingApiBaseUrl
5921
6456
  )
5922
6457
  })),
@@ -5953,7 +6488,8 @@ var OwneySDK = class {
5953
6488
  agentAmount,
5954
6489
  asset,
5955
6490
  effectiveCallback,
5956
- depositCallback
6491
+ depositCallback,
6492
+ onProgress
5957
6493
  );
5958
6494
  } catch (error) {
5959
6495
  if (Object.keys(agentResults).length === 0) throw error;
@@ -6000,17 +6536,11 @@ var OwneySDK = class {
6000
6536
  * propagates unchanged. Scoped per agent invocation so one agent's failure
6001
6537
  * doesn't force a retry of sibling agents in a multi-agent split.
6002
6538
  */
6003
- async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
6539
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
6004
6540
  const attempt = (callback) => withFailureReporting(
6005
6541
  this.apiKey,
6006
6542
  agent.id,
6007
- () => agent.deposit(
6008
- state,
6009
- chainId,
6010
- amount,
6011
- asset,
6012
- callback
6013
- ),
6543
+ () => onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
6014
6544
  this.routingApiBaseUrl
6015
6545
  );
6016
6546
  let approvalAttempted = false;
@@ -6190,15 +6720,81 @@ var OwneySDK = class {
6190
6720
  while (current && typeof current === "object" && !seen.has(current)) {
6191
6721
  seen.add(current);
6192
6722
  const candidate = current;
6193
- if (candidate.code === 4001 || candidate.code === "4001") return true;
6723
+ if (candidate.code === 4001 || candidate.code === "4001" || candidate.code === "ACTION_REJECTED") return true;
6194
6724
  const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
6195
- if (/user (?:rejected|denied)|rejected by user/i.test(message)) {
6725
+ if (/user (?:rejected|denied)|rejected by user|request rejected/i.test(message)) {
6196
6726
  return true;
6197
6727
  }
6198
6728
  current = candidate.cause;
6199
6729
  }
6200
6730
  return false;
6201
6731
  }
6732
+ captureReadScope(requestedChain) {
6733
+ const original = this.requireState();
6734
+ const chainId = this.requireChainId();
6735
+ if (requestedChain !== void 0 && requestedChain !== chainId) {
6736
+ throw new OwneyError("NO_ACTIVE_CHAIN", "Account read scope changed. Please retry.");
6737
+ }
6738
+ const wallet = original.walletAddress;
6739
+ return { state: { ...original }, chainId, assertCurrent: () => {
6740
+ if (this.state !== original || this.state.walletAddress !== wallet || this.state.chainId !== chainId)
6741
+ throw new OwneyError("NOT_CONNECTED", "Account read scope changed. Please retry.");
6742
+ } };
6743
+ }
6744
+ /** Revalidate a saved transfer against the authenticated account and actual receipt. */
6745
+ async inspectFunding(evidence, direction, options = {}) {
6746
+ const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
6747
+ if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase() || !evidence.txHash)
6748
+ throw new OwneyError("AGENT_API_ERROR", "Funding evidence does not match the connected account.");
6749
+ const agent = this.getAgent(evidence.agentId);
6750
+ const verifiedWallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
6751
+ const agentWallet = evidence.agentWallet ?? verifiedWallet;
6752
+ if (!agentWallet || agentWallet.toLowerCase() !== verifiedWallet.toLowerCase())
6753
+ throw new OwneyError("AGENT_API_ERROR", "Funding wallet does not match this account.");
6754
+ const token = SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token);
6755
+ if (!token) throw new OwneyError("CHAIN_UNSUPPORTED", "Unsupported funding asset.");
6756
+ const rpc = this.getPaidRpcClient(chainId);
6757
+ const receipt = await rpc.getTransactionReceipt({ hash: evidence.txHash });
6758
+ assertCurrent();
6759
+ let verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet }, token.address, direction);
6760
+ if (direction === "withdraw" && evidence.agentId === "yieldseeker" && verified.transfer === "unknown" && verified.confirmedAmount && BigInt(verified.confirmedAmount) > 0n) {
6761
+ const transaction = await rpc.getTransaction({ hash: evidence.txHash });
6762
+ assertCurrent();
6763
+ if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address))
6764
+ verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
6765
+ }
6766
+ const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
6767
+ if ((verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
6768
+ if (options.refreshBalance !== false)
6769
+ agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil);
6770
+ this.recoveredFundingReads.add(key2);
6771
+ if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.values().next().value);
6772
+ }
6773
+ return verified;
6774
+ }
6775
+ /** A display acknowledgement controls polling only; it never confirms a financial outcome. */
6776
+ endFundingBalanceRefresh(evidence, read2) {
6777
+ const { state, chainId } = this.captureReadScope(evidence.chainId);
6778
+ if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase() || read2.chainId !== chainId || read2.asset.toUpperCase() !== evidence.token) return false;
6779
+ return this.getAgent(evidence.agentId).endFundingBalanceRefresh?.(state, chainId, evidence.token, read2) ?? false;
6780
+ }
6781
+ /** History lookup for receipt recovery; adapters may not authenticate or sign here. */
6782
+ async getFundingHistory(evidence, fromDate, options) {
6783
+ const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
6784
+ if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase())
6785
+ throw new OwneyError("NOT_CONNECTED", "Funding account changed.");
6786
+ const agent = this.getAgent(evidence.agentId);
6787
+ if (!agent.getFundingHistory) return { data: [], hasMore: false };
6788
+ const filters = { tokenSymbol: evidence.token, fromDate, limit: 50, ...options };
6789
+ const page = await this.readAgent(agent, "history", () => agent.getFundingHistory(state, chainId, filters), filters);
6790
+ assertCurrent();
6791
+ return page;
6792
+ }
6793
+ async recoverDepositRegistration(evidence) {
6794
+ const verified = await this.inspectFunding(evidence, "deposit");
6795
+ if (verified.agentId !== "zyfai") return verified;
6796
+ return this.getAgent("zyfai").retryDepositRegistration(this.requireState(), verified);
6797
+ }
6202
6798
  // --- Fund operations ---
6203
6799
  /**
6204
6800
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -6210,17 +6806,66 @@ var OwneySDK = class {
6210
6806
  * @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
6211
6807
  */
6212
6808
  async withdraw(options) {
6809
+ const state = this.requireState();
6810
+ const chainId = this.requireChainId();
6811
+ const ids = options.agentId ? [options.agentId] : this.getActiveAgentIds().filter((id) => !options.excludeAgentIds?.includes(id));
6812
+ const attempted = /* @__PURE__ */ new Set();
6813
+ try {
6814
+ return await this.withdrawFromAgents(options, state, chainId, attempted);
6815
+ } catch (error) {
6816
+ for (const id of ids) if (!attempted.has(id)) {
6817
+ notifyFunding(options.onProgress, {
6818
+ agentId: id,
6819
+ chainId,
6820
+ token: options.asset,
6821
+ walletAddress: state.walletAddress,
6822
+ intendedAmount: options.amount ?? "MAX",
6823
+ transfer: "failed",
6824
+ registration: "not-applicable",
6825
+ delivery: "failed"
6826
+ });
6827
+ }
6828
+ throw error;
6829
+ }
6830
+ }
6831
+ async withdrawFromAgents(options, state, chainId, attempted) {
6213
6832
  const { asset, amount, agentId, onAgentResult } = options;
6214
6833
  const notifyAgentResult = (id, result) => {
6215
6834
  try {
6835
+ if (result.evidence) notifyFunding(options.onProgress, result.evidence);
6216
6836
  onAgentResult?.(id, result);
6217
6837
  } catch (error) {
6218
6838
  console.warn("Withdrawal result observer failed:", error);
6219
6839
  }
6220
6840
  };
6221
- const state = this.requireState();
6222
- const chainId = this.requireChainId();
6223
6841
  const token = asset;
6842
+ const submissionEvidence = /* @__PURE__ */ new Map();
6843
+ const submit = async (agent, requested2) => {
6844
+ attempted.add(agent.id);
6845
+ let evidence = {
6846
+ agentId: agent.id,
6847
+ chainId,
6848
+ token,
6849
+ walletAddress: state.walletAddress,
6850
+ intendedAmount: requested2 ?? "MAX",
6851
+ transfer: "pending",
6852
+ registration: "not-applicable",
6853
+ delivery: "pending"
6854
+ };
6855
+ const progress = (next) => {
6856
+ evidence = { ...evidence, ...next };
6857
+ submissionEvidence.set(agent.id, evidence);
6858
+ notifyFunding(options.onProgress, evidence);
6859
+ };
6860
+ progress(evidence);
6861
+ try {
6862
+ return await agent.withdraw(state, chainId, token, requested2, progress);
6863
+ } catch (error) {
6864
+ if (!evidence.txHash && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
6865
+ progress({ ...evidence, transfer: "failed", delivery: "failed" });
6866
+ throw error;
6867
+ }
6868
+ };
6224
6869
  const assetInfo = SupportedAssets.find(
6225
6870
  (a) => a.chainId === chainId && a.symbol === token
6226
6871
  );
@@ -6236,23 +6881,24 @@ var OwneySDK = class {
6236
6881
  const result = await withFailureReporting(
6237
6882
  this.apiKey,
6238
6883
  agent.id,
6239
- () => agent.withdraw(state, chainId, token, amount),
6884
+ () => submit(agent, amount),
6240
6885
  this.routingApiBaseUrl
6241
6886
  );
6242
6887
  notifyAgentResult(agent.id, result);
6243
6888
  return result;
6244
6889
  }
6245
- const eligibleAgents = this.getEligibleAgents(chainId, asset);
6890
+ const eligibleAgents = this.getEligibleAgents(chainId, asset).filter((agent) => !options.excludeAgentIds?.includes(agent.id));
6246
6891
  const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
6247
6892
  if (!amount) {
6248
6893
  const results2 = {};
6249
6894
  const agentErrors2 = {};
6250
6895
  for (const agent of withdrawalAgents) {
6251
6896
  try {
6252
- results2[agent.id] = await agent.withdraw(state, chainId, token);
6897
+ results2[agent.id] = await submit(agent);
6253
6898
  notifyAgentResult(agent.id, results2[agent.id]);
6254
6899
  } catch (err) {
6255
6900
  if (this.isUserRejectedWithdrawal(err)) throw err;
6901
+ if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
6256
6902
  console.error(`withdraw failed for agent "${agent.id}":`, err);
6257
6903
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
6258
6904
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
@@ -6271,6 +6917,13 @@ var OwneySDK = class {
6271
6917
  { asset, agentErrors: agentErrors2 }
6272
6918
  );
6273
6919
  }
6920
+ if (Object.keys(agentErrors2).length > 0) {
6921
+ throw new OwneyError(
6922
+ "WITHDRAW_PARTIAL_FAILURE",
6923
+ "Some agents could not complete the withdrawal.",
6924
+ { asset, partialResults: results2, agentErrors: agentErrors2 }
6925
+ );
6926
+ }
6274
6927
  const amountsKnown = Object.values(results2).every(
6275
6928
  (result) => typeof result.amount === "string" && /^\d+$/.test(result.amount)
6276
6929
  );
@@ -6280,7 +6933,7 @@ var OwneySDK = class {
6280
6933
  };
6281
6934
  }
6282
6935
  const requested = BigInt(amount);
6283
- const aggregated = await this.getBalances();
6936
+ const aggregated = await this.getBalances(void 0, { chainId, asset: token });
6284
6937
  const unavailableAgents = eligibleAgents.filter(
6285
6938
  (agent) => !(agent.id in aggregated.agentBalances)
6286
6939
  );
@@ -6342,12 +6995,7 @@ var OwneySDK = class {
6342
6995
  const p = orderedPlans[i];
6343
6996
  if (p.planned === 0n) continue;
6344
6997
  try {
6345
- results[p.agent.id] = await p.agent.withdraw(
6346
- state,
6347
- chainId,
6348
- token,
6349
- p.planned.toString()
6350
- );
6998
+ results[p.agent.id] = await submit(p.agent, p.planned.toString());
6351
6999
  notifyAgentResult(p.agent.id, results[p.agent.id]);
6352
7000
  } catch (err) {
6353
7001
  if (this.isUserRejectedWithdrawal(err)) throw err;
@@ -6362,7 +7010,8 @@ var OwneySDK = class {
6362
7010
  );
6363
7011
  const failedAmount = p.planned;
6364
7012
  p.planned = 0n;
6365
- redistributeShare(orderedPlans, i, failedAmount);
7013
+ if (submissionEvidence.get(p.agent.id)?.transfer === "failed")
7014
+ redistributeShare(orderedPlans, i, failedAmount);
6366
7015
  }
6367
7016
  }
6368
7017
  if (Object.keys(results).length === 0) {
@@ -6399,12 +7048,19 @@ var OwneySDK = class {
6399
7048
  * @param agentId - Optional. Agent to query. Omit for aggregated balances.
6400
7049
  * @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
6401
7050
  */
6402
- async getBalances(agentId) {
6403
- const state = this.requireState();
6404
- const chainId = this.requireChainId();
7051
+ async getBalances(agentId, options) {
7052
+ const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
7053
+ const asset = options?.asset;
7054
+ const readBalance = (agent) => this.readAgent(
7055
+ agent,
7056
+ "balances",
7057
+ () => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
7058
+ asset ? { asset } : null
7059
+ );
6405
7060
  if (agentId) {
6406
7061
  const agent = this.getAgent(agentId);
6407
- const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
7062
+ const result = await readBalance(agent);
7063
+ assertCurrent();
6408
7064
  return {
6409
7065
  ...result,
6410
7066
  balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
@@ -6415,7 +7071,7 @@ var OwneySDK = class {
6415
7071
  const entries = [...this.getActiveAgents().entries()];
6416
7072
  const balanceResults = await Promise.allSettled(
6417
7073
  entries.map(async ([id, agent]) => {
6418
- const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
7074
+ const b = await readBalance(agent);
6419
7075
  return [
6420
7076
  id,
6421
7077
  {
@@ -6425,6 +7081,7 @@ var OwneySDK = class {
6425
7081
  ];
6426
7082
  })
6427
7083
  );
7084
+ assertCurrent();
6428
7085
  let successCount = 0;
6429
7086
  const agentErrors = {};
6430
7087
  const agentRetryAt = {};
@@ -6692,12 +7349,13 @@ var OwneySDK = class {
6692
7349
  * @returns {OwneyAgentHistory} `data` (up to `limit` entries), `hasMore`, `nextCursor`.
6693
7350
  */
6694
7351
  async getHistory(options) {
6695
- const state = this.requireState();
6696
- const chainId = this.requireChainId();
7352
+ const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
6697
7353
  const { agentId, filters } = options ?? {};
6698
7354
  if (agentId) {
6699
7355
  const agent = this.getAgent(agentId);
6700
- return this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
7356
+ const page = await this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
7357
+ assertCurrent();
7358
+ return page;
6701
7359
  }
6702
7360
  const activeAgents = [...this.getActiveAgents().values()];
6703
7361
  const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
@@ -6707,20 +7365,35 @@ var OwneySDK = class {
6707
7365
  if (filters?.cursor && agentCursor === void 0) {
6708
7366
  return { agentId: agent.id, page: null };
6709
7367
  }
6710
- const agentFilters = { ...filters, cursor: agentCursor };
6711
- const page = await this.readAgent(
6712
- agent,
6713
- "history",
6714
- () => agent.getHistory(state, chainId, agentFilters),
6715
- agentFilters
6716
- );
6717
- return { agentId: agent.id, page };
7368
+ const agentFilters = { ...filters, cursor: agentCursor || void 0 };
7369
+ try {
7370
+ const page = await this.readAgent(
7371
+ agent,
7372
+ "history",
7373
+ () => agent.getHistory(state, chainId, agentFilters),
7374
+ agentFilters
7375
+ );
7376
+ return { agentId: agent.id, page };
7377
+ } catch (error) {
7378
+ return { agentId: agent.id, page: null, error };
7379
+ }
6718
7380
  })
6719
7381
  );
7382
+ assertCurrent();
7383
+ const agentErrors = {};
7384
+ const agentRetryAt = {};
6720
7385
  const merged = [];
6721
7386
  const nextCursorMap = {};
6722
7387
  let hasMore = false;
6723
- for (const { agentId: id, page } of perAgent) {
7388
+ for (const result of perAgent) {
7389
+ const { agentId: id, page } = result;
7390
+ if ("error" in result) {
7391
+ agentErrors[id] = result.error instanceof Error ? result.error.message : String(result.error);
7392
+ const delay = rateLimitDelay(result.error);
7393
+ if (delay !== void 0) agentRetryAt[id] = Date.now() + delay;
7394
+ nextCursorMap[id] = cursorMap[id] ?? "";
7395
+ hasMore = true;
7396
+ }
6724
7397
  if (!page) continue;
6725
7398
  merged.push(...page.data);
6726
7399
  if (page.hasMore && page.nextCursor) {
@@ -6740,6 +7413,7 @@ var OwneySDK = class {
6740
7413
  });
6741
7414
  return {
6742
7415
  data: merged,
7416
+ ...Object.keys(agentErrors).length ? { agentErrors, agentRetryAt } : {},
6743
7417
  hasMore,
6744
7418
  nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
6745
7419
  };