@owney/sdk 0.7.25-beta.15 → 0.7.25-beta.17

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
@@ -370,20 +370,44 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
370
370
  };
371
371
  const notify = (outcome) => {
372
372
  if (outcome === "rejected" && matching.length !== 1) return;
373
+ const evidence = matching.length === 1 ? { ...request2 } : {
374
+ attempted: request2.attempted,
375
+ method: request2.method,
376
+ path: request2.path,
377
+ ...request2.elapsedMs !== void 0 ? { elapsedMs: request2.elapsedMs } : {}
378
+ };
373
379
  for (const observer of matching) {
374
380
  try {
375
- observer.notify(outcome, { ...request2 });
381
+ observer.notify(outcome, evidence);
376
382
  } catch {
377
383
  }
378
384
  }
379
385
  };
380
386
  notify("attempted");
387
+ let callArgs = args;
388
+ if (withdrawal && client.client?.defaults) {
389
+ const configIndex = method === "get" ? 1 : 2;
390
+ const config = args[configIndex];
391
+ const transforms = config?.transformResponse !== void 0 ? config.transformResponse : client.client.defaults.transformResponse;
392
+ const captureStatus = (data, _headers, status) => {
393
+ if (typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599) request2.httpStatus = status;
394
+ return data;
395
+ };
396
+ callArgs = [...args];
397
+ callArgs[configIndex] = { ...config, transformResponse: [
398
+ ...Array.isArray(transforms) ? transforms : typeof transforms === "function" ? [transforms] : [],
399
+ captureStatus
400
+ ] };
401
+ }
381
402
  try {
382
- return await original.apply(this, args);
403
+ return await original.apply(this, callArgs);
383
404
  } catch (error) {
384
405
  const message = error instanceof Error ? error.message : "";
385
- 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" : message === "Network error: Unable to reach the server" ? "network" : "other";
386
- if (["unauthorized", "forbidden", "rate-limited", "insufficient-balance", "session-key-missing"].includes(request2.failure)) notify("rejected");
406
+ const status = error?.response?.status;
407
+ if (request2.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
408
+ 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";
410
+ 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");
387
411
  throw error;
388
412
  } finally {
389
413
  request2.elapsedMs = Math.max(0, Date.now() - startedAt);
@@ -1464,6 +1488,7 @@ var VIEM_CHAIN = { 8453: base, 42161: arbitrum, 1: mainnet };
1464
1488
  var SPLITTING_ASSETS = ["USDC", "WETH"];
1465
1489
  var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
1466
1490
  var EARNINGS_REFRESH_COOLDOWN_MS = 60 * 60 * 1e3;
1491
+ var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
1467
1492
  var ZyfaiAgent = class _ZyfaiAgent {
1468
1493
  id = "zyfai";
1469
1494
  supportedChainIds = [8453, 42161, 1];
@@ -1518,6 +1543,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1518
1543
  earningsRefreshes = /* @__PURE__ */ new Map();
1519
1544
  earningsSnapshot = null;
1520
1545
  earningsGeneration = 0;
1546
+ balanceSnapshots = /* @__PURE__ */ new WeakMap();
1521
1547
  constructor(apiKey, rpcUrls, referralSource) {
1522
1548
  this.rpcUrls = resolveRpcUrls(rpcUrls);
1523
1549
  this.sdk = new ZyfaiSDK({
@@ -1721,7 +1747,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1721
1747
  * chain/account switch fires the provider's `accountsChanged` →
1722
1748
  * `disconnectAccount` (nulls walletClient + authenticatedUserId) while our
1723
1749
  * own `connectAuth` guard still believes this chain is connected and
1724
- * early-returns. Without this, authenticated calls (depositFunds,
1750
+ * early-returns. Without this, authenticated calls (sendDeposit,
1725
1751
  * withdrawFunds) fail with "No account connected".
1726
1752
  */
1727
1753
  async ensureConnected(state, chainId) {
@@ -2343,7 +2369,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2343
2369
  return { txHash, smartWallet, amount, evidence: evidence2 };
2344
2370
  }
2345
2371
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
2346
- const raw2 = await this.sdk.depositFunds(
2372
+ const raw2 = await this.sdk.sendDeposit(
2347
2373
  this.getAddress(),
2348
2374
  validChainId,
2349
2375
  amount,
@@ -2359,7 +2385,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2359
2385
  intendedAmount: amount,
2360
2386
  txHash: raw2.txHash,
2361
2387
  transfer: "unknown",
2362
- registration: "unknown",
2388
+ registration: raw2.registration ? "confirmed" : "unknown",
2363
2389
  delivery: "not-applicable"
2364
2390
  };
2365
2391
  notifyFunding(onProgress, evidence);
@@ -2378,9 +2404,74 @@ var ZyfaiAgent = class _ZyfaiAgent {
2378
2404
  }
2379
2405
  }
2380
2406
  // --- IAgent: Fund operations ---
2407
+ /** Only after a definitive endpoint rejection, combine a new provider
2408
+ * snapshot with the Safe's exact idle balance. Never parse its rounded error
2409
+ * amount as proof of zero, or use this to skip sending a new MAX request. */
2410
+ async assertNotEmptyRejectedWithdrawal(state, chainId, token, owner) {
2411
+ const internals = this.sdk;
2412
+ const generation = this.earningsGeneration;
2413
+ let expired = false;
2414
+ const unavailable = () => new OwneyError(
2415
+ "WITHDRAW_BALANCE_UNAVAILABLE",
2416
+ "Could not verify Zyfai's balance after its withdrawal rejection.",
2417
+ { chainId, token },
2418
+ this.id
2419
+ );
2420
+ const assertScope = () => {
2421
+ if (expired) throw unavailable();
2422
+ const account = internals.walletClient?.account?.address;
2423
+ if (generation !== this.earningsGeneration || state.chainId !== chainId || state.walletAddress.toLowerCase() !== owner.toLowerCase() || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
2424
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
2425
+ };
2426
+ const read2 = async () => {
2427
+ assertScope();
2428
+ const [wallet, registered, portfolio] = await Promise.all([
2429
+ this.sdk.getSmartWalletAddress(owner, chainId),
2430
+ this.sdk.getSmartWalletByEOA(owner),
2431
+ this.sdk.getPortfolio(owner).catch(() => void 0)
2432
+ ]);
2433
+ assertScope();
2434
+ if (!isAddress(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
2435
+ throw new OwneyError(
2436
+ "AGENT_AUTH_FAILED",
2437
+ "Could not verify Zyfai's withdrawal wallet.",
2438
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2439
+ this.id
2440
+ );
2441
+ if (hasEmptyWithdrawalSnapshot(portfolio, chainId, token)) {
2442
+ const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
2443
+ const idle = asset ? await this.getPublicClient(chainId).readContract({
2444
+ address: asset.address,
2445
+ abi: erc20Abi2,
2446
+ functionName: "balanceOf",
2447
+ args: [wallet.address]
2448
+ }).catch(() => void 0) : void 0;
2449
+ assertScope();
2450
+ if (idle === 0n) throw new OwneyError(
2451
+ "WITHDRAW_INSUFFICIENT_BALANCE",
2452
+ "No funds remain in Zyfai for this asset.",
2453
+ { available: "0", chainId, token },
2454
+ this.id
2455
+ );
2456
+ }
2457
+ };
2458
+ let timer;
2459
+ try {
2460
+ await Promise.race([read2(), new Promise((_, reject) => {
2461
+ timer = setTimeout(() => {
2462
+ expired = true;
2463
+ reject(unavailable());
2464
+ }, WITHDRAW_BALANCE_TIMEOUT_MS);
2465
+ })]);
2466
+ } finally {
2467
+ expired = true;
2468
+ if (timer) clearTimeout(timer);
2469
+ }
2470
+ }
2381
2471
  /** Called only after the execution endpoint explicitly rejected a request.
2382
2472
  * A timeout or an accepted request never authorizes repair and resubmission. */
2383
2473
  async recoverWithdrawalSession(state, chainId, token, owner) {
2474
+ await this.assertNotEmptyRejectedWithdrawal(state, chainId, token, owner);
2384
2475
  const internals = this.sdk;
2385
2476
  const assertScope = () => {
2386
2477
  const account = internals.walletClient?.account?.address;
@@ -2388,35 +2479,6 @@ var ZyfaiAgent = class _ZyfaiAgent {
2388
2479
  throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
2389
2480
  };
2390
2481
  assertScope();
2391
- const [wallet, registered, portfolio] = await Promise.all([
2392
- this.sdk.getSmartWalletAddress(owner, chainId),
2393
- this.sdk.getSmartWalletByEOA(owner),
2394
- this.sdk.getPortfolio(owner).catch(() => void 0)
2395
- ]);
2396
- assertScope();
2397
- if (!isAddress(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
2398
- throw new OwneyError(
2399
- "AGENT_AUTH_FAILED",
2400
- "Could not verify Zyfai's withdrawal wallet.",
2401
- { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2402
- this.id
2403
- );
2404
- if (hasEmptyWithdrawalSnapshot(portfolio, chainId, token)) {
2405
- const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
2406
- const idle = asset ? await this.getPublicClient(chainId).readContract({
2407
- address: asset.address,
2408
- abi: erc20Abi2,
2409
- functionName: "balanceOf",
2410
- args: [wallet.address]
2411
- }).catch(() => void 0) : void 0;
2412
- assertScope();
2413
- if (idle === 0n) throw new OwneyError(
2414
- "WITHDRAW_INSUFFICIENT_BALANCE",
2415
- "No funds remain in Zyfai for this asset.",
2416
- { available: "0", chainId, token },
2417
- this.id
2418
- );
2419
- }
2420
2482
  const walletState = await this.readWalletState(owner);
2421
2483
  assertScope();
2422
2484
  const unavailable = () => new OwneyError(
@@ -2468,6 +2530,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
2468
2530
  raw2 = await this.sdk.withdrawFunds(owner, validChainId, amount, token);
2469
2531
  break;
2470
2532
  } catch (error) {
2533
+ if (!amount && observation.rejected() && observation.request()?.failure === "insufficient-balance") {
2534
+ try {
2535
+ await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner);
2536
+ } catch (proofError) {
2537
+ if (proofError instanceof OwneyError && (proofError.code === "WITHDRAW_INSUFFICIENT_BALANCE" || proofError.code === "NOT_CONNECTED")) throw proofError;
2538
+ }
2539
+ }
2471
2540
  if (!observation.rejected() || observation.request()?.failure !== "session-key-missing") throw error;
2472
2541
  if (attempt === 1)
2473
2542
  throw new OwneyError(
@@ -2561,12 +2630,100 @@ var ZyfaiAgent = class _ZyfaiAgent {
2561
2630
  async getFundingWallet(state, chainId) {
2562
2631
  return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
2563
2632
  }
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. */
2636
+ async getWithdrawableBalance(state, chainId, token) {
2637
+ const validChainId = isValidChainId(chainId);
2638
+ const asset = SupportedAssets.find((value) => value.chainId === validChainId && value.symbol === token);
2639
+ if (!asset) throw new OwneyError("WITHDRAW_NO_PERMITTED_TOKENS", "Asset selected is not supported", void 0, this.id);
2640
+ const owner = state.walletAddress;
2641
+ const generation = this.earningsGeneration;
2642
+ const internals = this.sdk;
2643
+ let expired = false;
2644
+ const unavailable = () => new OwneyError(
2645
+ "WITHDRAW_BALANCE_UNAVAILABLE",
2646
+ "Could not verify Zyfai's withdrawal balance. Its vault positions may still contain funds.",
2647
+ { reason: "ZYFAI_VAULT_BALANCE_UNVERIFIED", chainId, token },
2648
+ this.id
2649
+ );
2650
+ const assertScope = () => {
2651
+ if (expired) throw unavailable();
2652
+ const account = internals.walletClient?.account?.address;
2653
+ if (generation !== this.earningsGeneration || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== validChainId || state.walletAddress.toLowerCase() !== owner.toLowerCase() || state.chainId !== validChainId || account && account.toLowerCase() !== owner.toLowerCase())
2654
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while checking Zyfai's withdrawal balance.", void 0, this.id);
2655
+ };
2656
+ let timer;
2657
+ const read2 = async () => {
2658
+ assertScope();
2659
+ const [wallet, registered] = await Promise.all([
2660
+ this.sdk.getSmartWalletAddress(owner, validChainId),
2661
+ this.sdk.getSmartWalletByEOA(owner)
2662
+ ]);
2663
+ assertScope();
2664
+ if (!isAddress(wallet.address) || wallet.isDeployed !== true || wallet.isOwner !== true || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase()) throw unavailable();
2665
+ const client = this.getPublicClient(validChainId);
2666
+ const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
2667
+ assertScope();
2668
+ const idle = await client.readContract({
2669
+ address: asset.address,
2670
+ abi: erc20Abi2,
2671
+ functionName: "balanceOf",
2672
+ args: [wallet.address],
2673
+ blockNumber
2674
+ });
2675
+ assertScope();
2676
+ if (typeof idle !== "bigint" || idle <= 0n) throw unavailable();
2677
+ return idle;
2678
+ };
2679
+ try {
2680
+ return await Promise.race([read2(), new Promise((_, reject) => {
2681
+ timer = setTimeout(() => {
2682
+ expired = true;
2683
+ reject(unavailable());
2684
+ }, WITHDRAW_BALANCE_TIMEOUT_MS);
2685
+ })]);
2686
+ } catch (error) {
2687
+ if (error instanceof OwneyError) throw error;
2688
+ throw unavailable();
2689
+ } finally {
2690
+ expired = true;
2691
+ if (timer) clearTimeout(timer);
2692
+ }
2693
+ }
2564
2694
  async getBalances(state, chainId) {
2565
2695
  const requestedAt = Date.now();
2696
+ const generation = this.earningsGeneration;
2566
2697
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
2567
2698
  const raw2 = await this.sdk.getPortfolio(this.getAddress());
2568
2699
  const balance = mapBalances(raw2, validChainId, smartWallet);
2569
- return { ...balance, readMetadata: balance.readMetadata?.map((read2) => ({ ...read2, requestedAt })) };
2700
+ const reads = balance.readMetadata?.map((read2) => ({ ...read2, requestedAt })) ?? [];
2701
+ for (const asset of SupportedAssets.filter((item) => item.chainId === chainId)) {
2702
+ if (!reads.some((read2) => read2.chainId === chainId && read2.asset === asset.symbol) && hasEmptyWithdrawalSnapshot(raw2, chainId, asset.symbol)) {
2703
+ reads.push({ chainId, asset: asset.symbol, requestedAt, fetchedAt: Date.now(), positionsStatus: "available" });
2704
+ }
2705
+ }
2706
+ const result = { ...balance, readMetadata: reads };
2707
+ for (const read2 of reads) this.balanceSnapshots.set(read2, { raw: raw2, generation, owner: state.walletAddress.toLowerCase() });
2708
+ return result;
2709
+ }
2710
+ async verifyWithdrawalBalance(state, evidence, balance) {
2711
+ const read2 = balance.readMetadata?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
2712
+ const snapshot = read2 && this.balanceSnapshots.get(read2);
2713
+ const asset = SupportedAssets.find((item) => item.chainId === evidence.chainId && item.symbol === evidence.token);
2714
+ if (!snapshot || snapshot.generation !== this.earningsGeneration || snapshot.owner !== state.walletAddress.toLowerCase() || !read2 || !asset || !evidence.txHash || !evidence.blockNumber || !balance.smartWallet || balance.smartWallet.toLowerCase() !== evidence.agentWallet?.toLowerCase() || !hasEmptyWithdrawalSnapshot(snapshot.raw, evidence.chainId, evidence.token)) return void 0;
2715
+ const client = this.getPublicClient(evidence.chainId);
2716
+ const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
2717
+ if (blockNumber < BigInt(evidence.blockNumber)) return void 0;
2718
+ const idle = await client.readContract({
2719
+ address: asset.address,
2720
+ abi: erc20Abi2,
2721
+ functionName: "balanceOf",
2722
+ args: [balance.smartWallet],
2723
+ blockNumber
2724
+ });
2725
+ if (idle !== 0n || snapshot.generation !== this.earningsGeneration || this.connectedAddress?.toLowerCase() !== snapshot.owner || this.connectedChainId !== evidence.chainId) return void 0;
2726
+ return { ...read2, withdrawalCheck: { txHash: evidence.txHash, blockNumber: blockNumber.toString() } };
2570
2727
  }
2571
2728
  earningsKey(state, chainId, smartWallet) {
2572
2729
  return JSON.stringify([
@@ -2772,10 +2929,11 @@ import {
2772
2929
  decodeEventLog as decodeEventLog2,
2773
2930
  encodeEventTopics,
2774
2931
  encodeFunctionData,
2775
- erc20Abi as erc20Abi3,
2776
- erc4626Abi,
2777
- getAddress as getAddress4,
2778
- isAddress as isAddress3
2932
+ erc20Abi as erc20Abi4,
2933
+ erc4626Abi as erc4626Abi2,
2934
+ getAddress as getAddress5,
2935
+ isAddress as isAddress3,
2936
+ parseUnits
2779
2937
  } from "viem";
2780
2938
  import { base as base4 } from "viem/chains";
2781
2939
 
@@ -3565,6 +3723,7 @@ function mapYieldseekerBalances(contexts) {
3565
3723
  chainId: context.agent.chainId,
3566
3724
  asset: context.asset,
3567
3725
  fetchedAt: context.fetchedAt,
3726
+ requestedAt: context.requestedAt,
3568
3727
  readVersion: context.readVersion,
3569
3728
  providerUpdatedAt: context.snapshot.updatedDate,
3570
3729
  snapshotId: String(context.snapshot.agentWalletSnapshotId),
@@ -3942,9 +4101,55 @@ function mapYieldseekerAgentApy(options, days) {
3942
4101
  };
3943
4102
  }
3944
4103
 
4104
+ // src/lib/remaining-vault-balance.ts
4105
+ import { erc20Abi as erc20Abi3, erc4626Abi, getAddress as getAddress2 } from "viem";
4106
+ async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber) {
4107
+ const addresses = [...new Set(vaults.map((value) => value.toLowerCase()))];
4108
+ if (addresses.length > 64) return void 0;
4109
+ const batches = [];
4110
+ for (let index = 0; index < addresses.length; index += 10) batches.push(addresses.slice(index, index + 10));
4111
+ const values = await Promise.all(batches.map(async (batch) => {
4112
+ const balances = await client.multicall({
4113
+ blockNumber,
4114
+ allowFailure: true,
4115
+ contracts: batch.map((address) => ({
4116
+ address: getAddress2(address),
4117
+ abi: erc20Abi3,
4118
+ functionName: "balanceOf",
4119
+ args: [wallet]
4120
+ }))
4121
+ });
4122
+ if (balances.length !== batch.length) return void 0;
4123
+ const funded = [];
4124
+ for (let index = 0; index < batch.length; index++) {
4125
+ const result = balances[index];
4126
+ if (result?.status !== "success" || typeof result.result !== "bigint" || result.result < 0n) return void 0;
4127
+ if (result.result > 0n) funded.push({ address: getAddress2(batch[index]), shares: result.result });
4128
+ }
4129
+ if (!funded.length) return 0n;
4130
+ const valuations = await client.multicall({
4131
+ blockNumber,
4132
+ allowFailure: true,
4133
+ contracts: funded.flatMap(({ address, shares }) => [
4134
+ { address, abi: erc4626Abi, functionName: "asset" },
4135
+ { address, abi: erc4626Abi, functionName: "convertToAssets", args: [shares] }
4136
+ ])
4137
+ });
4138
+ if (valuations.length !== funded.length * 2) return void 0;
4139
+ let total = 0n;
4140
+ for (let index = 0; index < funded.length; index++) {
4141
+ const underlying = valuations[index * 2], value = valuations[index * 2 + 1];
4142
+ if (underlying?.status !== "success" || typeof underlying.result !== "string" || underlying.result.toLowerCase() !== asset.toLowerCase() || value?.status !== "success" || typeof value.result !== "bigint" || value.result < 0n) return void 0;
4143
+ total += value.result;
4144
+ }
4145
+ return total;
4146
+ }));
4147
+ return values.some((value) => value === void 0) ? void 0 : values.reduce((sum, value) => sum + value, 0n);
4148
+ }
4149
+
3945
4150
  // src/agents/yieldseeker/yieldseeker.wallet-version.ts
3946
4151
  import {
3947
- getAddress as getAddress2,
4152
+ getAddress as getAddress3,
3948
4153
  isAddressEqual,
3949
4154
  parseAbi as parseAbi3
3950
4155
  } from "viem";
@@ -3968,9 +4173,9 @@ function yieldseekerOwnerPath(code) {
3968
4173
  return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
3969
4174
  }
3970
4175
  async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
3971
- const agentWallet = getAddress2(wallet);
3972
- const expectedOwner = getAddress2(owner);
3973
- const expectedAsset = getAddress2(asset);
4176
+ const agentWallet = getAddress3(wallet);
4177
+ const expectedOwner = getAddress3(owner);
4178
+ const expectedAsset = getAddress3(asset);
3974
4179
  const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
3975
4180
  client.getBytecode({ address: agentWallet, blockNumber }),
3976
4181
  client.getBytecode({ address: expectedOwner, blockNumber }),
@@ -3984,7 +4189,7 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNu
3984
4189
  if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
3985
4190
  throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
3986
4191
  }
3987
- const implementation = getAddress2(`0x${slot.slice(-40)}`);
4192
+ const implementation = getAddress3(`0x${slot.slice(-40)}`);
3988
4193
  const mapped = await client.readContract({
3989
4194
  address: YIELDSEEKER_FACTORY,
3990
4195
  abi: factoryAbi,
@@ -4011,7 +4216,7 @@ var YIELDSEEKER_UPGRADE_ABI = walletAbi;
4011
4216
  import {
4012
4217
  createWalletClient as createWalletClient2,
4013
4218
  custom as custom2,
4014
- getAddress as getAddress3,
4219
+ getAddress as getAddress4,
4015
4220
  toHex
4016
4221
  } from "viem";
4017
4222
  import { getUserOperationHash as getUserOperationHash2 } from "viem/account-abstraction";
@@ -4520,7 +4725,7 @@ async function submitSmart(input) {
4520
4725
  );
4521
4726
  }
4522
4727
  async function submitYieldseekerSponsoredWithdrawal(input) {
4523
- const owner = getAddress3(input.state.walletAddress);
4728
+ const owner = getAddress4(input.state.walletAddress);
4524
4729
  const { intent, rpcUrl } = await requestIntent({
4525
4730
  apiKey: input.apiKey,
4526
4731
  routingApiBaseUrl: input.routingApiBaseUrl,
@@ -4538,7 +4743,7 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
4538
4743
  return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
4539
4744
  }
4540
4745
  async function submitYieldseekerSponsoredUpgrade(input) {
4541
- const owner = getAddress3(input.state.walletAddress);
4746
+ const owner = getAddress4(input.state.walletAddress);
4542
4747
  const { intent, rpcUrl } = await requestIntent({
4543
4748
  apiKey: input.apiKey,
4544
4749
  routingApiBaseUrl: input.routingApiBaseUrl,
@@ -4573,6 +4778,8 @@ var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
4573
4778
  var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
4574
4779
  var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
4575
4780
  var YIELDSEEKER_MAX_POSITION_VERIFICATION_MS = 2e3;
4781
+ var YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT = 64;
4782
+ var YIELDSEEKER_VAULTS_PER_RPC_BATCH = 10;
4576
4783
  function generateYieldseekerUsername() {
4577
4784
  const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
4578
4785
  return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
@@ -4653,6 +4860,8 @@ var YieldseekerAgent = class {
4653
4860
  transactionExecutor;
4654
4861
  unwindReceiptWaiter;
4655
4862
  agentContexts = /* @__PURE__ */ new Map();
4863
+ balanceContexts = /* @__PURE__ */ new WeakMap();
4864
+ balanceVaults = /* @__PURE__ */ new Map();
4656
4865
  users = /* @__PURE__ */ new Map();
4657
4866
  pendingAgents = /* @__PURE__ */ new Map();
4658
4867
  pendingWalletContexts = /* @__PURE__ */ new Map();
@@ -4698,8 +4907,8 @@ var YieldseekerAgent = class {
4698
4907
  if (useCachedV2 && cached && Date.now() - cached.at < 15 * 6e4) return { ...cached.status };
4699
4908
  const status = await readYieldseekerWalletStatus(
4700
4909
  this.getReceiptClient(),
4701
- getAddress4(context.wallet.walletAddress),
4702
- getAddress4(state.walletAddress),
4910
+ getAddress5(context.wallet.walletAddress),
4911
+ getAddress5(state.walletAddress),
4703
4912
  YIELDSEEKER_ASSET_METADATA[asset].address
4704
4913
  );
4705
4914
  this.rememberWalletStatus(key2, status);
@@ -4895,7 +5104,7 @@ var YieldseekerAgent = class {
4895
5104
  }
4896
5105
  } else {
4897
5106
  txHash = await this.submitTransaction(state, chainId, {
4898
- from: getAddress4(state.walletAddress),
5107
+ from: getAddress5(state.walletAddress),
4899
5108
  to: before.agentWallet,
4900
5109
  data,
4901
5110
  value: "0",
@@ -4918,6 +5127,7 @@ var YieldseekerAgent = class {
4918
5127
  }
4919
5128
  this.users.clear();
4920
5129
  this.agentContexts.clear();
5130
+ this.balanceVaults.clear();
4921
5131
  this.pendingAgents.clear();
4922
5132
  this.pendingWalletContexts.clear();
4923
5133
  this.readCache.clear();
@@ -4981,12 +5191,12 @@ var YieldseekerAgent = class {
4981
5191
  } else {
4982
5192
  try {
4983
5193
  txHash = await this.submitTransaction(state, chainId, {
4984
- from: getAddress4(state.walletAddress),
5194
+ from: getAddress5(state.walletAddress),
4985
5195
  to: YIELDSEEKER_ASSET_METADATA[asset].address,
4986
5196
  data: encodeFunctionData({
4987
- abi: erc20Abi3,
5197
+ abi: erc20Abi4,
4988
5198
  functionName: "transfer",
4989
- args: [getAddress4(context.wallet.walletAddress), BigInt(amount)]
5199
+ args: [getAddress5(context.wallet.walletAddress), BigInt(amount)]
4990
5200
  }),
4991
5201
  value: "0",
4992
5202
  chainId
@@ -5096,80 +5306,98 @@ var YieldseekerAgent = class {
5096
5306
  let settlement = null;
5097
5307
  try {
5098
5308
  const maxSettlementBasis = amount === void 0 ? this.snapshotMovement(state, chainId, context, 0n) : null;
5099
- this.advancePortfolioVersion(this.contextKey(state, chainId, asset));
5100
- const portfolioRead = amount === void 0 ? this.loadActivityContext(state, chainId, context, {}).then((value) => ({ positions: value.positions, feeRateBps: 0 })) : this.loadPortfolioContext(state, chainId, context).then((value) => ({ positions: value.positions, feeRateBps: value.snapshot.feeRateBps }));
5309
+ const contextKey = this.contextKey(state, chainId, asset);
5310
+ const priorPositions = this.withdrawalVaultHints(state, chainId, context);
5311
+ this.advancePortfolioVersion(contextKey);
5312
+ const boundedVerification = async (read2, timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
5313
+ let timer;
5314
+ try {
5315
+ return await Promise.race([read2, new Promise((resolve) => {
5316
+ timer = setTimeout(() => resolve(void 0), Math.max(0, timeoutMs));
5317
+ })]);
5318
+ } finally {
5319
+ if (timer) clearTimeout(timer);
5320
+ }
5321
+ };
5322
+ const feeRateRead = amount === void 0 ? Promise.resolve(0) : boundedVerification(
5323
+ (() => {
5324
+ try {
5325
+ return this.portfolioSnapshot(state, chainId, context, contextKey).read.then((value) => value.agentSnapshot.feeRateBps);
5326
+ } catch (error) {
5327
+ return Promise.reject(error);
5328
+ }
5329
+ })().catch(() => void 0)
5330
+ );
5331
+ const positionsRead = this.readWithdrawalPositions(state, chainId, context);
5101
5332
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
5102
- const agentWallet = getAddress4(context.wallet.walletAddress);
5103
- const feeTrackerRead = withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5104
- address: agentWallet,
5105
- abi: YIELDSEEKER_AGENT_WALLET_ABI,
5106
- functionName: "feeTracker"
5107
- }), chainId, "eth_call", this.id);
5108
- const readOwnerFunds = async () => {
5109
- const feeTracker = await feeTrackerRead;
5333
+ const agentWallet = getAddress5(context.wallet.walletAddress);
5334
+ const readOwnerFunds = async (blockNumber) => {
5335
+ const feeTracker = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5336
+ address: agentWallet,
5337
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
5338
+ functionName: "feeTracker",
5339
+ blockNumber
5340
+ }), chainId, "eth_call", this.id);
5110
5341
  this.assertSession(generation, chainId, asset);
5111
5342
  if (!isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker))
5112
5343
  throw this.invalidResponse("wallet fee tracker");
5113
5344
  const [idle2, fees] = await Promise.all([
5114
5345
  withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5115
5346
  address: metadata.address,
5116
- abi: erc20Abi3,
5347
+ abi: erc20Abi4,
5117
5348
  functionName: "balanceOf",
5118
- args: [agentWallet]
5349
+ args: [agentWallet],
5350
+ blockNumber
5119
5351
  }), chainId, "eth_call", this.id),
5120
5352
  withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5121
- address: getAddress4(feeTracker),
5353
+ address: getAddress5(feeTracker),
5122
5354
  abi: YIELDSEEKER_FEE_TRACKER_ABI,
5123
5355
  functionName: "getFeesOwed",
5124
- args: [agentWallet]
5356
+ args: [agentWallet],
5357
+ blockNumber
5125
5358
  }), chainId, "eth_call", this.id)
5126
5359
  ]);
5127
5360
  this.assertSession(generation, chainId, asset);
5128
5361
  if (typeof idle2 !== "bigint" || idle2 < 0n || typeof fees !== "bigint" || fees < 0n)
5129
5362
  throw this.invalidResponse("wallet withdrawal balance");
5130
- return { idle: idle2, fees, available: idle2 > fees ? idle2 - fees : 0n };
5363
+ return { blockNumber, idle: idle2, fees, available: idle2 > fees ? idle2 - fees : 0n };
5131
5364
  };
5132
- const [portfolio, initialFunds] = await Promise.all([portfolioRead, readOwnerFunds()]);
5365
+ const [funds, feeRateBps] = await Promise.all([
5366
+ positionsRead.then((positions2) => this.resolveWithdrawalLiquidity(
5367
+ state,
5368
+ chainId,
5369
+ context,
5370
+ positions2,
5371
+ priorPositions,
5372
+ amount === void 0 ? void 0 : BigInt(amount)
5373
+ )),
5374
+ feeRateRead
5375
+ ]);
5376
+ const initialFunds = funds;
5133
5377
  const idle = initialFunds.idle;
5134
- let positions = portfolio.positions;
5378
+ const maxLiquidity = amount === void 0 ? funds : void 0;
5379
+ let positions = [...funds.positions, ...funds.indexedPositions.filter((position2) => !funds.verifiedVaults.includes(position2.yieldOption.address.toLowerCase()))];
5135
5380
  let positionsStatus = "empty";
5136
- const validatePositions = (values) => {
5137
- const seen = /* @__PURE__ */ new Set();
5138
- for (const value of values) {
5139
- const address = value?.yieldOption?.address;
5140
- if (typeof address !== "string" || !isAddress3(address) || seen.has(address.toLowerCase()) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
5141
- throw this.invalidResponse("yield positions");
5142
- seen.add(address.toLowerCase());
5143
- }
5144
- return values;
5145
- };
5381
+ const validatePositions = (values) => this.validateWithdrawalPositions(values, chainId, asset);
5382
+ validatePositions(positions);
5146
5383
  const zeroValuePositions = /* @__PURE__ */ new WeakSet();
5147
5384
  const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
5148
5385
  const knownPositions = /* @__PURE__ */ new Map();
5149
5386
  const rememberPositions = (values) => {
5150
5387
  for (const position2 of values) knownPositions.set(position2.yieldOption.address.toLowerCase(), position2);
5151
5388
  };
5152
- const boundedVerification = async (read2, timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
5153
- let timer;
5154
- try {
5155
- return await Promise.race([read2, new Promise((resolve) => {
5156
- timer = setTimeout(() => resolve(void 0), Math.max(0, timeoutMs));
5157
- })]);
5158
- } finally {
5159
- if (timer) clearTimeout(timer);
5160
- }
5161
- };
5162
5389
  const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
5163
5390
  const verifiedPositions = [];
5164
5391
  if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
5165
- const reads = values.map(async (position2) => {
5166
- const address = getAddress4(position2.yieldOption.address);
5392
+ let active = true;
5393
+ const checks = this.readVaultLiquidity(values.map((position2) => position2.yieldOption), agentWallet, blockNumber).catch(() => []);
5394
+ const reads = values.map(async (position2, index) => {
5395
+ const address = getAddress5(position2.yieldOption.address);
5167
5396
  try {
5168
- const [underlying, shares, withdrawable] = await Promise.all([
5169
- this.getReceiptClient().readContract({ address, abi: erc4626Abi, functionName: "asset", blockNumber }),
5170
- this.getReceiptClient().readContract({ address, abi: erc20Abi3, functionName: "balanceOf", args: [agentWallet], blockNumber }),
5171
- this.getReceiptClient().readContract({ address, abi: erc4626Abi, functionName: "maxWithdraw", args: [agentWallet], blockNumber })
5172
- ]);
5397
+ const group = (await checks).slice(index * 3, index * 3 + 3);
5398
+ if (!active || group.length !== 3 || group.some((value) => value.status !== "success")) return null;
5399
+ this.assertSession(generation, chainId, asset);
5400
+ const [underlying, shares, withdrawable] = group.map((value) => value.result);
5173
5401
  if (typeof underlying !== "string" || !isAddress3(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
5174
5402
  const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: withdrawable.toString() };
5175
5403
  if (shares > 0n && withdrawable === 0n) {
@@ -5178,7 +5406,7 @@ var YieldseekerAgent = class {
5178
5406
  try {
5179
5407
  const value = await this.getReceiptClient().readContract({
5180
5408
  address,
5181
- abi: erc4626Abi,
5409
+ abi: erc4626Abi2,
5182
5410
  functionName,
5183
5411
  args: [shares],
5184
5412
  blockNumber
@@ -5204,6 +5432,7 @@ var YieldseekerAgent = class {
5204
5432
  }
5205
5433
  });
5206
5434
  const result = await boundedVerification(Promise.all(reads), timeoutMs);
5435
+ active = false;
5207
5436
  this.assertSession(generation, chainId, asset);
5208
5437
  return {
5209
5438
  verified: result !== void 0 && result.every((position2) => position2 !== null),
@@ -5222,7 +5451,7 @@ var YieldseekerAgent = class {
5222
5451
  return validatePositions(response.yieldPositions);
5223
5452
  };
5224
5453
  if (amount === void 0) {
5225
- validatePositions(positions);
5454
+ rememberPositions(funds.indexedPositions);
5226
5455
  rememberPositions(positions);
5227
5456
  positionsStatus = hasPositions(positions) ? "remaining" : "empty";
5228
5457
  }
@@ -5238,8 +5467,8 @@ var YieldseekerAgent = class {
5238
5467
  const zeroPayoutCandidates = positions.filter((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
5239
5468
  if (amount === void 0 && zeroPayoutCandidates.length) {
5240
5469
  const deadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
5241
- const blockNumber = await boundedVerification(
5242
- Promise.resolve().then(() => this.getReceiptClient().getBlockNumber()).catch(() => void 0)
5470
+ const blockNumber = initialFunds.blockNumber ?? await boundedVerification(
5471
+ Promise.resolve().then(() => this.getReceiptClient().getBlockNumber({ cacheTime: 0 })).catch(() => void 0)
5243
5472
  );
5244
5473
  const proof = typeof blockNumber === "bigint" ? await verifyKnownPositions(blockNumber, zeroPayoutCandidates, deadline - Date.now()) : { verified: false, positions: [] };
5245
5474
  positions = proof.positions;
@@ -5363,8 +5592,9 @@ var YieldseekerAgent = class {
5363
5592
  remaining: remaining.toString()
5364
5593
  });
5365
5594
  }
5366
- const availableForOwner = confirmedMovement ? (await readOwnerFunds()).available : initialFunds.available;
5367
- const feeRate = portfolio.feeRateBps;
5595
+ const ownerFunds = confirmedMovement ? await readOwnerFunds(latestUnwindBlock) : initialFunds;
5596
+ const availableForOwner = ownerFunds.available;
5597
+ const feeRate = feeRateBps ?? 0;
5368
5598
  const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
5369
5599
  const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
5370
5600
  if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
@@ -5389,7 +5619,7 @@ var YieldseekerAgent = class {
5389
5619
  delta: maxSettlementBasis.delta - payout,
5390
5620
  expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
5391
5621
  } : this.snapshotMovement(state, chainId, context, -payout);
5392
- const account = getAddress4(state.walletAddress);
5622
+ const account = getAddress5(state.walletAddress);
5393
5623
  let receipt;
5394
5624
  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" };
5395
5625
  const data = amount === void 0 ? encodeFunctionData({
@@ -5412,7 +5642,7 @@ var YieldseekerAgent = class {
5412
5642
  refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
5413
5643
  userId: context.user.userId,
5414
5644
  agentId: context.agent.agentId,
5415
- agentWallet: getAddress4(context.wallet.walletAddress),
5645
+ agentWallet: getAddress5(context.wallet.walletAddress),
5416
5646
  asset: metadata.address,
5417
5647
  callData: data,
5418
5648
  onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending }),
@@ -5427,7 +5657,7 @@ var YieldseekerAgent = class {
5427
5657
  onOwnerTransferAttempted?.();
5428
5658
  txHash = await this.submitTransaction(state, chainId, {
5429
5659
  from: account,
5430
- to: getAddress4(context.wallet.walletAddress),
5660
+ to: getAddress5(context.wallet.walletAddress),
5431
5661
  data,
5432
5662
  value: "0",
5433
5663
  chainId
@@ -5439,7 +5669,7 @@ var YieldseekerAgent = class {
5439
5669
  settlement = plannedMovement;
5440
5670
  const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
5441
5671
  notifyFunding(onProgress, evidence);
5442
- let completion;
5672
+ let completion = maxLiquidity && !maxLiquidity.complete ? "unverified" : void 0;
5443
5673
  if (amount === void 0) {
5444
5674
  const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
5445
5675
  const knownAtPayout = new Set(knownPositions.keys());
@@ -5486,10 +5716,14 @@ var YieldseekerAgent = class {
5486
5716
  { asset, available: "0", txHash },
5487
5717
  this.id
5488
5718
  );
5719
+ const confirmedAmount = evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) ? BigInt(evidence.confirmedAmount) : void 0;
5720
+ const feeIncrease = ownerFunds.fees - initialFunds.fees;
5721
+ const feeAdjustment = amount !== void 0 && receipt && initialFunds.blockNumber !== void 0 && latestUnwindBlock !== void 0 && initialFunds.blockNumber <= latestUnwindBlock && receipt.blockNumber >= latestUnwindBlock && evidence.transfer === "confirmed" && evidence.delivery === "confirmed" && confirmedAmount === payout && feeIncrease > 0n && feeIncrease <= maxFee && requested - payout === feeIncrease ? { grossAmount: requested.toString(), feeAmount: feeIncrease.toString() } : void 0;
5489
5722
  return {
5490
5723
  txHash,
5491
5724
  type: amount === void 0 && !completion ? "full" : "partial",
5492
5725
  ...completion ? { completion } : {},
5726
+ ...feeAdjustment ? { feeAdjustment } : {},
5493
5727
  amount: evidence.confirmedAmount ?? payout.toString(),
5494
5728
  evidence
5495
5729
  };
@@ -5505,10 +5739,298 @@ var YieldseekerAgent = class {
5505
5739
  }
5506
5740
  }
5507
5741
  }
5742
+ /** A withdrawal-only liquidity floor; display snapshots never authorize it. */
5743
+ async getWithdrawableBalance(state, chainId, asset, minimumAmount) {
5744
+ this.assertChain(chainId);
5745
+ this.assertAsset(asset);
5746
+ const generation = this.readGeneration;
5747
+ const context = await this.findAgent(state, chainId, asset);
5748
+ this.assertSession(generation, chainId, asset);
5749
+ if (!context) throw new OwneyError(
5750
+ "WITHDRAW_BALANCE_UNAVAILABLE",
5751
+ "The Yieldseeker withdrawal wallet could not be verified.",
5752
+ { asset },
5753
+ this.id
5754
+ );
5755
+ const hints = this.withdrawalVaultHints(state, chainId, context);
5756
+ const positions = await this.readWithdrawalPositions(state, chainId, context);
5757
+ const proof = await this.resolveWithdrawalLiquidity(state, chainId, context, positions, hints, minimumAmount);
5758
+ this.assertSession(generation, chainId, asset);
5759
+ if (proof.available === 0n && !proof.complete) throw new OwneyError(
5760
+ "WITHDRAW_BALANCE_UNAVAILABLE",
5761
+ "Yieldseeker withdrawal liquidity could not be verified yet.",
5762
+ { asset },
5763
+ this.id
5764
+ );
5765
+ return proof.available;
5766
+ }
5767
+ async readVaultLiquidity(options, wallet, blockNumber) {
5768
+ const owner = getAddress5(wallet);
5769
+ const batches = Array.from({ length: Math.ceil(options.length / YIELDSEEKER_VAULTS_PER_RPC_BATCH) }, (_, index) => options.slice(index * YIELDSEEKER_VAULTS_PER_RPC_BATCH, (index + 1) * YIELDSEEKER_VAULTS_PER_RPC_BATCH));
5770
+ const results = await Promise.all(batches.map((batch) => this.getReceiptClient().multicall({
5771
+ blockNumber,
5772
+ allowFailure: true,
5773
+ contracts: batch.flatMap((option) => {
5774
+ const address = getAddress5(option.address);
5775
+ return [
5776
+ { address, abi: erc4626Abi2, functionName: "asset" },
5777
+ { address, abi: erc20Abi4, functionName: "balanceOf", args: [owner] },
5778
+ { address, abi: erc4626Abi2, functionName: "maxWithdraw", args: [owner] }
5779
+ ];
5780
+ })
5781
+ })));
5782
+ return results.flat();
5783
+ }
5784
+ validateWithdrawalPositions(values, chainId, asset) {
5785
+ const underlying = YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase();
5786
+ const seen = /* @__PURE__ */ new Set();
5787
+ for (const value of values) {
5788
+ const option = value?.yieldOption;
5789
+ 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))
5790
+ throw this.invalidResponse("yield positions");
5791
+ seen.add(option.address.toLowerCase());
5792
+ }
5793
+ return values;
5794
+ }
5795
+ withdrawalVaultHints(state, chainId, context) {
5796
+ const key2 = this.contextKey(state, chainId, context.asset);
5797
+ const positions = this.readCache.get(`positions:${key2}`)?.value?.yieldPositions ?? [];
5798
+ const hints = new Map(this.validateWithdrawalPositions(positions, chainId, context.asset).map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
5799
+ const historic = this.readCache.get(`historic:${key2}`)?.value?.position;
5800
+ 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()));
5801
+ for (const option of this.yieldOptions.get(context.asset)?.value ?? []) {
5802
+ 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;
5803
+ hints.set(option.address.toLowerCase(), {
5804
+ yieldOption: option,
5805
+ assetsRaw: "0",
5806
+ assetsBase: "0",
5807
+ assetsUsd: 0,
5808
+ withdrawableAssetsRaw: "0",
5809
+ withdrawableAssetsBase: "0"
5810
+ });
5811
+ }
5812
+ return [...hints.values()];
5813
+ }
5814
+ async readWithdrawalPositions(state, chainId, context) {
5815
+ const generation = this.readGeneration;
5816
+ let timer;
5817
+ try {
5818
+ const positions = await Promise.race([
5819
+ this.loadActivityContext(state, chainId, context, {}).then((value) => value.positions).catch((error) => {
5820
+ if (!(error instanceof OwneyError) || !["AGENT_API_ERROR", "AGENT_TIMEOUT"].includes(error.code)) throw error;
5821
+ return void 0;
5822
+ }),
5823
+ new Promise((resolve) => {
5824
+ timer = setTimeout(() => resolve(void 0), YIELDSEEKER_MAX_POSITION_VERIFICATION_MS);
5825
+ })
5826
+ ]);
5827
+ this.assertSession(generation, chainId, context.asset);
5828
+ return positions === void 0 ? void 0 : this.validateWithdrawalPositions(positions, chainId, context.asset);
5829
+ } finally {
5830
+ if (timer) clearTimeout(timer);
5831
+ }
5832
+ }
5833
+ async resolveWithdrawalLiquidity(state, chainId, context, positions, previousPositions = [], requested) {
5834
+ const generation = this.readGeneration;
5835
+ const contextKey = this.contextKey(state, chainId, context.asset);
5836
+ const version = this.portfolioVersion(contextKey);
5837
+ const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
5838
+ const unavailable = () => new OwneyError(
5839
+ "WITHDRAW_BALANCE_UNAVAILABLE",
5840
+ "Yieldseeker withdrawal liquidity could not be verified yet.",
5841
+ { asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
5842
+ this.id
5843
+ );
5844
+ let active = true;
5845
+ let knownProof;
5846
+ const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.verifiedVaults.includes(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
5847
+ const assertReadCurrent = () => {
5848
+ this.assertSession(generation, chainId, context.asset);
5849
+ if (!active || this.portfolioVersion(contextKey) !== version) throw unavailable();
5850
+ };
5851
+ const read2 = async () => {
5852
+ const client = this.getReceiptClient();
5853
+ const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
5854
+ const wallet = getAddress5(context.wallet.walletAddress);
5855
+ assertReadCurrent();
5856
+ if (typeof blockNumber !== "bigint") throw unavailable();
5857
+ const indexedPositions = this.validateWithdrawalPositions(positions ?? [], chainId, context.asset);
5858
+ const candidates = /* @__PURE__ */ new Map();
5859
+ for (const position2 of this.validateWithdrawalPositions([...previousPositions], chainId, context.asset))
5860
+ candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
5861
+ for (const position2 of indexedPositions) candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
5862
+ const options = [...candidates.values()];
5863
+ if (options.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
5864
+ const [checks, idle, feeTracker] = await Promise.all([
5865
+ this.readVaultLiquidity(options, wallet, blockNumber),
5866
+ client.readContract({
5867
+ address: metadata.address,
5868
+ abi: erc20Abi4,
5869
+ functionName: "balanceOf",
5870
+ args: [wallet],
5871
+ blockNumber
5872
+ }),
5873
+ client.readContract({
5874
+ address: wallet,
5875
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
5876
+ functionName: "feeTracker",
5877
+ blockNumber
5878
+ })
5879
+ ]);
5880
+ if (typeof idle !== "bigint" || idle < 0n || !isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker)) throw unavailable();
5881
+ assertReadCurrent();
5882
+ const fees = await client.readContract({
5883
+ address: getAddress5(feeTracker),
5884
+ abi: YIELDSEEKER_FEE_TRACKER_ABI,
5885
+ functionName: "getFeesOwed",
5886
+ args: [wallet],
5887
+ blockNumber
5888
+ });
5889
+ if (typeof fees !== "bigint" || fees < 0n) throw unavailable();
5890
+ const verified = [];
5891
+ const verifiedVaults = [];
5892
+ let deployed = 0n;
5893
+ const collect = (options2, results) => {
5894
+ assertReadCurrent();
5895
+ const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
5896
+ this.balanceVaults.set(contextKey, known);
5897
+ let valid2 = true;
5898
+ options2.forEach((option, index) => {
5899
+ const group = results.slice(index * 3, index * 3 + 3);
5900
+ const shareProbe = group[1];
5901
+ if (shareProbe?.status === "success" && typeof shareProbe.result === "bigint" && shareProbe.result > 0n)
5902
+ known.add(option.address.toLowerCase());
5903
+ if (group.length !== 3 || group.some((value) => value.status !== "success")) {
5904
+ valid2 = false;
5905
+ return;
5906
+ }
5907
+ const [underlying, shares, withdrawable] = group.map((value) => value.result);
5908
+ 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) {
5909
+ valid2 = false;
5910
+ return;
5911
+ }
5912
+ verifiedVaults.push(option.address.toLowerCase());
5913
+ deployed += withdrawable;
5914
+ verified.push({
5915
+ yieldOption: option,
5916
+ assetsRaw: shares.toString(),
5917
+ assetsBase: "0",
5918
+ assetsUsd: 0,
5919
+ withdrawableAssetsRaw: withdrawable.toString(),
5920
+ withdrawableAssetsBase: withdrawable.toString()
5921
+ });
5922
+ });
5923
+ return valid2;
5924
+ };
5925
+ const allKnownVerified = collect(options, checks);
5926
+ const proof = (complete) => ({
5927
+ blockNumber,
5928
+ idle,
5929
+ fees,
5930
+ available: idle + deployed > fees ? idle + deployed - fees : 0n,
5931
+ positions: [...verified],
5932
+ verifiedVaults: [...verifiedVaults],
5933
+ indexedPositions,
5934
+ complete
5935
+ });
5936
+ knownProof = proof(false);
5937
+ const indexed = new Map(indexedPositions.map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
5938
+ const discrepancy = verified.some((position2) => {
5939
+ const row = indexed.get(position2.yieldOption.address.toLowerCase());
5940
+ return row ? BigInt(position2.withdrawableAssetsRaw) < BigInt(row.withdrawableAssetsRaw) : BigInt(position2.assetsRaw) > 0n;
5941
+ });
5942
+ const typedCoveredByIdle = requested !== void 0 && idle >= requested + fees;
5943
+ const expand = positions === void 0 || indexedPositions.length === 0 && !typedCoveredByIdle || !allKnownVerified || discrepancy || knownProof.available === 0n || requested !== void 0 && requested > knownProof.available;
5944
+ if (!expand) {
5945
+ assertReadCurrent();
5946
+ return proof(true);
5947
+ }
5948
+ const catalog = await this.loadYieldOptions(context.asset).catch(() => void 0);
5949
+ assertReadCurrent();
5950
+ const eligible = /* @__PURE__ */ new Map();
5951
+ for (const option of catalog ?? []) {
5952
+ if (option?.chainId === chainId && typeof option.assetAddress === "string" && option.assetAddress.toLowerCase() === metadata.address.toLowerCase() && isAddress3(option.address))
5953
+ eligible.set(option.address.toLowerCase(), option);
5954
+ }
5955
+ const extra = [...eligible].filter(([address]) => !candidates.has(address)).map(([, option]) => option);
5956
+ if (options.length + extra.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
5957
+ const allExtraVerified = collect(extra, await this.readVaultLiquidity(extra, wallet, blockNumber));
5958
+ assertReadCurrent();
5959
+ const result = proof(eligible.size > 0 && allKnownVerified && allExtraVerified);
5960
+ if (result.available === 0n && !result.complete && !hasUnverifiedIndexedFunds(result)) throw unavailable();
5961
+ return result;
5962
+ };
5963
+ let timer;
5964
+ try {
5965
+ return await Promise.race([read2(), new Promise((_, reject) => {
5966
+ timer = setTimeout(() => reject(unavailable()), YIELDSEEKER_MAX_POSITION_VERIFICATION_MS);
5967
+ })]);
5968
+ } catch (error) {
5969
+ assertReadCurrent();
5970
+ if (knownProof && (knownProof.available > 0n || hasUnverifiedIndexedFunds(knownProof)) && (!(error instanceof OwneyError) || error.code === "WITHDRAW_BALANCE_UNAVAILABLE")) return knownProof;
5971
+ if (error instanceof OwneyError) throw error;
5972
+ throw unavailable();
5973
+ } finally {
5974
+ active = false;
5975
+ if (timer) clearTimeout(timer);
5976
+ }
5977
+ }
5508
5978
  async getBalances(state, chainId, asset) {
5509
5979
  this.assertChain(chainId);
5510
5980
  if (asset !== void 0) this.assertAsset(asset);
5511
- return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, { asset, allowMissingPositions: true }));
5981
+ const contexts = await this.loadPortfolio(state, chainId, { asset, allowMissingPositions: true });
5982
+ const balance = mapYieldseekerBalances(contexts);
5983
+ for (const context of contexts) {
5984
+ const read2 = balance.readMetadata.find((item) => item.asset === context.asset);
5985
+ this.balanceContexts.set(read2, { context, generation: this.readGeneration });
5986
+ const key2 = this.contextKey(state, chainId, context.asset);
5987
+ const known = this.balanceVaults.get(key2) ?? /* @__PURE__ */ new Set();
5988
+ for (const position2 of context.positions) known.add(position2.yieldOption.address.toLowerCase());
5989
+ this.balanceVaults.set(key2, known);
5990
+ }
5991
+ return balance;
5992
+ }
5993
+ async verifyWithdrawalBalance(state, evidence, balance) {
5994
+ this.assertAsset(evidence.token);
5995
+ this.assertChain(evidence.chainId);
5996
+ const read2 = balance.readMetadata?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
5997
+ const cached = read2 && this.balanceContexts.get(read2);
5998
+ const context = cached?.generation === this.readGeneration ? cached.context : void 0;
5999
+ if (!context || context.positionsUnavailable || !read2 || !evidence.blockNumber || !evidence.txHash || context.wallet.walletAddress.toLowerCase() !== evidence.agentWallet?.toLowerCase()) return void 0;
6000
+ const key2 = this.contextKey(state, evidence.chainId, evidence.token);
6001
+ const generation = this.readGeneration, version = this.portfolioVersion(key2);
6002
+ if (read2.readVersion !== version) return void 0;
6003
+ const wallet = getAddress5(context.wallet.walletAddress);
6004
+ const asset = YIELDSEEKER_ASSET_METADATA[evidence.token];
6005
+ const client = this.getReceiptClient();
6006
+ const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
6007
+ if (blockNumber < BigInt(evidence.blockNumber)) return void 0;
6008
+ const vaults = [...this.balanceVaults.get(key2) ?? []];
6009
+ const [invested, idle, tracker] = await Promise.all([
6010
+ remainingVaultBalance(client, wallet, asset.address, vaults, blockNumber),
6011
+ client.readContract({ address: asset.address, abi: erc20Abi4, functionName: "balanceOf", args: [wallet], blockNumber }),
6012
+ client.readContract({ address: wallet, abi: YIELDSEEKER_AGENT_WALLET_ABI, functionName: "feeTracker", blockNumber })
6013
+ ]);
6014
+ if (invested === void 0 || typeof idle !== "bigint" || idle < 0n || !isAddress3(tracker) || /^0x0{40}$/i.test(tracker)) return void 0;
6015
+ const fees = await client.readContract({
6016
+ address: tracker,
6017
+ abi: YIELDSEEKER_FEE_TRACKER_ABI,
6018
+ functionName: "getFeesOwed",
6019
+ args: [wallet],
6020
+ blockNumber
6021
+ });
6022
+ this.assertSession(generation, evidence.chainId, evidence.token);
6023
+ if (version !== this.portfolioVersion(key2) || typeof fees !== "bigint" || fees < 0n) return void 0;
6024
+ const total = idle + invested > fees ? idle + invested - fees : 0n;
6025
+ const displayed = balance.assetBalances?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
6026
+ if (!displayed || !evidence.confirmedAmount || !/^\d+$/.test(evidence.confirmedAmount)) return void 0;
6027
+ const amount = parseUnits(displayed.amount, asset.decimals);
6028
+ if (amount < 0n) return void 0;
6029
+ const displayUnit = evidence.token === "USDC" ? 5000n : 500000000000n;
6030
+ const movementUnit = BigInt(evidence.confirmedAmount) / 1000n;
6031
+ const tolerance = movementUnit < displayUnit ? movementUnit : displayUnit;
6032
+ if ((total > amount ? total - amount : amount - total) > tolerance) return void 0;
6033
+ return { ...read2, withdrawalCheck: { txHash: evidence.txHash, blockNumber: blockNumber.toString() } };
5512
6034
  }
5513
6035
  invalidateFundingRead(state, chainId, asset, refreshUntil = Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS) {
5514
6036
  this.assertAsset(asset);
@@ -5548,9 +6070,9 @@ var YieldseekerAgent = class {
5548
6070
  if (!context) return void 0;
5549
6071
  const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5550
6072
  address: YIELDSEEKER_ASSET_METADATA[asset].address,
5551
- abi: erc20Abi3,
6073
+ abi: erc20Abi4,
5552
6074
  functionName: "balanceOf",
5553
- args: [getAddress4(context.wallet.walletAddress)]
6075
+ args: [getAddress5(context.wallet.walletAddress)]
5554
6076
  }), chainId, "eth_call", this.id);
5555
6077
  this.assertSession(generation, chainId, asset);
5556
6078
  if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
@@ -5662,11 +6184,13 @@ var YieldseekerAgent = class {
5662
6184
  const pending = this.pendingReads.get(key2);
5663
6185
  if (pending) return pending;
5664
6186
  const generation = this.readGeneration;
6187
+ const requestedAt = Date.now();
5665
6188
  const request = Promise.resolve().then(read2).then((value) => {
5666
6189
  if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
5667
6190
  this.readCache.set(key2, {
5668
6191
  expiresAt: Date.now() + ttlMs,
5669
6192
  fetchedAt: Date.now(),
6193
+ requestedAt,
5670
6194
  value
5671
6195
  });
5672
6196
  }
@@ -5743,7 +6267,7 @@ var YieldseekerAgent = class {
5743
6267
  this.users.set(key2, persisted);
5744
6268
  return persisted;
5745
6269
  }
5746
- const walletAddress = getAddress4(state.walletAddress);
6270
+ const walletAddress = getAddress5(state.walletAddress);
5747
6271
  const generation = this.readGeneration;
5748
6272
  let user = null;
5749
6273
  try {
@@ -6035,6 +6559,7 @@ var YieldseekerAgent = class {
6035
6559
  ...context,
6036
6560
  snapshot: snapshot.agentSnapshot,
6037
6561
  fetchedAt: this.readCache.get(`snapshot:${contextKey}`)?.fetchedAt,
6562
+ requestedAt: this.readCache.get(`snapshot:${contextKey}`)?.requestedAt,
6038
6563
  readVersion: version,
6039
6564
  positionsUnavailable,
6040
6565
  ...portfolioParts
@@ -6234,7 +6759,7 @@ var YieldseekerAgent = class {
6234
6759
  return hash2;
6235
6760
  }
6236
6761
  this.assertTransaction(transaction, state, chainId);
6237
- const account = getAddress4(state.walletAddress);
6762
+ const account = getAddress5(state.walletAddress);
6238
6763
  const walletClient = createWalletClient3({
6239
6764
  account,
6240
6765
  chain: base4,
@@ -6252,7 +6777,7 @@ var YieldseekerAgent = class {
6252
6777
  const hash = await walletClient.sendTransaction({
6253
6778
  account,
6254
6779
  chain: base4,
6255
- to: getAddress4(transaction.to),
6780
+ to: getAddress5(transaction.to),
6256
6781
  data: transaction.data,
6257
6782
  value: BigInt(transaction.value)
6258
6783
  });
@@ -6356,7 +6881,7 @@ var YieldseekerAgent = class {
6356
6881
  return receipt;
6357
6882
  }
6358
6883
  assertTransaction(transaction, state, chainId) {
6359
- 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 || getAddress4(transaction.from) !== getAddress4(state.walletAddress)) {
6884
+ 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)) {
6360
6885
  throw this.invalidResponse("transaction");
6361
6886
  }
6362
6887
  }
@@ -6515,7 +7040,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
6515
7040
  }
6516
7041
 
6517
7042
  // src/lib/helpers/withdraw-helper.ts
6518
- import { parseUnits } from "viem";
7043
+ import { parseUnits as parseUnits2 } from "viem";
6519
7044
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
6520
7045
  const target = asset.toUpperCase();
6521
7046
  return agents.map((agent) => {
@@ -6523,7 +7048,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
6523
7048
  const tokenBalance = agentBalance?.tokens.find(
6524
7049
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
6525
7050
  );
6526
- let balance = tokenBalance ? parseUnits(tokenBalance.amount, decimals) : 0n;
7051
+ let balance = tokenBalance ? parseUnits2(tokenBalance.amount, decimals) : 0n;
6527
7052
  if (agent.balanceComposition === "tokens-plus-positions") {
6528
7053
  const chainNameById = {
6529
7054
  1: "ETHEREUM",
@@ -6535,14 +7060,14 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
6535
7060
  const positionChain = position2.chain.trim().toUpperCase();
6536
7061
  const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
6537
7062
  if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
6538
- balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : parseUnits(position2.amount, decimals);
7063
+ balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : parseUnits2(position2.amount, decimals);
6539
7064
  }
6540
7065
  }
6541
7066
  const assetTotal = agentBalance?.assetBalances?.find(
6542
7067
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
6543
7068
  );
6544
7069
  if (assetTotal) {
6545
- const total = parseUnits(assetTotal.amount, decimals);
7070
+ const total = parseUnits2(assetTotal.amount, decimals);
6546
7071
  if (total < balance) balance = total > 0n ? total : 0n;
6547
7072
  }
6548
7073
  return { agent, balance };
@@ -6702,6 +7227,7 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
6702
7227
 
6703
7228
  // src/client.ts
6704
7229
  import {
7230
+ parseUnits as parseUnits3,
6705
7231
  createPublicClient as createPublicClient4,
6706
7232
  createWalletClient as createWalletClient4,
6707
7233
  custom as custom4
@@ -6861,7 +7387,40 @@ async function execute(i, key2, plan) {
6861
7387
  const send = async (initial) => {
6862
7388
  let body = initial;
6863
7389
  save(key2, body);
7390
+ const rejectedPreparation = (error) => !body.serializedTransaction && error instanceof OwneyError && typeof error.details?.statusCode === "number" && error.details.statusCode >= 400 && error.details.statusCode < 500 && ![408, 429].includes(error.details.statusCode);
6864
7391
  try {
7392
+ const post = () => postSponsorBatchTransfer({ apiKey: i.apiKey, baseUrl: i.baseUrl, body });
7393
+ const pendingError = (phase, txHash) => new OwneyError(
7394
+ "DEPOSIT_IN_PROGRESS",
7395
+ phase === "approval" ? "Deposit approval is awaiting confirmation. Retry the same amount to continue it." : "Deposit is awaiting confirmation. Its transaction is still being checked.",
7396
+ { sponsorPending: true, phase, txHash, safeToFallback: false }
7397
+ );
7398
+ const assertReceipt = (receipt, phase) => {
7399
+ if (receipt.status !== "success") throw new OwneyError(
7400
+ "SPONSOR_REQUEST_FAILED",
7401
+ `Sponsored ${phase} transaction reverted. Please retry.`,
7402
+ { notSubmitted: true, safeToFallback: false }
7403
+ );
7404
+ };
7405
+ const waitForConfirmation = async (phase, hash2) => {
7406
+ let receipt;
7407
+ try {
7408
+ receipt = await i.pub.waitForTransactionReceipt({ hash: hash2, confirmations: 1, timeout: 9e4, checkReplacement: false });
7409
+ } catch {
7410
+ receipt = await i.pub.getTransactionReceipt({ hash: hash2 }).catch(() => void 0);
7411
+ }
7412
+ if (!receipt) throw pendingError(phase, hash2);
7413
+ assertReceipt(receipt, phase);
7414
+ };
7415
+ const postSigned = async (phase, hash2) => {
7416
+ try {
7417
+ return await post();
7418
+ } catch (error) {
7419
+ if (rejectedPreparation(error) || error instanceof OwneyError && error.details?.notSubmitted === true) throw error;
7420
+ await waitForConfirmation(phase, hash2);
7421
+ return void 0;
7422
+ }
7423
+ };
6865
7424
  if (body.erc2612Permit && !body.serializedPermitTransaction) {
6866
7425
  const preparedPermit = await postSponsorBatchTransfer({
6867
7426
  apiKey: i.apiKey,
@@ -6879,11 +7438,17 @@ async function execute(i, key2, plan) {
6879
7438
  save(key2, body);
6880
7439
  }
6881
7440
  if (!body.serializedTransaction) {
6882
- const prepared = await postSponsorBatchTransfer({
6883
- apiKey: i.apiKey,
6884
- baseUrl: i.baseUrl,
6885
- body
6886
- });
7441
+ const permitHash = body.serializedPermitTransaction ? keccak256(body.serializedPermitTransaction) : void 0;
7442
+ let prepared = permitHash ? await postSigned("approval", permitHash) : await post();
7443
+ if (prepared?.status === "pending") {
7444
+ if (!permitHash || prepared.phase !== "approval" || prepared.txHash !== permitHash)
7445
+ throw new Error("Sponsorship approval does not match the pending transaction.");
7446
+ await waitForConfirmation("approval", permitHash);
7447
+ prepared = void 0;
7448
+ }
7449
+ if (!prepared) prepared = await post();
7450
+ if (prepared.status === "pending" && permitHash && prepared.phase === "approval" && prepared.txHash === permitHash)
7451
+ throw pendingError("approval", permitHash);
6887
7452
  if (!prepared.serializedTransaction || keccak256(prepared.serializedTransaction) !== prepared.txHash)
6888
7453
  throw new Error(
6889
7454
  "Sponsorship API did not return a valid prepared transaction."
@@ -6898,19 +7463,30 @@ async function execute(i, key2, plan) {
6898
7463
  i.onSubmitted?.(keccak256(body.serializedTransaction));
6899
7464
  } catch {
6900
7465
  }
6901
- const result = await postSponsorBatchTransfer({
6902
- apiKey: i.apiKey,
6903
- baseUrl: i.baseUrl,
6904
- body
6905
- });
6906
- if (result.txHash !== keccak256(body.serializedTransaction))
6907
- throw new Error(
6908
- "Sponsorship receipt does not match the pending transaction."
6909
- );
7466
+ const hash = keccak256(body.serializedTransaction);
7467
+ const existing = await i.pub.getTransactionReceipt({ hash }).catch(() => void 0);
7468
+ if (existing) {
7469
+ assertReceipt(existing, "deposit");
7470
+ void post().catch(() => void 0);
7471
+ clear(key2);
7472
+ return hash;
7473
+ }
7474
+ let result = await postSigned("deposit", hash);
7475
+ if (result?.status === "pending" && result.phase === "approval" && body.serializedPermitTransaction && result.txHash === keccak256(body.serializedPermitTransaction)) {
7476
+ await waitForConfirmation("approval", result.txHash);
7477
+ result = await postSigned("deposit", hash);
7478
+ if (result?.status === "pending" && result.phase === "approval" && result.txHash === keccak256(body.serializedPermitTransaction)) throw pendingError("approval", result.txHash);
7479
+ }
7480
+ if (result) {
7481
+ if (result.txHash !== hash || result.status === "pending" && result.phase !== "deposit")
7482
+ throw new Error("Sponsorship receipt does not match the pending transaction.");
7483
+ if (result.status === "pending") await waitForConfirmation("deposit", hash);
7484
+ }
7485
+ if (!result || result.status === "pending") void post().catch(() => void 0);
6910
7486
  clear(key2);
6911
- return result.txHash;
7487
+ return hash;
6912
7488
  } catch (error) {
6913
- if (!body.serializedTransaction || error instanceof OwneyError && error.details?.notSubmitted === true)
7489
+ if (!body.serializedTransaction && !body.serializedPermitTransaction || rejectedPreparation(error) || error instanceof OwneyError && error.details?.notSubmitted === true)
6914
7490
  clear(key2);
6915
7491
  throw error;
6916
7492
  }
@@ -7116,7 +7692,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
7116
7692
  }
7117
7693
 
7118
7694
  // src/lib/sponsored-calls-deposit.ts
7119
- import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi4, toHex as toHex2 } from "viem";
7695
+ import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi5, toHex as toHex2 } from "viem";
7120
7696
  var DEFAULT_POLL_INTERVAL_MS = 1500;
7121
7697
  var DEFAULT_MAX_POLLS = 30;
7122
7698
  async function paymasterSupported(provider, owner, chainId) {
@@ -7162,7 +7738,7 @@ function makeSponsoredCallsCallback(deps) {
7162
7738
  to: token,
7163
7739
  value: "0x0",
7164
7740
  data: encodeFunctionData2({
7165
- abi: erc20Abi4,
7741
+ abi: erc20Abi5,
7166
7742
  functionName: "transfer",
7167
7743
  args: [transfer.to, BigInt(transfer.amount)]
7168
7744
  })
@@ -7363,6 +7939,7 @@ var OwneySDK = class {
7363
7939
  cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
7364
7940
  agentReads = new AgentReads();
7365
7941
  recoveredFundingReads = /* @__PURE__ */ new Set();
7942
+ fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
7366
7943
  activationChecks = /* @__PURE__ */ new Map();
7367
7944
  readAgent(agent, method, fetch2, params = null) {
7368
7945
  const key2 = JSON.stringify([
@@ -7408,6 +7985,7 @@ var OwneySDK = class {
7408
7985
  }
7409
7986
  this.agentReads.clearInFlight();
7410
7987
  this.activationChecks.clear();
7988
+ this.fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
7411
7989
  this.state = { provider, walletAddress, chainId: null };
7412
7990
  this.cachedSponsoredCallback = null;
7413
7991
  this.cachedWethSponsoredCallback = null;
@@ -7419,6 +7997,7 @@ var OwneySDK = class {
7419
7997
  */
7420
7998
  async disconnect() {
7421
7999
  this.state = null;
8000
+ this.fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
7422
8001
  for (const agent of this.agents.values()) {
7423
8002
  await agent.disconnect();
7424
8003
  }
@@ -8319,9 +8898,8 @@ var OwneySDK = class {
8319
8898
  verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
8320
8899
  }
8321
8900
  const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
8322
- if ((verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
8323
- if (options.refreshBalance !== false)
8324
- agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil);
8901
+ if (options.refreshBalance !== false && (verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
8902
+ agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil);
8325
8903
  this.recoveredFundingReads.add(key2);
8326
8904
  if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.values().next().value);
8327
8905
  }
@@ -8429,6 +9007,44 @@ var OwneySDK = class {
8429
9007
  assertCurrent();
8430
9008
  return true;
8431
9009
  }
9010
+ /** Optional post-payout display proof; receipt verification and the provider
9011
+ * adapter must agree before attaching it to this exact balance object. */
9012
+ async verifyWithdrawalBalance(evidence, balance) {
9013
+ const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
9014
+ const agent = this.getAgent(evidence.agentId);
9015
+ if (!agent.verifyWithdrawalBalance || !evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return balance;
9016
+ const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}:${evidence.intendedAmount}`;
9017
+ const checks = this.fundingBalanceChecks.get(balance) ?? /* @__PURE__ */ new Map();
9018
+ this.fundingBalanceChecks.set(balance, checks);
9019
+ const previous = checks.get(key2);
9020
+ if (previous) {
9021
+ const result2 = await previous;
9022
+ assertCurrent();
9023
+ return result2;
9024
+ }
9025
+ let timer;
9026
+ const read2 = async () => {
9027
+ const verified = await this.inspectFunding(evidence, "withdraw", { refreshBalance: false });
9028
+ assertCurrent();
9029
+ if (verified.transfer !== "confirmed" || verified.delivery !== "confirmed" || !verified.blockNumber) return balance;
9030
+ const metadata = await agent.verifyWithdrawalBalance(state, verified, balance);
9031
+ assertCurrent();
9032
+ if (!metadata || metadata.chainId !== chainId || metadata.asset.toUpperCase() !== evidence.token || !metadata.withdrawalCheck || metadata.withdrawalCheck.txHash.toLowerCase() !== verified.txHash?.toLowerCase() || BigInt(metadata.withdrawalCheck.blockNumber) < BigInt(verified.blockNumber)) return balance;
9033
+ return { ...balance, readMetadata: [
9034
+ ...(balance.readMetadata ?? []).filter((item) => !(item.chainId === chainId && item.asset.toUpperCase() === evidence.token)),
9035
+ metadata
9036
+ ] };
9037
+ };
9038
+ const pending = Promise.race([read2(), new Promise((resolve) => {
9039
+ timer = setTimeout(() => resolve(balance), 2e3);
9040
+ })]).catch(() => balance).finally(() => {
9041
+ if (timer) clearTimeout(timer);
9042
+ });
9043
+ checks.set(key2, pending);
9044
+ const result = await pending;
9045
+ assertCurrent();
9046
+ return result;
9047
+ }
8432
9048
  /** A display acknowledgement controls polling only; it never confirms a financial outcome. */
8433
9049
  endFundingBalanceRefresh(evidence, read2) {
8434
9050
  const { state, chainId } = this.captureReadScope(evidence.chainId);
@@ -8674,11 +9290,10 @@ var OwneySDK = class {
8674
9290
  };
8675
9291
  }
8676
9292
  const requested = BigInt(amount);
8677
- const aggregated = await this.getBalances(void 0, { chainId, asset: token });
9293
+ const { balances: aggregated, failures: balanceFailures } = await this.readAggregatedBalances({ chainId, asset: token });
8678
9294
  const unavailableAgents = eligibleAgents.filter(
8679
9295
  (agent) => !(agent.id in aggregated.agentBalances)
8680
9296
  );
8681
- const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
8682
9297
  const balances = projectAgentBalancesForAsset(
8683
9298
  eligibleAgents,
8684
9299
  aggregated.agentBalances,
@@ -8686,6 +9301,44 @@ var OwneySDK = class {
8686
9301
  asset,
8687
9302
  assetInfo.decimals
8688
9303
  );
9304
+ const indexedAvailable = balances.reduce((sum, entry) => sum + entry.balance, 0n);
9305
+ await Promise.all(balances.map(async (entry) => {
9306
+ if (entry.balance !== 0n && indexedAvailable >= requested || !entry.agent.getWithdrawableBalance) return;
9307
+ const originalFailure = balanceFailures.get(entry.agent.id);
9308
+ if (originalFailure instanceof OwneyError && ![
9309
+ "AGENT_API_ERROR",
9310
+ "AGENT_TIMEOUT",
9311
+ "AGENT_RATE_LIMITED"
9312
+ ].includes(originalFailure.code)) return;
9313
+ if (this.isUserRejectedWithdrawal(originalFailure)) throw originalFailure;
9314
+ if (entry.balance !== 0n && entry.agent.id !== "yieldseeker") {
9315
+ const total = aggregated.agentBalances[entry.agent.id]?.assetBalances?.find((value) => value.chainId === chainId && value.asset.toUpperCase() === token);
9316
+ if (!total || parseUnits3(total.amount, assetInfo.decimals) <= entry.balance) return;
9317
+ }
9318
+ try {
9319
+ const otherAvailable = indexedAvailable - entry.balance;
9320
+ const minimumAmount = requested > otherAvailable ? requested - otherAvailable : void 0;
9321
+ const verified = await entry.agent.getWithdrawableBalance(state, chainId, token, minimumAmount);
9322
+ if (typeof verified !== "bigint" || verified < 0n)
9323
+ throw new OwneyError("WITHDRAW_BALANCE_UNAVAILABLE", "Invalid withdrawal balance proof.", {}, entry.agent.id);
9324
+ if (this.state !== state || this.state.walletAddress !== state.walletAddress || this.state.chainId !== chainId)
9325
+ throw new OwneyError("NOT_CONNECTED", "Account changed while planning withdrawal.");
9326
+ entry.balance = verified;
9327
+ const unavailableIndex = unavailableAgents.indexOf(entry.agent);
9328
+ if (unavailableIndex !== -1) unavailableAgents.splice(unavailableIndex, 1);
9329
+ if (aggregated.agentErrors) delete aggregated.agentErrors[entry.agent.id];
9330
+ } catch (error) {
9331
+ entry.balance = 0n;
9332
+ if (!unavailableAgents.includes(entry.agent)) unavailableAgents.push(entry.agent);
9333
+ aggregated.agentErrors = {
9334
+ ...aggregated.agentErrors,
9335
+ [entry.agent.id]: error instanceof Error ? error.message : String(error)
9336
+ };
9337
+ }
9338
+ }));
9339
+ if (this.state !== state || this.state.chainId !== chainId)
9340
+ throw new OwneyError("NOT_CONNECTED", "Account changed while planning withdrawal.");
9341
+ const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
8689
9342
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
8690
9343
  if (totalAvailable === 0n && unavailableAgents.length > 0) {
8691
9344
  throw new OwneyError(
@@ -8770,7 +9423,18 @@ var OwneySDK = class {
8770
9423
  );
8771
9424
  }
8772
9425
  const withdrawn = sumWithdrawnAmount(results);
8773
- if (withdrawn < requested) {
9426
+ const verifiedFeeDeductions = orderedPlans.reduce((total, plan) => {
9427
+ const result = results[plan.agent.id];
9428
+ const adjustment = result?.feeAdjustment;
9429
+ const evidence = result?.evidence;
9430
+ const submitted = submissionEvidence.get(plan.agent.id);
9431
+ 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;
9432
+ const fee = BigInt(adjustment.feeAmount);
9433
+ const net = BigInt(result.amount);
9434
+ const gross = BigInt(adjustment.grossAmount);
9435
+ return fee > 0n && net > 0n && gross === plan.planned && net + fee === gross ? total + fee : total;
9436
+ }, 0n);
9437
+ if (withdrawn + verifiedFeeDeductions < requested) {
8774
9438
  throw new OwneyError(
8775
9439
  "WITHDRAW_PARTIAL_FAILURE",
8776
9440
  `Withdrew "${withdrawn.toString()}" of requested "${amount}" of "${asset}". One or more agents failed; remaining balance could not cover the shortfall.`,
@@ -8794,23 +9458,42 @@ var OwneySDK = class {
8794
9458
  * @returns {AgentBalance} for a single agent, or {OwneyBalances} with totalBalance and per-agent breakdown
8795
9459
  */
8796
9460
  async getBalances(agentId, options) {
8797
- const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
8798
- const asset = options?.asset;
8799
- const readBalance = (agent) => this.readAgent(
8800
- agent,
8801
- "balances",
8802
- () => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
8803
- asset ? { asset } : null
8804
- );
8805
9461
  if (agentId) {
9462
+ const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
9463
+ const asset = options?.asset;
8806
9464
  const agent = this.getAgent(agentId);
8807
- const result = await readBalance(agent);
9465
+ const result = await this.readAgent(
9466
+ agent,
9467
+ "balances",
9468
+ () => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
9469
+ asset ? { asset } : null
9470
+ );
8808
9471
  assertCurrent();
8809
9472
  return {
8810
9473
  ...result,
8811
9474
  balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
8812
9475
  };
8813
9476
  }
9477
+ const { balances, failures } = await this.readAggregatedBalances(options);
9478
+ if (Object.keys(balances.agentBalances).length === 0) {
9479
+ throw new OwneyError(
9480
+ "BALANCE_ALL_FAILED",
9481
+ "Failed to fetch balances for all active agents.",
9482
+ { agentErrors: balances.agentErrors ?? {}, failures: [...failures.values()] }
9483
+ );
9484
+ }
9485
+ return balances;
9486
+ }
9487
+ /** Preserve failure objects for withdrawal recovery without changing public balance results. */
9488
+ async readAggregatedBalances(options) {
9489
+ const { state, chainId, assertCurrent } = this.captureReadScope(options?.chainId);
9490
+ const asset = options?.asset;
9491
+ const readBalance = (agent) => this.readAgent(
9492
+ agent,
9493
+ "balances",
9494
+ () => asset ? agent.getBalances(state, chainId, asset) : agent.getBalances(state, chainId),
9495
+ asset ? { asset } : null
9496
+ );
8814
9497
  let totalBalance = 0;
8815
9498
  const results = {};
8816
9499
  const entries = [...this.getActiveAgents().entries()];
@@ -8827,39 +9510,33 @@ var OwneySDK = class {
8827
9510
  })
8828
9511
  );
8829
9512
  assertCurrent();
8830
- let successCount = 0;
8831
9513
  const agentErrors = {};
8832
9514
  const agentRetryAt = {};
8833
- const agentFailures = [];
9515
+ const agentFailures = /* @__PURE__ */ new Map();
8834
9516
  for (let i = 0; i < balanceResults.length; i++) {
8835
9517
  const settledResult = balanceResults[i];
8836
- const [agentId2] = entries[i];
9518
+ const [agentId] = entries[i];
8837
9519
  if (settledResult.status === "fulfilled") {
8838
9520
  const [id, b] = settledResult.value;
8839
9521
  results[id] = b;
8840
9522
  totalBalance += Number(b.totalBalance);
8841
- successCount += 1;
8842
9523
  continue;
8843
9524
  }
8844
9525
  const reason = settledResult.reason;
8845
- agentFailures.push(reason);
9526
+ agentFailures.set(agentId, reason);
8846
9527
  const retryDelay = rateLimitDelay(reason);
8847
- if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
8848
- agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
8849
- console.error(`[owney-sdk] Balance fetch failed for agent "${agentId2}":`, reason);
8850
- }
8851
- if (successCount === 0) {
8852
- throw new OwneyError(
8853
- "BALANCE_ALL_FAILED",
8854
- "Failed to fetch balances for all active agents.",
8855
- { agentErrors, failures: agentFailures }
8856
- );
9528
+ if (retryDelay !== void 0) agentRetryAt[agentId] = Date.now() + retryDelay;
9529
+ agentErrors[agentId] = reason instanceof Error ? reason.message : String(reason);
9530
+ console.error(`[owney-sdk] Balance fetch failed for agent "${agentId}":`, reason);
8857
9531
  }
8858
9532
  return {
8859
- totalBalance: String(totalBalance),
8860
- totalBalanceAsset: "usdc",
8861
- agentBalances: results,
8862
- ...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
9533
+ balances: {
9534
+ totalBalance: String(totalBalance),
9535
+ totalBalanceAsset: "usdc",
9536
+ agentBalances: results,
9537
+ ...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
9538
+ },
9539
+ failures: agentFailures
8863
9540
  };
8864
9541
  }
8865
9542
  /**
@@ -9411,7 +10088,7 @@ var OwneySDK = class {
9411
10088
  };
9412
10089
 
9413
10090
  // src/agents/zyfai/zyfai.siwx.ts
9414
- import { getAddress as getAddress5 } from "viem";
10091
+ import { getAddress as getAddress6 } from "viem";
9415
10092
  import { SiweMessage as SiweMessage2 } from "siwe";
9416
10093
  import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
9417
10094
 
@@ -9538,7 +10215,7 @@ function buildSIWXConfig(deps) {
9538
10215
  issuedAt,
9539
10216
  toString() {
9540
10217
  return new SiweMessage2({
9541
- address: getAddress5(accountAddress),
10218
+ address: getAddress6(accountAddress),
9542
10219
  chainId: numericChainId(chainId),
9543
10220
  domain,
9544
10221
  uri,