@owney/sdk 0.7.25-beta.10 → 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;
@@ -246,9 +336,72 @@ var AgentReads = class {
246
336
  }
247
337
  };
248
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
+
249
402
  // src/agents/zyfai/zyfai.agent.ts
250
403
  var import_sdk = require("@zyfai/sdk");
251
- var import_viem2 = require("viem");
404
+ var import_viem3 = require("viem");
252
405
  var import_chains = require("viem/chains");
253
406
 
254
407
  // src/types/config.ts
@@ -421,7 +574,11 @@ function mapPendingAllocations(raw2) {
421
574
  }
422
575
  function mapBalances(raw2, _chainId, smartWallet) {
423
576
  const portfolio = raw2.portfolio;
424
- 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;
425
582
  let totalBalance = 0;
426
583
  const tokens = [];
427
584
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
@@ -430,7 +587,8 @@ function mapBalances(raw2, _chainId, smartWallet) {
430
587
  symbol,
431
588
  { balance, balanceWithFee, decimals }
432
589
  ] of Object.entries(tokensBySymbol ?? {})) {
433
- 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);
434
592
  totalBalance += amount;
435
593
  tokens.push({
436
594
  chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
@@ -480,6 +638,15 @@ function mapBalances(raw2, _chainId, smartWallet) {
480
638
  totalBalanceAsset: "usdc",
481
639
  tokens,
482
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
+ })),
483
650
  ...pendingAllocations ? { pendingAllocations } : {}
484
651
  };
485
652
  }
@@ -820,28 +987,6 @@ function mapDailyEarnings(raw2, chainId, tokenSymbol) {
820
987
  return { walletAddress: raw2.walletAddress, chainId, assets };
821
988
  }
822
989
 
823
- // src/agents/zyfai/zyfai.withdraw-amount.ts
824
- var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
825
- function topicToAddress(topic) {
826
- return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
827
- }
828
- function extractWithdrawnAmount(logs, recipient, token, decimals) {
829
- const wantRecipient = recipient.toLowerCase();
830
- const wantToken = token.toLowerCase();
831
- let total = 0n;
832
- let matched = false;
833
- for (const log of logs) {
834
- if (log.address.toLowerCase() !== wantToken) continue;
835
- if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC) continue;
836
- if (topicToAddress(log.topics[2]) !== wantRecipient) continue;
837
- if (!log.data || log.data === "0x") continue;
838
- total += BigInt(log.data);
839
- matched = true;
840
- }
841
- if (!matched) return void 0;
842
- return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
843
- }
844
-
845
990
  // src/agents/zyfai/zyfai.history-cursor.ts
846
991
  var InvalidHistoryCursorError = class extends Error {
847
992
  code = "invalid_cursor";
@@ -1130,7 +1275,7 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
1130
1275
  }
1131
1276
 
1132
1277
  // src/lib/paid-rpc.ts
1133
- var import_viem = require("viem");
1278
+ var import_viem2 = require("viem");
1134
1279
  var PAID_RPC_RETRY_COUNT = 3;
1135
1280
  var PAID_RPC_RETRY_DELAY_MS = 1e3;
1136
1281
  function configuredRpcProxyBaseUrl(baseUrl) {
@@ -1166,9 +1311,9 @@ function createPaidRpcClient(chain, rpcUrls) {
1166
1311
  rpcUrls,
1167
1312
  chain.id
1168
1313
  );
1169
- return (0, import_viem.createPublicClient)({
1314
+ return (0, import_viem2.createPublicClient)({
1170
1315
  chain,
1171
- transport: (0, import_viem.http)(url, {
1316
+ transport: (0, import_viem2.http)(url, {
1172
1317
  retryCount: PAID_RPC_RETRY_COUNT,
1173
1318
  retryDelay: PAID_RPC_RETRY_DELAY_MS
1174
1319
  })
@@ -1265,7 +1410,7 @@ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
1265
1410
  }
1266
1411
 
1267
1412
  // src/agents/zyfai/zyfai.agent.ts
1268
- var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem2.parseAbi)([
1413
+ var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem3.parseAbi)([
1269
1414
  "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
1270
1415
  ]);
1271
1416
  var WETH_ADDRESS_BY_CHAIN = {
@@ -1324,10 +1469,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
1324
1469
  connectAuthPromise = null;
1325
1470
  rpcUrls;
1326
1471
  publicClients = /* @__PURE__ */ new Map();
1327
- // txHash -> resolved withdrawn amount. Receipts are immutable, so a
1328
- // successful decode is cached forever; failures are NOT cached so a transient
1329
- // RPC error retries on the next fetch.
1330
- 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();
1331
1475
  earningsReads = /* @__PURE__ */ new Map();
1332
1476
  earningsRefreshes = /* @__PURE__ */ new Map();
1333
1477
  earningsSnapshot = null;
@@ -1355,8 +1499,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
1355
1499
  return client;
1356
1500
  }
1357
1501
  /**
1358
- * Fills in the true withdrawn amount for `Withdraw` entries by decoding the
1359
- * 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
1360
1505
  * payload only reports the *remaining* position for a partial withdraw (see
1361
1506
  * the note in `mapEntries`), so without this the UI shows the leftover
1362
1507
  * balance instead of the amount withdrawn.
@@ -1368,48 +1513,62 @@ var ZyfaiAgent = class _ZyfaiAgent {
1368
1513
  * Entries with no symbol (older payload shapes) fall back to USDC to
1369
1514
  * preserve prior behavior.
1370
1515
  *
1371
- * Mutates `entries` in place. Runs all lookups concurrently and is tolerant
1372
- * of RPC failures: an entry whose amount can't be resolved keeps
1373
- * `amount: undefined`, which consumers render as a hidden/retryable row
1374
- * 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.
1375
1518
  */
1376
- async resolveWithdrawnAmounts(entries, chainId, recipient) {
1377
- const withdrawals = entries.filter(
1378
- (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]
1379
1522
  );
1380
- if (withdrawals.length === 0) return;
1523
+ if (movements.length === 0) return;
1381
1524
  const client = this.getPublicClient(chainId);
1525
+ const receipts = /* @__PURE__ */ new Map();
1382
1526
  await Promise.all(
1383
- withdrawals.map(async (entry) => {
1527
+ movements.map(async (entry) => {
1384
1528
  const tx = entry.transactions[0];
1385
1529
  const txHash = tx.txHashes[0];
1386
- const rawSymbol = tx.tokenSymbol;
1530
+ const rawSymbol = tx.tokenSymbol ?? tokenSymbol;
1387
1531
  const normalizedSymbol = rawSymbol ? rawSymbol.toLowerCase() === "eth" ? "WETH" : rawSymbol.toUpperCase() : "USDC";
1388
1532
  const asset = SupportedAssets.find(
1389
1533
  (a) => a.chainId === chainId && a.symbol === normalizedSymbol
1390
- ) ?? SupportedAssets.find(
1391
- (a) => a.chainId === chainId && a.symbol === "USDC"
1392
1534
  );
1393
1535
  if (!asset) return;
1394
- const cacheKey = `${txHash}:${asset.address}`;
1395
- 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);
1396
1543
  if (cached !== void 0) {
1397
- tx.amount = cached;
1544
+ Object.assign(tx, cached);
1398
1545
  return;
1399
1546
  }
1400
1547
  try {
1401
- const receipt = await client.getTransactionReceipt({
1402
- hash: txHash
1403
- });
1404
- const amount = extractWithdrawnAmount(
1405
- receipt.logs,
1406
- recipient,
1407
- asset.address,
1408
- asset.decimals
1409
- );
1410
- if (amount !== void 0) {
1411
- this.withdrawAmountCache.set(cacheKey, amount);
1412
- 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 });
1413
1572
  }
1414
1573
  } catch {
1415
1574
  }
@@ -1498,7 +1657,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1498
1657
  * Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
1499
1658
  * cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
1500
1659
  * we drop the cache and try again — the second attempt has no cache entry,
1501
- * 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
1502
1661
  * an `"Unauthorized: Invalid API key"` error so we can't distinguish revoked
1503
1662
  * JWT from a misconfigured API key; we retry once either way, and if the
1504
1663
  * second attempt still fails the original error propagates.
@@ -1531,6 +1690,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
1531
1690
  await this.connectAuth(state, chainId);
1532
1691
  }
1533
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
+ }
1534
1704
  /**
1535
1705
  * Sync newly-available protocols onto an existing user's profile at wallet
1536
1706
  * connection. Zyfai's own protocol sync (`updateUserProtocols`) runs only when
@@ -2076,7 +2246,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2076
2246
  await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
2077
2247
  return redeployed.address;
2078
2248
  }
2079
- async deposit(state, chainId, amount, asset, depositCallback) {
2249
+ async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
2080
2250
  try {
2081
2251
  const validChainId = isValidChainId(chainId);
2082
2252
  await this.ensureConnected(state, validChainId);
@@ -2085,17 +2255,48 @@ var ZyfaiAgent = class _ZyfaiAgent {
2085
2255
  this.getAddress(),
2086
2256
  validChainId
2087
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
+ });
2088
2269
  const txHash = await depositCallback(smartWallet, validChainId, amount);
2089
- 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);
2090
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);
2291
+ try {
2292
+ const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
2091
2293
  await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
2092
- } catch (logError) {
2093
- console.warn(
2094
- "[owney-sdk] Deposit landed on-chain but logDeposit failed (non-fatal):",
2095
- logError
2096
- );
2294
+ evidence2 = { ...evidence2, registration: "confirmed" };
2295
+ } catch {
2296
+ evidence2 = { ...evidence2, registration: "unknown" };
2097
2297
  }
2098
- return { txHash, smartWallet, amount };
2298
+ notifyFunding(onProgress, evidence2);
2299
+ return { txHash, smartWallet, amount, evidence: evidence2 };
2099
2300
  }
2100
2301
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
2101
2302
  const raw2 = await this.sdk.depositFunds(
@@ -2105,22 +2306,88 @@ var ZyfaiAgent = class _ZyfaiAgent {
2105
2306
  asset,
2106
2307
  "aggressive"
2107
2308
  );
2108
- 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 };
2109
2330
  } catch (error) {
2110
2331
  throw error;
2111
2332
  }
2112
2333
  }
2113
2334
  // --- IAgent: Fund operations ---
2114
- async withdraw(state, chainId, token, amount) {
2335
+ async withdraw(state, chainId, token, amount, onProgress) {
2115
2336
  const validChainId = isValidChainId(chainId);
2116
- await this.ensureConnected(state, validChainId);
2117
- const raw2 = await this.sdk.withdrawFunds(
2118
- this.getAddress(),
2119
- validChainId,
2120
- amount,
2121
- token
2122
- );
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
+ }
2123
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
+ });
2124
2391
  throw new OwneyError(
2125
2392
  "WITHDRAW_FAILED",
2126
2393
  raw2.message || "Zyfai withdraw failed.",
@@ -2128,13 +2395,45 @@ var ZyfaiAgent = class _ZyfaiAgent {
2128
2395
  this.id
2129
2396
  );
2130
2397
  }
2131
- 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" };
2132
2426
  }
2133
2427
  // --- IAgent: Portfolio reads ---
2428
+ async getFundingWallet(state, chainId) {
2429
+ return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
2430
+ }
2134
2431
  async getBalances(state, chainId) {
2432
+ const requestedAt = Date.now();
2135
2433
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
2136
2434
  const raw2 = await this.sdk.getPortfolio(this.getAddress());
2137
- return mapBalances(raw2, validChainId, smartWallet);
2435
+ const balance = mapBalances(raw2, validChainId, smartWallet);
2436
+ return { ...balance, readMetadata: balance.readMetadata?.map((read2) => ({ ...read2, requestedAt })) };
2138
2437
  }
2139
2438
  earningsKey(state, chainId, smartWallet) {
2140
2439
  return JSON.stringify([
@@ -2207,7 +2506,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2207
2506
  /**
2208
2507
  * Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
2209
2508
  * takes lowercase `assetType` and denominates WETH as "eth" (the same
2210
- * spelling `resolveWithdrawnAmounts` normalizes on the way in). Anything
2509
+ * spelling `resolveFundingTransfers` normalizes on the way in). Anything
2211
2510
  * unrecognised returns undefined so the request stays unfiltered rather
2212
2511
  * than silently narrowing to nothing.
2213
2512
  */
@@ -2226,6 +2525,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
2226
2525
  }
2227
2526
  async getHistory(state, chainId, options) {
2228
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) {
2229
2536
  const limit = options?.limit ?? 10;
2230
2537
  const assetType = _ZyfaiAgent.toZyfaiAssetType(options?.tokenSymbol);
2231
2538
  const cursor = options?.cursor ? decodeHistoryCursor(options.cursor) : null;
@@ -2267,9 +2574,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
2267
2574
  rawOffset: firstOvershoot ? firstOvershoot.rawIdx : offset
2268
2575
  }) : void 0;
2269
2576
  const data = mapEntries(pageData, validChainId);
2270
- 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
+ });
2271
2585
  return {
2272
- data,
2586
+ data: unique,
2273
2587
  hasMore,
2274
2588
  nextCursor
2275
2589
  };
@@ -2318,7 +2632,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2318
2632
  };
2319
2633
 
2320
2634
  // src/agents/yieldseeker/yieldseeker.agent.ts
2321
- var import_viem7 = require("viem");
2635
+ var import_viem8 = require("viem");
2322
2636
  var import_chains3 = require("viem/chains");
2323
2637
 
2324
2638
  // src/lib/chain-guard.ts
@@ -2357,7 +2671,7 @@ async function ensureWalletOnChain(pub, wallet, expected) {
2357
2671
  }
2358
2672
 
2359
2673
  // src/lib/transfer-auth.ts
2360
- var import_viem3 = require("viem");
2674
+ var import_viem4 = require("viem");
2361
2675
 
2362
2676
  // src/lib/sponsor-client.ts
2363
2677
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
@@ -2473,7 +2787,7 @@ async function postSponsorBatchTransfer(input) {
2473
2787
  }
2474
2788
 
2475
2789
  // src/lib/permit2.ts
2476
- var import_viem4 = require("viem");
2790
+ var import_viem5 = require("viem");
2477
2791
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2478
2792
  var MAX_UINT256 = 2n ** 256n - 1n;
2479
2793
  function permit2ApprovalAmount(requiredAmount) {
@@ -2514,7 +2828,7 @@ var ERC20_ALLOWANCE_ABI = [
2514
2828
  function randomPermit2Nonce() {
2515
2829
  const bytes = new Uint8Array(32);
2516
2830
  globalThis.crypto.getRandomValues(bytes);
2517
- return BigInt((0, import_viem4.bytesToHex)(bytes));
2831
+ return BigInt((0, import_viem5.bytesToHex)(bytes));
2518
2832
  }
2519
2833
  async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
2520
2834
  return publicClient.readContract({
@@ -2557,7 +2871,7 @@ function makeVerificationAwareDepositCallback(implementation) {
2557
2871
 
2558
2872
  // src/agents/yieldseeker/yieldseeker.auth.ts
2559
2873
  var import_siwe = require("siwe");
2560
- var import_viem5 = require("viem");
2874
+ var import_viem6 = require("viem");
2561
2875
  var import_chains2 = require("viem/chains");
2562
2876
 
2563
2877
  // src/agents/yieldseeker/yieldseeker.auth-cache.ts
@@ -2670,7 +2984,7 @@ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
2670
2984
  return new import_siwe.SiweMessage({
2671
2985
  scheme: url.protocol.slice(0, -1),
2672
2986
  domain: url.host,
2673
- address: (0, import_viem5.getAddress)(address),
2987
+ address: (0, import_viem6.getAddress)(address),
2674
2988
  uri: url.origin,
2675
2989
  version: "1",
2676
2990
  chainId,
@@ -2756,15 +3070,15 @@ var YieldseekerAuth = class {
2756
3070
  clearYieldseekerSession(state.walletAddress, chainId);
2757
3071
  }
2758
3072
  async sign(state, chainId) {
2759
- const account = (0, import_viem5.getAddress)(state.walletAddress);
2760
- const publicClient = (0, import_viem5.createPublicClient)({
3073
+ const account = (0, import_viem6.getAddress)(state.walletAddress);
3074
+ const publicClient = (0, import_viem6.createPublicClient)({
2761
3075
  chain: import_chains2.base,
2762
- transport: (0, import_viem5.custom)(state.provider)
3076
+ transport: (0, import_viem6.custom)(state.provider)
2763
3077
  });
2764
- const walletClient = (0, import_viem5.createWalletClient)({
3078
+ const walletClient = (0, import_viem6.createWalletClient)({
2765
3079
  account,
2766
3080
  chain: import_chains2.base,
2767
- transport: (0, import_viem5.custom)(state.provider)
3081
+ transport: (0, import_viem6.custom)(state.provider)
2768
3082
  });
2769
3083
  await ensureWalletOnChain(
2770
3084
  publicClient,
@@ -2931,7 +3245,7 @@ var YieldseekerApiClient = class {
2931
3245
  };
2932
3246
 
2933
3247
  // src/agents/yieldseeker/yieldseeker.mapper.ts
2934
- var import_viem6 = require("viem");
3248
+ var import_viem7 = require("viem");
2935
3249
 
2936
3250
  // src/lib/helpers/snapshot-apy.ts
2937
3251
  var DAY_MS = 864e5;
@@ -2998,10 +3312,10 @@ function raw(value, endpoint) {
2998
3312
  return BigInt(value);
2999
3313
  }
3000
3314
  function decimal(value, decimals, endpoint) {
3001
- return (0, import_viem6.formatUnits)(raw(value, endpoint), decimals);
3315
+ return (0, import_viem7.formatUnits)(raw(value, endpoint), decimals);
3002
3316
  }
3003
3317
  function usd(rawAmount, decimals, price) {
3004
- return Number((0, import_viem6.formatUnits)(rawAmount, decimals)) * price;
3318
+ return Number((0, import_viem7.formatUnits)(rawAmount, decimals)) * price;
3005
3319
  }
3006
3320
  function percent(value) {
3007
3321
  const result = Number(value);
@@ -3027,7 +3341,7 @@ function assetAddressValue(record, address) {
3027
3341
  }
3028
3342
  function position(value, asset, baseAssetDecimals) {
3029
3343
  const option = value?.yieldOption;
3030
- if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem6.isAddress)(option.address)) {
3344
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem7.isAddress)(option.address)) {
3031
3345
  return invalid("yield positions", "missing vault metadata");
3032
3346
  }
3033
3347
  return {
@@ -3097,7 +3411,17 @@ function mapYieldseekerBalances(contexts) {
3097
3411
  totalBalanceAsset: "usdc",
3098
3412
  assetBalances,
3099
3413
  tokens,
3100
- 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
+ }))
3101
3425
  };
3102
3426
  }
3103
3427
  function mapYieldseekerEarnings(contexts) {
@@ -3109,7 +3433,7 @@ function mapYieldseekerEarnings(contexts) {
3109
3433
  chain: "BASE",
3110
3434
  chainId: 8453,
3111
3435
  asset: context.asset,
3112
- amount: (0, import_viem6.formatUnits)(amount, context.snapshot.baseAssetDecimals)
3436
+ amount: (0, import_viem7.formatUnits)(amount, context.snapshot.baseAssetDecimals)
3113
3437
  });
3114
3438
  lifetimeEarnings += usd(
3115
3439
  amount,
@@ -3211,6 +3535,7 @@ function transactionHashes(details) {
3211
3535
  function actionEntry(action) {
3212
3536
  if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
3213
3537
  return {
3538
+ id: `yieldseeker:action:${action.agentActionId}`,
3214
3539
  agent: "yieldseeker",
3215
3540
  action: actionType(action.actionType),
3216
3541
  date: action.createdDate,
@@ -3240,7 +3565,7 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddres
3240
3565
  const owner = ownerAddress.toLowerCase();
3241
3566
  const agentWallet = wallet.walletAddress.toLowerCase();
3242
3567
  const baseAsset = agent.assetAddress.toLowerCase();
3243
- 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) {
3244
3569
  return void 0;
3245
3570
  }
3246
3571
  let action;
@@ -3265,6 +3590,12 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddres
3265
3590
  transactions: [
3266
3591
  {
3267
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),
3268
3599
  chainId: agent.chainId,
3269
3600
  tokenSymbol: asset,
3270
3601
  amount: decimal(
@@ -3310,7 +3641,8 @@ function mapYieldseekerHistory(contexts, options) {
3310
3641
  ungrouped.push(entry);
3311
3642
  continue;
3312
3643
  }
3313
- 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;
3314
3646
  const previous = grouped.get(key2);
3315
3647
  if (!previous) {
3316
3648
  grouped.set(key2, entry);
@@ -3323,14 +3655,52 @@ function mapYieldseekerHistory(contexts, options) {
3323
3655
  }
3324
3656
  previous.positions.push(...entry.positions);
3325
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.");
3326
3688
  }
3327
3689
  }
3328
- 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);
3329
3695
  return {
3330
- data: filtered.slice(0, options.limit),
3331
- // v1 returns the whole action/movement collection and defines no cursor.
3332
- // Report a terminal page so callers never loop over the same prefix.
3333
- 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
+ }))) } : {}
3334
3704
  };
3335
3705
  }
3336
3706
  function mapYieldseekerProfile(address, contexts) {
@@ -3505,7 +3875,7 @@ var YieldseekerAgent = class {
3505
3875
  this.assertAsset(targetAsset);
3506
3876
  await this.ensureAgent(state, chainId, targetAsset);
3507
3877
  }
3508
- async deposit(state, chainId, amount, asset, depositCallback) {
3878
+ async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
3509
3879
  this.assertChain(chainId);
3510
3880
  this.assertAsset(asset);
3511
3881
  if (BigInt(amount) <= 0n) {
@@ -3519,7 +3889,20 @@ var YieldseekerAgent = class {
3519
3889
  const context = await this.ensureAgent(state, chainId, asset);
3520
3890
  const generation = this.readGeneration;
3521
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
+ });
3522
3903
  let txHash;
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" });
3523
3906
  if (depositCallback) {
3524
3907
  provideDepositVerificationContext(depositCallback, {
3525
3908
  agentId: "yieldseeker",
@@ -3532,30 +3915,57 @@ var YieldseekerAgent = class {
3532
3915
  chainId,
3533
3916
  amount
3534
3917
  );
3535
- await this.waitForReceipt(state, chainId, txHash);
3918
+ submitted(txHash);
3919
+ receipt = await this.waitForReceipt(state, chainId, txHash);
3536
3920
  } else {
3537
3921
  txHash = await this.submitTransaction(state, chainId, {
3538
- from: (0, import_viem7.getAddress)(state.walletAddress),
3922
+ from: (0, import_viem8.getAddress)(state.walletAddress),
3539
3923
  to: YIELDSEEKER_ASSET_METADATA[asset].address,
3540
- data: (0, import_viem7.encodeFunctionData)({
3541
- abi: import_viem7.erc20Abi,
3924
+ data: (0, import_viem8.encodeFunctionData)({
3925
+ abi: import_viem8.erc20Abi,
3542
3926
  functionName: "transfer",
3543
- args: [(0, import_viem7.getAddress)(context.wallet.walletAddress), BigInt(amount)]
3927
+ args: [(0, import_viem8.getAddress)(context.wallet.walletAddress), BigInt(amount)]
3544
3928
  }),
3545
3929
  value: "0",
3546
3930
  chainId
3931
+ }, submitted, (value) => {
3932
+ receipt = value;
3547
3933
  });
3548
3934
  }
3549
3935
  if (this.readGeneration === generation) {
3550
3936
  void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
3551
3937
  }
3552
- return {
3553
- txHash,
3554
- smartWallet: context.wallet.walletAddress,
3555
- amount
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"
3556
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
+ }
3557
3967
  }
3558
- async withdraw(state, chainId, asset, amount) {
3968
+ async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted) {
3559
3969
  this.assertChain(chainId);
3560
3970
  this.assertAsset(asset);
3561
3971
  if (amount !== void 0 && BigInt(amount) <= 0n) {
@@ -3585,17 +3995,32 @@ var YieldseekerAgent = class {
3585
3995
  context
3586
3996
  );
3587
3997
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3588
- const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
3589
- ([address]) => address.toLowerCase() === metadata.address.toLowerCase()
3590
- );
3591
- 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();
3592
4009
  const deployed = portfolio.positions.reduce(
3593
4010
  (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
3594
4011
  0n
3595
4012
  );
3596
4013
  const totalAvailable = idle + deployed;
3597
4014
  const requested = amount === void 0 ? totalAvailable : BigInt(amount);
3598
- const plannedMovement = this.snapshotMovement(state, chainId, context, -requested);
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
+ }
3599
4024
  if (requested > totalAvailable) {
3600
4025
  throw new OwneyError(
3601
4026
  "WITHDRAW_INSUFFICIENT_BALANCE",
@@ -3640,29 +4065,51 @@ var YieldseekerAgent = class {
3640
4065
  remaining: remaining.toString()
3641
4066
  });
3642
4067
  }
3643
- const account = (0, import_viem7.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?.();
3644
4084
  const txHash = await this.submitTransaction(state, chainId, {
3645
4085
  from: account,
3646
- to: (0, import_viem7.getAddress)(context.wallet.walletAddress),
3647
- data: amount === void 0 ? (0, import_viem7.encodeFunctionData)({
4086
+ to: (0, import_viem8.getAddress)(context.wallet.walletAddress),
4087
+ data: amount === void 0 ? (0, import_viem8.encodeFunctionData)({
3648
4088
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
3649
4089
  functionName: "withdrawAllAssetToUser",
3650
4090
  args: [account, metadata.address]
3651
- }) : (0, import_viem7.encodeFunctionData)({
4091
+ }) : (0, import_viem8.encodeFunctionData)({
3652
4092
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
3653
4093
  functionName: "withdrawAssetToUser",
3654
4094
  args: [account, metadata.address, requested]
3655
4095
  }),
3656
4096
  value: "0",
3657
4097
  chainId
4098
+ }, (hash) => notifyFunding(onProgress, { ...base5, txHash: hash }), (value) => {
4099
+ receipt = value;
3658
4100
  });
3659
4101
  confirmedMovement = true;
3660
4102
  settlement = plannedMovement;
3661
- return {
3662
- txHash,
3663
- type: amount === void 0 ? "full" : "partial",
3664
- amount: requested.toString()
3665
- };
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 };
3666
4113
  } finally {
3667
4114
  if (confirmedMovement && this.readGeneration === generation) {
3668
4115
  void this.refreshSnapshotAfterMovement(
@@ -3675,9 +4122,39 @@ var YieldseekerAgent = class {
3675
4122
  }
3676
4123
  }
3677
4124
  }
3678
- async getBalances(state, chainId) {
4125
+ async getBalances(state, chainId, asset) {
3679
4126
  this.assertChain(chainId);
3680
- 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 }));
4129
+ }
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) {
4142
+ this.assertChain(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;
4152
+ }
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;
3681
4158
  }
3682
4159
  async getEarnings(state, chainId) {
3683
4160
  this.assertChain(chainId);
@@ -3709,11 +4186,12 @@ var YieldseekerAgent = class {
3709
4186
  );
3710
4187
  const vaultAddresses = new Set(
3711
4188
  catalog.flat().filter(
3712
- (yieldOption) => yieldOption.chainId === chainId && (0, import_viem7.isAddress)(yieldOption.address)
4189
+ (yieldOption) => yieldOption.chainId === chainId && (0, import_viem8.isAddress)(yieldOption.address)
3713
4190
  ).map((yieldOption) => yieldOption.address.toLowerCase())
3714
4191
  );
3715
4192
  return mapYieldseekerHistory(contexts, {
3716
4193
  limit: options?.limit ?? 10,
4194
+ cursor: options?.cursor,
3717
4195
  ownerAddress: state.walletAddress,
3718
4196
  vaultAddresses,
3719
4197
  ...options?.fromDate ? { fromDate: options.fromDate } : {},
@@ -3788,6 +4266,7 @@ var YieldseekerAgent = class {
3788
4266
  if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
3789
4267
  this.readCache.set(key2, {
3790
4268
  expiresAt: Date.now() + ttlMs,
4269
+ fetchedAt: Date.now(),
3791
4270
  value
3792
4271
  });
3793
4272
  }
@@ -3834,7 +4313,7 @@ var YieldseekerAgent = class {
3834
4313
  `/users/${user.userId}/agents/${agent.agentId}/wallet`
3835
4314
  ).then((walletResponse) => {
3836
4315
  this.assertSession(generation, chainId, asset);
3837
- if (!walletResponse?.agentWallet || !(0, import_viem7.isAddress)(walletResponse.agentWallet.walletAddress)) {
4316
+ if (!walletResponse?.agentWallet || !(0, import_viem8.isAddress)(walletResponse.agentWallet.walletAddress)) {
3838
4317
  throw this.invalidResponse("agent wallet");
3839
4318
  }
3840
4319
  const context = {
@@ -3864,7 +4343,7 @@ var YieldseekerAgent = class {
3864
4343
  this.users.set(key2, persisted);
3865
4344
  return persisted;
3866
4345
  }
3867
- const walletAddress = (0, import_viem7.getAddress)(state.walletAddress);
4346
+ const walletAddress = (0, import_viem8.getAddress)(state.walletAddress);
3868
4347
  const generation = this.readGeneration;
3869
4348
  let user = null;
3870
4349
  try {
@@ -3988,6 +4467,7 @@ var YieldseekerAgent = class {
3988
4467
  if (agent) {
3989
4468
  this.readCache.set(this.agentListKey(state, chainId), {
3990
4469
  expiresAt: Date.now() + YIELDSEEKER_AGENT_LIST_CACHE_MS,
4470
+ fetchedAt: Date.now(),
3991
4471
  value: {
3992
4472
  agents: [...agents, agent]
3993
4473
  }
@@ -4048,7 +4528,6 @@ var YieldseekerAgent = class {
4048
4528
  ...previous,
4049
4529
  epoch: this.movementEpochs.get(key2) ?? 0,
4050
4530
  delta: previous.delta + delta,
4051
- expectedBalance: previous.expectedBalance + delta,
4052
4531
  expectedNetDeposits: previous.expectedNetDeposits + delta
4053
4532
  };
4054
4533
  }
@@ -4061,7 +4540,6 @@ var YieldseekerAgent = class {
4061
4540
  epoch: this.movementEpochs.get(key2) ?? 0,
4062
4541
  delta,
4063
4542
  decimals: snapshot.baseAssetDecimals,
4064
- expectedBalance: BigInt(snapshot.totalValueBase) + delta,
4065
4543
  expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
4066
4544
  };
4067
4545
  }
@@ -4090,9 +4568,8 @@ var YieldseekerAgent = class {
4090
4568
  const movement = this.snapshotMovements.get(contextKey);
4091
4569
  const snapshot = response.agentSnapshot;
4092
4570
  if (movement && snapshot.baseAssetDecimals === movement.decimals && /^-?\d+$/.test(snapshot.netDepositsBase) && /^\d+$/.test(snapshot.totalValueBase)) {
4093
- const balance = BigInt(snapshot.totalValueBase);
4094
4571
  const netDeposits = BigInt(snapshot.netDepositsBase);
4095
- const reconciled = movement.delta > 0n ? balance >= movement.expectedBalance && netDeposits >= movement.expectedNetDeposits : balance <= movement.expectedBalance && netDeposits <= movement.expectedNetDeposits;
4572
+ const reconciled = movement.delta > 0n ? netDeposits >= movement.expectedNetDeposits : netDeposits <= movement.expectedNetDeposits;
4096
4573
  if (reconciled) {
4097
4574
  this.snapshotMovements.delete(contextKey);
4098
4575
  this.reconcileUntil.delete(contextKey);
@@ -4146,12 +4623,20 @@ var YieldseekerAgent = class {
4146
4623
  const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
4147
4624
  const parts = this.loadPortfolioParts(state, chainId, context, options, contextKey);
4148
4625
  try {
4149
- const [snapshot, portfolioParts] = await Promise.all([read2, parts]);
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]);
4150
4632
  assertSession();
4151
4633
  if (this.portfolioVersion(contextKey) !== version) continue;
4152
4634
  return {
4153
4635
  ...context,
4154
4636
  snapshot: snapshot.agentSnapshot,
4637
+ fetchedAt: this.readCache.get(`snapshot:${contextKey}`)?.fetchedAt,
4638
+ readVersion: version,
4639
+ positionsUnavailable,
4155
4640
  ...portfolioParts
4156
4641
  };
4157
4642
  } catch (error) {
@@ -4241,7 +4726,7 @@ var YieldseekerAgent = class {
4241
4726
  this.agentPath(context, "deploy"),
4242
4727
  { method: "POST", body: {} }
4243
4728
  );
4244
- if (!deployed?.agentWallet || !(0, import_viem7.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) {
4245
4730
  throw this.invalidResponse("agent deployment", {
4246
4731
  reason: "Deploy did not return the expected Agent Wallet."
4247
4732
  });
@@ -4342,20 +4827,22 @@ var YieldseekerAgent = class {
4342
4827
  this.id
4343
4828
  );
4344
4829
  }
4345
- async submitTransaction(state, chainId, transaction) {
4830
+ async submitTransaction(state, chainId, transaction, onSubmitted, onReceipt) {
4346
4831
  if (this.transactionExecutor) {
4347
- return this.transactionExecutor(state, chainId, transaction);
4832
+ const hash2 = await this.transactionExecutor(state, chainId, transaction);
4833
+ onSubmitted?.(hash2);
4834
+ return hash2;
4348
4835
  }
4349
4836
  this.assertTransaction(transaction, state, chainId);
4350
- const account = (0, import_viem7.getAddress)(state.walletAddress);
4351
- const walletClient = (0, import_viem7.createWalletClient)({
4837
+ const account = (0, import_viem8.getAddress)(state.walletAddress);
4838
+ const walletClient = (0, import_viem8.createWalletClient)({
4352
4839
  account,
4353
4840
  chain: import_chains3.base,
4354
- transport: (0, import_viem7.custom)(state.provider)
4841
+ transport: (0, import_viem8.custom)(state.provider)
4355
4842
  });
4356
- const walletChainClient = (0, import_viem7.createPublicClient)({
4843
+ const walletChainClient = (0, import_viem8.createPublicClient)({
4357
4844
  chain: import_chains3.base,
4358
- transport: (0, import_viem7.custom)(state.provider)
4845
+ transport: (0, import_viem8.custom)(state.provider)
4359
4846
  });
4360
4847
  await ensureWalletOnChain(
4361
4848
  walletChainClient,
@@ -4365,10 +4852,11 @@ var YieldseekerAgent = class {
4365
4852
  const hash = await walletClient.sendTransaction({
4366
4853
  account,
4367
4854
  chain: import_chains3.base,
4368
- to: (0, import_viem7.getAddress)(transaction.to),
4855
+ to: (0, import_viem8.getAddress)(transaction.to),
4369
4856
  data: transaction.data,
4370
4857
  value: BigInt(transaction.value)
4371
4858
  });
4859
+ onSubmitted?.(hash);
4372
4860
  const receipt = await withPaidRpcDiagnostics(
4373
4861
  () => this.getReceiptClient().waitForTransactionReceipt({
4374
4862
  hash,
@@ -4386,6 +4874,7 @@ var YieldseekerAgent = class {
4386
4874
  this.id
4387
4875
  );
4388
4876
  }
4877
+ onReceipt?.(receipt);
4389
4878
  return hash;
4390
4879
  }
4391
4880
  async waitForReceipt(state, chainId, transactionHash) {
@@ -4410,14 +4899,15 @@ var YieldseekerAgent = class {
4410
4899
  this.id
4411
4900
  );
4412
4901
  }
4902
+ return receipt;
4413
4903
  }
4414
4904
  assertTransaction(transaction, state, chainId) {
4415
- if (!transaction || typeof transaction.from !== "string" || !(0, import_viem7.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem7.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_viem7.getAddress)(transaction.from) !== (0, import_viem7.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)) {
4416
4906
  throw this.invalidResponse("transaction");
4417
4907
  }
4418
4908
  }
4419
4909
  assertAgent(agent) {
4420
- if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem7.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) {
4421
4911
  throw this.invalidResponse("agent");
4422
4912
  }
4423
4913
  }
@@ -4570,7 +5060,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
4570
5060
  }
4571
5061
 
4572
5062
  // src/lib/helpers/withdraw-helper.ts
4573
- var import_viem8 = require("viem");
5063
+ var import_viem9 = require("viem");
4574
5064
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
4575
5065
  const target = asset.toUpperCase();
4576
5066
  return agents.map((agent) => {
@@ -4578,7 +5068,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
4578
5068
  const tokenBalance = agentBalance?.tokens.find(
4579
5069
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
4580
5070
  );
4581
- let balance = tokenBalance ? (0, import_viem8.parseUnits)(tokenBalance.amount, decimals) : 0n;
5071
+ let balance = tokenBalance ? (0, import_viem9.parseUnits)(tokenBalance.amount, decimals) : 0n;
4582
5072
  if (agent.balanceComposition === "tokens-plus-positions") {
4583
5073
  const chainNameById = {
4584
5074
  1: "ETHEREUM",
@@ -4590,14 +5080,14 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
4590
5080
  const positionChain = position2.chain.trim().toUpperCase();
4591
5081
  const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
4592
5082
  if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
4593
- balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : (0, import_viem8.parseUnits)(position2.amount, decimals);
5083
+ balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : (0, import_viem9.parseUnits)(position2.amount, decimals);
4594
5084
  }
4595
5085
  }
4596
5086
  const assetTotal = agentBalance?.assetBalances?.find(
4597
5087
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
4598
5088
  );
4599
5089
  if (assetTotal) {
4600
- const total = (0, import_viem8.parseUnits)(assetTotal.amount, decimals);
5090
+ const total = (0, import_viem9.parseUnits)(assetTotal.amount, decimals);
4601
5091
  if (total < balance) balance = total > 0n ? total : 0n;
4602
5092
  }
4603
5093
  return { agent, balance };
@@ -4756,14 +5246,14 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
4756
5246
  }
4757
5247
 
4758
5248
  // src/client.ts
4759
- var import_viem12 = require("viem");
5249
+ var import_viem13 = require("viem");
4760
5250
  var import_chains4 = require("viem/chains");
4761
5251
 
4762
5252
  // src/lib/sponsored-token-batch.ts
4763
- var import_viem10 = require("viem");
5253
+ var import_viem11 = require("viem");
4764
5254
 
4765
5255
  // src/lib/permit2-batch.ts
4766
- var import_viem9 = require("viem");
5256
+ var import_viem10 = require("viem");
4767
5257
  var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
4768
5258
  var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
4769
5259
  var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
@@ -4776,7 +5266,7 @@ var ERC2612_PERMIT_TYPES = {
4776
5266
  { name: "deadline", type: "uint256" }
4777
5267
  ]
4778
5268
  };
4779
- var ERC2612_READ_ABI = (0, import_viem9.parseAbi)([
5269
+ var ERC2612_READ_ABI = (0, import_viem10.parseAbi)([
4780
5270
  "function name() view returns (string)",
4781
5271
  "function version() view returns (string)",
4782
5272
  "function nonces(address owner) view returns (uint256)"
@@ -4814,7 +5304,7 @@ var PERMIT_BATCH_TYPES = {
4814
5304
  { name: "amount", type: "uint256" }
4815
5305
  ]
4816
5306
  };
4817
- var PERMIT2_BATCH_ABI = (0, import_viem9.parseAbi)([
5307
+ var PERMIT2_BATCH_ABI = (0, import_viem10.parseAbi)([
4818
5308
  "struct TokenPermissions { address token; uint256 amount; }",
4819
5309
  "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
4820
5310
  "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
@@ -4873,7 +5363,7 @@ function clear(key2) {
4873
5363
  else window.localStorage.removeItem(key2);
4874
5364
  }
4875
5365
  function sponsorTokenBatch(i) {
4876
- const key2 = `owney.token-batch.v1:${(0, import_viem10.keccak256)((0, import_viem10.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()}`;
4877
5367
  const plan = planOf(i.transfers);
4878
5368
  const active = inflight.get(key2);
4879
5369
  if (active) {
@@ -4904,7 +5394,7 @@ async function execute(i, key2, plan) {
4904
5394
  baseUrl: i.baseUrl,
4905
5395
  body
4906
5396
  });
4907
- if (!prepared.serializedTransaction || (0, import_viem10.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
5397
+ if (!prepared.serializedTransaction || (0, import_viem11.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
4908
5398
  throw new Error(
4909
5399
  "Sponsorship API did not return a valid prepared transaction."
4910
5400
  );
@@ -4914,12 +5404,16 @@ async function execute(i, key2, plan) {
4914
5404
  };
4915
5405
  save(key2, body);
4916
5406
  }
5407
+ try {
5408
+ i.onSubmitted?.((0, import_viem11.keccak256)(body.serializedTransaction));
5409
+ } catch {
5410
+ }
4917
5411
  const result = await postSponsorBatchTransfer({
4918
5412
  apiKey: i.apiKey,
4919
5413
  baseUrl: i.baseUrl,
4920
5414
  body
4921
5415
  });
4922
- if (result.txHash !== (0, import_viem10.keccak256)(body.serializedTransaction))
5416
+ if (result.txHash !== (0, import_viem11.keccak256)(body.serializedTransaction))
4923
5417
  throw new Error(
4924
5418
  "Sponsorship receipt does not match the pending transaction."
4925
5419
  );
@@ -4934,7 +5428,7 @@ async function execute(i, key2, plan) {
4934
5428
  const saved = read(key2);
4935
5429
  if (saved) {
4936
5430
  const previous = JSON.parse(saved);
4937
- if (previous.chainId !== i.chainId || !(0, import_viem10.isAddressEqual)(previous.from, i.owner) || !(0, import_viem10.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)
4938
5432
  throw new Error(
4939
5433
  "Retry the previous token deposit and agent split first to reconcile its status."
4940
5434
  );
@@ -4951,7 +5445,7 @@ async function execute(i, key2, plan) {
4951
5445
  "DEPOSIT_INSUFFICIENT_BALANCE",
4952
5446
  "Insufficient token balance for this deposit."
4953
5447
  );
4954
- if (allowance < total && (i.chainId !== 8453 || !(0, import_viem10.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
5448
+ if (allowance < total && (i.chainId !== 8453 || !(0, import_viem11.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
4955
5449
  throw new OwneyError(
4956
5450
  "PERMIT2_APPROVAL_REQUIRED",
4957
5451
  "token deposits need a one-time Permit2 approval."
@@ -5042,7 +5536,8 @@ function makeSponsoredTokenCallback(deps) {
5042
5536
  transfers,
5043
5537
  pub,
5044
5538
  wallet,
5045
- onApproved: deps.onApproved
5539
+ onApproved: deps.onApproved,
5540
+ onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
5046
5541
  });
5047
5542
  };
5048
5543
  const callback = makeVerificationAwareDepositCallback(
@@ -5128,7 +5623,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
5128
5623
  }
5129
5624
 
5130
5625
  // src/lib/sponsored-calls-deposit.ts
5131
- var import_viem11 = require("viem");
5626
+ var import_viem12 = require("viem");
5132
5627
  var DEFAULT_POLL_INTERVAL_MS = 1500;
5133
5628
  var DEFAULT_MAX_POLLS = 30;
5134
5629
  async function paymasterSupported(provider, owner, chainId) {
@@ -5136,7 +5631,7 @@ async function paymasterSupported(provider, owner, chainId) {
5136
5631
  method: "wallet_getCapabilities",
5137
5632
  params: [owner]
5138
5633
  });
5139
- const forChain = caps?.[(0, import_viem11.toHex)(chainId)] ?? caps?.[String(chainId)];
5634
+ const forChain = caps?.[(0, import_viem12.toHex)(chainId)] ?? caps?.[String(chainId)];
5140
5635
  return Boolean(forChain?.paymasterService?.supported);
5141
5636
  }
5142
5637
  function makeSponsoredCallsCallback(deps) {
@@ -5173,8 +5668,8 @@ function makeSponsoredCallsCallback(deps) {
5173
5668
  const calls = transfers.map((transfer) => ({
5174
5669
  to: token,
5175
5670
  value: "0x0",
5176
- data: (0, import_viem11.encodeFunctionData)({
5177
- abi: import_viem11.erc20Abi,
5671
+ data: (0, import_viem12.encodeFunctionData)({
5672
+ abi: import_viem12.erc20Abi,
5178
5673
  functionName: "transfer",
5179
5674
  args: [transfer.to, BigInt(transfer.amount)]
5180
5675
  })
@@ -5212,7 +5707,7 @@ function makeSponsoredCallsCallback(deps) {
5212
5707
  {
5213
5708
  version: "2.0.0",
5214
5709
  from: deps.ownerAddress,
5215
- chainId: (0, import_viem11.toHex)(chainId),
5710
+ chainId: (0, import_viem12.toHex)(chainId),
5216
5711
  atomicRequired: transfers.length > 1,
5217
5712
  calls,
5218
5713
  capabilities: {
@@ -5236,6 +5731,12 @@ function makeSponsoredCallsCallback(deps) {
5236
5731
  params: [callsId]
5237
5732
  });
5238
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
+ }
5239
5740
  if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
5240
5741
  throw new OwneyError(
5241
5742
  "SPONSOR_REQUEST_FAILED",
@@ -5345,6 +5846,7 @@ var OwneySDK = class {
5345
5846
  paymasterServiceUrl;
5346
5847
  cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
5347
5848
  agentReads = new AgentReads();
5849
+ recoveredFundingReads = /* @__PURE__ */ new Set();
5348
5850
  activationChecks = /* @__PURE__ */ new Map();
5349
5851
  readAgent(agent, method, fetch2, params = null) {
5350
5852
  const key2 = JSON.stringify([
@@ -5458,8 +5960,8 @@ var OwneySDK = class {
5458
5960
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
5459
5961
  }
5460
5962
  /** Builds the default USDC batch callback for the connected wallet. */
5461
- getDefaultSponsoredCallback(onApproved) {
5462
- if (!onApproved && this.cachedSponsoredCallback)
5963
+ getDefaultSponsoredCallback(onApproved, onSubmitted) {
5964
+ if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
5463
5965
  return this.cachedSponsoredCallback;
5464
5966
  const provider = this.requireConnectedProvider();
5465
5967
  const owner = this.state.walletAddress;
@@ -5468,28 +5970,29 @@ var OwneySDK = class {
5468
5970
  baseUrl: this.routingApiBaseUrl,
5469
5971
  ownerAddress: owner,
5470
5972
  onApproved,
5973
+ onSubmitted,
5471
5974
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
5472
5975
  // Casts work around viem's chain-narrowed Client vs the generic
5473
5976
  // PublicClient/WalletClient param types — structurally identical at
5474
5977
  // runtime, but the two share a name TS treats as unrelated.
5475
5978
  getPublicClient: (cid) => this.getPaidRpcClient(cid),
5476
- getWalletChainClient: (cid) => (0, import_viem12.createPublicClient)({
5979
+ getWalletChainClient: (cid) => (0, import_viem13.createPublicClient)({
5477
5980
  chain: VIEM_CHAIN2[cid],
5478
- transport: (0, import_viem12.custom)(provider)
5981
+ transport: (0, import_viem13.custom)(provider)
5479
5982
  }),
5480
- getWalletClient: (cid) => (0, import_viem12.createWalletClient)({
5983
+ getWalletClient: (cid) => (0, import_viem13.createWalletClient)({
5481
5984
  account: owner,
5482
5985
  chain: VIEM_CHAIN2[cid],
5483
- transport: (0, import_viem12.custom)(provider)
5986
+ transport: (0, import_viem13.custom)(provider)
5484
5987
  })
5485
5988
  });
5486
- if (!onApproved) this.cachedSponsoredCallback = callback;
5989
+ if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
5487
5990
  return callback;
5488
5991
  }
5489
5992
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
5490
- getDefaultSponsoredCallsCallback(asset, onApproved) {
5993
+ getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
5491
5994
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
5492
- if (!onApproved && cached) return cached;
5995
+ if (!onApproved && !onSubmitted && cached) return cached;
5493
5996
  const provider = this.requireConnectedProvider();
5494
5997
  const callback = makeSponsoredCallsCallback({
5495
5998
  apiKey: this.apiKey,
@@ -5498,9 +6001,10 @@ var OwneySDK = class {
5498
6001
  ownerAddress: this.state.walletAddress,
5499
6002
  paymasterServiceUrl: this.paymasterServiceUrl,
5500
6003
  onApproved,
6004
+ onSubmitted,
5501
6005
  tokenAddressByChain: sponsoredTokensFor(asset)
5502
6006
  });
5503
- if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
6007
+ if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
5504
6008
  return callback;
5505
6009
  }
5506
6010
  /**
@@ -5509,8 +6013,8 @@ var OwneySDK = class {
5509
6013
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
5510
6014
  * single-use batch authorization instead of an EIP-3009 authorization.
5511
6015
  */
5512
- getDefaultWethSponsoredCallback(onApproved) {
5513
- if (!onApproved && this.cachedWethSponsoredCallback)
6016
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
6017
+ if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
5514
6018
  return this.cachedWethSponsoredCallback;
5515
6019
  const provider = this.requireConnectedProvider();
5516
6020
  const owner = this.state.walletAddress;
@@ -5519,22 +6023,23 @@ var OwneySDK = class {
5519
6023
  baseUrl: this.routingApiBaseUrl,
5520
6024
  ownerAddress: owner,
5521
6025
  onApproved,
6026
+ onSubmitted,
5522
6027
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
5523
6028
  // Casts work around viem's chain-narrowed Client vs the generic
5524
6029
  // PublicClient/WalletClient param types — structurally identical at
5525
6030
  // runtime, but the two share a name TS treats as unrelated.
5526
6031
  getPublicClient: (cid) => this.getPaidRpcClient(cid),
5527
- getWalletChainClient: (cid) => (0, import_viem12.createPublicClient)({
6032
+ getWalletChainClient: (cid) => (0, import_viem13.createPublicClient)({
5528
6033
  chain: VIEM_CHAIN2[cid],
5529
- transport: (0, import_viem12.custom)(provider)
6034
+ transport: (0, import_viem13.custom)(provider)
5530
6035
  }),
5531
- getWalletClient: (cid) => (0, import_viem12.createWalletClient)({
6036
+ getWalletClient: (cid) => (0, import_viem13.createWalletClient)({
5532
6037
  account: owner,
5533
6038
  chain: VIEM_CHAIN2[cid],
5534
- transport: (0, import_viem12.custom)(provider)
6039
+ transport: (0, import_viem13.custom)(provider)
5535
6040
  })
5536
6041
  });
5537
- if (!onApproved) this.cachedWethSponsoredCallback = callback;
6042
+ if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
5538
6043
  return callback;
5539
6044
  }
5540
6045
  getAgent(agentId) {
@@ -5544,6 +6049,9 @@ var OwneySDK = class {
5544
6049
  }
5545
6050
  return agent;
5546
6051
  }
6052
+ getActiveAgentIds() {
6053
+ return [...this.getActiveAgents().keys()].sort();
6054
+ }
5547
6055
  getActiveAgents() {
5548
6056
  const active = /* @__PURE__ */ new Map();
5549
6057
  for (const id of this.activeAgents) {
@@ -5838,12 +6346,23 @@ var OwneySDK = class {
5838
6346
  */
5839
6347
  async deposit(options) {
5840
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;
5841
6360
  if (BigInt(amount) === 0n) {
5842
6361
  return { agentResults: {} };
5843
6362
  }
5844
6363
  const state = this.requireState();
5845
6364
  const chainId = this.requireChainId();
5846
- 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));
5847
6366
  if (agentId) {
5848
6367
  if (this.isAgentDisabled(agentId)) {
5849
6368
  throw new OwneyError(
@@ -5862,7 +6381,8 @@ var OwneySDK = class {
5862
6381
  amount,
5863
6382
  asset,
5864
6383
  effectiveCallback,
5865
- depositCallback
6384
+ depositCallback,
6385
+ onProgress
5866
6386
  );
5867
6387
  }
5868
6388
  const totalAmount = BigInt(amount);
@@ -5926,7 +6446,7 @@ var OwneySDK = class {
5926
6446
  run: (callback) => withFailureReporting(
5927
6447
  this.apiKey,
5928
6448
  agent.id,
5929
- () => agent.deposit(state, chainId, amount2, asset, callback),
6449
+ () => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
5930
6450
  this.routingApiBaseUrl
5931
6451
  )
5932
6452
  })),
@@ -5963,7 +6483,8 @@ var OwneySDK = class {
5963
6483
  agentAmount,
5964
6484
  asset,
5965
6485
  effectiveCallback,
5966
- depositCallback
6486
+ depositCallback,
6487
+ onProgress
5967
6488
  );
5968
6489
  } catch (error) {
5969
6490
  if (Object.keys(agentResults).length === 0) throw error;
@@ -6010,17 +6531,11 @@ var OwneySDK = class {
6010
6531
  * propagates unchanged. Scoped per agent invocation so one agent's failure
6011
6532
  * doesn't force a retry of sibling agents in a multi-agent split.
6012
6533
  */
6013
- async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
6534
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
6014
6535
  const attempt = (callback) => withFailureReporting(
6015
6536
  this.apiKey,
6016
6537
  agent.id,
6017
- () => agent.deposit(
6018
- state,
6019
- chainId,
6020
- amount,
6021
- asset,
6022
- callback
6023
- ),
6538
+ () => onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
6024
6539
  this.routingApiBaseUrl
6025
6540
  );
6026
6541
  let approvalAttempted = false;
@@ -6200,15 +6715,81 @@ var OwneySDK = class {
6200
6715
  while (current && typeof current === "object" && !seen.has(current)) {
6201
6716
  seen.add(current);
6202
6717
  const candidate = current;
6203
- if (candidate.code === 4001 || candidate.code === "4001") return true;
6718
+ if (candidate.code === 4001 || candidate.code === "4001" || candidate.code === "ACTION_REJECTED") return true;
6204
6719
  const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
6205
- if (/user (?:rejected|denied)|rejected by user/i.test(message)) {
6720
+ if (/user (?:rejected|denied)|rejected by user|request rejected/i.test(message)) {
6206
6721
  return true;
6207
6722
  }
6208
6723
  current = candidate.cause;
6209
6724
  }
6210
6725
  return false;
6211
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
+ }
6212
6793
  // --- Fund operations ---
6213
6794
  /**
6214
6795
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -6220,17 +6801,66 @@ var OwneySDK = class {
6220
6801
  * @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
6221
6802
  */
6222
6803
  async withdraw(options) {
6804
+ const state = this.requireState();
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) {
6223
6827
  const { asset, amount, agentId, onAgentResult } = options;
6224
6828
  const notifyAgentResult = (id, result) => {
6225
6829
  try {
6830
+ if (result.evidence) notifyFunding(options.onProgress, result.evidence);
6226
6831
  onAgentResult?.(id, result);
6227
6832
  } catch (error) {
6228
6833
  console.warn("Withdrawal result observer failed:", error);
6229
6834
  }
6230
6835
  };
6231
- const state = this.requireState();
6232
- const chainId = this.requireChainId();
6233
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
+ };
6234
6864
  const assetInfo = SupportedAssets.find(
6235
6865
  (a) => a.chainId === chainId && a.symbol === token
6236
6866
  );
@@ -6246,23 +6876,24 @@ var OwneySDK = class {
6246
6876
  const result = await withFailureReporting(
6247
6877
  this.apiKey,
6248
6878
  agent.id,
6249
- () => agent.withdraw(state, chainId, token, amount),
6879
+ () => submit(agent, amount),
6250
6880
  this.routingApiBaseUrl
6251
6881
  );
6252
6882
  notifyAgentResult(agent.id, result);
6253
6883
  return result;
6254
6884
  }
6255
- const eligibleAgents = this.getEligibleAgents(chainId, asset);
6885
+ const eligibleAgents = this.getEligibleAgents(chainId, asset).filter((agent) => !options.excludeAgentIds?.includes(agent.id));
6256
6886
  const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
6257
6887
  if (!amount) {
6258
6888
  const results2 = {};
6259
6889
  const agentErrors2 = {};
6260
6890
  for (const agent of withdrawalAgents) {
6261
6891
  try {
6262
- results2[agent.id] = await agent.withdraw(state, chainId, token);
6892
+ results2[agent.id] = await submit(agent);
6263
6893
  notifyAgentResult(agent.id, results2[agent.id]);
6264
6894
  } catch (err) {
6265
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;
6266
6897
  console.error(`withdraw failed for agent "${agent.id}":`, err);
6267
6898
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
6268
6899
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
@@ -6281,6 +6912,13 @@ var OwneySDK = class {
6281
6912
  { asset, agentErrors: agentErrors2 }
6282
6913
  );
6283
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
+ }
6284
6922
  const amountsKnown = Object.values(results2).every(
6285
6923
  (result) => typeof result.amount === "string" && /^\d+$/.test(result.amount)
6286
6924
  );
@@ -6290,7 +6928,7 @@ var OwneySDK = class {
6290
6928
  };
6291
6929
  }
6292
6930
  const requested = BigInt(amount);
6293
- const aggregated = await this.getBalances();
6931
+ const aggregated = await this.getBalances(void 0, { chainId, asset: token });
6294
6932
  const unavailableAgents = eligibleAgents.filter(
6295
6933
  (agent) => !(agent.id in aggregated.agentBalances)
6296
6934
  );
@@ -6352,12 +6990,7 @@ var OwneySDK = class {
6352
6990
  const p = orderedPlans[i];
6353
6991
  if (p.planned === 0n) continue;
6354
6992
  try {
6355
- results[p.agent.id] = await p.agent.withdraw(
6356
- state,
6357
- chainId,
6358
- token,
6359
- p.planned.toString()
6360
- );
6993
+ results[p.agent.id] = await submit(p.agent, p.planned.toString());
6361
6994
  notifyAgentResult(p.agent.id, results[p.agent.id]);
6362
6995
  } catch (err) {
6363
6996
  if (this.isUserRejectedWithdrawal(err)) throw err;
@@ -6372,7 +7005,8 @@ var OwneySDK = class {
6372
7005
  );
6373
7006
  const failedAmount = p.planned;
6374
7007
  p.planned = 0n;
6375
- redistributeShare(orderedPlans, i, failedAmount);
7008
+ if (submissionEvidence.get(p.agent.id)?.transfer === "failed")
7009
+ redistributeShare(orderedPlans, i, failedAmount);
6376
7010
  }
6377
7011
  }
6378
7012
  if (Object.keys(results).length === 0) {
@@ -6409,12 +7043,19 @@ var OwneySDK = class {
6409
7043
  * @param agentId - Optional. Agent to query. Omit for aggregated balances.
6410
7044
  * @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
6411
7045
  */
6412
- async getBalances(agentId) {
6413
- const state = this.requireState();
6414
- 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
+ );
6415
7055
  if (agentId) {
6416
7056
  const agent = this.getAgent(agentId);
6417
- const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
7057
+ const result = await readBalance(agent);
7058
+ assertCurrent();
6418
7059
  return {
6419
7060
  ...result,
6420
7061
  balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
@@ -6425,7 +7066,7 @@ var OwneySDK = class {
6425
7066
  const entries = [...this.getActiveAgents().entries()];
6426
7067
  const balanceResults = await Promise.allSettled(
6427
7068
  entries.map(async ([id, agent]) => {
6428
- const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
7069
+ const b = await readBalance(agent);
6429
7070
  return [
6430
7071
  id,
6431
7072
  {
@@ -6435,6 +7076,7 @@ var OwneySDK = class {
6435
7076
  ];
6436
7077
  })
6437
7078
  );
7079
+ assertCurrent();
6438
7080
  let successCount = 0;
6439
7081
  const agentErrors = {};
6440
7082
  const agentRetryAt = {};
@@ -6702,12 +7344,13 @@ var OwneySDK = class {
6702
7344
  * @returns {OwneyAgentHistory} `data` (up to `limit` entries), `hasMore`, `nextCursor`.
6703
7345
  */
6704
7346
  async getHistory(options) {
6705
- const state = this.requireState();
6706
- const chainId = this.requireChainId();
7347
+ const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
6707
7348
  const { agentId, filters } = options ?? {};
6708
7349
  if (agentId) {
6709
7350
  const agent = this.getAgent(agentId);
6710
- 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;
6711
7354
  }
6712
7355
  const activeAgents = [...this.getActiveAgents().values()];
6713
7356
  const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
@@ -6717,20 +7360,35 @@ var OwneySDK = class {
6717
7360
  if (filters?.cursor && agentCursor === void 0) {
6718
7361
  return { agentId: agent.id, page: null };
6719
7362
  }
6720
- const agentFilters = { ...filters, cursor: agentCursor };
6721
- const page = await this.readAgent(
6722
- agent,
6723
- "history",
6724
- () => agent.getHistory(state, chainId, agentFilters),
6725
- agentFilters
6726
- );
6727
- 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
+ }
6728
7375
  })
6729
7376
  );
7377
+ assertCurrent();
7378
+ const agentErrors = {};
7379
+ const agentRetryAt = {};
6730
7380
  const merged = [];
6731
7381
  const nextCursorMap = {};
6732
7382
  let hasMore = false;
6733
- 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
+ }
6734
7392
  if (!page) continue;
6735
7393
  merged.push(...page.data);
6736
7394
  if (page.hasMore && page.nextCursor) {
@@ -6750,6 +7408,7 @@ var OwneySDK = class {
6750
7408
  });
6751
7409
  return {
6752
7410
  data: merged,
7411
+ ...Object.keys(agentErrors).length ? { agentErrors, agentRetryAt } : {},
6753
7412
  hasMore,
6754
7413
  nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
6755
7414
  };
@@ -6831,16 +7490,16 @@ var OwneySDK = class {
6831
7490
  );
6832
7491
  }
6833
7492
  const provider = this.requireConnectedProvider();
6834
- const walletChainClient = (0, import_viem12.createPublicClient)({
7493
+ const walletChainClient = (0, import_viem13.createPublicClient)({
6835
7494
  chain: VIEM_CHAIN2[chainId],
6836
- transport: (0, import_viem12.custom)(provider)
7495
+ transport: (0, import_viem13.custom)(provider)
6837
7496
  });
6838
7497
  const publicClient = this.getPaidRpcClient(chainId);
6839
7498
  const approvalAmount = permit2ApprovalAmount(requiredAmount);
6840
- const wallet = (0, import_viem12.createWalletClient)({
7499
+ const wallet = (0, import_viem13.createWalletClient)({
6841
7500
  account: state.walletAddress,
6842
7501
  chain: VIEM_CHAIN2[chainId],
6843
- transport: (0, import_viem12.custom)(provider)
7502
+ transport: (0, import_viem13.custom)(provider)
6844
7503
  });
6845
7504
  await ensureWalletOnChain(walletChainClient, wallet, chainId);
6846
7505
  const hash = await wallet.writeContract({
@@ -7002,7 +7661,7 @@ var OwneySDK = class {
7002
7661
  };
7003
7662
 
7004
7663
  // src/agents/zyfai/zyfai.siwx.ts
7005
- var import_viem13 = require("viem");
7664
+ var import_viem14 = require("viem");
7006
7665
  var import_siwe2 = require("siwe");
7007
7666
  var import_sdk2 = require("@zyfai/sdk");
7008
7667
 
@@ -7129,7 +7788,7 @@ function buildSIWXConfig(deps) {
7129
7788
  issuedAt,
7130
7789
  toString() {
7131
7790
  return new import_siwe2.SiweMessage({
7132
- address: (0, import_viem13.getAddress)(accountAddress),
7791
+ address: (0, import_viem14.getAddress)(accountAddress),
7133
7792
  chainId: numericChainId(chainId),
7134
7793
  domain,
7135
7794
  uri,