@owney/sdk 0.7.25-beta.19 → 0.7.25-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,6 +16,29 @@ var accountExecutionAbi = parseAbi([
16
16
  ]);
17
17
  var entryPoint06 = "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789";
18
18
  var entryPoint07 = "0x0000000071727de22e5e9d8baf0edac6f37da032";
19
+ function yieldseekerExecution(receipt, evidence) {
20
+ const operationEvents = receipt.logs.flatMap((log) => {
21
+ if (![entryPoint06, entryPoint07].includes(log.address.toLowerCase())) return [];
22
+ try {
23
+ const event = decodeEventLog({ abi: entryPoint06Abi, data: log.data, topics: log.topics });
24
+ return event.eventName === "UserOperationEvent" ? [{ ...event.args, logIndex: log.logIndex }] : [];
25
+ } catch {
26
+ return [];
27
+ }
28
+ }).sort((a, b) => a.logIndex - b.logIndex);
29
+ const bundled = !!evidence.userOpHash || !!evidence.callsId || operationEvents.length > 0 || [entryPoint06, entryPoint07].includes(receipt.to?.toLowerCase() ?? "");
30
+ if (!bundled) return { status: "confirmed", logs: receipt.logs };
31
+ const senders = evidence.userOperation?.sender ? [evidence.userOperation.sender.toLowerCase()] : evidence.callsId ? [evidence.walletAddress.toLowerCase()] : [evidence.walletAddress.toLowerCase(), evidence.agentWallet?.toLowerCase()];
32
+ const matches = operationEvents.filter((event) => senders.includes(event.sender.toLowerCase()) && (!evidence.userOpHash || event.userOpHash.toLowerCase() === evidence.userOpHash.toLowerCase()));
33
+ if (matches.length !== 1) return { status: "unknown", logs: [] };
34
+ const operation = matches[0];
35
+ if (!operation.success) return { status: "failed", logs: [] };
36
+ const previous = operationEvents.filter((event) => event.logIndex < operation.logIndex).at(-1)?.logIndex ?? -1;
37
+ return {
38
+ status: "confirmed",
39
+ logs: receipt.logs.filter((log) => log.logIndex > previous && log.logIndex < operation.logIndex)
40
+ };
41
+ }
19
42
  function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receipt) {
20
43
  if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || !evidence.agentWallet) return false;
21
44
  const matchesFullCall = (data) => {
@@ -61,8 +84,27 @@ function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receip
61
84
  return false;
62
85
  }
63
86
  }
64
- function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
87
+ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction, options = {}) {
88
+ evidence = {
89
+ ...evidence,
90
+ confirmedAmount: void 0,
91
+ amountCompletion: void 0,
92
+ shortfallAmount: void 0,
93
+ blockNumber: void 0,
94
+ logIndexes: void 0,
95
+ transferLogs: void 0
96
+ };
97
+ if (evidence.txHash && receipt.transactionHash && evidence.txHash.toLowerCase() !== receipt.transactionHash.toLowerCase())
98
+ return { ...evidence, transfer: "unknown", delivery: direction === "withdraw" ? "unknown" : "not-applicable" };
65
99
  if (receipt.status !== "success") return { ...evidence, transfer: "failed", delivery: "failed" };
100
+ const execution = direction === "withdraw" && evidence.agentId === "yieldseeker" ? yieldseekerExecution(receipt, evidence) : { status: "confirmed", logs: receipt.logs };
101
+ if (execution.status !== "confirmed") return {
102
+ ...evidence,
103
+ blockNumber: receipt.blockNumber.toString(),
104
+ ...execution.status === "failed" ? { confirmedAmount: "0" } : {},
105
+ transfer: execution.status,
106
+ delivery: execution.status
107
+ };
66
108
  let amount = 0n;
67
109
  const seen = /* @__PURE__ */ new Set();
68
110
  const attributed = /* @__PURE__ */ new Map();
@@ -73,7 +115,7 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
73
115
  const logIndexes = [];
74
116
  const transferLogs = [];
75
117
  const zeroTransferLogs = [];
76
- for (const log of [...receipt.logs].sort((a, b) => a.logIndex - b.logIndex)) {
118
+ for (const log of [...execution.logs].sort((a, b) => a.logIndex - b.logIndex)) {
77
119
  if (log.address.toLowerCase() !== tokenAddress.toLowerCase()) continue;
78
120
  if (seen.has(log.logIndex)) continue;
79
121
  seen.add(log.logIndex);
@@ -99,7 +141,7 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
99
141
  }
100
142
  }
101
143
  const expected = /^\d+$/.test(evidence.intendedAmount) ? BigInt(evidence.intendedAmount) : void 0;
102
- const emptyPayout = direction === "withdraw" && evidence.agentId === "yieldseeker" && amount === 0n && zeroTransferLogs.length > 0 && receipt.logs.some((log) => {
144
+ const emptyPayout = direction === "withdraw" && evidence.agentId === "yieldseeker" && amount === 0n && zeroTransferLogs.length > 0 && execution.logs.some((log) => {
103
145
  if (log.address.toLowerCase() !== agent) return false;
104
146
  try {
105
147
  const { args } = decodeEventLog({ abi: withdrawalEventAbi, data: log.data, topics: log.topics });
@@ -117,18 +159,30 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
117
159
  transfer: "failed",
118
160
  delivery: "failed"
119
161
  };
120
- const delivered = amount > 0n && (expected === void 0 || amount >= expected);
162
+ const exactReceipt = !!evidence.txHash && /^0x[\da-f]{64}$/i.test(evidence.txHash) && receipt.transactionHash?.toLowerCase() === evidence.txHash.toLowerCase();
163
+ const delivered = amount > 0n && (options.requireAmountMatch ? expected === void 0 || amount === expected : direction === "withdraw" && exactReceipt || expected === void 0 || amount >= expected);
121
164
  return {
122
165
  ...evidence,
123
166
  blockNumber: receipt.blockNumber.toString(),
124
167
  logIndexes,
125
168
  transferLogs,
126
169
  ...amount > 0n ? { confirmedAmount: amount.toString() } : {},
170
+ ...delivered && direction === "withdraw" && expected !== void 0 ? {
171
+ amountCompletion: amount < expected ? "shortfall" : "complete",
172
+ shortfallAmount: (amount < expected ? expected - amount : 0n).toString()
173
+ } : {},
127
174
  transfer: delivered ? "confirmed" : "unknown",
128
175
  delivery: direction === "withdraw" ? delivered ? "confirmed" : "unknown" : "not-applicable"
129
176
  };
130
177
  }
131
178
 
179
+ // src/lib/helpers/funding-amount.ts
180
+ function isFundingAmount(value, allowZero = false) {
181
+ if (typeof value !== "string" || !/^\d{1,78}$/.test(value)) return false;
182
+ const amount = BigInt(value);
183
+ return (allowZero ? amount >= 0n : amount > 0n) && amount <= (1n << 256n) - 1n;
184
+ }
185
+
132
186
  // src/lib/deposit-batch-callback.ts
133
187
  var batchCallbacks = /* @__PURE__ */ new WeakMap();
134
188
  var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
@@ -345,7 +399,7 @@ var AgentReads = class {
345
399
 
346
400
  // src/agents/zyfai/zyfai.withdraw-submission.ts
347
401
  var listenersByClient = /* @__PURE__ */ new WeakMap();
348
- function observeWithdrawalSubmission(sdk, chainId, token) {
402
+ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
349
403
  const client = sdk.httpClient;
350
404
  if (!client || typeof client.get !== "function" || typeof client.post !== "function")
351
405
  return { attempted: () => void 0, rejected: () => false, request: () => void 0, stop: () => {
@@ -362,6 +416,7 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
362
416
  const withdrawal = method === "get" && url === "/users/withdraw" || method === "post" && url === "/users/partial-withdraw";
363
417
  const payload = method === "get" ? options?.params : options;
364
418
  const matching = withdrawal ? [...observers].filter((observer) => observer.chainId === payload?.chainId && observer.token === payload?.tokenSymbol) : [];
419
+ for (const observer of matching) observer.assertCurrent?.();
365
420
  const startedAt = Date.now();
366
421
  const request2 = {
367
422
  attempted: true,
@@ -406,7 +461,7 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
406
461
  const status = error?.response?.status;
407
462
  if (request2.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
408
463
  request2.httpStatus = status;
409
- request2.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : /^Idle balance \d+(?:\.\d+)? (USDC|WETH|ETH|EURC) is below the minimum withdrawal of \d+(?:\.\d+)? \1\.?$/.test(message) ? "insufficient-balance" : /^User doesn't have any active session key\.?$/.test(message) ? "session-key-missing" : method === "post" && request2.httpStatus === 400 && message === "Partial withdrawal already in progress" ? "withdrawal-in-progress" : message === "Network error: Unable to reach the server" ? "network" : "other";
464
+ request2.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : request2.httpStatus === 400 && message === "No balance to withdraw" ? "insufficient-balance" : /^Idle balance \d+(?:\.\d+)? (USDC|WETH|ETH|EURC) is below the minimum withdrawal of \d+(?:\.\d+)? \1\.?$/.test(message) ? "insufficient-balance" : /^User doesn't have any active session key\.?$/.test(message) ? "session-key-missing" : method === "post" && request2.httpStatus === 400 && message === "Partial withdrawal already in progress" ? "withdrawal-in-progress" : message === "Network error: Unable to reach the server" ? "network" : "other";
410
465
  if (["unauthorized", "forbidden", "rate-limited", "insufficient-balance", "session-key-missing", "withdrawal-in-progress"].includes(request2.failure) && (request2.httpStatus === void 0 || request2.httpStatus >= 400 && request2.httpStatus < 500)) notify("rejected");
411
466
  throw error;
412
467
  } finally {
@@ -419,7 +474,7 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
419
474
  let submitted = false;
420
475
  let rejected = false;
421
476
  let request = { attempted: false };
422
- const listener = { chainId, token, notify: (outcome, evidence) => {
477
+ const listener = { chainId, token, assertCurrent, notify: (outcome, evidence) => {
423
478
  request = evidence;
424
479
  if (outcome === "attempted") submitted = true;
425
480
  else if (outcome === "rejected") rejected = true;
@@ -470,10 +525,8 @@ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
470
525
  return true;
471
526
  }
472
527
 
473
- // src/agents/zyfai/zyfai.agent.ts
474
- import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
475
- import { erc20Abi as erc20Abi2, formatUnits as formatUnits2, isAddress, parseAbi as parseAbi2 } from "viem";
476
- import { base, arbitrum, mainnet } from "viem/chains";
528
+ // src/agents/zyfai/zyfai.deposit.ts
529
+ import { createWalletClient, custom, encodeFunctionData, erc20Abi as erc20Abi2, isAddress } from "viem";
477
530
 
478
531
  // src/types/config.ts
479
532
  var P_SUPPORTED_CHAINS = {
@@ -522,6 +575,43 @@ var SupportedAssets = [
522
575
  }
523
576
  ];
524
577
 
578
+ // src/agents/zyfai/zyfai.deposit.ts
579
+ async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWallet, assertCurrent }) {
580
+ const owner = state.walletAddress;
581
+ const provider = state.provider;
582
+ const token = SupportedAssets.find((value) => value.chainId === chain.id && value.symbol === asset);
583
+ if (!token || !isAddress(owner) || !isAddress(smartWallet) || !/^\d+$/.test(amount) || BigInt(amount) <= 0n)
584
+ throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId: chain.id, asset }, "zyfai");
585
+ assertCurrent();
586
+ const prepared = await sdk.prepareEnterPosition({ userAddress: owner, chainId: chain.id, amount, asset, strategy: "aggressive" });
587
+ assertCurrent();
588
+ const expectedData = encodeFunctionData({ abi: erc20Abi2, functionName: "transfer", args: [smartWallet, BigInt(amount)] });
589
+ const transfer = prepared?.transfer;
590
+ if (!transfer || transfer.safeAddress?.toLowerCase() !== smartWallet.toLowerCase() || transfer.tokenAddress?.toLowerCase() !== token.address.toLowerCase() || transfer.to?.toLowerCase() !== token.address.toLowerCase() || transfer.data?.toLowerCase() !== expectedData.toLowerCase() || transfer.value !== "0")
591
+ throw new OwneyError("AGENT_INVALID_RESPONSE", "Zyfai prepared a different deposit transfer.", { chainId: chain.id, asset }, "zyfai");
592
+ const wallet = createWalletClient({ account: owner, chain, transport: custom({
593
+ request: async (args) => {
594
+ assertCurrent();
595
+ if (args.method === "eth_sendTransaction") {
596
+ const [accounts, chainId] = await Promise.all([
597
+ provider.request({ method: "eth_accounts" }),
598
+ provider.request({ method: "eth_chainId" })
599
+ ]);
600
+ assertCurrent();
601
+ if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase() || Number(chainId) !== chain.id)
602
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai deposit.", void 0, "zyfai");
603
+ }
604
+ return provider.request(args);
605
+ }
606
+ }, { retryCount: 0 }) });
607
+ return wallet.sendTransaction({ account: owner, chain, to: token.address, data: expectedData, value: 0n });
608
+ }
609
+
610
+ // src/agents/zyfai/zyfai.agent.ts
611
+ import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
612
+ import { erc20Abi as erc20Abi3, formatUnits as formatUnits2, isAddress as isAddress2, parseAbi as parseAbi2 } from "viem";
613
+ import { base, arbitrum, mainnet } from "viem/chains";
614
+
525
615
  // src/lib/debug.ts
526
616
  var configuredDebug = false;
527
617
  function setOwneyDebug(enabled) {
@@ -581,13 +671,6 @@ function tokenDecimals(symbol, explicit) {
581
671
  return explicit;
582
672
  return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
583
673
  }
584
- function mapDeposit(raw2) {
585
- return {
586
- txHash: raw2.txHash,
587
- smartWallet: raw2.smartWallet,
588
- amount: raw2.amount
589
- };
590
- }
591
674
  function mapWithdraw(raw2) {
592
675
  return {
593
676
  txHash: raw2.txHash,
@@ -1490,6 +1573,14 @@ var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
1490
1573
  var EARNINGS_REFRESH_COOLDOWN_MS = 60 * 60 * 1e3;
1491
1574
  var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
1492
1575
  var ZyfaiAgent = class _ZyfaiAgent {
1576
+ constructor(apiKey, rpcUrls, referralSource) {
1577
+ this.apiKey = apiKey;
1578
+ this.referralSource = referralSource;
1579
+ this.rpcUrls = resolveRpcUrls(rpcUrls);
1580
+ this.sdk = this.createSdk();
1581
+ }
1582
+ apiKey;
1583
+ referralSource;
1493
1584
  id = "zyfai";
1494
1585
  supportedChainIds = [8453, 42161, 1];
1495
1586
  supportedAssets = [
@@ -1534,6 +1625,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
1534
1625
  */
1535
1626
  sessionKeyAttempts = /* @__PURE__ */ new Set();
1536
1627
  connectAuthPromise = null;
1628
+ connectAuthScope = null;
1629
+ connectionGeneration = 0;
1630
+ sdkHasConnected = false;
1537
1631
  rpcUrls;
1538
1632
  publicClients = /* @__PURE__ */ new Map();
1539
1633
  // Receipt-derived amounts and log identities, scoped to the owner and asset.
@@ -1544,17 +1638,20 @@ var ZyfaiAgent = class _ZyfaiAgent {
1544
1638
  earningsSnapshot = null;
1545
1639
  earningsGeneration = 0;
1546
1640
  balanceSnapshots = /* @__PURE__ */ new WeakMap();
1547
- constructor(apiKey, rpcUrls, referralSource) {
1548
- this.rpcUrls = resolveRpcUrls(rpcUrls);
1549
- this.sdk = new ZyfaiSDK({
1550
- apiKey,
1641
+ /** A replaced wallet session gets its own vendor client. In-flight calls
1642
+ * keep their original client; authentication must never change owners under
1643
+ * an operation that is waiting for provider reads or an HTTP response. */
1644
+ createSdk() {
1645
+ const sdk = new ZyfaiSDK({
1646
+ apiKey: this.apiKey,
1551
1647
  rpcUrls: this.rpcUrls,
1552
1648
  // Forwarded to /auth/login by the SDK's own SIWE handshake, so
1553
1649
  // attribution survives without the AppKit SIWX config (which carried it
1554
1650
  // via `createOwneySIWX({ referralSource })` when apps used the modal).
1555
- referralSource
1651
+ referralSource: this.referralSource
1556
1652
  });
1557
- this.installAuthCache();
1653
+ this.installAuthCache(sdk);
1654
+ return sdk;
1558
1655
  }
1559
1656
  getPublicClient(chainId) {
1560
1657
  const cached = this.publicClients.get(chainId);
@@ -1653,9 +1750,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
1653
1750
  * No-op on the server (no `window`) — the SDK is constructed during SSR
1654
1751
  * for some apps and the patch only matters in the browser anyway.
1655
1752
  */
1656
- installAuthCache() {
1753
+ installAuthCache(sdk = this.sdk) {
1657
1754
  if (typeof window === "undefined") return;
1658
- const internals = this.sdk;
1755
+ const internals = sdk;
1659
1756
  const original = internals.authenticateUser.bind(internals);
1660
1757
  internals.authenticateUser = async () => {
1661
1758
  if (internals.authenticatedUserId !== null) return;
@@ -1700,25 +1797,41 @@ var ZyfaiAgent = class _ZyfaiAgent {
1700
1797
  /** Light connect: SIWE auth only. Re-auths if chain changed. */
1701
1798
  async connectAuth(state, chainId) {
1702
1799
  const validChainId = isValidChainId(chainId);
1703
- if (this.connectedAddress && this.connectedChainId === validChainId) {
1800
+ const owner = state.walletAddress.toLowerCase();
1801
+ const scope = `${owner}:${validChainId}`;
1802
+ if (this.connectedAddress?.toLowerCase() === owner && this.connectedChainId === validChainId) {
1704
1803
  return;
1705
1804
  }
1706
- if (this.connectAuthPromise) {
1805
+ if (this.connectAuthPromise && this.connectAuthScope === scope) {
1707
1806
  await this.connectAuthPromise;
1708
- return;
1807
+ return this.connectAuth(state, chainId);
1709
1808
  }
1710
- this.connectAuthPromise = (async () => {
1809
+ if (this.sdkHasConnected || this.connectedAddress) this.sdk = this.createSdk();
1810
+ this.sdkHasConnected = true;
1811
+ const executionSdk = this.sdk;
1812
+ const generation = ++this.connectionGeneration;
1813
+ this.connectedAddress = null;
1814
+ this.connectedChainId = null;
1815
+ const pending = (async () => {
1711
1816
  const address = await this.connectAccountWithRetry(
1712
1817
  state.provider,
1713
- validChainId
1818
+ validChainId,
1819
+ executionSdk
1714
1820
  );
1821
+ if (generation !== this.connectionGeneration || address.toLowerCase() !== owner)
1822
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while connecting Zyfai.", void 0, this.id);
1715
1823
  this.connectedAddress = address;
1716
1824
  this.connectedChainId = validChainId;
1717
1825
  })();
1826
+ this.connectAuthPromise = pending;
1827
+ this.connectAuthScope = scope;
1718
1828
  try {
1719
- await this.connectAuthPromise;
1829
+ await pending;
1720
1830
  } finally {
1721
- this.connectAuthPromise = null;
1831
+ if (this.connectAuthPromise === pending) {
1832
+ this.connectAuthPromise = null;
1833
+ this.connectAuthScope = null;
1834
+ }
1722
1835
  }
1723
1836
  }
1724
1837
  /**
@@ -1730,16 +1843,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
1730
1843
  * JWT from a misconfigured API key; we retry once either way, and if the
1731
1844
  * second attempt still fails the original error propagates.
1732
1845
  */
1733
- async connectAccountWithRetry(provider, chainId) {
1846
+ async connectAccountWithRetry(provider, chainId, executionSdk = this.sdk) {
1734
1847
  try {
1735
- return await this.sdk.connectAccount(provider, chainId);
1848
+ return await executionSdk.connectAccount(provider, chainId);
1736
1849
  } catch (error) {
1737
1850
  const message = error instanceof Error ? error.message : String(error);
1738
1851
  if (!message.toLowerCase().includes("unauthorized")) throw error;
1739
- const internals = this.sdk;
1852
+ const internals = executionSdk;
1740
1853
  const address = internals.walletClient?.account?.address;
1741
1854
  if (address) clearSession(address, chainId);
1742
- return await this.sdk.connectAccount(provider, chainId);
1855
+ return await executionSdk.connectAccount(provider, chainId);
1743
1856
  }
1744
1857
  }
1745
1858
  /**
@@ -1751,11 +1864,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
1751
1864
  * withdrawFunds) fail with "No account connected".
1752
1865
  */
1753
1866
  async ensureConnected(state, chainId) {
1867
+ const assertOwner = () => {
1868
+ if (this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== chainId)
1869
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while connecting Zyfai.", void 0, this.id);
1870
+ };
1754
1871
  await this.connectAuth(state, chainId);
1872
+ assertOwner();
1755
1873
  const internals = this.sdk;
1756
1874
  if (!internals.walletClient || internals.authenticatedUserId === null) {
1757
1875
  this.connectedChainId = null;
1758
1876
  await this.connectAuth(state, chainId);
1877
+ assertOwner();
1759
1878
  }
1760
1879
  }
1761
1880
  /** A rejected execution session must not be reused by the next foreground attempt. */
@@ -1816,6 +1935,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
1816
1935
  }
1817
1936
  // --- IAgent: Connection lifecycle ---
1818
1937
  async disconnect() {
1938
+ const executionSdk = this.sdk;
1939
+ this.connectionGeneration++;
1819
1940
  this.earningsGeneration++;
1820
1941
  this.earningsReads.clear();
1821
1942
  this.earningsRefreshes.clear();
@@ -1823,10 +1944,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1823
1944
  if (this.connectedAddress && this.connectedChainId !== null) {
1824
1945
  clearSession(this.connectedAddress, this.connectedChainId);
1825
1946
  }
1826
- await this.sdk.disconnectAccount();
1827
1947
  this.connectedAddress = null;
1828
1948
  this.connectedChainId = null;
1949
+ this.connectAuthPromise = null;
1950
+ this.connectAuthScope = null;
1829
1951
  this.smartWalletCache.clear();
1952
+ this.sdk = this.createSdk();
1953
+ this.sdkHasConnected = false;
1954
+ await executionSdk.disconnectAccount();
1830
1955
  }
1831
1956
  // --- IAgent: Account lifecycle ---
1832
1957
  async activateAgent(state, chainId) {
@@ -2315,93 +2440,79 @@ var ZyfaiAgent = class _ZyfaiAgent {
2315
2440
  return redeployed.address;
2316
2441
  }
2317
2442
  async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
2443
+ const validChainId = isValidChainId(chainId);
2444
+ const owner = state.walletAddress;
2445
+ const provider = state.provider;
2446
+ await this.ensureConnected(state, validChainId);
2447
+ const executionSdk = this.sdk;
2448
+ const generation = this.connectionGeneration;
2449
+ const assertCurrent = () => {
2450
+ const account = executionSdk.walletClient?.account?.address;
2451
+ if (generation !== this.connectionGeneration || executionSdk !== this.sdk || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== validChainId || state.walletAddress.toLowerCase() !== owner.toLowerCase() || state.provider !== provider || state.chainId !== null && state.chainId !== validChainId || account && account.toLowerCase() !== owner.toLowerCase())
2452
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai deposit.", void 0, this.id);
2453
+ };
2454
+ assertCurrent();
2455
+ const smartWallet = await this.ensureWalletDeployed(owner, validChainId);
2456
+ assertCurrent();
2457
+ notifyFunding(onProgress, {
2458
+ agentId: this.id,
2459
+ chainId,
2460
+ token: asset,
2461
+ walletAddress: owner,
2462
+ agentWallet: smartWallet,
2463
+ intendedAmount: amount,
2464
+ transfer: "pending",
2465
+ registration: "pending",
2466
+ delivery: "not-applicable"
2467
+ });
2468
+ const txHash = depositCallback ? await depositCallback(smartWallet, validChainId, amount) : await submitZyfaiDeposit({
2469
+ sdk: executionSdk,
2470
+ state: { ...state, walletAddress: owner, provider },
2471
+ chain: VIEM_CHAIN[validChainId],
2472
+ amount,
2473
+ asset,
2474
+ smartWallet,
2475
+ assertCurrent
2476
+ });
2477
+ let evidence = {
2478
+ agentId: this.id,
2479
+ chainId,
2480
+ token: asset,
2481
+ walletAddress: owner,
2482
+ agentWallet: smartWallet,
2483
+ intendedAmount: amount,
2484
+ txHash,
2485
+ transfer: "pending",
2486
+ registration: "pending",
2487
+ delivery: "not-applicable"
2488
+ };
2489
+ notifyFunding(onProgress, evidence);
2318
2490
  try {
2319
- const validChainId = isValidChainId(chainId);
2320
- await this.ensureConnected(state, validChainId);
2321
- if (depositCallback) {
2322
- const smartWallet = await this.ensureWalletDeployed(
2323
- this.getAddress(),
2324
- validChainId
2325
- );
2326
- notifyFunding(onProgress, {
2327
- agentId: this.id,
2328
- chainId,
2329
- token: asset,
2330
- walletAddress: state.walletAddress,
2331
- agentWallet: smartWallet,
2332
- intendedAmount: amount,
2333
- transfer: "pending",
2334
- registration: "pending",
2335
- delivery: "not-applicable"
2336
- });
2337
- const txHash = await depositCallback(smartWallet, validChainId, amount);
2338
- let evidence2 = {
2339
- agentId: this.id,
2340
- chainId,
2341
- token: asset,
2342
- walletAddress: state.walletAddress,
2343
- agentWallet: smartWallet,
2344
- intendedAmount: amount,
2345
- txHash,
2346
- transfer: "pending",
2347
- registration: "pending",
2348
- delivery: "not-applicable"
2349
- };
2350
- notifyFunding(onProgress, evidence2);
2351
- try {
2352
- const receipt = await this.getPublicClient(validChainId).getTransactionReceipt({ hash: txHash });
2353
- const token = SupportedAssets.find((token2) => token2.chainId === chainId && token2.symbol === asset);
2354
- evidence2 = inspectFundingReceipt(receipt, evidence2, token.address, "deposit");
2355
- } catch {
2356
- evidence2 = { ...evidence2, transfer: "unknown" };
2357
- }
2358
- notifyFunding(onProgress, evidence2);
2359
- if (evidence2.transfer === "failed")
2360
- throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
2361
- try {
2362
- const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
2363
- await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
2364
- evidence2 = { ...evidence2, registration: "confirmed" };
2365
- } catch {
2366
- evidence2 = { ...evidence2, registration: "unknown" };
2367
- }
2368
- notifyFunding(onProgress, evidence2);
2369
- return { txHash, smartWallet, amount, evidence: evidence2 };
2370
- }
2371
- await this.ensureWalletDeployed(this.getAddress(), validChainId);
2372
- const raw2 = await this.sdk.sendDeposit(
2373
- this.getAddress(),
2374
- validChainId,
2375
- amount,
2376
- asset,
2377
- "aggressive"
2378
- );
2379
- let evidence = {
2380
- agentId: this.id,
2381
- chainId,
2382
- token: asset,
2383
- walletAddress: state.walletAddress,
2384
- agentWallet: raw2.smartWallet,
2385
- intendedAmount: amount,
2386
- txHash: raw2.txHash,
2387
- transfer: "unknown",
2388
- registration: raw2.registration ? "confirmed" : "unknown",
2389
- delivery: "not-applicable"
2390
- };
2391
- notifyFunding(onProgress, evidence);
2392
- try {
2393
- const receipt = await this.getPublicClient(validChainId).getTransactionReceipt({ hash: raw2.txHash });
2394
- const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
2395
- evidence = inspectFundingReceipt(receipt, evidence, token.address, "deposit");
2396
- } catch {
2397
- }
2398
- notifyFunding(onProgress, evidence);
2399
- if (evidence.transfer === "failed")
2400
- throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash: raw2.txHash }, this.id);
2401
- return { ...mapDeposit(raw2), evidence };
2402
- } catch (error) {
2403
- throw error;
2491
+ const receipt = await this.getPublicClient(validChainId).waitForTransactionReceipt({
2492
+ hash: txHash,
2493
+ confirmations: 1,
2494
+ timeout: 9e4,
2495
+ checkReplacement: false
2496
+ });
2497
+ const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
2498
+ evidence = inspectFundingReceipt(receipt, evidence, token.address, "deposit");
2499
+ } catch {
2500
+ evidence = { ...evidence, transfer: "unknown" };
2501
+ }
2502
+ notifyFunding(onProgress, evidence);
2503
+ if (evidence.transfer === "failed")
2504
+ throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
2505
+ if (evidence.transfer !== "confirmed") return { txHash, smartWallet, amount, evidence };
2506
+ try {
2507
+ assertCurrent();
2508
+ const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
2509
+ await (tokenAddress ? executionSdk.logDeposit(validChainId, txHash, amount, tokenAddress) : executionSdk.logDeposit(validChainId, txHash, amount));
2510
+ evidence = { ...evidence, registration: "confirmed" };
2511
+ } catch {
2512
+ evidence = { ...evidence, registration: "unknown" };
2404
2513
  }
2514
+ notifyFunding(onProgress, evidence);
2515
+ return { txHash, smartWallet, amount, evidence };
2405
2516
  }
2406
2517
  // --- IAgent: Fund operations ---
2407
2518
  /** Only after a definitive endpoint rejection, combine a new provider
@@ -2431,7 +2542,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2431
2542
  this.sdk.getPortfolio(owner).catch(() => void 0)
2432
2543
  ]);
2433
2544
  assertScope();
2434
- if (!isAddress(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
2545
+ if (!isAddress2(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
2435
2546
  throw new OwneyError(
2436
2547
  "AGENT_AUTH_FAILED",
2437
2548
  "Could not verify Zyfai's withdrawal wallet.",
@@ -2442,7 +2553,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2442
2553
  const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
2443
2554
  const idle = asset ? await this.getPublicClient(chainId).readContract({
2444
2555
  address: asset.address,
2445
- abi: erc20Abi2,
2556
+ abi: erc20Abi3,
2446
2557
  functionName: "balanceOf",
2447
2558
  args: [wallet.address]
2448
2559
  }).catch(() => void 0) : void 0;
@@ -2471,11 +2582,12 @@ var ZyfaiAgent = class _ZyfaiAgent {
2471
2582
  /** Called only after the execution endpoint explicitly rejected a request.
2472
2583
  * A timeout or an accepted request never authorizes repair and resubmission. */
2473
2584
  async recoverWithdrawalSession(state, chainId, token, owner) {
2585
+ const generation = this.connectionGeneration;
2474
2586
  await this.assertNotEmptyRejectedWithdrawal(state, chainId, token, owner);
2475
2587
  const internals = this.sdk;
2476
2588
  const assertScope = () => {
2477
2589
  const account = internals.walletClient?.account?.address;
2478
- if (state.walletAddress.toLowerCase() !== owner.toLowerCase() || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
2590
+ if (generation !== this.connectionGeneration || state.walletAddress.toLowerCase() !== owner.toLowerCase() || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
2479
2591
  throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
2480
2592
  };
2481
2593
  assertScope();
@@ -2504,6 +2616,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
2504
2616
  }
2505
2617
  }
2506
2618
  async withdraw(state, chainId, token, amount, onProgress) {
2619
+ if (amount !== void 0 && (typeof amount !== "string" || !/^[0-9]+$/.test(amount) || BigInt(amount) <= 0n))
2620
+ throw new OwneyError("WITHDRAW_INVALID_AMOUNT", "Withdrawal amount must be a positive integer in token base units.", void 0, this.id);
2507
2621
  const validChainId = isValidChainId(chainId);
2508
2622
  try {
2509
2623
  await this.ensureConnected(state, validChainId);
@@ -2520,16 +2634,40 @@ var ZyfaiAgent = class _ZyfaiAgent {
2520
2634
  });
2521
2635
  throw error;
2522
2636
  }
2523
- let observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2524
- const authToken = this.sdk.httpClient?.authToken;
2525
- const owner = this.getAddress();
2637
+ const executionSdk = this.sdk;
2638
+ const internals = executionSdk;
2639
+ const generation = this.connectionGeneration;
2640
+ const owner = state.walletAddress;
2641
+ let staleSessionError;
2642
+ const assertCurrent = () => {
2643
+ const account = internals.walletClient?.account?.address;
2644
+ if (generation !== this.connectionGeneration || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== validChainId || state.chainId !== null && state.chainId !== validChainId || account && account.toLowerCase() !== owner.toLowerCase()) {
2645
+ staleSessionError = new OwneyError(
2646
+ "NOT_CONNECTED",
2647
+ "Wallet changed before Zyfai withdrawal submission.",
2648
+ { phase: "prepare", rejectedBeforeSubmission: true },
2649
+ this.id
2650
+ );
2651
+ throw staleSessionError;
2652
+ }
2653
+ };
2654
+ let observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertCurrent);
2655
+ const authToken = internals.httpClient?.authToken;
2526
2656
  let raw2;
2527
2657
  try {
2528
2658
  for (let attempt = 0; attempt < 2; attempt++) {
2529
2659
  try {
2530
- raw2 = await this.sdk.withdrawFunds(owner, validChainId, amount, token);
2660
+ assertCurrent();
2661
+ raw2 = await executionSdk.withdrawFunds(owner, validChainId, amount, token);
2531
2662
  break;
2532
2663
  } catch (error) {
2664
+ if (observation.rejected()) {
2665
+ try {
2666
+ assertCurrent();
2667
+ } catch {
2668
+ throw error;
2669
+ }
2670
+ }
2533
2671
  if (!amount && observation.rejected() && observation.request()?.failure === "insufficient-balance") {
2534
2672
  try {
2535
2673
  await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner);
@@ -2547,7 +2685,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2547
2685
  );
2548
2686
  await this.recoverWithdrawalSession(state, validChainId, token, owner);
2549
2687
  observation.stop();
2550
- observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2688
+ observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertCurrent);
2551
2689
  }
2552
2690
  }
2553
2691
  if (!raw2) throw new OwneyError(
@@ -2557,6 +2695,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2557
2695
  this.id
2558
2696
  );
2559
2697
  } catch (error) {
2698
+ if (staleSessionError && observation.attempted() === false) error = staleSessionError;
2560
2699
  this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
2561
2700
  if (observation.attempted() !== void 0) {
2562
2701
  const failed = observation.attempted() === false || observation.rejected();
@@ -2577,7 +2716,6 @@ var ZyfaiAgent = class _ZyfaiAgent {
2577
2716
  observation.stop();
2578
2717
  }
2579
2718
  if (!raw2.success) {
2580
- const unresolved = !!raw2.txHash || /already|pending|queued|in.progress/i.test(raw2.message ?? "");
2581
2719
  notifyFunding(onProgress, {
2582
2720
  agentId: this.id,
2583
2721
  chainId,
@@ -2585,9 +2723,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
2585
2723
  walletAddress: state.walletAddress,
2586
2724
  intendedAmount: amount ?? "MAX",
2587
2725
  txHash: raw2.txHash,
2588
- transfer: unresolved ? "unknown" : "failed",
2726
+ transfer: "unknown",
2589
2727
  registration: "not-applicable",
2590
- delivery: unresolved ? "unknown" : "failed",
2728
+ delivery: "unknown",
2591
2729
  withdrawalRequest: observation.request()
2592
2730
  });
2593
2731
  throw new OwneyError(
@@ -2614,8 +2752,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
2614
2752
  }
2615
2753
  /** Registration recovery never signs, transfers, activates, or refreshes authentication. */
2616
2754
  async retryDepositRegistration(state, evidence) {
2617
- const internals = this.sdk;
2618
- if (!internals.httpClient.authToken || internals.authenticatedUserId === null || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== evidence.chainId) {
2755
+ const executionSdk = this.sdk;
2756
+ const internals = executionSdk;
2757
+ if (evidence.agentId !== this.id || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.chainId !== state.chainId || !internals.httpClient.authToken || internals.authenticatedUserId === null || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== evidence.chainId) {
2619
2758
  throw new OwneyError("NOT_CONNECTED", "Reconnect the wallet before recovering deposit registration.");
2620
2759
  }
2621
2760
  if (evidence.transfer !== "confirmed" || !evidence.txHash) {
@@ -2623,16 +2762,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
2623
2762
  }
2624
2763
  const cid = isValidChainId(evidence.chainId);
2625
2764
  const tokenAddress = evidence.token === "WETH" ? WETH_ADDRESS_BY_CHAIN[cid] : void 0;
2626
- await this.sdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
2765
+ await executionSdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
2627
2766
  return { ...evidence, registration: "confirmed" };
2628
2767
  }
2629
2768
  // --- IAgent: Portfolio reads ---
2630
2769
  async getFundingWallet(state, chainId) {
2631
2770
  return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
2632
2771
  }
2633
- /** A positive Safe balance is a withdrawal planning floor when the portfolio
2634
- * index reports zero. Idle zero cannot prove that Zyfai's protocol positions
2635
- * are empty: its SDK does not expose a complete contract catalog for them. */
2772
+ /** A positive deployed Safe balance is a conservative planning floor. Zero
2773
+ * requires both the mapped Safe's exact idle balance and a fresh complete
2774
+ * empty portfolio, including for an undeployed counterfactual Safe. */
2636
2775
  async getWithdrawableBalance(state, chainId, token) {
2637
2776
  const validChainId = isValidChainId(chainId);
2638
2777
  const asset = SupportedAssets.find((value) => value.chainId === validChainId && value.symbol === token);
@@ -2661,20 +2800,27 @@ var ZyfaiAgent = class _ZyfaiAgent {
2661
2800
  this.sdk.getSmartWalletByEOA(owner)
2662
2801
  ]);
2663
2802
  assertScope();
2664
- if (!isAddress(wallet.address) || wallet.isDeployed !== true || wallet.isOwner !== true || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase()) throw unavailable();
2803
+ if (!isAddress2(wallet.address) || wallet.isDeployed !== false && (wallet.isDeployed !== true || wallet.isOwner !== true) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase()) throw unavailable();
2665
2804
  const client = this.getPublicClient(validChainId);
2666
2805
  const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
2667
2806
  assertScope();
2668
2807
  const idle = await client.readContract({
2669
2808
  address: asset.address,
2670
- abi: erc20Abi2,
2809
+ abi: erc20Abi3,
2671
2810
  functionName: "balanceOf",
2672
2811
  args: [wallet.address],
2673
2812
  blockNumber
2674
2813
  });
2675
2814
  assertScope();
2676
- if (typeof idle !== "bigint" || idle <= 0n) throw unavailable();
2677
- return idle;
2815
+ if (typeof idle !== "bigint" || idle < 0n) throw unavailable();
2816
+ if (idle > 0n) {
2817
+ if (wallet.isDeployed !== true || wallet.isOwner !== true) throw unavailable();
2818
+ return idle;
2819
+ }
2820
+ const portfolio = await this.sdk.getPortfolio(owner);
2821
+ assertScope();
2822
+ if (!hasEmptyWithdrawalSnapshot(portfolio, validChainId, token)) throw unavailable();
2823
+ return 0n;
2678
2824
  };
2679
2825
  try {
2680
2826
  return await Promise.race([read2(), new Promise((_, reject) => {
@@ -2717,7 +2863,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2717
2863
  if (blockNumber < BigInt(evidence.blockNumber)) return void 0;
2718
2864
  const idle = await client.readContract({
2719
2865
  address: asset.address,
2720
- abi: erc20Abi2,
2866
+ abi: erc20Abi3,
2721
2867
  functionName: "balanceOf",
2722
2868
  args: [balance.smartWallet],
2723
2869
  blockNumber
@@ -2924,15 +3070,15 @@ var ZyfaiAgent = class _ZyfaiAgent {
2924
3070
  // src/agents/yieldseeker/yieldseeker.agent.ts
2925
3071
  import {
2926
3072
  createPublicClient as createPublicClient3,
2927
- createWalletClient as createWalletClient3,
2928
- custom as custom3,
3073
+ createWalletClient as createWalletClient4,
3074
+ custom as custom4,
2929
3075
  decodeEventLog as decodeEventLog2,
2930
3076
  encodeEventTopics,
2931
- encodeFunctionData,
2932
- erc20Abi as erc20Abi4,
3077
+ encodeFunctionData as encodeFunctionData2,
3078
+ erc20Abi as erc20Abi5,
2933
3079
  erc4626Abi as erc4626Abi2,
2934
3080
  getAddress as getAddress5,
2935
- isAddress as isAddress3,
3081
+ isAddress as isAddress4,
2936
3082
  parseUnits
2937
3083
  } from "viem";
2938
3084
  import { base as base4 } from "viem/chains";
@@ -3175,8 +3321,8 @@ function makeVerificationAwareDepositCallback(implementation) {
3175
3321
  import { SiweMessage, generateNonce } from "siwe";
3176
3322
  import {
3177
3323
  createPublicClient as createPublicClient2,
3178
- createWalletClient,
3179
- custom,
3324
+ createWalletClient as createWalletClient2,
3325
+ custom as custom2,
3180
3326
  getAddress
3181
3327
  } from "viem";
3182
3328
  import { base as base2 } from "viem/chains";
@@ -3380,12 +3526,12 @@ var YieldseekerAuth = class {
3380
3526
  const account = getAddress(state.walletAddress);
3381
3527
  const publicClient = createPublicClient2({
3382
3528
  chain: base2,
3383
- transport: custom(state.provider)
3529
+ transport: custom2(state.provider)
3384
3530
  });
3385
- const walletClient = createWalletClient({
3531
+ const walletClient = createWalletClient2({
3386
3532
  account,
3387
3533
  chain: base2,
3388
- transport: custom(state.provider)
3534
+ transport: custom2(state.provider)
3389
3535
  });
3390
3536
  await ensureWalletOnChain(
3391
3537
  publicClient,
@@ -3552,7 +3698,7 @@ var YieldseekerApiClient = class {
3552
3698
  };
3553
3699
 
3554
3700
  // src/agents/yieldseeker/yieldseeker.mapper.ts
3555
- import { formatUnits as formatUnits3, isAddress as isAddress2 } from "viem";
3701
+ import { formatUnits as formatUnits3, isAddress as isAddress3 } from "viem";
3556
3702
 
3557
3703
  // src/lib/helpers/snapshot-apy.ts
3558
3704
  var DAY_MS = 864e5;
@@ -3621,6 +3767,14 @@ function raw(value, endpoint) {
3621
3767
  function decimal(value, decimals, endpoint) {
3622
3768
  return formatUnits3(raw(value, endpoint), decimals);
3623
3769
  }
3770
+ function balanceRaw(value, endpoint) {
3771
+ const amount = raw(value, endpoint);
3772
+ if (amount < 0n) return invalid(endpoint, "expected a non-negative balance");
3773
+ return amount;
3774
+ }
3775
+ function balanceDecimal(value, decimals, endpoint) {
3776
+ return formatUnits3(balanceRaw(value, endpoint), decimals);
3777
+ }
3624
3778
  function usd(rawAmount, decimals, price) {
3625
3779
  return Number(formatUnits3(rawAmount, decimals)) * price;
3626
3780
  }
@@ -3648,7 +3802,7 @@ function assetAddressValue(record, address) {
3648
3802
  }
3649
3803
  function position(value, asset, baseAssetDecimals) {
3650
3804
  const option = value?.yieldOption;
3651
- if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress2(option.address)) {
3805
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress3(option.address)) {
3652
3806
  return invalid("yield positions", "missing vault metadata");
3653
3807
  }
3654
3808
  return {
@@ -3662,13 +3816,13 @@ function position(value, asset, baseAssetDecimals) {
3662
3816
  // underlying base-asset units in `assetsBase`; pair that value with the
3663
3817
  // snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
3664
3818
  // share quantity separate from the withdrawable underlying asset amount.
3665
- amount: decimal(
3819
+ amount: balanceDecimal(
3666
3820
  value.assetsBase,
3667
3821
  baseAssetDecimals,
3668
3822
  "yield positions"
3669
3823
  ),
3670
- amountRaw: String(value.assetsRaw),
3671
- withdrawableAmountRaw: String(value.withdrawableAssetsRaw),
3824
+ amountRaw: balanceRaw(value.assetsRaw, "yield positions").toString(),
3825
+ withdrawableAmountRaw: balanceRaw(value.withdrawableAssetsRaw, "yield positions").toString(),
3672
3826
  apy: percent(option.riskAdjustedApy),
3673
3827
  tvl: Number(option.totalDepositsUsd),
3674
3828
  liquidity: Number(option.withdrawableDepositsUsd)
@@ -3685,7 +3839,7 @@ function mapYieldseekerBalances(contexts) {
3685
3839
  chain: "BASE",
3686
3840
  chainId: 8453,
3687
3841
  asset: context.asset,
3688
- amount: decimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
3842
+ amount: balanceDecimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
3689
3843
  });
3690
3844
  const idle = assetAddressValue(
3691
3845
  context.snapshot.tokenBalances,
@@ -3695,7 +3849,7 @@ function mapYieldseekerBalances(contexts) {
3695
3849
  chain: "BASE",
3696
3850
  chainId: 8453,
3697
3851
  asset: context.asset,
3698
- amount: decimal(idle, metadata.decimals, "snapshot")
3852
+ amount: balanceDecimal(idle, metadata.decimals, "snapshot")
3699
3853
  });
3700
3854
  positions.push(
3701
3855
  ...context.positions.map(
@@ -3707,7 +3861,7 @@ function mapYieldseekerBalances(contexts) {
3707
3861
  )
3708
3862
  );
3709
3863
  totalUsd += usd(
3710
- raw(context.snapshot.totalValueBase, "snapshot"),
3864
+ balanceRaw(context.snapshot.totalValueBase, "snapshot"),
3711
3865
  context.snapshot.baseAssetDecimals,
3712
3866
  context.snapshot.baseAssetPriceUsd
3713
3867
  );
@@ -3899,7 +4053,7 @@ function allocationEntry(action, context) {
3899
4053
  const leg = deposit;
3900
4054
  const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
3901
4055
  if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
3902
- if (typeof leg.vaultAddress !== "string" || !isAddress2(leg.vaultAddress)) return void 0;
4056
+ if (typeof leg.vaultAddress !== "string" || !isAddress3(leg.vaultAddress)) return void 0;
3903
4057
  const rawAmount = typeof leg.rawAmount === "string" && /^\d+$/.test(leg.rawAmount) ? BigInt(leg.rawAmount) : typeof leg.rawAmount === "number" && Number.isSafeInteger(leg.rawAmount) ? BigInt(leg.rawAmount) : void 0;
3904
4058
  if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
3905
4059
  const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
@@ -4131,7 +4285,7 @@ function mapYieldseekerAgentApy(options, days) {
4131
4285
  }
4132
4286
 
4133
4287
  // src/lib/remaining-vault-balance.ts
4134
- import { erc20Abi as erc20Abi3, erc4626Abi, getAddress as getAddress2 } from "viem";
4288
+ import { erc20Abi as erc20Abi4, erc4626Abi, getAddress as getAddress2 } from "viem";
4135
4289
  async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber) {
4136
4290
  const addresses = [...new Set(vaults.map((value) => value.toLowerCase()))];
4137
4291
  if (addresses.length > 64) return void 0;
@@ -4143,7 +4297,7 @@ async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber)
4143
4297
  allowFailure: true,
4144
4298
  contracts: batch.map((address) => ({
4145
4299
  address: getAddress2(address),
4146
- abi: erc20Abi3,
4300
+ abi: erc20Abi4,
4147
4301
  functionName: "balanceOf",
4148
4302
  args: [wallet]
4149
4303
  }))
@@ -4243,8 +4397,8 @@ var YIELDSEEKER_UPGRADE_ABI = walletAbi;
4243
4397
 
4244
4398
  // src/agents/yieldseeker/yieldseeker.sponsored-withdraw.ts
4245
4399
  import {
4246
- createWalletClient as createWalletClient2,
4247
- custom as custom2,
4400
+ createWalletClient as createWalletClient3,
4401
+ custom as custom3,
4248
4402
  getAddress as getAddress4,
4249
4403
  toHex
4250
4404
  } from "viem";
@@ -4594,10 +4748,10 @@ async function submitEoa(input) {
4594
4748
  const userOpHash = operationHash(operation);
4595
4749
  await assertOwnerSession(input.state, input.owner);
4596
4750
  assertIntentFresh(input.expiresAt);
4597
- const wallet = createWalletClient2({
4751
+ const wallet = createWalletClient3({
4598
4752
  account: input.owner,
4599
4753
  chain: base3,
4600
- transport: custom2(input.state.provider)
4754
+ transport: custom3(input.state.provider)
4601
4755
  });
4602
4756
  const signature = await wallet.signMessage({ message: { raw: userOpHash } });
4603
4757
  operation = { ...operation, signature };
@@ -4899,6 +5053,7 @@ var YieldseekerAgent = class {
4899
5053
  receiptClient;
4900
5054
  // A client preparation hint only; the routing API still checks live identity.
4901
5055
  withdrawalVersionCache = /* @__PURE__ */ new Map();
5056
+ pendingUpgrades = /* @__PURE__ */ new Map();
4902
5057
  transactionExecutor;
4903
5058
  unwindReceiptWaiter;
4904
5059
  agentContexts = /* @__PURE__ */ new Map();
@@ -5014,10 +5169,30 @@ var YieldseekerAgent = class {
5014
5169
  } catch {
5015
5170
  storage5 = void 0;
5016
5171
  }
5017
- const clearConfirmedUpgrade = () => {
5172
+ const clearRecordedUpgrade = () => {
5173
+ this.pendingUpgrades.delete(pendingKey2);
5018
5174
  try {
5019
5175
  storage5?.removeItem(pendingKey2);
5020
5176
  } catch {
5177
+ this.pendingUpgrades.set(pendingKey2, null);
5178
+ }
5179
+ };
5180
+ const pendingUpgrade = (hash) => new OwneyError(
5181
+ "WITHDRAW_IN_PROGRESS",
5182
+ "Yieldseeker wallet upgrade is awaiting confirmation. Retry to check its status.",
5183
+ { txHash: hash, reason: "UPGRADE_CONFIRMATION_PENDING", phase: "observe", safeToFallback: false },
5184
+ this.id
5185
+ );
5186
+ const terminalUpgradeFailure = (error) => error instanceof OwneyError && (error.code === "AGENT_TRANSACTION_REVERTED" || error.details?.reason === "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL");
5187
+ const readUpgradeReceipt = async (hash) => {
5188
+ try {
5189
+ return await this.waitForReceipt(state, chainId, hash);
5190
+ } catch (error) {
5191
+ if (terminalUpgradeFailure(error)) {
5192
+ clearRecordedUpgrade();
5193
+ throw error;
5194
+ }
5195
+ throw pendingUpgrade(hash);
5021
5196
  }
5022
5197
  };
5023
5198
  if (!storage5 && (gasMode === "sponsored" || before.ownerPath === "smart"))
@@ -5029,7 +5204,7 @@ var YieldseekerAgent = class {
5029
5204
  );
5030
5205
  let prior = null;
5031
5206
  try {
5032
- const saved = storage5?.getItem(pendingKey2);
5207
+ const saved = this.pendingUpgrades.has(pendingKey2) ? JSON.stringify(this.pendingUpgrades.get(pendingKey2)) : storage5?.getItem(pendingKey2);
5033
5208
  try {
5034
5209
  prior = JSON.parse(saved ?? "null");
5035
5210
  } catch {
@@ -5073,7 +5248,7 @@ var YieldseekerAgent = class {
5073
5248
  this.id
5074
5249
  );
5075
5250
  if (Number(callsStatus?.status) === 400 || Number(callsStatus?.status) === 500) {
5076
- storage5.removeItem(pendingKey2);
5251
+ clearRecordedUpgrade();
5077
5252
  throw new OwneyError(
5078
5253
  "SPONSOR_REQUEST_FAILED",
5079
5254
  "The earlier sponsored upgrade failed.",
@@ -5085,8 +5260,8 @@ var YieldseekerAgent = class {
5085
5260
  earlierHash = hashes.length === 1 ? hashes[0] : void 0;
5086
5261
  }
5087
5262
  if (earlierHash) {
5088
- const recovered = await confirmUpgrade(earlierHash, await this.waitForReceipt(state, chainId, earlierHash));
5089
- clearConfirmedUpgrade();
5263
+ const recovered = await confirmUpgrade(earlierHash, await readUpgradeReceipt(earlierHash));
5264
+ clearRecordedUpgrade();
5090
5265
  return recovered;
5091
5266
  }
5092
5267
  throw new OwneyError(
@@ -5103,7 +5278,7 @@ var YieldseekerAgent = class {
5103
5278
  { version: before.version },
5104
5279
  this.id
5105
5280
  );
5106
- const data = encodeFunctionData({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
5281
+ const data = encodeFunctionData2({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
5107
5282
  let txHash;
5108
5283
  let receipt;
5109
5284
  if (gasMode === "sponsored") {
@@ -5130,33 +5305,52 @@ var YieldseekerAgent = class {
5130
5305
  asset: YIELDSEEKER_ASSET_METADATA[asset].address,
5131
5306
  callData: data,
5132
5307
  onPending: (pending) => {
5133
- const current = storage5.getItem(pendingKey2);
5134
- storage5.setItem(pendingKey2, JSON.stringify({
5135
- startedAt: Date.now(),
5136
- ...current ? JSON.parse(current) : {},
5137
- ...pending
5138
- }));
5308
+ const current = this.pendingUpgrades.has(pendingKey2) ? this.pendingUpgrades.get(pendingKey2) : JSON.parse(storage5.getItem(pendingKey2) ?? "null");
5309
+ const next = { startedAt: Date.now(), ...current, ...pending };
5310
+ storage5.setItem(pendingKey2, JSON.stringify(next));
5311
+ this.pendingUpgrades.delete(pendingKey2);
5139
5312
  }
5140
5313
  });
5141
5314
  txHash = upgraded.txHash;
5142
5315
  receipt = upgraded.receipt;
5143
5316
  } catch (error) {
5144
- if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey2);
5317
+ if (error?.code === 4001 || terminalUpgradeFailure(error) || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) clearRecordedUpgrade();
5145
5318
  throw error;
5146
5319
  }
5147
5320
  } else {
5148
- txHash = await this.submitTransaction(state, chainId, {
5149
- from: getAddress5(state.walletAddress),
5150
- to: before.agentWallet,
5151
- data,
5152
- value: "0",
5153
- chainId
5154
- }, void 0, (value) => {
5155
- receipt = value;
5156
- });
5321
+ let submittedHash;
5322
+ try {
5323
+ txHash = await this.submitTransaction(state, chainId, {
5324
+ from: getAddress5(state.walletAddress),
5325
+ to: before.agentWallet,
5326
+ data,
5327
+ value: "0",
5328
+ chainId
5329
+ }, (hash) => {
5330
+ submittedHash = hash;
5331
+ const pending = { txHash: hash };
5332
+ this.pendingUpgrades.set(pendingKey2, pending);
5333
+ try {
5334
+ if (storage5) {
5335
+ storage5.setItem(pendingKey2, JSON.stringify(pending));
5336
+ this.pendingUpgrades.delete(pendingKey2);
5337
+ }
5338
+ } catch {
5339
+ }
5340
+ }, (value) => {
5341
+ receipt = value;
5342
+ });
5343
+ } catch (error) {
5344
+ if (terminalUpgradeFailure(error)) {
5345
+ clearRecordedUpgrade();
5346
+ throw error;
5347
+ }
5348
+ if (submittedHash) throw pendingUpgrade(submittedHash);
5349
+ throw error;
5350
+ }
5157
5351
  }
5158
- const confirmed = await confirmUpgrade(txHash, receipt ?? await this.waitForReceipt(state, chainId, txHash));
5159
- if (gasMode === "sponsored") clearConfirmedUpgrade();
5352
+ const confirmed = await confirmUpgrade(txHash, receipt ?? await readUpgradeReceipt(txHash));
5353
+ clearRecordedUpgrade();
5160
5354
  return confirmed;
5161
5355
  }
5162
5356
  async disconnect() {
@@ -5235,8 +5429,8 @@ var YieldseekerAgent = class {
5235
5429
  txHash = await this.submitTransaction(state, chainId, {
5236
5430
  from: getAddress5(state.walletAddress),
5237
5431
  to: YIELDSEEKER_ASSET_METADATA[asset].address,
5238
- data: encodeFunctionData({
5239
- abi: erc20Abi4,
5432
+ data: encodeFunctionData2({
5433
+ abi: erc20Abi5,
5240
5434
  functionName: "transfer",
5241
5435
  args: [getAddress5(context.wallet.walletAddress), BigInt(amount)]
5242
5436
  }),
@@ -5381,12 +5575,12 @@ var YieldseekerAgent = class {
5381
5575
  blockNumber
5382
5576
  }), chainId, "eth_call", this.id);
5383
5577
  this.assertSession(generation, chainId, asset);
5384
- if (!isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker))
5578
+ if (!isAddress4(feeTracker) || /^0x0{40}$/i.test(feeTracker))
5385
5579
  throw this.invalidResponse("wallet fee tracker");
5386
5580
  const [idle2, fees] = await Promise.all([
5387
5581
  withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5388
5582
  address: metadata.address,
5389
- abi: erc20Abi4,
5583
+ abi: erc20Abi5,
5390
5584
  functionName: "balanceOf",
5391
5585
  args: [agentWallet],
5392
5586
  blockNumber
@@ -5440,7 +5634,7 @@ var YieldseekerAgent = class {
5440
5634
  if (!active || group.length !== 3 || group.some((value) => value.status !== "success")) return null;
5441
5635
  this.assertSession(generation, chainId, asset);
5442
5636
  const [underlying, shares, withdrawable] = group.map((value) => value.result);
5443
- if (typeof underlying !== "string" || !isAddress3(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
5637
+ if (typeof underlying !== "string" || !isAddress4(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
5444
5638
  const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: withdrawable.toString() };
5445
5639
  if (shares > 0n && withdrawable === 0n) {
5446
5640
  let positiveValue = false;
@@ -5538,6 +5732,18 @@ var YieldseekerAgent = class {
5538
5732
  );
5539
5733
  }
5540
5734
  if (requested > totalAvailable) {
5735
+ if (!funds.complete)
5736
+ throw new OwneyError(
5737
+ "WITHDRAW_BALANCE_UNAVAILABLE",
5738
+ "Yieldseeker withdrawal liquidity could not be verified for the requested amount.",
5739
+ {
5740
+ asset,
5741
+ requested: requested.toString(),
5742
+ verifiedAvailable: totalAvailable.toString(),
5743
+ reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED"
5744
+ },
5745
+ this.id
5746
+ );
5541
5747
  throw new OwneyError(
5542
5748
  "WITHDRAW_INSUFFICIENT_BALANCE",
5543
5749
  `Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
@@ -5552,6 +5758,7 @@ var YieldseekerAgent = class {
5552
5758
  const requiredIdle = requested + initialFunds.fees;
5553
5759
  let remaining = requiredIdle > idle ? requiredIdle - idle : 0n;
5554
5760
  let grossUnwound = 0n;
5761
+ let confirmedUnwinds = 0n;
5555
5762
  let latestUnwindBlock;
5556
5763
  const unwind = async (position2, assetsRaw) => {
5557
5764
  this.assertSession(generation, chainId, asset);
@@ -5569,7 +5776,10 @@ var YieldseekerAgent = class {
5569
5776
  if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
5570
5777
  unwindTransactionHash = response.transactionHash;
5571
5778
  const unwindReceipt = await this.waitForReceipt(state, chainId, unwindTransactionHash);
5572
- if (unwindReceipt) latestUnwindBlock = unwindReceipt.blockNumber;
5779
+ if (unwindReceipt) {
5780
+ latestUnwindBlock = unwindReceipt.blockNumber;
5781
+ confirmedUnwinds++;
5782
+ }
5573
5783
  } catch (error) {
5574
5784
  this.invalidateFundingRead(state, chainId, asset);
5575
5785
  throw new OwneyError(
@@ -5639,7 +5849,7 @@ var YieldseekerAgent = class {
5639
5849
  const feeRate = feeRateBps ?? 0;
5640
5850
  const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
5641
5851
  const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
5642
- if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
5852
+ if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee + confirmedUnwinds) {
5643
5853
  this.invalidateFundingRead(state, chainId, asset);
5644
5854
  if (amount === void 0 && positionsStatus !== "empty")
5645
5855
  throw new OwneyError(
@@ -5663,12 +5873,13 @@ var YieldseekerAgent = class {
5663
5873
  } : this.snapshotMovement(state, chainId, context, -payout);
5664
5874
  const account = getAddress5(state.walletAddress);
5665
5875
  let receipt;
5666
- const base6 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount === void 0 ? "MAX" : payout.toString(), transfer: "pending", registration: "not-applicable", delivery: "pending" };
5667
- const data = amount === void 0 ? encodeFunctionData({
5876
+ const base6 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount ?? "MAX", transfer: "pending", registration: "not-applicable", delivery: "pending" };
5877
+ let submissionEvidence = base6;
5878
+ const data = amount === void 0 ? encodeFunctionData2({
5668
5879
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
5669
5880
  functionName: "withdrawAllAssetToUser",
5670
5881
  args: [account, metadata.address]
5671
- }) : encodeFunctionData({
5882
+ }) : encodeFunctionData2({
5672
5883
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
5673
5884
  functionName: "withdrawAssetToUser",
5674
5885
  args: [account, metadata.address, payout]
@@ -5687,7 +5898,10 @@ var YieldseekerAgent = class {
5687
5898
  agentWallet: getAddress5(context.wallet.walletAddress),
5688
5899
  asset: metadata.address,
5689
5900
  callData: data,
5690
- onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending }),
5901
+ onPending: (pending) => {
5902
+ submissionEvidence = { ...submissionEvidence, ...pending };
5903
+ notifyFunding(onProgress, submissionEvidence);
5904
+ },
5691
5905
  onBeforeSubmission: async (pending) => {
5692
5906
  await onBeforeSubmission?.({ ...base6, ...pending });
5693
5907
  onOwnerTransferAttempted?.();
@@ -5707,10 +5921,20 @@ var YieldseekerAgent = class {
5707
5921
  receipt = value;
5708
5922
  });
5709
5923
  }
5710
- confirmedMovement = true;
5711
- settlement = plannedMovement;
5712
- const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
5924
+ const evidence = receipt ? inspectFundingReceipt(receipt, { ...submissionEvidence, txHash }, metadata.address, "withdraw") : { ...submissionEvidence, txHash, transfer: "pending", delivery: "pending" };
5713
5925
  notifyFunding(onProgress, evidence);
5926
+ const explicitEmptyPayout = evidence.transfer === "failed" && evidence.confirmedAmount === "0" && evidence.transferLogs?.some((log) => log.amountRaw === "0");
5927
+ if (evidence.transfer === "failed" && !explicitEmptyPayout)
5928
+ throw new OwneyError(
5929
+ "AGENT_TRANSACTION_REVERTED",
5930
+ "The Yieldseeker withdrawal completed without transferring funds.",
5931
+ { asset, available: "0", txHash },
5932
+ this.id
5933
+ );
5934
+ if (!explicitEmptyPayout && (evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || !evidence.confirmedAmount))
5935
+ return { txHash, type: "partial", completion: "unverified", amount: "0", evidence };
5936
+ confirmedMovement = true;
5937
+ settlement = evidence.confirmedAmount === payout.toString() ? plannedMovement : null;
5714
5938
  let completion = maxLiquidity && !maxLiquidity.complete ? "unverified" : void 0;
5715
5939
  if (amount === void 0) {
5716
5940
  const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
@@ -5731,7 +5955,7 @@ var YieldseekerAgent = class {
5731
5955
  positions = proof.positions;
5732
5956
  positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
5733
5957
  if (positionsStatus === "remaining") {
5734
- const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
5958
+ const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? "0", evidence };
5735
5959
  throw new OwneyError(
5736
5960
  "WITHDRAW_PARTIAL_FAILURE",
5737
5961
  "Yieldseeker MAX could not confirm that all vault positions were cleared.",
@@ -5751,7 +5975,7 @@ var YieldseekerAgent = class {
5751
5975
  }
5752
5976
  if (!proof.verified) completion = "unverified";
5753
5977
  }
5754
- if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
5978
+ if (explicitEmptyPayout)
5755
5979
  throw new OwneyError(
5756
5980
  "WITHDRAW_INSUFFICIENT_BALANCE",
5757
5981
  "The Yieldseeker withdrawal completed without transferring funds.",
@@ -5766,7 +5990,7 @@ var YieldseekerAgent = class {
5766
5990
  type: amount === void 0 && !completion ? "full" : "partial",
5767
5991
  ...completion ? { completion } : {},
5768
5992
  ...feeAdjustment ? { feeAdjustment } : {},
5769
- amount: evidence.confirmedAmount ?? payout.toString(),
5993
+ amount: evidence.confirmedAmount ?? "0",
5770
5994
  evidence
5771
5995
  };
5772
5996
  } finally {
@@ -5816,7 +6040,7 @@ var YieldseekerAgent = class {
5816
6040
  const address = getAddress5(option.address);
5817
6041
  return [
5818
6042
  { address, abi: erc4626Abi2, functionName: "asset" },
5819
- { address, abi: erc20Abi4, functionName: "balanceOf", args: [owner] },
6043
+ { address, abi: erc20Abi5, functionName: "balanceOf", args: [owner] },
5820
6044
  { address, abi: erc4626Abi2, functionName: "maxWithdraw", args: [owner] }
5821
6045
  ];
5822
6046
  })
@@ -5828,7 +6052,7 @@ var YieldseekerAgent = class {
5828
6052
  const seen = /* @__PURE__ */ new Set();
5829
6053
  for (const value of values) {
5830
6054
  const option = value?.yieldOption;
5831
- if (!option || typeof option.address !== "string" || !isAddress3(option.address) || seen.has(option.address.toLowerCase()) || option.chainId !== void 0 && option.chainId !== chainId || option.assetAddress !== void 0 && (typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== underlying) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
6055
+ if (!option || typeof option.address !== "string" || !isAddress4(option.address) || seen.has(option.address.toLowerCase()) || option.chainId !== void 0 && option.chainId !== chainId || option.assetAddress !== void 0 && (typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== underlying) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
5832
6056
  throw this.invalidResponse("yield positions");
5833
6057
  seen.add(option.address.toLowerCase());
5834
6058
  }
@@ -5841,7 +6065,7 @@ var YieldseekerAgent = class {
5841
6065
  const historic = this.readCache.get(`historic:${key2}`)?.value?.position;
5842
6066
  const receivedAssets = new Set((historic?.movements ?? []).filter((movement) => movement.chainId === chainId && movement.agentWalletId === context.wallet.agentWalletId && movement.toAddress?.toLowerCase() === context.wallet.walletAddress.toLowerCase()).filter((movement) => typeof movement.assetAddress === "string").map((movement) => movement.assetAddress.toLowerCase()));
5843
6067
  for (const option of this.yieldOptions.get(context.asset)?.value ?? []) {
5844
- if (option.chainId !== chainId || typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== YIELDSEEKER_ASSET_METADATA[context.asset].address.toLowerCase() || !isAddress3(option.address) || !receivedAssets.has(option.address.toLowerCase()) || hints.has(option.address.toLowerCase())) continue;
6068
+ if (option.chainId !== chainId || typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== YIELDSEEKER_ASSET_METADATA[context.asset].address.toLowerCase() || !isAddress4(option.address) || !receivedAssets.has(option.address.toLowerCase()) || hints.has(option.address.toLowerCase())) continue;
5845
6069
  hints.set(option.address.toLowerCase(), {
5846
6070
  yieldOption: option,
5847
6071
  assetsRaw: "0",
@@ -5903,11 +6127,11 @@ var YieldseekerAgent = class {
5903
6127
  for (const position2 of indexedPositions) candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
5904
6128
  const options = [...candidates.values()];
5905
6129
  if (options.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
5906
- const [checks, idle, feeTracker] = await Promise.all([
5907
- this.readVaultLiquidity(options, wallet, blockNumber),
6130
+ const checksRead = this.readVaultLiquidity(options, wallet, blockNumber).then((value) => ({ value }), (error) => ({ error }));
6131
+ const [idle, feeTracker] = await Promise.all([
5908
6132
  client.readContract({
5909
6133
  address: metadata.address,
5910
- abi: erc20Abi4,
6134
+ abi: erc20Abi5,
5911
6135
  functionName: "balanceOf",
5912
6136
  args: [wallet],
5913
6137
  blockNumber
@@ -5919,7 +6143,7 @@ var YieldseekerAgent = class {
5919
6143
  blockNumber
5920
6144
  })
5921
6145
  ]);
5922
- if (typeof idle !== "bigint" || idle < 0n || !isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker)) throw unavailable();
6146
+ if (typeof idle !== "bigint" || idle < 0n || !isAddress4(feeTracker) || /^0x0{40}$/i.test(feeTracker)) throw unavailable();
5923
6147
  assertReadCurrent();
5924
6148
  const fees = await client.readContract({
5925
6149
  address: getAddress5(feeTracker),
@@ -5932,6 +6156,18 @@ var YieldseekerAgent = class {
5932
6156
  const verified = [];
5933
6157
  const verifiedVaults = [];
5934
6158
  let deployed = 0n;
6159
+ const proof = (complete) => ({
6160
+ blockNumber,
6161
+ idle,
6162
+ fees,
6163
+ available: idle + deployed > fees ? idle + deployed - fees : 0n,
6164
+ positions: [...verified],
6165
+ verifiedVaults: [...verifiedVaults],
6166
+ indexedPositions,
6167
+ complete
6168
+ });
6169
+ assertReadCurrent();
6170
+ knownProof = proof(false);
5935
6171
  const collect = (options2, results) => {
5936
6172
  assertReadCurrent();
5937
6173
  const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
@@ -5947,7 +6183,7 @@ var YieldseekerAgent = class {
5947
6183
  return;
5948
6184
  }
5949
6185
  const [underlying, shares, withdrawable] = group.map((value) => value.result);
5950
- if (typeof underlying !== "string" || !isAddress3(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n || shares === 0n && withdrawable > 0n) {
6186
+ if (typeof underlying !== "string" || !isAddress4(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n || shares === 0n && withdrawable > 0n) {
5951
6187
  valid2 = false;
5952
6188
  return;
5953
6189
  }
@@ -5964,17 +6200,10 @@ var YieldseekerAgent = class {
5964
6200
  });
5965
6201
  return valid2;
5966
6202
  };
5967
- const allKnownVerified = collect(options, checks);
5968
- const proof = (complete) => ({
5969
- blockNumber,
5970
- idle,
5971
- fees,
5972
- available: idle + deployed > fees ? idle + deployed - fees : 0n,
5973
- positions: [...verified],
5974
- verifiedVaults: [...verifiedVaults],
5975
- indexedPositions,
5976
- complete
5977
- });
6203
+ const checks = await checksRead;
6204
+ assertReadCurrent();
6205
+ if ("error" in checks) throw checks.error;
6206
+ const allKnownVerified = collect(options, checks.value);
5978
6207
  knownProof = proof(false);
5979
6208
  const indexed = new Map(indexedPositions.map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
5980
6209
  const discrepancy = verified.some((position2) => {
@@ -5991,7 +6220,7 @@ var YieldseekerAgent = class {
5991
6220
  assertReadCurrent();
5992
6221
  const eligible = /* @__PURE__ */ new Map();
5993
6222
  for (const option of catalog ?? []) {
5994
- if (option?.chainId === chainId && typeof option.assetAddress === "string" && option.assetAddress.toLowerCase() === metadata.address.toLowerCase() && isAddress3(option.address))
6223
+ if (option?.chainId === chainId && typeof option.assetAddress === "string" && option.assetAddress.toLowerCase() === metadata.address.toLowerCase() && isAddress4(option.address))
5995
6224
  eligible.set(option.address.toLowerCase(), option);
5996
6225
  }
5997
6226
  const extra = [...eligible].filter(([address]) => !candidates.has(address)).map(([, option]) => option);
@@ -6050,10 +6279,10 @@ var YieldseekerAgent = class {
6050
6279
  const vaults = [...this.balanceVaults.get(key2) ?? []];
6051
6280
  const [invested, idle, tracker] = await Promise.all([
6052
6281
  remainingVaultBalance(client, wallet, asset.address, vaults, blockNumber),
6053
- client.readContract({ address: asset.address, abi: erc20Abi4, functionName: "balanceOf", args: [wallet], blockNumber }),
6282
+ client.readContract({ address: asset.address, abi: erc20Abi5, functionName: "balanceOf", args: [wallet], blockNumber }),
6054
6283
  client.readContract({ address: wallet, abi: YIELDSEEKER_AGENT_WALLET_ABI, functionName: "feeTracker", blockNumber })
6055
6284
  ]);
6056
- if (invested === void 0 || typeof idle !== "bigint" || idle < 0n || !isAddress3(tracker) || /^0x0{40}$/i.test(tracker)) return void 0;
6285
+ if (invested === void 0 || typeof idle !== "bigint" || idle < 0n || !isAddress4(tracker) || /^0x0{40}$/i.test(tracker)) return void 0;
6057
6286
  const fees = await client.readContract({
6058
6287
  address: tracker,
6059
6288
  abi: YIELDSEEKER_FEE_TRACKER_ABI,
@@ -6112,7 +6341,7 @@ var YieldseekerAgent = class {
6112
6341
  if (!context) return void 0;
6113
6342
  const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
6114
6343
  address: YIELDSEEKER_ASSET_METADATA[asset].address,
6115
- abi: erc20Abi4,
6344
+ abi: erc20Abi5,
6116
6345
  functionName: "balanceOf",
6117
6346
  args: [getAddress5(context.wallet.walletAddress)]
6118
6347
  }), chainId, "eth_call", this.id);
@@ -6122,7 +6351,7 @@ var YieldseekerAgent = class {
6122
6351
  }
6123
6352
  async getEarnings(state, chainId) {
6124
6353
  this.assertChain(chainId);
6125
- return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
6354
+ return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, { includePositions: false }));
6126
6355
  }
6127
6356
  async getAccountApy(state, chainId, days, tokenSymbol) {
6128
6357
  this.assertChain(chainId);
@@ -6161,7 +6390,7 @@ var YieldseekerAgent = class {
6161
6390
  );
6162
6391
  const vaultAddresses = new Set(
6163
6392
  catalog.flat().filter(
6164
- (yieldOption) => yieldOption.chainId === chainId && isAddress3(yieldOption.address)
6393
+ (yieldOption) => yieldOption.chainId === chainId && isAddress4(yieldOption.address)
6165
6394
  ).map((yieldOption) => yieldOption.address.toLowerCase())
6166
6395
  );
6167
6396
  return mapYieldseekerHistory(contexts, {
@@ -6290,7 +6519,7 @@ var YieldseekerAgent = class {
6290
6519
  `/users/${user.userId}/agents/${agent.agentId}/wallet`
6291
6520
  ).then((walletResponse) => {
6292
6521
  this.assertSession(generation, chainId, asset);
6293
- if (!walletResponse?.agentWallet || !isAddress3(walletResponse.agentWallet.walletAddress)) {
6522
+ if (!walletResponse?.agentWallet || !isAddress4(walletResponse.agentWallet.walletAddress)) {
6294
6523
  throw this.invalidResponse("agent wallet");
6295
6524
  }
6296
6525
  const context = {
@@ -6704,7 +6933,7 @@ var YieldseekerAgent = class {
6704
6933
  this.agentPath(context, "deploy"),
6705
6934
  { method: "POST", body: {} }
6706
6935
  );
6707
- if (!deployed?.agentWallet || !isAddress3(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
6936
+ if (!deployed?.agentWallet || !isAddress4(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
6708
6937
  throw this.invalidResponse("agent deployment", {
6709
6938
  reason: "Deploy did not return the expected Agent Wallet."
6710
6939
  });
@@ -6807,20 +7036,20 @@ var YieldseekerAgent = class {
6807
7036
  }
6808
7037
  async submitTransaction(state, chainId, transaction, onSubmitted, onReceipt) {
6809
7038
  if (this.transactionExecutor) {
6810
- const hash2 = await this.transactionExecutor(state, chainId, transaction);
7039
+ const hash2 = await this.transactionExecutor(state, chainId, transaction, onReceipt);
6811
7040
  onSubmitted?.(hash2);
6812
7041
  return hash2;
6813
7042
  }
6814
7043
  this.assertTransaction(transaction, state, chainId);
6815
7044
  const account = getAddress5(state.walletAddress);
6816
- const walletClient = createWalletClient3({
7045
+ const walletClient = createWalletClient4({
6817
7046
  account,
6818
7047
  chain: base4,
6819
- transport: custom3(state.provider)
7048
+ transport: custom4(state.provider)
6820
7049
  });
6821
7050
  const walletChainClient = createPublicClient3({
6822
7051
  chain: base4,
6823
- transport: custom3(state.provider)
7052
+ transport: custom4(state.provider)
6824
7053
  });
6825
7054
  await ensureWalletOnChain(
6826
7055
  walletChainClient,
@@ -6934,12 +7163,12 @@ var YieldseekerAgent = class {
6934
7163
  return receipt;
6935
7164
  }
6936
7165
  assertTransaction(transaction, state, chainId) {
6937
- if (!transaction || typeof transaction.from !== "string" || !isAddress3(transaction.from) || typeof transaction.to !== "string" || !isAddress3(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 || getAddress5(transaction.from) !== getAddress5(state.walletAddress)) {
7166
+ if (!transaction || typeof transaction.from !== "string" || !isAddress4(transaction.from) || typeof transaction.to !== "string" || !isAddress4(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 || getAddress5(transaction.from) !== getAddress5(state.walletAddress)) {
6938
7167
  throw this.invalidResponse("transaction");
6939
7168
  }
6940
7169
  }
6941
7170
  assertAgent(agent) {
6942
- if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress3(agent.assetAddress) || agent.chainId !== 8453) {
7171
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress4(agent.assetAddress) || agent.chainId !== 8453) {
6943
7172
  throw this.invalidResponse("agent");
6944
7173
  }
6945
7174
  }
@@ -7097,11 +7326,20 @@ import { parseUnits as parseUnits2 } from "viem";
7097
7326
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
7098
7327
  const target = asset.toUpperCase();
7099
7328
  return agents.map((agent) => {
7329
+ let invalidBalance = false;
7330
+ const readAmount = (value, raw2 = false) => {
7331
+ if (typeof value !== "string" || !(raw2 ? /^\d+$/ : /^\d+(?:\.\d+)?$/).test(value)) {
7332
+ invalidBalance = true;
7333
+ return 0n;
7334
+ }
7335
+ const [whole, fraction] = value.split(".");
7336
+ return raw2 ? BigInt(value) : parseUnits2(fraction === void 0 ? whole : `${whole}.${fraction.slice(0, decimals)}`, decimals);
7337
+ };
7100
7338
  const agentBalance = aggregated[agent.id];
7101
7339
  const tokenBalance = agentBalance?.tokens.find(
7102
7340
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
7103
7341
  );
7104
- let balance = tokenBalance ? parseUnits2(tokenBalance.amount, decimals) : 0n;
7342
+ let balance = tokenBalance ? readAmount(tokenBalance.amount) : 0n;
7105
7343
  if (agent.balanceComposition === "tokens-plus-positions") {
7106
7344
  const chainNameById = {
7107
7345
  1: "ETHEREUM",
@@ -7113,20 +7351,22 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
7113
7351
  const positionChain = position2.chain.trim().toUpperCase();
7114
7352
  const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
7115
7353
  if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
7116
- balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : parseUnits2(position2.amount, decimals);
7354
+ balance += position2.withdrawableAmountRaw !== void 0 ? readAmount(position2.withdrawableAmountRaw, true) : readAmount(position2.amount);
7117
7355
  }
7118
7356
  }
7119
7357
  const assetTotal = agentBalance?.assetBalances?.find(
7120
7358
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
7121
7359
  );
7122
7360
  if (assetTotal) {
7123
- const total = parseUnits2(assetTotal.amount, decimals);
7361
+ const total = readAmount(assetTotal.amount);
7124
7362
  if (total < balance) balance = total > 0n ? total : 0n;
7125
7363
  }
7126
- return { agent, balance };
7364
+ return invalidBalance ? { agent, balance: 0n, invalidBalance: true } : { agent, balance };
7127
7365
  });
7128
7366
  }
7129
7367
  function planProportionalShares(balances, requested, totalAvailable) {
7368
+ if (requested < 0n || totalAvailable < 0n || balances.some((entry) => entry.balance < 0n) || balances.reduce((sum, entry) => sum + entry.balance, 0n) !== totalAvailable || requested > totalAvailable)
7369
+ throw new Error("Invalid withdrawal planning amounts.");
7130
7370
  const plans = balances.map(({ agent, balance }) => ({
7131
7371
  agent,
7132
7372
  balance,
@@ -7149,6 +7389,8 @@ function planProportionalShares(balances, requested, totalAvailable) {
7149
7389
  return plans;
7150
7390
  }
7151
7391
  function planDisabledDrain(disabled, requested) {
7392
+ if (requested < 0n || disabled.some((entry) => entry.balance < 0n))
7393
+ throw new Error("Invalid withdrawal planning amounts.");
7152
7394
  const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
7153
7395
  (a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
7154
7396
  );
@@ -7166,6 +7408,7 @@ function planDisabledDrain(disabled, requested) {
7166
7408
  return { plans, remaining };
7167
7409
  }
7168
7410
  function redistributeShare(plans, fromIndex, amount, candidatePool) {
7411
+ if (amount <= 0n) return;
7169
7412
  const pool = candidatePool ?? plans.slice(fromIndex + 1);
7170
7413
  const candidates = pool.filter((c) => c.balance - c.planned > 0n);
7171
7414
  const totalHeadroom = candidates.reduce(
@@ -7192,7 +7435,13 @@ function redistributeShare(plans, fromIndex, amount, candidatePool) {
7192
7435
  }
7193
7436
  }
7194
7437
  function sumWithdrawnAmount(results) {
7195
- return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
7438
+ return Object.values(results).reduce((sum, result) => sum + (confirmedWithdrawalAmount(result) ?? 0n), 0n);
7439
+ }
7440
+ function confirmedWithdrawalAmount(result) {
7441
+ const evidence = result.evidence;
7442
+ if (evidence && (evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed")) return void 0;
7443
+ const value = evidence ? evidence.confirmedAmount : result.amount;
7444
+ return typeof value === "string" && /^\d+$/.test(value) ? BigInt(value) : void 0;
7196
7445
  }
7197
7446
 
7198
7447
  // src/lib/helpers/account-apy-helper.ts
@@ -7319,15 +7568,17 @@ function aggregateDailyEarnings(series, tokenSymbol) {
7319
7568
  import {
7320
7569
  parseUnits as parseUnits3,
7321
7570
  createPublicClient as createPublicClient4,
7322
- createWalletClient as createWalletClient4,
7323
- custom as custom4
7571
+ createWalletClient as createWalletClient5,
7572
+ custom as custom5
7324
7573
  } from "viem";
7325
7574
  import { base as base5, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
7326
7575
 
7327
7576
  // src/lib/sponsored-token-batch.ts
7328
7577
  import {
7329
7578
  isAddressEqual as isAddressEqual2,
7579
+ encodeFunctionData as encodeFunctionData3,
7330
7580
  keccak256,
7581
+ parseTransaction,
7331
7582
  toBytes
7332
7583
  } from "viem";
7333
7584
 
@@ -7415,9 +7666,29 @@ function batchTypedData(b, spender) {
7415
7666
  }
7416
7667
  };
7417
7668
  }
7669
+ var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
7670
+ function batchWitness(b) {
7671
+ return hashStruct({
7672
+ types: PERMIT_BATCH_TYPES,
7673
+ primaryType: "Deposit",
7674
+ data: { recipients: b.transfers.map((t) => t.to) }
7675
+ });
7676
+ }
7677
+ function batchArgs(b) {
7678
+ return [
7679
+ batchPermit(b),
7680
+ b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
7681
+ b.from,
7682
+ batchWitness(b),
7683
+ BATCH_WITNESS_TYPE,
7684
+ b.signature
7685
+ ];
7686
+ }
7418
7687
 
7419
7688
  // src/lib/sponsored-token-batch.ts
7420
7689
  var memory = /* @__PURE__ */ new Map();
7690
+ var settledRecords = /* @__PURE__ */ new Map();
7691
+ var expiredRecords = /* @__PURE__ */ new Map();
7421
7692
  var inflight = /* @__PURE__ */ new Map();
7422
7693
  var planOf = (transfers) => JSON.stringify(
7423
7694
  transfers.map((t) => ({
@@ -7437,20 +7708,97 @@ function pendingTokenBatch(i) {
7437
7708
  return;
7438
7709
  }
7439
7710
  }
7711
+ async function releaseExpiredTokenBatch(i) {
7712
+ const key2 = pendingKey(i);
7713
+ let timer;
7714
+ let expired = false;
7715
+ const check = async () => {
7716
+ let active = read(key2);
7717
+ if (active && i.expectedTxHash) {
7718
+ const current = JSON.parse(active);
7719
+ if (!current.serializedTransaction || keccak256(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
7720
+ active = void 0;
7721
+ }
7722
+ const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
7723
+ const saved = active ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
7724
+ if (!saved) return;
7725
+ const body = JSON.parse(saved);
7726
+ if (!body.serializedTransaction || body.chainId !== i.chainId || !isAddressEqual2(body.from, i.owner) || !isAddressEqual2(body.token, i.token) || !body.transfers?.length) return;
7727
+ const txHash = keccak256(body.serializedTransaction);
7728
+ if (i.expectedTxHash && txHash.toLowerCase() !== i.expectedTxHash.toLowerCase()) return;
7729
+ const transaction = parseTransaction(body.serializedTransaction);
7730
+ if (transaction.chainId !== i.chainId || !transaction.to || !isAddressEqual2(transaction.to, BATCH_PERMIT2_ADDRESS) || (transaction.value ?? 0n) !== 0n || transaction.data !== encodeFunctionData3({
7731
+ abi: PERMIT2_BATCH_ABI,
7732
+ functionName: "permitWitnessTransferFrom",
7733
+ args: batchArgs(body)
7734
+ })) return;
7735
+ const nonce = BigInt(body.nonce);
7736
+ const deadline = BigInt(body.deadline);
7737
+ if (nonce < 0n || nonce > MAX_UINT256 || deadline < 0n) return;
7738
+ const block = await i.pub.getBlock({ blockTag: "finalized" });
7739
+ if (expired || typeof block.number !== "bigint" || typeof block.timestamp !== "bigint" || block.timestamp <= deadline) return;
7740
+ const bitmap = await i.pub.readContract({
7741
+ address: BATCH_PERMIT2_ADDRESS,
7742
+ abi: PERMIT2_BATCH_ABI,
7743
+ functionName: "nonceBitmap",
7744
+ args: [i.owner, nonce >> 8n],
7745
+ blockNumber: block.number
7746
+ });
7747
+ if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active && read(key2) !== active) return;
7748
+ if (active) {
7749
+ const terminalKey = `${key2}:expired:${txHash.toLowerCase()}`;
7750
+ expiredRecords.set(terminalKey, saved);
7751
+ let persisted = typeof window === "undefined";
7752
+ try {
7753
+ if (typeof window !== "undefined") {
7754
+ window.localStorage.setItem(terminalKey, saved);
7755
+ persisted = window.localStorage.getItem(terminalKey) === saved;
7756
+ }
7757
+ } catch {
7758
+ }
7759
+ if (!persisted) settledRecords.set(key2, saved);
7760
+ else clear(key2, body);
7761
+ }
7762
+ return { txHash, transfers: body.transfers.map(({ to, amount }) => ({ to, amount })), blockNumber: block.number };
7763
+ };
7764
+ try {
7765
+ return await Promise.race([check(), new Promise((resolve) => {
7766
+ timer = setTimeout(() => {
7767
+ expired = true;
7768
+ resolve(void 0);
7769
+ }, 8e3);
7770
+ })]);
7771
+ } catch {
7772
+ return;
7773
+ } finally {
7774
+ expired = true;
7775
+ if (timer) clearTimeout(timer);
7776
+ }
7777
+ }
7440
7778
  function read(key2) {
7441
- return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
7779
+ const value = typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
7780
+ return value && settledRecords.get(key2) === value ? void 0 : value;
7442
7781
  }
7443
- function save(key2, body) {
7444
- const value = JSON.stringify({
7782
+ function storedBody(body) {
7783
+ return JSON.stringify({
7445
7784
  ...body,
7446
7785
  transfers: body.transfers.map(({ to, amount }) => ({ to, amount }))
7447
7786
  });
7787
+ }
7788
+ function save(key2, body) {
7789
+ const value = storedBody(body);
7448
7790
  if (typeof window === "undefined") memory.set(key2, value);
7449
7791
  else window.localStorage.setItem(key2, value);
7792
+ settledRecords.delete(key2);
7450
7793
  }
7451
- function clear(key2) {
7452
- if (typeof window === "undefined") memory.delete(key2);
7453
- else window.localStorage.removeItem(key2);
7794
+ function clear(key2, body) {
7795
+ try {
7796
+ if (typeof window === "undefined") memory.delete(key2);
7797
+ else window.localStorage.removeItem(key2);
7798
+ settledRecords.delete(key2);
7799
+ } catch {
7800
+ settledRecords.set(key2, storedBody(body));
7801
+ }
7454
7802
  }
7455
7803
  function sponsorTokenBatch(i) {
7456
7804
  const key2 = pendingKey(i);
@@ -7558,7 +7906,7 @@ async function execute(i, key2, plan) {
7558
7906
  if (existing) {
7559
7907
  assertReceipt(existing, "deposit");
7560
7908
  void post().catch(() => void 0);
7561
- clear(key2);
7909
+ clear(key2, body);
7562
7910
  return hash;
7563
7911
  }
7564
7912
  let result = await postSigned("deposit", hash);
@@ -7573,11 +7921,11 @@ async function execute(i, key2, plan) {
7573
7921
  if (result.status === "pending") await waitForConfirmation("deposit", hash);
7574
7922
  }
7575
7923
  if (!result || result.status === "pending") void post().catch(() => void 0);
7576
- clear(key2);
7924
+ clear(key2, body);
7577
7925
  return hash;
7578
7926
  } catch (error) {
7579
7927
  if (!body.serializedTransaction && !body.serializedPermitTransaction || rejectedPreparation(error) || error instanceof OwneyError && error.details?.notSubmitted === true)
7580
- clear(key2);
7928
+ clear(key2, body);
7581
7929
  throw error;
7582
7930
  }
7583
7931
  };
@@ -7590,6 +7938,12 @@ async function execute(i, key2, plan) {
7590
7938
  throw new Error(
7591
7939
  "Retry the previous token deposit and agent split first to reconcile its status."
7592
7940
  );
7941
+ if (await releaseExpiredTokenBatch({ ...i, expectedTxHash: previous.serializedTransaction ? keccak256(previous.serializedTransaction) : void 0 }))
7942
+ throw new OwneyError(
7943
+ "SPONSOR_REQUEST_FAILED",
7944
+ "The deposit authorization expired without transferring funds. Start a new deposit.",
7945
+ { notSubmitted: true, safeToFallback: false }
7946
+ );
7593
7947
  i.onApproved?.();
7594
7948
  return send({ ...previous, transfers: i.transfers });
7595
7949
  }
@@ -7782,7 +8136,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
7782
8136
  }
7783
8137
 
7784
8138
  // src/lib/sponsored-calls-deposit.ts
7785
- import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi5, toHex as toHex2 } from "viem";
8139
+ import { encodeFunctionData as encodeFunctionData4, erc20Abi as erc20Abi6, toHex as toHex2 } from "viem";
7786
8140
  var DEFAULT_POLL_INTERVAL_MS = 1500;
7787
8141
  var DEFAULT_MAX_POLLS = 30;
7788
8142
  async function paymasterSupported(provider, owner, chainId) {
@@ -7827,8 +8181,8 @@ function makeSponsoredCallsCallback(deps) {
7827
8181
  const calls = transfers.map((transfer) => ({
7828
8182
  to: token,
7829
8183
  value: "0x0",
7830
- data: encodeFunctionData2({
7831
- abi: erc20Abi5,
8184
+ data: encodeFunctionData4({
8185
+ abi: erc20Abi6,
7832
8186
  functionName: "transfer",
7833
8187
  args: [transfer.to, BigInt(transfer.amount)]
7834
8188
  })
@@ -8010,6 +8364,8 @@ var OwneySDK = class {
8010
8364
  */
8011
8365
  disabledAgents = /* @__PURE__ */ new Set();
8012
8366
  state = null;
8367
+ connectionRevision = 0;
8368
+ disconnectingAgents = null;
8013
8369
  apiKey;
8014
8370
  // The org's agent policy, fetched once per client. Applied to each agent on
8015
8371
  // activation (ROUT-229). `undefined` means "not fetched yet", `null` means
@@ -8031,6 +8387,9 @@ var OwneySDK = class {
8031
8387
  recoveredFundingReads = /* @__PURE__ */ new Set();
8032
8388
  fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
8033
8389
  activationChecks = /* @__PURE__ */ new Map();
8390
+ // Protect the SDK entry point as well as the mini-app hook. This owns only
8391
+ // a running call, never a saved/pending provider outcome or a later retry.
8392
+ withdrawalSubmissions = /* @__PURE__ */ new Set();
8034
8393
  readAgent(agent, method, fetch2, params = null) {
8035
8394
  const key2 = JSON.stringify([
8036
8395
  this.state?.walletAddress.toLowerCase() ?? null,
@@ -8063,9 +8422,10 @@ var OwneySDK = class {
8063
8422
  */
8064
8423
  // TODO: replace with a proper EIP-1193 Provider type
8065
8424
  async connect(provider) {
8066
- const accounts = await provider.request({
8067
- method: "eth_accounts"
8068
- });
8425
+ const revision = ++this.connectionRevision;
8426
+ const accounts = await provider.request({ method: "eth_accounts" });
8427
+ if (revision !== this.connectionRevision)
8428
+ throw new OwneyError("NOT_CONNECTED", "Wallet connection changed while connecting.");
8069
8429
  const walletAddress = accounts[0];
8070
8430
  if (!walletAddress) {
8071
8431
  throw new OwneyError(
@@ -8073,33 +8433,46 @@ var OwneySDK = class {
8073
8433
  "No accounts found. Ensure the wallet is unlocked and connected."
8074
8434
  );
8075
8435
  }
8076
- this.agentReads.clearInFlight();
8077
- this.activationChecks.clear();
8078
- this.fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
8436
+ const replacingOwner = this.state !== null && this.state.walletAddress.toLowerCase() !== walletAddress.toLowerCase();
8437
+ this.clearConnectionState();
8438
+ await (replacingOwner ? this.disconnectAgents() : this.disconnectingAgents);
8439
+ if (revision !== this.connectionRevision)
8440
+ throw new OwneyError("NOT_CONNECTED", "Wallet connection changed while connecting.");
8079
8441
  this.state = { provider, walletAddress, chainId: null };
8080
- this.cachedSponsoredCallback = null;
8081
- this.cachedWethSponsoredCallback = null;
8082
- this.cachedSponsoredCallsCallbacks.clear();
8083
8442
  }
8084
8443
  /**
8085
8444
  * Disconnect from all agents and clear connection state.
8086
8445
  * After calling this, `connect()` must be called again before using agent methods.
8087
8446
  */
8088
8447
  async disconnect() {
8448
+ ++this.connectionRevision;
8449
+ this.clearConnectionState();
8450
+ await this.disconnectAgents();
8451
+ }
8452
+ clearConnectionState() {
8089
8453
  this.state = null;
8090
8454
  this.fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
8091
- for (const agent of this.agents.values()) {
8092
- await agent.disconnect();
8093
- }
8094
8455
  this.activeAgents.clear();
8095
- this.disabledAgents.clear();
8096
8456
  this.agentReads.clearInFlight();
8097
8457
  this.activationChecks.clear();
8098
- this.state = null;
8099
8458
  this.cachedSponsoredCallback = null;
8100
8459
  this.cachedWethSponsoredCallback = null;
8101
8460
  this.cachedSponsoredCallsCallbacks.clear();
8102
8461
  }
8462
+ disconnectAgents() {
8463
+ if (this.disconnectingAgents) return this.disconnectingAgents;
8464
+ const pending = Promise.allSettled([...this.agents.values()].map(async (agent) => agent.disconnect())).then((results) => {
8465
+ const failure = results.find((result) => result.status === "rejected");
8466
+ if (failure?.status === "rejected") throw failure.reason;
8467
+ });
8468
+ this.disconnectingAgents = pending;
8469
+ void pending.then(() => {
8470
+ if (this.disconnectingAgents === pending) this.disconnectingAgents = null;
8471
+ }, () => {
8472
+ if (this.disconnectingAgents === pending) this.disconnectingAgents = null;
8473
+ });
8474
+ return pending;
8475
+ }
8103
8476
  isAgentDisabled(agentId) {
8104
8477
  return this.disabledAgents.has(agentId);
8105
8478
  }
@@ -8164,12 +8537,12 @@ var OwneySDK = class {
8164
8537
  getPublicClient: (cid) => this.getPaidRpcClient(cid),
8165
8538
  getWalletChainClient: (cid) => createPublicClient4({
8166
8539
  chain: VIEM_CHAIN2[cid],
8167
- transport: custom4(provider)
8540
+ transport: custom5(provider)
8168
8541
  }),
8169
- getWalletClient: (cid) => createWalletClient4({
8542
+ getWalletClient: (cid) => createWalletClient5({
8170
8543
  account: owner,
8171
8544
  chain: VIEM_CHAIN2[cid],
8172
- transport: custom4(provider)
8545
+ transport: custom5(provider)
8173
8546
  })
8174
8547
  });
8175
8548
  if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedSponsoredCallback = callback;
@@ -8219,12 +8592,12 @@ var OwneySDK = class {
8219
8592
  getPublicClient: (cid) => this.getPaidRpcClient(cid),
8220
8593
  getWalletChainClient: (cid) => createPublicClient4({
8221
8594
  chain: VIEM_CHAIN2[cid],
8222
- transport: custom4(provider)
8595
+ transport: custom5(provider)
8223
8596
  }),
8224
- getWalletClient: (cid) => createWalletClient4({
8597
+ getWalletClient: (cid) => createWalletClient5({
8225
8598
  account: owner,
8226
8599
  chain: VIEM_CHAIN2[cid],
8227
- transport: custom4(provider)
8600
+ transport: custom5(provider)
8228
8601
  })
8229
8602
  });
8230
8603
  if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedWethSponsoredCallback = callback;
@@ -8535,6 +8908,12 @@ var OwneySDK = class {
8535
8908
  */
8536
8909
  async deposit(options) {
8537
8910
  const { amount, asset, depositCallback, agentId, onApproved } = options;
8911
+ if (!isFundingAmount(amount, true))
8912
+ throw new OwneyError(
8913
+ "DEPOSIT_INVALID_AMOUNT",
8914
+ "Deposit amount must be an unsigned base-unit integer.",
8915
+ { phase: "prepare", rejectedBeforeSubmission: true }
8916
+ );
8538
8917
  const prepared = /* @__PURE__ */ new Map();
8539
8918
  const paidFallbackAgents = /* @__PURE__ */ new Set();
8540
8919
  const onProgress = options.onProgress ? (evidence) => {
@@ -8562,6 +8941,47 @@ var OwneySDK = class {
8562
8941
  throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
8563
8942
  const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash));
8564
8943
  try {
8944
+ if (options.resumeTxHash) {
8945
+ const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
8946
+ const pending = token && pendingTokenBatch({
8947
+ apiKey: this.apiKey,
8948
+ baseUrl: this.routingApiBaseUrl,
8949
+ owner: state.walletAddress,
8950
+ chainId,
8951
+ token: token.address
8952
+ });
8953
+ const invalid2 = () => new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit does not match this request. Check its status before depositing again.");
8954
+ if (!pending || pending.txHash.toLowerCase() !== options.resumeTxHash.toLowerCase() || pending.transfers.some((transfer) => !/^\d+$/.test(transfer.amount) || BigInt(transfer.amount) <= 0n) || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n) !== BigInt(amount)) throw invalid2();
8955
+ const candidates = agentId ? [this.getAgent(agentId)] : this.getEligibleAgents(chainId, asset);
8956
+ const wallets = await Promise.all(candidates.map(async (agent) => {
8957
+ this.validateAssetSupport(agent, chainId, asset);
8958
+ const wallet = await (agent.getFundingWallet ? agent.getFundingWallet(state, chainId, asset) : agent.getUserProfile(state, chainId).then((profile) => profile.smartWallet)).catch(() => void 0);
8959
+ return { agent, wallet };
8960
+ }));
8961
+ if (this.state !== state || this.state.chainId !== chainId)
8962
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while recovering the deposit.");
8963
+ const usedAgents = /* @__PURE__ */ new Set();
8964
+ const legs = pending.transfers.map((transfer) => {
8965
+ const matches = wallets.filter((value) => value.wallet?.toLowerCase() === transfer.to.toLowerCase());
8966
+ if (matches.length !== 1 || usedAgents.has(matches[0].agent.id)) throw invalid2();
8967
+ const { agent } = matches[0];
8968
+ usedAgents.add(agent.id);
8969
+ return {
8970
+ id: agent.id,
8971
+ amount: transfer.amount,
8972
+ run: (callback) => withFailureReporting(
8973
+ this.apiKey,
8974
+ agent.id,
8975
+ () => agent.deposit(state, chainId, transfer.amount, asset, callback, onProgress),
8976
+ this.routingApiBaseUrl
8977
+ )
8978
+ };
8979
+ });
8980
+ const batchTransfer2 = getDepositBatchTransfer(effectiveCallback);
8981
+ if (!batchTransfer2) throw invalid2();
8982
+ const result = await runAgentDepositBatch(chainId, legs, batchTransfer2);
8983
+ return agentId ? result.agentResults[agentId] : result;
8984
+ }
8565
8985
  if (agentId) {
8566
8986
  if (this.isAgentDisabled(agentId)) {
8567
8987
  throw new OwneyError(
@@ -8978,9 +9398,36 @@ var OwneySDK = class {
8978
9398
  const token = SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token);
8979
9399
  if (!token) throw new OwneyError("CHAIN_UNSUPPORTED", "Unsupported funding asset.");
8980
9400
  const rpc2 = this.getPaidRpcClient(chainId);
8981
- const receipt = await rpc2.getTransactionReceipt({ hash: evidence.txHash });
9401
+ let receipt;
9402
+ try {
9403
+ receipt = await rpc2.getTransactionReceipt({ hash: evidence.txHash });
9404
+ } catch (error) {
9405
+ if (direction === "deposit") {
9406
+ const expired = await releaseExpiredTokenBatch({
9407
+ apiKey: this.apiKey,
9408
+ baseUrl: this.routingApiBaseUrl,
9409
+ owner: state.walletAddress,
9410
+ chainId,
9411
+ token: token.address,
9412
+ pub: rpc2,
9413
+ expectedTxHash: evidence.txHash
9414
+ });
9415
+ assertCurrent();
9416
+ if (expired?.transfers.some((transfer) => transfer.to.toLowerCase() === agentWallet.toLowerCase() && transfer.amount === evidence.intendedAmount))
9417
+ return {
9418
+ ...evidence,
9419
+ agentWallet,
9420
+ transfer: "failed",
9421
+ delivery: "not-applicable",
9422
+ registration: "not-applicable",
9423
+ confirmedAmount: "0",
9424
+ blockNumber: expired.blockNumber.toString()
9425
+ };
9426
+ }
9427
+ throw error;
9428
+ }
8982
9429
  assertCurrent();
8983
- let verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet }, token.address, direction);
9430
+ let verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet }, token.address, direction, options);
8984
9431
  if (direction === "withdraw" && evidence.agentId === "yieldseeker" && verified.transfer === "unknown" && verified.confirmedAmount && BigInt(verified.confirmedAmount) > 0n) {
8985
9432
  const transaction = await rpc2.getTransaction({ hash: evidence.txHash });
8986
9433
  assertCurrent();
@@ -9154,9 +9601,11 @@ var OwneySDK = class {
9154
9601
  return page;
9155
9602
  }
9156
9603
  async recoverDepositRegistration(evidence) {
9604
+ const { state, assertCurrent } = this.captureReadScope(evidence.chainId);
9157
9605
  const verified = await this.inspectFunding(evidence, "deposit");
9606
+ assertCurrent();
9158
9607
  if (verified.agentId !== "zyfai") return verified;
9159
- return this.getAgent("zyfai").retryDepositRegistration(this.requireState(), verified);
9608
+ return this.getAgent("zyfai").retryDepositRegistration(state, verified);
9160
9609
  }
9161
9610
  // --- Fund operations ---
9162
9611
  /** On-chain version and ownership status for this owner's Yieldseeker agent. */
@@ -9202,10 +9651,24 @@ var OwneySDK = class {
9202
9651
  * @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
9203
9652
  */
9204
9653
  async withdraw(options) {
9654
+ if (options.amount !== void 0 && !isFundingAmount(options.amount))
9655
+ throw new OwneyError(
9656
+ "WITHDRAW_INVALID_AMOUNT",
9657
+ "Withdrawal amount must be a positive base-unit integer.",
9658
+ { phase: "prepare", rejectedBeforeSubmission: true }
9659
+ );
9205
9660
  const state = this.requireState();
9206
9661
  const chainId = this.requireChainId();
9662
+ const submissionKey = `${state.walletAddress.toLowerCase()}:${chainId}:${options.asset.toUpperCase()}`;
9663
+ if (this.withdrawalSubmissions.has(submissionKey))
9664
+ throw new OwneyError(
9665
+ "WITHDRAW_IN_PROGRESS",
9666
+ "A withdrawal request is already running.",
9667
+ { chainId, asset: options.asset, phase: "prepare", rejectedBeforeSubmission: true }
9668
+ );
9207
9669
  const ids = options.agentId ? [options.agentId] : this.getActiveAgentIds().filter((id) => !options.excludeAgentIds?.includes(id));
9208
9670
  const attempted = /* @__PURE__ */ new Set();
9671
+ this.withdrawalSubmissions.add(submissionKey);
9209
9672
  try {
9210
9673
  return await this.withdrawFromAgents(options, state, chainId, attempted);
9211
9674
  } catch (error) {
@@ -9222,6 +9685,8 @@ var OwneySDK = class {
9222
9685
  });
9223
9686
  }
9224
9687
  throw error;
9688
+ } finally {
9689
+ this.withdrawalSubmissions.delete(submissionKey);
9225
9690
  }
9226
9691
  }
9227
9692
  async withdrawFromAgents(options, state, chainId, attempted) {
@@ -9260,7 +9725,19 @@ var OwneySDK = class {
9260
9725
  };
9261
9726
  };
9262
9727
  const submissionEvidence = /* @__PURE__ */ new Map();
9728
+ const pendingSubmissions = () => Object.fromEntries([...submissionEvidence].filter(([id, evidence]) => evidence.agentId === id && evidence.chainId === chainId && evidence.token === token && evidence.walletAddress.toLowerCase() === state.walletAddress.toLowerCase() && evidence.transfer !== "failed" && evidence.delivery !== "failed" && (evidence.transfer === "unknown" || evidence.txHash || evidence.userOpHash || evidence.callsId)));
9729
+ const throwUnconfirmed = (agentErrors2) => {
9730
+ const pending = pendingSubmissions();
9731
+ if (Object.keys(pending).length)
9732
+ throw new OwneyError(
9733
+ "WITHDRAW_UNCONFIRMED",
9734
+ "The withdrawal was submitted, but its outcome could not be confirmed yet.",
9735
+ { asset, amount, agentErrors: agentErrors2, agentFailures, pendingSubmissions: pending }
9736
+ );
9737
+ };
9263
9738
  const submit = async (agent, requested2) => {
9739
+ if (this.state !== state || this.state.chainId !== chainId)
9740
+ throw new OwneyError("NOT_CONNECTED", "Wallet connection changed before withdrawal submission.");
9264
9741
  attempted.add(agent.id);
9265
9742
  let evidence = {
9266
9743
  agentId: agent.id,
@@ -9272,12 +9749,16 @@ var OwneySDK = class {
9272
9749
  registration: "not-applicable",
9273
9750
  delivery: "pending"
9274
9751
  };
9752
+ let adapterReported = false;
9275
9753
  const progress = (next) => {
9754
+ if (next.agentId !== agent.id || next.chainId !== chainId || next.token !== token || next.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase()) return;
9755
+ adapterReported = true;
9276
9756
  evidence = { ...evidence, ...next };
9277
9757
  submissionEvidence.set(agent.id, evidence);
9278
9758
  notifyFunding(options.onProgress, evidence);
9279
9759
  };
9280
9760
  progress(evidence);
9761
+ adapterReported = false;
9281
9762
  try {
9282
9763
  if (options.onBeforeSubmission)
9283
9764
  return await agent.withdraw(
@@ -9291,7 +9772,7 @@ var OwneySDK = class {
9291
9772
  );
9292
9773
  return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
9293
9774
  } catch (error) {
9294
- if (!evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
9775
+ if (!adapterReported && !evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
9295
9776
  progress({ ...evidence, transfer: "failed", delivery: "failed" });
9296
9777
  throw error;
9297
9778
  }
@@ -9352,6 +9833,7 @@ var OwneySDK = class {
9352
9833
  }
9353
9834
  }
9354
9835
  if (Object.keys(results2).length === 0) {
9836
+ throwUnconfirmed(agentErrors2);
9355
9837
  if (Object.keys(agentErrors2).length === 0)
9356
9838
  throw new OwneyError(
9357
9839
  "WITHDRAW_INSUFFICIENT_BALANCE",
@@ -9372,7 +9854,7 @@ var OwneySDK = class {
9372
9854
  );
9373
9855
  }
9374
9856
  const amountsKnown = Object.values(results2).every(
9375
- (result) => typeof result.amount === "string" && /^\d+$/.test(result.amount)
9857
+ (result) => confirmedWithdrawalAmount(result) !== void 0
9376
9858
  );
9377
9859
  return {
9378
9860
  agentResult: results2,
@@ -9391,6 +9873,13 @@ var OwneySDK = class {
9391
9873
  asset,
9392
9874
  assetInfo.decimals
9393
9875
  );
9876
+ for (const entry of balances) if (entry.invalidBalance) {
9877
+ if (!unavailableAgents.includes(entry.agent)) unavailableAgents.push(entry.agent);
9878
+ aggregated.agentErrors = {
9879
+ ...aggregated.agentErrors,
9880
+ [entry.agent.id]: "Provider returned an invalid withdrawal balance."
9881
+ };
9882
+ }
9394
9883
  const indexedAvailable = balances.reduce((sum, entry) => sum + entry.balance, 0n);
9395
9884
  await Promise.all(balances.map(async (entry) => {
9396
9885
  if (entry.balance !== 0n && indexedAvailable >= requested || !entry.agent.getWithdrawableBalance) return;
@@ -9430,12 +9919,15 @@ var OwneySDK = class {
9430
9919
  throw new OwneyError("NOT_CONNECTED", "Account changed while planning withdrawal.");
9431
9920
  const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
9432
9921
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
9433
- if (totalAvailable === 0n && unavailableAgents.length > 0) {
9922
+ if (totalAvailable < requested && unavailableAgents.length > 0) {
9434
9923
  throw new OwneyError(
9435
9924
  "WITHDRAW_BALANCE_UNAVAILABLE",
9436
- `Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
9925
+ `Cannot cover the requested withdrawal with verified available balances: ${unavailableAgentIds.join(", ")}.`,
9437
9926
  {
9438
9927
  asset,
9928
+ requested: amount,
9929
+ available: totalAvailable.toString(),
9930
+ rejectedBeforeSubmission: true,
9439
9931
  unavailableAgents: unavailableAgentIds,
9440
9932
  agentErrors: aggregated.agentErrors
9441
9933
  }
@@ -9452,7 +9944,7 @@ var OwneySDK = class {
9452
9944
  }
9453
9945
  );
9454
9946
  }
9455
- const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
9947
+ const plannedTarget = requested;
9456
9948
  const disabledBalances = balances.filter(
9457
9949
  (b) => this.isAgentDisabled(b.agent.id)
9458
9950
  );
@@ -9471,13 +9963,23 @@ var OwneySDK = class {
9471
9963
  }));
9472
9964
  const plans = [...disabledPlans, ...enabledPlans];
9473
9965
  const orderedPlans = this.orderAgentsForWithdrawal(plans.map((p) => p.agent)).map((agent) => plans.find((plan) => plan.agent === agent));
9966
+ const assertPlan = () => {
9967
+ if (orderedPlans.some((plan) => plan.planned < 0n || plan.planned > plan.balance) || orderedPlans.reduce((sum, plan) => sum + plan.planned, 0n) > plannedTarget)
9968
+ throw new OwneyError(
9969
+ "WITHDRAW_BALANCE_UNAVAILABLE",
9970
+ "Withdrawal plan failed amount validation.",
9971
+ { asset, rejectedBeforeSubmission: true }
9972
+ );
9973
+ };
9974
+ assertPlan();
9474
9975
  const results = {};
9475
9976
  const agentErrors = {
9476
9977
  ...aggregated.agentErrors ?? {}
9477
9978
  };
9478
9979
  for (let i = 0; i < orderedPlans.length; i++) {
9479
9980
  const p = orderedPlans[i];
9480
- if (p.planned === 0n) continue;
9981
+ assertPlan();
9982
+ if (p.planned <= 0n) continue;
9481
9983
  try {
9482
9984
  results[p.agent.id] = await submit(p.agent, p.planned.toString());
9483
9985
  notifyAgentResult(p.agent.id, results[p.agent.id]);
@@ -9501,6 +10003,7 @@ var OwneySDK = class {
9501
10003
  }
9502
10004
  }
9503
10005
  if (Object.keys(results).length === 0) {
10006
+ throwUnconfirmed(agentErrors);
9504
10007
  throw new OwneyError(
9505
10008
  "WITHDRAW_ALL_FAILED",
9506
10009
  `Failed to withdraw "${amount}" of "${asset}" from all eligible agents.`,
@@ -9518,13 +10021,17 @@ var OwneySDK = class {
9518
10021
  const adjustment = result?.feeAdjustment;
9519
10022
  const evidence = result?.evidence;
9520
10023
  const submitted = submissionEvidence.get(plan.agent.id);
9521
- if (plan.agent.id !== "yieldseeker" || !adjustment || !evidence || !submitted || typeof result.amount !== "string" || !/^\d+$/.test(result.amount) || typeof adjustment.grossAmount !== "string" || !/^\d+$/.test(adjustment.grossAmount) || typeof adjustment.feeAmount !== "string" || !/^\d+$/.test(adjustment.feeAmount) || evidence.agentId !== plan.agent.id || evidence.chainId !== chainId || evidence.token !== token || typeof evidence.walletAddress !== "string" || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || typeof evidence.agentWallet !== "string" || !/^0x[\da-f]{40}$/i.test(evidence.agentWallet) || typeof result.txHash !== "string" || !/^0x[\da-f]{64}$/i.test(result.txHash) || typeof evidence.txHash !== "string" || typeof submitted.txHash !== "string" || evidence.txHash?.toLowerCase() !== result.txHash.toLowerCase() || submitted.txHash?.toLowerCase() !== result.txHash.toLowerCase() || submitted.agentId !== evidence.agentId || submitted.chainId !== chainId || submitted.token !== token || typeof submitted.walletAddress !== "string" || typeof submitted.agentWallet !== "string" || submitted.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || submitted.agentWallet?.toLowerCase() !== evidence.agentWallet.toLowerCase() || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || submitted.transfer !== "confirmed" || submitted.delivery !== "confirmed" || evidence.confirmedAmount !== result.amount || submitted.confirmedAmount !== result.amount || evidence.intendedAmount !== result.amount || submitted.intendedAmount !== result.amount) return total;
10024
+ if (plan.agent.id !== "yieldseeker" || !adjustment || !evidence || !submitted || typeof result.amount !== "string" || !/^\d+$/.test(result.amount) || typeof adjustment.grossAmount !== "string" || !/^\d+$/.test(adjustment.grossAmount) || typeof adjustment.feeAmount !== "string" || !/^\d+$/.test(adjustment.feeAmount) || evidence.agentId !== plan.agent.id || evidence.chainId !== chainId || evidence.token !== token || typeof evidence.walletAddress !== "string" || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || typeof evidence.agentWallet !== "string" || !/^0x[\da-f]{40}$/i.test(evidence.agentWallet) || typeof result.txHash !== "string" || !/^0x[\da-f]{64}$/i.test(result.txHash) || typeof evidence.txHash !== "string" || typeof submitted.txHash !== "string" || evidence.txHash?.toLowerCase() !== result.txHash.toLowerCase() || submitted.txHash?.toLowerCase() !== result.txHash.toLowerCase() || submitted.agentId !== evidence.agentId || submitted.chainId !== chainId || submitted.token !== token || typeof submitted.walletAddress !== "string" || typeof submitted.agentWallet !== "string" || submitted.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || submitted.agentWallet?.toLowerCase() !== evidence.agentWallet.toLowerCase() || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || submitted.transfer !== "confirmed" || submitted.delivery !== "confirmed" || evidence.confirmedAmount !== result.amount || submitted.confirmedAmount !== result.amount || ![result.amount, adjustment.grossAmount].includes(evidence.intendedAmount) || ![result.amount, adjustment.grossAmount].includes(submitted.intendedAmount)) return total;
9522
10025
  const fee = BigInt(adjustment.feeAmount);
9523
10026
  const net = BigInt(result.amount);
9524
10027
  const gross = BigInt(adjustment.grossAmount);
9525
10028
  return fee > 0n && net > 0n && gross === plan.planned && net + fee === gross ? total + fee : total;
9526
10029
  }, 0n);
9527
- if (withdrawn + verifiedFeeDeductions < requested) {
10030
+ const pendingAmount = orderedPlans.reduce((sum, plan) => {
10031
+ const evidence = results[plan.agent.id]?.evidence;
10032
+ return evidence && (evidence.transfer === "pending" || evidence.transfer === "unknown") && evidence.delivery !== "failed" ? sum + plan.planned : sum;
10033
+ }, 0n);
10034
+ if (withdrawn + verifiedFeeDeductions + pendingAmount < requested) {
9528
10035
  throw new OwneyError(
9529
10036
  "WITHDRAW_PARTIAL_FAILURE",
9530
10037
  `Withdrew "${withdrawn.toString()}" of requested "${amount}" of "${asset}". One or more agents failed; remaining balance could not cover the shortfall.`,
@@ -9532,6 +10039,7 @@ var OwneySDK = class {
9532
10039
  asset,
9533
10040
  requested: amount,
9534
10041
  withdrawn: withdrawn.toString(),
10042
+ pendingAmount: pendingAmount.toString(),
9535
10043
  partialResults: results,
9536
10044
  agentErrors,
9537
10045
  agentFailures,
@@ -9539,7 +10047,7 @@ var OwneySDK = class {
9539
10047
  }
9540
10048
  );
9541
10049
  }
9542
- return { agentResult: results, totalWithdrawn: withdrawn.toString() };
10050
+ return { agentResult: results, totalWithdrawn: Object.values(results).every((result) => confirmedWithdrawalAmount(result) !== void 0) ? withdrawn.toString() : null };
9543
10051
  }
9544
10052
  // --- Portfolio reads ---
9545
10053
  /**
@@ -9999,14 +10507,14 @@ var OwneySDK = class {
9999
10507
  const provider = this.requireConnectedProvider();
10000
10508
  const walletChainClient = createPublicClient4({
10001
10509
  chain: VIEM_CHAIN2[chainId],
10002
- transport: custom4(provider)
10510
+ transport: custom5(provider)
10003
10511
  });
10004
10512
  const publicClient = this.getPaidRpcClient(chainId);
10005
10513
  const approvalAmount = permit2ApprovalAmount(requiredAmount);
10006
- const wallet = createWalletClient4({
10514
+ const wallet = createWalletClient5({
10007
10515
  account: state.walletAddress,
10008
10516
  chain: VIEM_CHAIN2[chainId],
10009
- transport: custom4(provider)
10517
+ transport: custom5(provider)
10010
10518
  });
10011
10519
  await ensureWalletOnChain(walletChainClient, wallet, chainId);
10012
10520
  const hash = await wallet.writeContract({