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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
+ }
3326
3670
  }
3327
3671
  }
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));
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.");
3688
+ }
3689
+ }
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) {
4126
+ this.assertChain(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) {
3679
4142
  this.assertChain(chainId);
3680
- return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
4143
+ this.assertAsset(asset);
4144
+ const key2 = this.contextKey(state, chainId, asset);
4145
+ const cached = this.readCache.get(`snapshot:${key2}`);
4146
+ const snapshot = cached?.value?.agentSnapshot;
4147
+ if (!cached || !snapshot || read2.chainId !== chainId || read2.asset.toUpperCase() !== asset || read2.readVersion !== this.portfolioVersion(key2) || read2.fetchedAt !== cached.fetchedAt || read2.snapshotId !== String(snapshot.agentWalletSnapshotId) || read2.providerUpdatedAt !== snapshot.updatedDate || read2.netDepositsRaw !== snapshot.netDepositsBase) return false;
4148
+ this.snapshotMovements.delete(key2);
4149
+ this.reconcileUntil.delete(key2);
4150
+ cached.expiresAt = cached.fetchedAt + YIELDSEEKER_PORTFOLIO_CACHE_MS;
4151
+ return true;
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,17 +5246,16 @@ 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
- var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
4770
5259
  var ERC2612_PERMIT_TYPES = {
4771
5260
  Permit: [
4772
5261
  { name: "owner", type: "address" },
@@ -4776,7 +5265,7 @@ var ERC2612_PERMIT_TYPES = {
4776
5265
  { name: "deadline", type: "uint256" }
4777
5266
  ]
4778
5267
  };
4779
- var ERC2612_READ_ABI = (0, import_viem9.parseAbi)([
5268
+ var ERC2612_READ_ABI = (0, import_viem10.parseAbi)([
4780
5269
  "function name() view returns (string)",
4781
5270
  "function version() view returns (string)",
4782
5271
  "function nonces(address owner) view returns (uint256)"
@@ -4814,7 +5303,7 @@ var PERMIT_BATCH_TYPES = {
4814
5303
  { name: "amount", type: "uint256" }
4815
5304
  ]
4816
5305
  };
4817
- var PERMIT2_BATCH_ABI = (0, import_viem9.parseAbi)([
5306
+ var PERMIT2_BATCH_ABI = (0, import_viem10.parseAbi)([
4818
5307
  "struct TokenPermissions { address token; uint256 amount; }",
4819
5308
  "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
4820
5309
  "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
@@ -4873,7 +5362,7 @@ function clear(key2) {
4873
5362
  else window.localStorage.removeItem(key2);
4874
5363
  }
4875
5364
  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()}`;
5365
+ 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
5366
  const plan = planOf(i.transfers);
4878
5367
  const active = inflight.get(key2);
4879
5368
  if (active) {
@@ -4898,13 +5387,29 @@ async function execute(i, key2, plan) {
4898
5387
  let body = initial;
4899
5388
  save(key2, body);
4900
5389
  try {
5390
+ if (body.erc2612Permit && !body.serializedPermitTransaction) {
5391
+ const preparedPermit = await postSponsorBatchTransfer({
5392
+ apiKey: i.apiKey,
5393
+ baseUrl: i.baseUrl,
5394
+ body
5395
+ });
5396
+ if (!preparedPermit.serializedPermitTransaction || !preparedPermit.permitTxHash || (0, import_viem11.keccak256)(preparedPermit.serializedPermitTransaction) !== preparedPermit.permitTxHash)
5397
+ throw new Error(
5398
+ "Sponsorship API did not return a valid prepared permit transaction."
5399
+ );
5400
+ body = {
5401
+ ...body,
5402
+ serializedPermitTransaction: preparedPermit.serializedPermitTransaction
5403
+ };
5404
+ save(key2, body);
5405
+ }
4901
5406
  if (!body.serializedTransaction) {
4902
5407
  const prepared = await postSponsorBatchTransfer({
4903
5408
  apiKey: i.apiKey,
4904
5409
  baseUrl: i.baseUrl,
4905
5410
  body
4906
5411
  });
4907
- if (!prepared.serializedTransaction || (0, import_viem10.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
5412
+ if (!prepared.serializedTransaction || (0, import_viem11.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
4908
5413
  throw new Error(
4909
5414
  "Sponsorship API did not return a valid prepared transaction."
4910
5415
  );
@@ -4914,12 +5419,16 @@ async function execute(i, key2, plan) {
4914
5419
  };
4915
5420
  save(key2, body);
4916
5421
  }
5422
+ try {
5423
+ i.onSubmitted?.((0, import_viem11.keccak256)(body.serializedTransaction));
5424
+ } catch {
5425
+ }
4917
5426
  const result = await postSponsorBatchTransfer({
4918
5427
  apiKey: i.apiKey,
4919
5428
  baseUrl: i.baseUrl,
4920
5429
  body
4921
5430
  });
4922
- if (result.txHash !== (0, import_viem10.keccak256)(body.serializedTransaction))
5431
+ if (result.txHash !== (0, import_viem11.keccak256)(body.serializedTransaction))
4923
5432
  throw new Error(
4924
5433
  "Sponsorship receipt does not match the pending transaction."
4925
5434
  );
@@ -4934,7 +5443,7 @@ async function execute(i, key2, plan) {
4934
5443
  const saved = read(key2);
4935
5444
  if (saved) {
4936
5445
  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)
5446
+ 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
5447
  throw new Error(
4939
5448
  "Retry the previous token deposit and agent split first to reconcile its status."
4940
5449
  );
@@ -4951,7 +5460,7 @@ async function execute(i, key2, plan) {
4951
5460
  "DEPOSIT_INSUFFICIENT_BALANCE",
4952
5461
  "Insufficient token balance for this deposit."
4953
5462
  );
4954
- if (allowance < total && (i.chainId !== 8453 || !(0, import_viem10.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
5463
+ if (allowance < total && (i.chainId !== 8453 || !(0, import_viem11.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
4955
5464
  throw new OwneyError(
4956
5465
  "PERMIT2_APPROVAL_REQUIRED",
4957
5466
  "token deposits need a one-time Permit2 approval."
@@ -4995,7 +5504,7 @@ async function execute(i, key2, plan) {
4995
5504
  });
4996
5505
  erc2612Permit = { ...unsignedPermit, signature: signature2 };
4997
5506
  }
4998
- const spender = erc2612Permit ? MULTICALL3_ADDRESS : await getSponsorRelayerAddress({
5507
+ const spender = await getSponsorRelayerAddress({
4999
5508
  apiKey: i.apiKey,
5000
5509
  baseUrl: i.baseUrl,
5001
5510
  chainId: i.chainId
@@ -5042,7 +5551,8 @@ function makeSponsoredTokenCallback(deps) {
5042
5551
  transfers,
5043
5552
  pub,
5044
5553
  wallet,
5045
- onApproved: deps.onApproved
5554
+ onApproved: deps.onApproved,
5555
+ onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
5046
5556
  });
5047
5557
  };
5048
5558
  const callback = makeVerificationAwareDepositCallback(
@@ -5128,7 +5638,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
5128
5638
  }
5129
5639
 
5130
5640
  // src/lib/sponsored-calls-deposit.ts
5131
- var import_viem11 = require("viem");
5641
+ var import_viem12 = require("viem");
5132
5642
  var DEFAULT_POLL_INTERVAL_MS = 1500;
5133
5643
  var DEFAULT_MAX_POLLS = 30;
5134
5644
  async function paymasterSupported(provider, owner, chainId) {
@@ -5136,7 +5646,7 @@ async function paymasterSupported(provider, owner, chainId) {
5136
5646
  method: "wallet_getCapabilities",
5137
5647
  params: [owner]
5138
5648
  });
5139
- const forChain = caps?.[(0, import_viem11.toHex)(chainId)] ?? caps?.[String(chainId)];
5649
+ const forChain = caps?.[(0, import_viem12.toHex)(chainId)] ?? caps?.[String(chainId)];
5140
5650
  return Boolean(forChain?.paymasterService?.supported);
5141
5651
  }
5142
5652
  function makeSponsoredCallsCallback(deps) {
@@ -5173,8 +5683,8 @@ function makeSponsoredCallsCallback(deps) {
5173
5683
  const calls = transfers.map((transfer) => ({
5174
5684
  to: token,
5175
5685
  value: "0x0",
5176
- data: (0, import_viem11.encodeFunctionData)({
5177
- abi: import_viem11.erc20Abi,
5686
+ data: (0, import_viem12.encodeFunctionData)({
5687
+ abi: import_viem12.erc20Abi,
5178
5688
  functionName: "transfer",
5179
5689
  args: [transfer.to, BigInt(transfer.amount)]
5180
5690
  })
@@ -5212,7 +5722,7 @@ function makeSponsoredCallsCallback(deps) {
5212
5722
  {
5213
5723
  version: "2.0.0",
5214
5724
  from: deps.ownerAddress,
5215
- chainId: (0, import_viem11.toHex)(chainId),
5725
+ chainId: (0, import_viem12.toHex)(chainId),
5216
5726
  atomicRequired: transfers.length > 1,
5217
5727
  calls,
5218
5728
  capabilities: {
@@ -5236,6 +5746,12 @@ function makeSponsoredCallsCallback(deps) {
5236
5746
  params: [callsId]
5237
5747
  });
5238
5748
  const txHash = status?.receipts?.[0]?.transactionHash;
5749
+ if (txHash && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
5750
+ try {
5751
+ deps.onSubmitted?.(txHash, transfers);
5752
+ } catch {
5753
+ }
5754
+ }
5239
5755
  if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
5240
5756
  throw new OwneyError(
5241
5757
  "SPONSOR_REQUEST_FAILED",
@@ -5345,6 +5861,7 @@ var OwneySDK = class {
5345
5861
  paymasterServiceUrl;
5346
5862
  cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
5347
5863
  agentReads = new AgentReads();
5864
+ recoveredFundingReads = /* @__PURE__ */ new Set();
5348
5865
  activationChecks = /* @__PURE__ */ new Map();
5349
5866
  readAgent(agent, method, fetch2, params = null) {
5350
5867
  const key2 = JSON.stringify([
@@ -5458,8 +5975,8 @@ var OwneySDK = class {
5458
5975
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
5459
5976
  }
5460
5977
  /** Builds the default USDC batch callback for the connected wallet. */
5461
- getDefaultSponsoredCallback(onApproved) {
5462
- if (!onApproved && this.cachedSponsoredCallback)
5978
+ getDefaultSponsoredCallback(onApproved, onSubmitted) {
5979
+ if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
5463
5980
  return this.cachedSponsoredCallback;
5464
5981
  const provider = this.requireConnectedProvider();
5465
5982
  const owner = this.state.walletAddress;
@@ -5468,28 +5985,29 @@ var OwneySDK = class {
5468
5985
  baseUrl: this.routingApiBaseUrl,
5469
5986
  ownerAddress: owner,
5470
5987
  onApproved,
5988
+ onSubmitted,
5471
5989
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
5472
5990
  // Casts work around viem's chain-narrowed Client vs the generic
5473
5991
  // PublicClient/WalletClient param types — structurally identical at
5474
5992
  // runtime, but the two share a name TS treats as unrelated.
5475
5993
  getPublicClient: (cid) => this.getPaidRpcClient(cid),
5476
- getWalletChainClient: (cid) => (0, import_viem12.createPublicClient)({
5994
+ getWalletChainClient: (cid) => (0, import_viem13.createPublicClient)({
5477
5995
  chain: VIEM_CHAIN2[cid],
5478
- transport: (0, import_viem12.custom)(provider)
5996
+ transport: (0, import_viem13.custom)(provider)
5479
5997
  }),
5480
- getWalletClient: (cid) => (0, import_viem12.createWalletClient)({
5998
+ getWalletClient: (cid) => (0, import_viem13.createWalletClient)({
5481
5999
  account: owner,
5482
6000
  chain: VIEM_CHAIN2[cid],
5483
- transport: (0, import_viem12.custom)(provider)
6001
+ transport: (0, import_viem13.custom)(provider)
5484
6002
  })
5485
6003
  });
5486
- if (!onApproved) this.cachedSponsoredCallback = callback;
6004
+ if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
5487
6005
  return callback;
5488
6006
  }
5489
6007
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
5490
- getDefaultSponsoredCallsCallback(asset, onApproved) {
6008
+ getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
5491
6009
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
5492
- if (!onApproved && cached) return cached;
6010
+ if (!onApproved && !onSubmitted && cached) return cached;
5493
6011
  const provider = this.requireConnectedProvider();
5494
6012
  const callback = makeSponsoredCallsCallback({
5495
6013
  apiKey: this.apiKey,
@@ -5498,9 +6016,10 @@ var OwneySDK = class {
5498
6016
  ownerAddress: this.state.walletAddress,
5499
6017
  paymasterServiceUrl: this.paymasterServiceUrl,
5500
6018
  onApproved,
6019
+ onSubmitted,
5501
6020
  tokenAddressByChain: sponsoredTokensFor(asset)
5502
6021
  });
5503
- if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
6022
+ if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
5504
6023
  return callback;
5505
6024
  }
5506
6025
  /**
@@ -5509,8 +6028,8 @@ var OwneySDK = class {
5509
6028
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
5510
6029
  * single-use batch authorization instead of an EIP-3009 authorization.
5511
6030
  */
5512
- getDefaultWethSponsoredCallback(onApproved) {
5513
- if (!onApproved && this.cachedWethSponsoredCallback)
6031
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
6032
+ if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
5514
6033
  return this.cachedWethSponsoredCallback;
5515
6034
  const provider = this.requireConnectedProvider();
5516
6035
  const owner = this.state.walletAddress;
@@ -5519,22 +6038,23 @@ var OwneySDK = class {
5519
6038
  baseUrl: this.routingApiBaseUrl,
5520
6039
  ownerAddress: owner,
5521
6040
  onApproved,
6041
+ onSubmitted,
5522
6042
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
5523
6043
  // Casts work around viem's chain-narrowed Client vs the generic
5524
6044
  // PublicClient/WalletClient param types — structurally identical at
5525
6045
  // runtime, but the two share a name TS treats as unrelated.
5526
6046
  getPublicClient: (cid) => this.getPaidRpcClient(cid),
5527
- getWalletChainClient: (cid) => (0, import_viem12.createPublicClient)({
6047
+ getWalletChainClient: (cid) => (0, import_viem13.createPublicClient)({
5528
6048
  chain: VIEM_CHAIN2[cid],
5529
- transport: (0, import_viem12.custom)(provider)
6049
+ transport: (0, import_viem13.custom)(provider)
5530
6050
  }),
5531
- getWalletClient: (cid) => (0, import_viem12.createWalletClient)({
6051
+ getWalletClient: (cid) => (0, import_viem13.createWalletClient)({
5532
6052
  account: owner,
5533
6053
  chain: VIEM_CHAIN2[cid],
5534
- transport: (0, import_viem12.custom)(provider)
6054
+ transport: (0, import_viem13.custom)(provider)
5535
6055
  })
5536
6056
  });
5537
- if (!onApproved) this.cachedWethSponsoredCallback = callback;
6057
+ if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
5538
6058
  return callback;
5539
6059
  }
5540
6060
  getAgent(agentId) {
@@ -5544,6 +6064,9 @@ var OwneySDK = class {
5544
6064
  }
5545
6065
  return agent;
5546
6066
  }
6067
+ getActiveAgentIds() {
6068
+ return [...this.getActiveAgents().keys()].sort();
6069
+ }
5547
6070
  getActiveAgents() {
5548
6071
  const active = /* @__PURE__ */ new Map();
5549
6072
  for (const id of this.activeAgents) {
@@ -5838,12 +6361,23 @@ var OwneySDK = class {
5838
6361
  */
5839
6362
  async deposit(options) {
5840
6363
  const { amount, asset, depositCallback, agentId, onApproved } = options;
6364
+ const prepared = /* @__PURE__ */ new Map();
6365
+ const onProgress = options.onProgress ? (evidence) => {
6366
+ if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
6367
+ notifyFunding(options.onProgress, evidence);
6368
+ } : void 0;
6369
+ const onSubmitted = onProgress ? (hash, destinations) => {
6370
+ for (const destination of destinations) {
6371
+ const evidence = prepared.get(destination.to.toLowerCase());
6372
+ if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
6373
+ }
6374
+ } : void 0;
5841
6375
  if (BigInt(amount) === 0n) {
5842
6376
  return { agentResults: {} };
5843
6377
  }
5844
6378
  const state = this.requireState();
5845
6379
  const chainId = this.requireChainId();
5846
- const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
6380
+ const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted) : this.getDefaultSponsoredCallback(onApproved, onSubmitted));
5847
6381
  if (agentId) {
5848
6382
  if (this.isAgentDisabled(agentId)) {
5849
6383
  throw new OwneyError(
@@ -5862,7 +6396,8 @@ var OwneySDK = class {
5862
6396
  amount,
5863
6397
  asset,
5864
6398
  effectiveCallback,
5865
- depositCallback
6399
+ depositCallback,
6400
+ onProgress
5866
6401
  );
5867
6402
  }
5868
6403
  const totalAmount = BigInt(amount);
@@ -5926,7 +6461,7 @@ var OwneySDK = class {
5926
6461
  run: (callback) => withFailureReporting(
5927
6462
  this.apiKey,
5928
6463
  agent.id,
5929
- () => agent.deposit(state, chainId, amount2, asset, callback),
6464
+ () => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
5930
6465
  this.routingApiBaseUrl
5931
6466
  )
5932
6467
  })),
@@ -5963,7 +6498,8 @@ var OwneySDK = class {
5963
6498
  agentAmount,
5964
6499
  asset,
5965
6500
  effectiveCallback,
5966
- depositCallback
6501
+ depositCallback,
6502
+ onProgress
5967
6503
  );
5968
6504
  } catch (error) {
5969
6505
  if (Object.keys(agentResults).length === 0) throw error;
@@ -6010,17 +6546,11 @@ var OwneySDK = class {
6010
6546
  * propagates unchanged. Scoped per agent invocation so one agent's failure
6011
6547
  * doesn't force a retry of sibling agents in a multi-agent split.
6012
6548
  */
6013
- async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
6549
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
6014
6550
  const attempt = (callback) => withFailureReporting(
6015
6551
  this.apiKey,
6016
6552
  agent.id,
6017
- () => agent.deposit(
6018
- state,
6019
- chainId,
6020
- amount,
6021
- asset,
6022
- callback
6023
- ),
6553
+ () => onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
6024
6554
  this.routingApiBaseUrl
6025
6555
  );
6026
6556
  let approvalAttempted = false;
@@ -6200,15 +6730,81 @@ var OwneySDK = class {
6200
6730
  while (current && typeof current === "object" && !seen.has(current)) {
6201
6731
  seen.add(current);
6202
6732
  const candidate = current;
6203
- if (candidate.code === 4001 || candidate.code === "4001") return true;
6733
+ if (candidate.code === 4001 || candidate.code === "4001" || candidate.code === "ACTION_REJECTED") return true;
6204
6734
  const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
6205
- if (/user (?:rejected|denied)|rejected by user/i.test(message)) {
6735
+ if (/user (?:rejected|denied)|rejected by user|request rejected/i.test(message)) {
6206
6736
  return true;
6207
6737
  }
6208
6738
  current = candidate.cause;
6209
6739
  }
6210
6740
  return false;
6211
6741
  }
6742
+ captureReadScope(requestedChain) {
6743
+ const original = this.requireState();
6744
+ const chainId = this.requireChainId();
6745
+ if (requestedChain !== void 0 && requestedChain !== chainId) {
6746
+ throw new OwneyError("NO_ACTIVE_CHAIN", "Account read scope changed. Please retry.");
6747
+ }
6748
+ const wallet = original.walletAddress;
6749
+ return { state: { ...original }, chainId, assertCurrent: () => {
6750
+ if (this.state !== original || this.state.walletAddress !== wallet || this.state.chainId !== chainId)
6751
+ throw new OwneyError("NOT_CONNECTED", "Account read scope changed. Please retry.");
6752
+ } };
6753
+ }
6754
+ /** Revalidate a saved transfer against the authenticated account and actual receipt. */
6755
+ async inspectFunding(evidence, direction, options = {}) {
6756
+ const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
6757
+ if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase() || !evidence.txHash)
6758
+ throw new OwneyError("AGENT_API_ERROR", "Funding evidence does not match the connected account.");
6759
+ const agent = this.getAgent(evidence.agentId);
6760
+ const verifiedWallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
6761
+ const agentWallet = evidence.agentWallet ?? verifiedWallet;
6762
+ if (!agentWallet || agentWallet.toLowerCase() !== verifiedWallet.toLowerCase())
6763
+ throw new OwneyError("AGENT_API_ERROR", "Funding wallet does not match this account.");
6764
+ const token = SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token);
6765
+ if (!token) throw new OwneyError("CHAIN_UNSUPPORTED", "Unsupported funding asset.");
6766
+ const rpc = this.getPaidRpcClient(chainId);
6767
+ const receipt = await rpc.getTransactionReceipt({ hash: evidence.txHash });
6768
+ assertCurrent();
6769
+ let verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet }, token.address, direction);
6770
+ if (direction === "withdraw" && evidence.agentId === "yieldseeker" && verified.transfer === "unknown" && verified.confirmedAmount && BigInt(verified.confirmedAmount) > 0n) {
6771
+ const transaction = await rpc.getTransaction({ hash: evidence.txHash });
6772
+ assertCurrent();
6773
+ if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address))
6774
+ verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
6775
+ }
6776
+ const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
6777
+ if ((verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
6778
+ if (options.refreshBalance !== false)
6779
+ agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil);
6780
+ this.recoveredFundingReads.add(key2);
6781
+ if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.values().next().value);
6782
+ }
6783
+ return verified;
6784
+ }
6785
+ /** A display acknowledgement controls polling only; it never confirms a financial outcome. */
6786
+ endFundingBalanceRefresh(evidence, read2) {
6787
+ const { state, chainId } = this.captureReadScope(evidence.chainId);
6788
+ if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase() || read2.chainId !== chainId || read2.asset.toUpperCase() !== evidence.token) return false;
6789
+ return this.getAgent(evidence.agentId).endFundingBalanceRefresh?.(state, chainId, evidence.token, read2) ?? false;
6790
+ }
6791
+ /** History lookup for receipt recovery; adapters may not authenticate or sign here. */
6792
+ async getFundingHistory(evidence, fromDate, options) {
6793
+ const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
6794
+ if (state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase())
6795
+ throw new OwneyError("NOT_CONNECTED", "Funding account changed.");
6796
+ const agent = this.getAgent(evidence.agentId);
6797
+ if (!agent.getFundingHistory) return { data: [], hasMore: false };
6798
+ const filters = { tokenSymbol: evidence.token, fromDate, limit: 50, ...options };
6799
+ const page = await this.readAgent(agent, "history", () => agent.getFundingHistory(state, chainId, filters), filters);
6800
+ assertCurrent();
6801
+ return page;
6802
+ }
6803
+ async recoverDepositRegistration(evidence) {
6804
+ const verified = await this.inspectFunding(evidence, "deposit");
6805
+ if (verified.agentId !== "zyfai") return verified;
6806
+ return this.getAgent("zyfai").retryDepositRegistration(this.requireState(), verified);
6807
+ }
6212
6808
  // --- Fund operations ---
6213
6809
  /**
6214
6810
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -6220,17 +6816,66 @@ var OwneySDK = class {
6220
6816
  * @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
6221
6817
  */
6222
6818
  async withdraw(options) {
6819
+ const state = this.requireState();
6820
+ const chainId = this.requireChainId();
6821
+ const ids = options.agentId ? [options.agentId] : this.getActiveAgentIds().filter((id) => !options.excludeAgentIds?.includes(id));
6822
+ const attempted = /* @__PURE__ */ new Set();
6823
+ try {
6824
+ return await this.withdrawFromAgents(options, state, chainId, attempted);
6825
+ } catch (error) {
6826
+ for (const id of ids) if (!attempted.has(id)) {
6827
+ notifyFunding(options.onProgress, {
6828
+ agentId: id,
6829
+ chainId,
6830
+ token: options.asset,
6831
+ walletAddress: state.walletAddress,
6832
+ intendedAmount: options.amount ?? "MAX",
6833
+ transfer: "failed",
6834
+ registration: "not-applicable",
6835
+ delivery: "failed"
6836
+ });
6837
+ }
6838
+ throw error;
6839
+ }
6840
+ }
6841
+ async withdrawFromAgents(options, state, chainId, attempted) {
6223
6842
  const { asset, amount, agentId, onAgentResult } = options;
6224
6843
  const notifyAgentResult = (id, result) => {
6225
6844
  try {
6845
+ if (result.evidence) notifyFunding(options.onProgress, result.evidence);
6226
6846
  onAgentResult?.(id, result);
6227
6847
  } catch (error) {
6228
6848
  console.warn("Withdrawal result observer failed:", error);
6229
6849
  }
6230
6850
  };
6231
- const state = this.requireState();
6232
- const chainId = this.requireChainId();
6233
6851
  const token = asset;
6852
+ const submissionEvidence = /* @__PURE__ */ new Map();
6853
+ const submit = async (agent, requested2) => {
6854
+ attempted.add(agent.id);
6855
+ let evidence = {
6856
+ agentId: agent.id,
6857
+ chainId,
6858
+ token,
6859
+ walletAddress: state.walletAddress,
6860
+ intendedAmount: requested2 ?? "MAX",
6861
+ transfer: "pending",
6862
+ registration: "not-applicable",
6863
+ delivery: "pending"
6864
+ };
6865
+ const progress = (next) => {
6866
+ evidence = { ...evidence, ...next };
6867
+ submissionEvidence.set(agent.id, evidence);
6868
+ notifyFunding(options.onProgress, evidence);
6869
+ };
6870
+ progress(evidence);
6871
+ try {
6872
+ return await agent.withdraw(state, chainId, token, requested2, progress);
6873
+ } catch (error) {
6874
+ if (!evidence.txHash && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
6875
+ progress({ ...evidence, transfer: "failed", delivery: "failed" });
6876
+ throw error;
6877
+ }
6878
+ };
6234
6879
  const assetInfo = SupportedAssets.find(
6235
6880
  (a) => a.chainId === chainId && a.symbol === token
6236
6881
  );
@@ -6246,23 +6891,24 @@ var OwneySDK = class {
6246
6891
  const result = await withFailureReporting(
6247
6892
  this.apiKey,
6248
6893
  agent.id,
6249
- () => agent.withdraw(state, chainId, token, amount),
6894
+ () => submit(agent, amount),
6250
6895
  this.routingApiBaseUrl
6251
6896
  );
6252
6897
  notifyAgentResult(agent.id, result);
6253
6898
  return result;
6254
6899
  }
6255
- const eligibleAgents = this.getEligibleAgents(chainId, asset);
6900
+ const eligibleAgents = this.getEligibleAgents(chainId, asset).filter((agent) => !options.excludeAgentIds?.includes(agent.id));
6256
6901
  const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
6257
6902
  if (!amount) {
6258
6903
  const results2 = {};
6259
6904
  const agentErrors2 = {};
6260
6905
  for (const agent of withdrawalAgents) {
6261
6906
  try {
6262
- results2[agent.id] = await agent.withdraw(state, chainId, token);
6907
+ results2[agent.id] = await submit(agent);
6263
6908
  notifyAgentResult(agent.id, results2[agent.id]);
6264
6909
  } catch (err) {
6265
6910
  if (this.isUserRejectedWithdrawal(err)) throw err;
6911
+ if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
6266
6912
  console.error(`withdraw failed for agent "${agent.id}":`, err);
6267
6913
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
6268
6914
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
@@ -6281,6 +6927,13 @@ var OwneySDK = class {
6281
6927
  { asset, agentErrors: agentErrors2 }
6282
6928
  );
6283
6929
  }
6930
+ if (Object.keys(agentErrors2).length > 0) {
6931
+ throw new OwneyError(
6932
+ "WITHDRAW_PARTIAL_FAILURE",
6933
+ "Some agents could not complete the withdrawal.",
6934
+ { asset, partialResults: results2, agentErrors: agentErrors2 }
6935
+ );
6936
+ }
6284
6937
  const amountsKnown = Object.values(results2).every(
6285
6938
  (result) => typeof result.amount === "string" && /^\d+$/.test(result.amount)
6286
6939
  );
@@ -6290,7 +6943,7 @@ var OwneySDK = class {
6290
6943
  };
6291
6944
  }
6292
6945
  const requested = BigInt(amount);
6293
- const aggregated = await this.getBalances();
6946
+ const aggregated = await this.getBalances(void 0, { chainId, asset: token });
6294
6947
  const unavailableAgents = eligibleAgents.filter(
6295
6948
  (agent) => !(agent.id in aggregated.agentBalances)
6296
6949
  );
@@ -6352,12 +7005,7 @@ var OwneySDK = class {
6352
7005
  const p = orderedPlans[i];
6353
7006
  if (p.planned === 0n) continue;
6354
7007
  try {
6355
- results[p.agent.id] = await p.agent.withdraw(
6356
- state,
6357
- chainId,
6358
- token,
6359
- p.planned.toString()
6360
- );
7008
+ results[p.agent.id] = await submit(p.agent, p.planned.toString());
6361
7009
  notifyAgentResult(p.agent.id, results[p.agent.id]);
6362
7010
  } catch (err) {
6363
7011
  if (this.isUserRejectedWithdrawal(err)) throw err;
@@ -6372,7 +7020,8 @@ var OwneySDK = class {
6372
7020
  );
6373
7021
  const failedAmount = p.planned;
6374
7022
  p.planned = 0n;
6375
- redistributeShare(orderedPlans, i, failedAmount);
7023
+ if (submissionEvidence.get(p.agent.id)?.transfer === "failed")
7024
+ redistributeShare(orderedPlans, i, failedAmount);
6376
7025
  }
6377
7026
  }
6378
7027
  if (Object.keys(results).length === 0) {
@@ -6409,12 +7058,19 @@ var OwneySDK = class {
6409
7058
  * @param agentId - Optional. Agent to query. Omit for aggregated balances.
6410
7059
  * @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
6411
7060
  */
6412
- async getBalances(agentId) {
6413
- const state = this.requireState();
6414
- const chainId = this.requireChainId();
7061
+ async getBalances(agentId, options) {
7062
+ const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
7063
+ const asset = options?.asset;
7064
+ const readBalance = (agent) => this.readAgent(
7065
+ agent,
7066
+ "balances",
7067
+ () => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
7068
+ asset ? { asset } : null
7069
+ );
6415
7070
  if (agentId) {
6416
7071
  const agent = this.getAgent(agentId);
6417
- const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
7072
+ const result = await readBalance(agent);
7073
+ assertCurrent();
6418
7074
  return {
6419
7075
  ...result,
6420
7076
  balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
@@ -6425,7 +7081,7 @@ var OwneySDK = class {
6425
7081
  const entries = [...this.getActiveAgents().entries()];
6426
7082
  const balanceResults = await Promise.allSettled(
6427
7083
  entries.map(async ([id, agent]) => {
6428
- const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
7084
+ const b = await readBalance(agent);
6429
7085
  return [
6430
7086
  id,
6431
7087
  {
@@ -6435,6 +7091,7 @@ var OwneySDK = class {
6435
7091
  ];
6436
7092
  })
6437
7093
  );
7094
+ assertCurrent();
6438
7095
  let successCount = 0;
6439
7096
  const agentErrors = {};
6440
7097
  const agentRetryAt = {};
@@ -6702,12 +7359,13 @@ var OwneySDK = class {
6702
7359
  * @returns {OwneyAgentHistory} `data` (up to `limit` entries), `hasMore`, `nextCursor`.
6703
7360
  */
6704
7361
  async getHistory(options) {
6705
- const state = this.requireState();
6706
- const chainId = this.requireChainId();
7362
+ const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
6707
7363
  const { agentId, filters } = options ?? {};
6708
7364
  if (agentId) {
6709
7365
  const agent = this.getAgent(agentId);
6710
- return this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
7366
+ const page = await this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
7367
+ assertCurrent();
7368
+ return page;
6711
7369
  }
6712
7370
  const activeAgents = [...this.getActiveAgents().values()];
6713
7371
  const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
@@ -6717,20 +7375,35 @@ var OwneySDK = class {
6717
7375
  if (filters?.cursor && agentCursor === void 0) {
6718
7376
  return { agentId: agent.id, page: null };
6719
7377
  }
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 };
7378
+ const agentFilters = { ...filters, cursor: agentCursor || void 0 };
7379
+ try {
7380
+ const page = await this.readAgent(
7381
+ agent,
7382
+ "history",
7383
+ () => agent.getHistory(state, chainId, agentFilters),
7384
+ agentFilters
7385
+ );
7386
+ return { agentId: agent.id, page };
7387
+ } catch (error) {
7388
+ return { agentId: agent.id, page: null, error };
7389
+ }
6728
7390
  })
6729
7391
  );
7392
+ assertCurrent();
7393
+ const agentErrors = {};
7394
+ const agentRetryAt = {};
6730
7395
  const merged = [];
6731
7396
  const nextCursorMap = {};
6732
7397
  let hasMore = false;
6733
- for (const { agentId: id, page } of perAgent) {
7398
+ for (const result of perAgent) {
7399
+ const { agentId: id, page } = result;
7400
+ if ("error" in result) {
7401
+ agentErrors[id] = result.error instanceof Error ? result.error.message : String(result.error);
7402
+ const delay = rateLimitDelay(result.error);
7403
+ if (delay !== void 0) agentRetryAt[id] = Date.now() + delay;
7404
+ nextCursorMap[id] = cursorMap[id] ?? "";
7405
+ hasMore = true;
7406
+ }
6734
7407
  if (!page) continue;
6735
7408
  merged.push(...page.data);
6736
7409
  if (page.hasMore && page.nextCursor) {
@@ -6750,6 +7423,7 @@ var OwneySDK = class {
6750
7423
  });
6751
7424
  return {
6752
7425
  data: merged,
7426
+ ...Object.keys(agentErrors).length ? { agentErrors, agentRetryAt } : {},
6753
7427
  hasMore,
6754
7428
  nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
6755
7429
  };
@@ -6831,16 +7505,16 @@ var OwneySDK = class {
6831
7505
  );
6832
7506
  }
6833
7507
  const provider = this.requireConnectedProvider();
6834
- const walletChainClient = (0, import_viem12.createPublicClient)({
7508
+ const walletChainClient = (0, import_viem13.createPublicClient)({
6835
7509
  chain: VIEM_CHAIN2[chainId],
6836
- transport: (0, import_viem12.custom)(provider)
7510
+ transport: (0, import_viem13.custom)(provider)
6837
7511
  });
6838
7512
  const publicClient = this.getPaidRpcClient(chainId);
6839
7513
  const approvalAmount = permit2ApprovalAmount(requiredAmount);
6840
- const wallet = (0, import_viem12.createWalletClient)({
7514
+ const wallet = (0, import_viem13.createWalletClient)({
6841
7515
  account: state.walletAddress,
6842
7516
  chain: VIEM_CHAIN2[chainId],
6843
- transport: (0, import_viem12.custom)(provider)
7517
+ transport: (0, import_viem13.custom)(provider)
6844
7518
  });
6845
7519
  await ensureWalletOnChain(walletChainClient, wallet, chainId);
6846
7520
  const hash = await wallet.writeContract({
@@ -7002,7 +7676,7 @@ var OwneySDK = class {
7002
7676
  };
7003
7677
 
7004
7678
  // src/agents/zyfai/zyfai.siwx.ts
7005
- var import_viem13 = require("viem");
7679
+ var import_viem14 = require("viem");
7006
7680
  var import_siwe2 = require("siwe");
7007
7681
  var import_sdk2 = require("@zyfai/sdk");
7008
7682
 
@@ -7129,7 +7803,7 @@ function buildSIWXConfig(deps) {
7129
7803
  issuedAt,
7130
7804
  toString() {
7131
7805
  return new import_siwe2.SiweMessage({
7132
- address: (0, import_viem13.getAddress)(accountAddress),
7806
+ address: (0, import_viem14.getAddress)(accountAddress),
7133
7807
  chainId: numericChainId(chainId),
7134
7808
  domain,
7135
7809
  uri,