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

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
@@ -68,6 +68,8 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
68
68
  const attributed = /* @__PURE__ */ new Map();
69
69
  const agent = evidence.agentWallet?.toLowerCase();
70
70
  const owner = evidence.walletAddress.toLowerCase();
71
+ const source = direction === "deposit" ? owner : agent;
72
+ const destination = direction === "deposit" ? agent : owner;
71
73
  const logIndexes = [];
72
74
  const transferLogs = [];
73
75
  const zeroTransferLogs = [];
@@ -82,17 +84,13 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
82
84
  if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
83
85
  zeroTransferLogs.push(log.logIndex);
84
86
  let matched = 0n;
85
- if (direction === "deposit") {
86
- if (to.toLowerCase() === agent) matched = value;
87
- } else {
88
- const sender = from.toLowerCase();
89
- const recipient = to.toLowerCase();
90
- const available = sender === agent ? value : attributed.get(sender) ?? 0n;
91
- const carried = available < value ? available : value;
92
- if (sender !== agent) attributed.set(sender, available - carried);
93
- if (recipient === owner) matched = carried;
94
- else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
95
- }
87
+ const sender = from.toLowerCase();
88
+ const recipient = to.toLowerCase();
89
+ const available = sender === source ? value : attributed.get(sender) ?? 0n;
90
+ const carried = available < value ? available : value;
91
+ if (sender !== source) attributed.set(sender, available - carried);
92
+ if (recipient === destination) matched = carried;
93
+ else if (recipient !== source) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
96
94
  if (matched === 0n) continue;
97
95
  amount += matched;
98
96
  transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
@@ -384,8 +382,8 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
384
382
  return await original.apply(this, args);
385
383
  } catch (error) {
386
384
  const message = error instanceof Error ? error.message : "";
387
- request2.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : message === "Network error: Unable to reach the server" ? "network" : "other";
388
- if (["unauthorized", "forbidden", "rate-limited"].includes(request2.failure)) notify("rejected");
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");
389
387
  throw error;
390
388
  } finally {
391
389
  request2.elapsedMs = Math.max(0, Date.now() - startedAt);
@@ -408,9 +406,49 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
408
406
  } };
409
407
  }
410
408
 
409
+ // src/agents/zyfai/zyfai.withdraw-empty.ts
410
+ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
411
+ const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
412
+ const response = record(raw2);
413
+ const portfolio = record(response?.portfolio);
414
+ const chains = record(portfolio?.portfolioByChain);
415
+ if (response?.success === false || !chains || !Array.isArray(portfolio?.positions) || !Array.isArray(portfolio.staleBalances)) return false;
416
+ if (portfolio.pendingAsyncWithdrawals !== void 0 && (!Array.isArray(portfolio.pendingAsyncWithdrawals) || portfolio.pendingAsyncWithdrawals.length > 0)) return false;
417
+ const symbol = (value) => typeof value === "string" ? value.toUpperCase() === "ETH" ? "WETH" : value.toUpperCase() : void 0;
418
+ const chain = (value) => {
419
+ if (typeof value === "number") return value;
420
+ if (typeof value !== "string") return void 0;
421
+ return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
422
+ };
423
+ const zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
424
+ for (const [id, assets] of Object.entries(chains)) {
425
+ const idNumber = chain(id);
426
+ if (idNumber === void 0) return false;
427
+ if (idNumber !== chainId) continue;
428
+ const entries = record(assets);
429
+ if (!entries) return false;
430
+ for (const [asset, value] of Object.entries(entries)) {
431
+ if (symbol(asset) !== token) continue;
432
+ const balance = record(value);
433
+ if (!balance || !zero(balance.balance)) return false;
434
+ }
435
+ }
436
+ for (const value of [...portfolio.positions, ...portfolio.staleBalances]) {
437
+ const entry = record(value);
438
+ if (!entry) return false;
439
+ const id = chain(entry.chainId ?? entry.chain);
440
+ const asset = symbol(entry.tokenSymbol ?? entry.token_symbol);
441
+ if (id === void 0 || asset === void 0) return false;
442
+ if (id !== chainId || asset !== token) continue;
443
+ const amounts = [entry.balance, entry.amount, entry.underlyingAmount].filter((value2) => value2 !== void 0);
444
+ if (!amounts.length || !amounts.every(zero)) return false;
445
+ }
446
+ return true;
447
+ }
448
+
411
449
  // src/agents/zyfai/zyfai.agent.ts
412
450
  import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
413
- import { formatUnits, parseAbi as parseAbi2 } from "viem";
451
+ import { erc20Abi as erc20Abi2, formatUnits as formatUnits2, isAddress, parseAbi as parseAbi2 } from "viem";
414
452
  import { base, arbitrum, mainnet } from "viem/chains";
415
453
 
416
454
  // src/types/config.ts
@@ -497,14 +535,6 @@ function hexToDecimal(hex, decimals = 6) {
497
535
  const remainder = parsed % divisor;
498
536
  return Number(whole) + Number(remainder) / Number(divisor);
499
537
  }
500
- function normalizeDecimalPlace(value, decimals = 6) {
501
- const parsed = BigInt(value);
502
- if (decimals <= 0) return Number(parsed);
503
- const divisor = BigInt(10 ** decimals);
504
- const whole = parsed / divisor;
505
- const remainder = parsed % divisor;
506
- return Number(whole) + Number(remainder) / Number(divisor);
507
- }
508
538
  var DayFilterMapping = {
509
539
  "30D": 30,
510
540
  "14D": 14,
@@ -512,6 +542,8 @@ var DayFilterMapping = {
512
542
  };
513
543
 
514
544
  // src/agents/zyfai/zyfai.mapper.ts
545
+ import { formatUnits } from "viem";
546
+ var exactHexAmount = (raw2, decimals) => formatUnits(BigInt(/^0x/i.test(raw2) ? raw2 : `0x${raw2}`), decimals);
515
547
  var DECIMALS_BY_SYMBOL = {
516
548
  usdc: 6,
517
549
  weth: 18,
@@ -566,15 +598,15 @@ function mapPendingAllocations(raw2) {
566
598
  if (typeof e.balance !== "string") continue;
567
599
  let amount;
568
600
  try {
569
- amount = hexToDecimal(e.balance, tokenDecimals(e.tokenSymbol));
601
+ if (BigInt(/^0x/i.test(e.balance) ? e.balance : `0x${e.balance}`) <= 0n) continue;
602
+ amount = exactHexAmount(e.balance, tokenDecimals(e.tokenSymbol));
570
603
  } catch {
571
604
  continue;
572
605
  }
573
- if (!Number.isFinite(amount) || amount <= 0) continue;
574
606
  pending.push({
575
607
  chainId: e.chainId,
576
608
  asset: e.tokenSymbol,
577
- amount: String(amount),
609
+ amount,
578
610
  isPending: e.isPending === true,
579
611
  ...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
580
612
  });
@@ -597,13 +629,13 @@ function mapBalances(raw2, _chainId, smartWallet) {
597
629
  { balance, balanceWithFee, decimals }
598
630
  ] of Object.entries(tokensBySymbol ?? {})) {
599
631
  if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
600
- const amount = hexToDecimal(balanceWithFee ?? balance, decimals);
601
- totalBalance += amount;
632
+ const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol, decimals));
633
+ totalBalance += Number(amount);
602
634
  tokens.push({
603
635
  chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
604
636
  chainId: positionChainId,
605
637
  asset: symbol,
606
- amount: String(amount)
638
+ amount
607
639
  });
608
640
  }
609
641
  }
@@ -617,19 +649,17 @@ function mapBalances(raw2, _chainId, smartWallet) {
617
649
  // legacy `amount` fallback is hex. Runtime
618
650
  // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
619
651
  // when present, otherwise fall back to the per-symbol lookup.
620
- amount: String(
621
- p.underlyingAmountWithFee ?? p.underlyingAmount ? normalizeDecimalPlace(
622
- p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0",
623
- tokenDecimals(
624
- p.token_symbol,
625
- p.decimals
626
- )
627
- ) : hexToDecimal(
628
- p.amount || "0",
629
- tokenDecimals(
630
- p.token_symbol,
631
- p.decimals
632
- )
652
+ amount: p.underlyingAmountWithFee ?? p.underlyingAmount ? formatUnits(
653
+ BigInt(p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0"),
654
+ tokenDecimals(
655
+ p.token_symbol,
656
+ p.decimals
657
+ )
658
+ ) : exactHexAmount(
659
+ p.amount || "0",
660
+ tokenDecimals(
661
+ p.token_symbol,
662
+ p.decimals
633
663
  )
634
664
  ),
635
665
  apy: netApy(p.pool_apy_withFee, p.pool_apy, "pool_apy_withFee"),
@@ -1573,7 +1603,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1573
1603
  }, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
1574
1604
  if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
1575
1605
  Object.assign(tx, {
1576
- amount: formatUnits(BigInt(evidence.confirmedAmount), asset.decimals),
1606
+ amount: formatUnits2(BigInt(evidence.confirmedAmount), asset.decimals),
1577
1607
  amountRaw: evidence.confirmedAmount,
1578
1608
  blockNumber: evidence.blockNumber,
1579
1609
  logIndexes: evidence.logIndexes,
@@ -2300,6 +2330,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
2300
2330
  evidence2 = { ...evidence2, transfer: "unknown" };
2301
2331
  }
2302
2332
  notifyFunding(onProgress, evidence2);
2333
+ if (evidence2.transfer === "failed")
2334
+ throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
2303
2335
  try {
2304
2336
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
2305
2337
  await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
@@ -2338,12 +2370,77 @@ var ZyfaiAgent = class _ZyfaiAgent {
2338
2370
  } catch {
2339
2371
  }
2340
2372
  notifyFunding(onProgress, evidence);
2373
+ if (evidence.transfer === "failed")
2374
+ throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash: raw2.txHash }, this.id);
2341
2375
  return { ...mapDeposit(raw2), evidence };
2342
2376
  } catch (error) {
2343
2377
  throw error;
2344
2378
  }
2345
2379
  }
2346
2380
  // --- IAgent: Fund operations ---
2381
+ /** Called only after the execution endpoint explicitly rejected a request.
2382
+ * A timeout or an accepted request never authorizes repair and resubmission. */
2383
+ async recoverWithdrawalSession(state, chainId, token, owner) {
2384
+ const internals = this.sdk;
2385
+ const assertScope = () => {
2386
+ const account = internals.walletClient?.account?.address;
2387
+ if (state.walletAddress.toLowerCase() !== owner.toLowerCase() || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
2388
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
2389
+ };
2390
+ 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
+ const walletState = await this.readWalletState(owner);
2421
+ assertScope();
2422
+ const unavailable = () => new OwneyError(
2423
+ "AGENT_AUTH_FAILED",
2424
+ "Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
2425
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2426
+ this.id
2427
+ );
2428
+ if (walletState.predeployed !== false || internals.isPredeployed === true) throw unavailable();
2429
+ internals.hasActiveSessionKey = false;
2430
+ const key2 = `${owner.toLowerCase()}:${chainId}`;
2431
+ this.sessionKeyAttempts.delete(key2);
2432
+ try {
2433
+ const result = await this.sdk.createSessionKey(owner, chainId);
2434
+ assertScope();
2435
+ if (result.success === false || result.alreadyActive === true) throw unavailable();
2436
+ this.sessionKeyAttempts.add(key2);
2437
+ } catch (error) {
2438
+ assertScope();
2439
+ const code = error?.code;
2440
+ if (code === 4001 || code === "ACTION_REJECTED" || error instanceof Error && /user (rejected|denied)|request rejected|action rejected/i.test(error.message)) throw error;
2441
+ throw unavailable();
2442
+ }
2443
+ }
2347
2444
  async withdraw(state, chainId, token, amount, onProgress) {
2348
2445
  const validChainId = isValidChainId(chainId);
2349
2446
  try {
@@ -2361,11 +2458,35 @@ var ZyfaiAgent = class _ZyfaiAgent {
2361
2458
  });
2362
2459
  throw error;
2363
2460
  }
2364
- const observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2461
+ let observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2365
2462
  const authToken = this.sdk.httpClient?.authToken;
2463
+ const owner = this.getAddress();
2366
2464
  let raw2;
2367
2465
  try {
2368
- raw2 = await this.sdk.withdrawFunds(this.getAddress(), validChainId, amount, token);
2466
+ for (let attempt = 0; attempt < 2; attempt++) {
2467
+ try {
2468
+ raw2 = await this.sdk.withdrawFunds(owner, validChainId, amount, token);
2469
+ break;
2470
+ } catch (error) {
2471
+ if (!observation.rejected() || observation.request()?.failure !== "session-key-missing") throw error;
2472
+ if (attempt === 1)
2473
+ throw new OwneyError(
2474
+ "AGENT_AUTH_FAILED",
2475
+ "Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
2476
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2477
+ this.id
2478
+ );
2479
+ await this.recoverWithdrawalSession(state, validChainId, token, owner);
2480
+ observation.stop();
2481
+ observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2482
+ }
2483
+ }
2484
+ if (!raw2) throw new OwneyError(
2485
+ "AGENT_INVALID_RESPONSE",
2486
+ "Zyfai returned no withdrawal result.",
2487
+ { chainId, token, phase: "observe" },
2488
+ this.id
2489
+ );
2369
2490
  } catch (error) {
2370
2491
  this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
2371
2492
  if (observation.attempted() !== void 0) {
@@ -2648,10 +2769,13 @@ import {
2648
2769
  createPublicClient as createPublicClient3,
2649
2770
  createWalletClient as createWalletClient3,
2650
2771
  custom as custom3,
2772
+ decodeEventLog as decodeEventLog2,
2773
+ encodeEventTopics,
2651
2774
  encodeFunctionData,
2652
- erc20Abi as erc20Abi2,
2775
+ erc20Abi as erc20Abi3,
2776
+ erc4626Abi,
2653
2777
  getAddress as getAddress4,
2654
- isAddress as isAddress2
2778
+ isAddress as isAddress3
2655
2779
  } from "viem";
2656
2780
  import { base as base4 } from "viem/chains";
2657
2781
 
@@ -3270,7 +3394,7 @@ var YieldseekerApiClient = class {
3270
3394
  };
3271
3395
 
3272
3396
  // src/agents/yieldseeker/yieldseeker.mapper.ts
3273
- import { formatUnits as formatUnits2, isAddress } from "viem";
3397
+ import { formatUnits as formatUnits3, isAddress as isAddress2 } from "viem";
3274
3398
 
3275
3399
  // src/lib/helpers/snapshot-apy.ts
3276
3400
  var DAY_MS = 864e5;
@@ -3337,10 +3461,10 @@ function raw(value, endpoint) {
3337
3461
  return BigInt(value);
3338
3462
  }
3339
3463
  function decimal(value, decimals, endpoint) {
3340
- return formatUnits2(raw(value, endpoint), decimals);
3464
+ return formatUnits3(raw(value, endpoint), decimals);
3341
3465
  }
3342
3466
  function usd(rawAmount, decimals, price) {
3343
- return Number(formatUnits2(rawAmount, decimals)) * price;
3467
+ return Number(formatUnits3(rawAmount, decimals)) * price;
3344
3468
  }
3345
3469
  function percent(value) {
3346
3470
  const result = Number(value);
@@ -3366,7 +3490,7 @@ function assetAddressValue(record, address) {
3366
3490
  }
3367
3491
  function position(value, asset, baseAssetDecimals) {
3368
3492
  const option = value?.yieldOption;
3369
- if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress(option.address)) {
3493
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress2(option.address)) {
3370
3494
  return invalid("yield positions", "missing vault metadata");
3371
3495
  }
3372
3496
  return {
@@ -3458,7 +3582,7 @@ function mapYieldseekerEarnings(contexts) {
3458
3582
  chain: "BASE",
3459
3583
  chainId: 8453,
3460
3584
  asset: context.asset,
3461
- amount: formatUnits2(amount, context.snapshot.baseAssetDecimals)
3585
+ amount: formatUnits3(amount, context.snapshot.baseAssetDecimals)
3462
3586
  });
3463
3587
  lifetimeEarnings += usd(
3464
3588
  amount,
@@ -3587,7 +3711,7 @@ function allocationEntry(action, context) {
3587
3711
  const leg = deposit;
3588
3712
  const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
3589
3713
  if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
3590
- if (typeof leg.vaultAddress !== "string" || !isAddress(leg.vaultAddress)) return void 0;
3714
+ if (typeof leg.vaultAddress !== "string" || !isAddress2(leg.vaultAddress)) return void 0;
3591
3715
  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;
3592
3716
  if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
3593
3717
  const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
@@ -3595,7 +3719,7 @@ function allocationEntry(action, context) {
3595
3719
  protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
3596
3720
  pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
3597
3721
  tokenSymbol: context.asset,
3598
- amount: formatUnits2(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3722
+ amount: formatUnits3(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3599
3723
  });
3600
3724
  amountRaw += rawAmount;
3601
3725
  }
@@ -3614,7 +3738,7 @@ function allocationEntry(action, context) {
3614
3738
  tokenSymbol: context.asset,
3615
3739
  agentWallet: context.wallet.walletAddress,
3616
3740
  amountRaw: String(amountRaw),
3617
- amount: formatUnits2(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3741
+ amount: formatUnits3(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3618
3742
  }]
3619
3743
  };
3620
3744
  }
@@ -3738,7 +3862,7 @@ function mapYieldseekerHistory(contexts, options) {
3738
3862
  const known = new Set(target.logIndexes ?? []);
3739
3863
  if (tx.logIndexes?.some((index) => !known.has(index))) {
3740
3864
  target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
3741
- target.amount = formatUnits2(
3865
+ target.amount = formatUnits3(
3742
3866
  BigInt(target.amountRaw),
3743
3867
  YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
3744
3868
  );
@@ -3833,7 +3957,8 @@ var walletAbi = parseAbi3([
3833
3957
  "function ownerAgentIndex() view returns (uint256)",
3834
3958
  "function baseAsset() view returns (address)",
3835
3959
  "function FACTORY() view returns (address)",
3836
- "function upgradeToLatest()"
3960
+ "function upgradeToLatest()",
3961
+ "event Upgraded(address indexed implementation)"
3837
3962
  ]);
3838
3963
  var factoryAbi = parseAbi3([
3839
3964
  "function userWallets(address owner, uint256 index) view returns (address)",
@@ -3842,19 +3967,19 @@ var factoryAbi = parseAbi3([
3842
3967
  function yieldseekerOwnerPath(code) {
3843
3968
  return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
3844
3969
  }
3845
- async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
3970
+ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
3846
3971
  const agentWallet = getAddress2(wallet);
3847
3972
  const expectedOwner = getAddress2(owner);
3848
3973
  const expectedAsset = getAddress2(asset);
3849
3974
  const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
3850
- client.getBytecode({ address: agentWallet }),
3851
- client.getBytecode({ address: expectedOwner }),
3852
- client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT }),
3853
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner" }),
3854
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex" }),
3855
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset" }),
3856
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY" }),
3857
- client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation" })
3975
+ client.getBytecode({ address: agentWallet, blockNumber }),
3976
+ client.getBytecode({ address: expectedOwner, blockNumber }),
3977
+ client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT, blockNumber }),
3978
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner", blockNumber }),
3979
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex", blockNumber }),
3980
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset", blockNumber }),
3981
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY", blockNumber }),
3982
+ client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation", blockNumber })
3858
3983
  ]);
3859
3984
  if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
3860
3985
  throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
@@ -3864,7 +3989,8 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
3864
3989
  address: YIELDSEEKER_FACTORY,
3865
3990
  abi: factoryAbi,
3866
3991
  functionName: "userWallets",
3867
- args: [expectedOwner, index]
3992
+ args: [expectedOwner, index],
3993
+ blockNumber
3868
3994
  });
3869
3995
  const verified = isAddressEqual(actualOwner, expectedOwner) && isAddressEqual(actualAsset, expectedAsset) && isAddressEqual(actualFactory, YIELDSEEKER_FACTORY) && isAddressEqual(mapped, agentWallet);
3870
3996
  const version = !verified ? "unknown" : isAddressEqual(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : isAddressEqual(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
@@ -3893,6 +4019,8 @@ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
3893
4019
  import { base as base3 } from "viem/chains";
3894
4020
  var ENTRY_POINT_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
3895
4021
  var USER_OP_FEE_HEADROOM = 2n;
4022
+ var OBSERVATION_WINDOW_MS = 6e4;
4023
+ var OBSERVATION_POLL_MS = 1500;
3896
4024
  var ENTRY_POINT_NONCE_ABI = [
3897
4025
  { type: "function", name: "getNonce", stateMutability: "view", inputs: [
3898
4026
  { name: "sender", type: "address" },
@@ -3925,10 +4053,119 @@ function sponsorError(message, safeToFallback, details = {}) {
3925
4053
  return new OwneyError(
3926
4054
  "SPONSOR_REQUEST_FAILED",
3927
4055
  message,
3928
- { safeToFallback, ...details },
4056
+ {
4057
+ safeToFallback,
4058
+ phase: safeToFallback ? "prepare" : "observe",
4059
+ failureCode: safeToFallback ? "SPONSOR_PREPARATION_FAILED" : "SPONSOR_OUTCOME_UNRESOLVED",
4060
+ ...safeToFallback ? { rejectedBeforeSubmission: true } : {},
4061
+ ...details
4062
+ },
3929
4063
  "yieldseeker"
3930
4064
  );
3931
4065
  }
4066
+ function providerReason(value) {
4067
+ if (typeof value !== "string") return void 0;
4068
+ return value.replace(/https?:\/\/\S+/gi, "[url]").replace(/0x[0-9a-fA-F]{40,}/g, "[hex]").replace(/[A-Za-z0-9_+=\/-]{48,}/g, "[token]").slice(0, 300);
4069
+ }
4070
+ function rpcDiagnostics(data) {
4071
+ const details = {};
4072
+ if (typeof data?.failureCode === "string" && [
4073
+ "SPONSOR_RATE_LIMIT",
4074
+ "SPONSOR_UPSTREAM_UNAVAILABLE",
4075
+ "SPONSOR_RPC_UNSUPPORTED",
4076
+ "SPONSOR_POLICY_REJECTED",
4077
+ "SPONSOR_BUDGET_REJECTED",
4078
+ "SPONSOR_EXECUTION_REVERTED"
4079
+ ].includes(data.failureCode))
4080
+ details.failureCode = data.failureCode;
4081
+ if (typeof data?.revertSelector === "string" && /^0x[0-9a-fA-F]{8}$/.test(data.revertSelector))
4082
+ details.revertSelector = data.revertSelector;
4083
+ if (typeof data?.revertName === "string" && [
4084
+ "InsufficientBalance",
4085
+ "InvalidAsset",
4086
+ "Unauthorized",
4087
+ "Error",
4088
+ "Panic",
4089
+ "FailedOp",
4090
+ "FailedOpWithRevert"
4091
+ ].includes(data.revertName))
4092
+ details.revertName = data.revertName;
4093
+ if (typeof data?.aaCode === "string" && /^AA[0-9]{2}$/.test(data.aaCode)) details.aaCode = data.aaCode;
4094
+ if (typeof data?.panicCode === "string" && /^0x[0-9a-fA-F]{1,2}$/.test(data.panicCode)) {
4095
+ const panicCode = toHex(BigInt(data.panicCode));
4096
+ if (["0x0", "0x1", "0x11", "0x12", "0x21", "0x22", "0x31", "0x32", "0x41", "0x51"].includes(panicCode))
4097
+ details.panicCode = panicCode;
4098
+ }
4099
+ return details;
4100
+ }
4101
+ function isHash(value) {
4102
+ return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
4103
+ }
4104
+ async function assertOwnerSession(state, owner) {
4105
+ const [chain, accounts] = await Promise.all([
4106
+ state.provider.request({ method: "eth_chainId" }),
4107
+ state.provider.request({ method: "eth_accounts" })
4108
+ ]);
4109
+ if (Number(chain) !== 8453)
4110
+ throw new OwneyError(
4111
+ "CHAIN_MISMATCH",
4112
+ "Switch back to Base before withdrawing.",
4113
+ { rejectedBeforeSubmission: true, phase: "prepare" },
4114
+ "yieldseeker"
4115
+ );
4116
+ if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase())
4117
+ throw new OwneyError(
4118
+ "NOT_CONNECTED",
4119
+ "The connected withdrawal account changed.",
4120
+ { rejectedBeforeSubmission: true, phase: "prepare" },
4121
+ "yieldseeker"
4122
+ );
4123
+ }
4124
+ function savePending(onPending, evidence, beforeSubmission = false) {
4125
+ try {
4126
+ onPending?.(evidence);
4127
+ } catch {
4128
+ if (beforeSubmission)
4129
+ throw sponsorError(
4130
+ "Withdrawal status cannot be saved before submission.",
4131
+ true,
4132
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
4133
+ );
4134
+ }
4135
+ }
4136
+ async function checkpoint(callback, pending) {
4137
+ try {
4138
+ await callback?.(pending);
4139
+ } catch {
4140
+ throw sponsorError(
4141
+ "Withdrawal status cannot be saved before submission.",
4142
+ true,
4143
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
4144
+ );
4145
+ }
4146
+ }
4147
+ function finalPaymasterFields(value) {
4148
+ const result = {};
4149
+ for (const key2 of [
4150
+ "paymasterAndData",
4151
+ "callGasLimit",
4152
+ "verificationGasLimit",
4153
+ "preVerificationGas",
4154
+ "maxFeePerGas",
4155
+ "maxPriorityFeePerGas"
4156
+ ]) {
4157
+ const field = value[key2];
4158
+ if (field === void 0) continue;
4159
+ if (typeof field !== "string" || !/^0x[0-9a-fA-F]+$/.test(field))
4160
+ throw sponsorError(
4161
+ "Paymaster returned an invalid final operation.",
4162
+ true,
4163
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INVALID_PAYMASTER_QUOTE" }
4164
+ );
4165
+ result[key2] = field;
4166
+ }
4167
+ return result;
4168
+ }
3932
4169
  async function requestIntent(input) {
3933
4170
  const baseUrl = (input.routingApiBaseUrl ?? DEFAULT_ROUTING_API).replace(/\/$/, "");
3934
4171
  let response;
@@ -3950,18 +4187,35 @@ async function requestIntent(input) {
3950
4187
  action: input.action,
3951
4188
  yieldseekerUserId: input.userId,
3952
4189
  yieldseekerAgentId: input.agentId
3953
- })
4190
+ }),
4191
+ signal: AbortSignal.timeout(15e3)
3954
4192
  });
3955
4193
  } catch {
3956
- throw sponsorError("Withdrawal sponsorship could not be requested.", true);
4194
+ throw sponsorError(
4195
+ "Withdrawal sponsorship could not be requested.",
4196
+ true,
4197
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INTENT_NETWORK_ERROR" }
4198
+ );
3957
4199
  }
3958
4200
  const envelope = await response.json().catch(() => null);
3959
4201
  const intent = envelope?.data;
3960
4202
  if (!response.ok || !envelope?.success || !intent?.intent || !["eoa", "smart"].includes(intent.ownerPath)) {
4203
+ const rawCode = typeof envelope?.failureCode === "string" ? envelope.failureCode : envelope?.code;
4204
+ const failureCode = typeof rawCode === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(rawCode) ? rawCode : void 0;
4205
+ if (input.refreshYieldseekerSignature && (failureCode === "YIELDSEEKER_AUTH_REJECTED" || failureCode === "AUTH_REJECTED" || envelope?.sponsorshipStage === "provider-auth" && (envelope.providerStatus === 401 || envelope.providerStatus === 403))) {
4206
+ const signature = await input.refreshYieldseekerSignature(input.yieldseekerSignature);
4207
+ return requestIntent({ ...input, yieldseekerSignature: signature, refreshYieldseekerSignature: void 0 });
4208
+ }
3961
4209
  throw sponsorError(
3962
4210
  "Withdrawal sponsorship is currently unavailable.",
3963
4211
  true,
3964
- { statusCode: response.status }
4212
+ {
4213
+ statusCode: response.status,
4214
+ phase: "prepare",
4215
+ rejectedBeforeSubmission: true,
4216
+ failureCode: failureCode ?? "INTENT_REJECTED",
4217
+ providerReason: providerReason(envelope?.message)
4218
+ }
3965
4219
  );
3966
4220
  }
3967
4221
  const url = new URL(`${baseUrl}/api/v1/sponsor/yieldseeker-withdraw/rpc`);
@@ -3974,19 +4228,55 @@ async function rpc(url, method, params, phase) {
3974
4228
  response = await fetch(url, {
3975
4229
  method: "POST",
3976
4230
  headers: { "content-type": "application/json" },
3977
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
4231
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
4232
+ signal: AbortSignal.timeout(15e3)
3978
4233
  });
3979
4234
  } catch {
3980
- throw sponsorError("Withdrawal sponsorship request status is unknown.", phase === "prepare");
4235
+ throw sponsorError(
4236
+ "Withdrawal sponsorship request status is unknown.",
4237
+ phase === "prepare",
4238
+ {
4239
+ phase,
4240
+ rpcMethod: method,
4241
+ ...phase === "prepare" ? { rejectedBeforeSubmission: true } : {},
4242
+ failureCode: "RPC_NETWORK_ERROR"
4243
+ }
4244
+ );
3981
4245
  }
3982
4246
  const payload = await response.json().catch(() => null);
3983
4247
  if (!response.ok || payload?.error || payload?.result === void 0) {
3984
4248
  const rejection = payload?.error;
3985
- const definitiveRejection = phase === "send" && response.status < 500 && !!rejection && (rejection.data?.rejectedBeforeSubmission === true || [-32501, -32502, -32503, -32504, -32505, -32507].includes(rejection.code ?? 0) || /maxFeePerGas.*(?:too low|lower than|less than|must be at least)/i.test(rejection.message ?? ""));
4249
+ const diagnostics = rpcDiagnostics(rejection?.data);
4250
+ const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
4251
+ const details = {
4252
+ statusCode: response.status,
4253
+ rpcCode: rejection?.code,
4254
+ rpcMethod: method,
4255
+ phase,
4256
+ providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
4257
+ failureCode: "RPC_REJECTED",
4258
+ ...diagnostics
4259
+ };
4260
+ if (phase === "prepare" && executionReverted)
4261
+ throw new OwneyError(
4262
+ "WITHDRAW_FAILED",
4263
+ "Yieldseeker withdrawal simulation reverted before submission.",
4264
+ {
4265
+ ...details,
4266
+ failureCode: "SPONSOR_EXECUTION_REVERTED",
4267
+ safeToFallback: false,
4268
+ rejectedBeforeSubmission: true
4269
+ },
4270
+ "yieldseeker"
4271
+ );
4272
+ const definitiveRejection = phase === "send" && !executionReverted && response.status < 500 && !!rejection && (rejection.data?.rejectedBeforeSubmission === true || [-32501, -32502, -32503, -32504, -32505, -32507].includes(rejection.code ?? 0) || /maxFeePerGas.*(?:too low|lower than|less than|must be at least)/i.test(rejection.message ?? ""));
3986
4273
  throw sponsorError(
3987
4274
  phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
3988
4275
  phase === "prepare" || definitiveRejection,
3989
- { statusCode: response.status, rpcCode: payload?.error?.code, rpcMethod: method }
4276
+ {
4277
+ ...details,
4278
+ ...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
4279
+ }
3990
4280
  );
3991
4281
  }
3992
4282
  return payload.result;
@@ -4006,13 +4296,15 @@ async function submitEoa(input) {
4006
4296
  let nonce;
4007
4297
  let fees;
4008
4298
  try {
4009
- nonce = await input.client.readContract({
4010
- address: ENTRY_POINT_V06,
4011
- abi: ENTRY_POINT_NONCE_ABI,
4012
- functionName: "getNonce",
4013
- args: [input.agentWallet, 0n]
4014
- });
4015
- fees = await input.client.estimateFeesPerGas();
4299
+ [nonce, fees] = await Promise.all([
4300
+ input.client.readContract({
4301
+ address: ENTRY_POINT_V06,
4302
+ abi: ENTRY_POINT_NONCE_ABI,
4303
+ functionName: "getNonce",
4304
+ args: [input.agentWallet, 0n]
4305
+ }),
4306
+ input.client.estimateFeesPerGas()
4307
+ ]);
4016
4308
  } catch {
4017
4309
  throw sponsorError("Could not prepare the sponsored withdrawal.", true);
4018
4310
  }
@@ -4055,9 +4347,10 @@ async function submitEoa(input) {
4055
4347
  );
4056
4348
  if (!final.paymasterAndData?.startsWith("0x"))
4057
4349
  throw sponsorError("CDP did not return final paymaster data.", true);
4058
- operation = { ...operation, paymasterAndData: final.paymasterAndData };
4350
+ operation = { ...operation, ...finalPaymasterFields(final) };
4059
4351
  }
4060
4352
  const userOpHash = operationHash(operation);
4353
+ await assertOwnerSession(input.state, input.owner);
4061
4354
  const wallet = createWalletClient2({
4062
4355
  account: input.owner,
4063
4356
  chain: base3,
@@ -4065,24 +4358,47 @@ async function submitEoa(input) {
4065
4358
  });
4066
4359
  const signature = await wallet.signMessage({ message: { raw: userOpHash } });
4067
4360
  operation = { ...operation, signature };
4068
- input.onPending?.({ userOpHash });
4361
+ await assertOwnerSession(input.state, input.owner);
4362
+ const userOperation = {
4363
+ sender: operation.sender,
4364
+ nonce: operation.nonce,
4365
+ initCode: operation.initCode,
4366
+ callData: operation.callData,
4367
+ callGasLimit: operation.callGasLimit,
4368
+ verificationGasLimit: operation.verificationGasLimit,
4369
+ preVerificationGas: operation.preVerificationGas,
4370
+ maxFeePerGas: operation.maxFeePerGas,
4371
+ maxPriorityFeePerGas: operation.maxPriorityFeePerGas,
4372
+ paymasterAndData: operation.paymasterAndData
4373
+ };
4374
+ savePending(input.onPending, { userOpHash, userOperation }, true);
4375
+ await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
4069
4376
  const submittedHash = await rpc(
4070
4377
  input.rpcUrl,
4071
4378
  "eth_sendUserOperation",
4072
4379
  [operation, ENTRY_POINT_V06],
4073
4380
  "send"
4074
4381
  );
4075
- if (submittedHash.toLowerCase() !== userOpHash.toLowerCase())
4382
+ if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
4076
4383
  throw sponsorError(
4077
4384
  "Bundler returned a different UserOperation hash.",
4078
4385
  false,
4079
- { userOpHash, submittedHash }
4386
+ { userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
4080
4387
  );
4081
- for (let poll = 0; poll < 40; poll++) {
4082
- const response = await rpc(input.rpcUrl, "eth_getUserOperationReceipt", [userOpHash], "observe");
4388
+ const deadline = Date.now() + OBSERVATION_WINDOW_MS;
4389
+ let lastReadError;
4390
+ for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
4391
+ let response;
4392
+ try {
4393
+ response = await rpc(input.rpcUrl, "eth_getUserOperationReceipt", [userOpHash], "observe");
4394
+ } catch (error) {
4395
+ lastReadError = error;
4396
+ await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
4397
+ continue;
4398
+ }
4083
4399
  const txHash = response?.receipt?.transactionHash;
4084
- if (txHash) {
4085
- input.onPending?.({ userOpHash, txHash });
4400
+ if (isHash(txHash)) {
4401
+ savePending(input.onPending, { userOpHash, txHash });
4086
4402
  if (response?.success === false)
4087
4403
  throw new OwneyError(
4088
4404
  "AGENT_TRANSACTION_REVERTED",
@@ -4092,12 +4408,17 @@ async function submitEoa(input) {
4092
4408
  );
4093
4409
  return { txHash, receipt: await waitForTransaction(input.client, txHash) };
4094
4410
  }
4095
- await new Promise((resolve) => setTimeout(resolve, 1500));
4411
+ await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
4096
4412
  }
4097
4413
  throw sponsorError(
4098
4414
  "Sponsored withdrawal is still pending; check status before retrying.",
4099
4415
  false,
4100
- { userOpHash }
4416
+ {
4417
+ userOpHash,
4418
+ phase: "observe",
4419
+ failureCode: "RECEIPT_UNRESOLVED",
4420
+ ...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
4421
+ }
4101
4422
  );
4102
4423
  }
4103
4424
  async function submitSmart(input) {
@@ -4110,14 +4431,14 @@ async function submitSmart(input) {
4110
4431
  } catch {
4111
4432
  throw sponsorError("This account cannot check paymaster support on Base.", true);
4112
4433
  }
4113
- if (!capabilities?.[toHex(8453)]?.paymasterService?.supported && !capabilities?.["8453"]?.paymasterService?.supported)
4434
+ const supportsPaymaster = capabilities?.[toHex(8453)]?.paymasterService?.supported ?? capabilities?.["8453"]?.paymasterService?.supported ?? capabilities?.["0x0"]?.paymasterService?.supported;
4435
+ if (supportsPaymaster !== true)
4114
4436
  throw sponsorError("This account cannot use a paymaster on Base.", true);
4437
+ await assertOwnerSession(input.state, input.owner);
4115
4438
  let sent;
4116
- try {
4117
- input.onPending?.({});
4118
- } catch {
4119
- throw sponsorError("Upgrade or withdrawal status cannot be saved.", true);
4120
- }
4439
+ const requestedId = toHex(globalThis.crypto.getRandomValues(new Uint8Array(32)));
4440
+ savePending(input.onPending, { callsId: requestedId }, true);
4441
+ await checkpoint(input.onBeforeSubmission, { callsId: requestedId });
4121
4442
  try {
4122
4443
  sent = await input.state.provider.request({
4123
4444
  method: "wallet_sendCalls",
@@ -4125,6 +4446,7 @@ async function submitSmart(input) {
4125
4446
  version: "2.0.0",
4126
4447
  from: input.owner,
4127
4448
  chainId: toHex(8453),
4449
+ id: requestedId,
4128
4450
  atomicRequired: false,
4129
4451
  calls: [{ to: input.agentWallet, value: "0x0", data: input.callData }],
4130
4452
  capabilities: { paymasterService: { url: input.rpcUrl } }
@@ -4133,17 +4455,27 @@ async function submitSmart(input) {
4133
4455
  } catch (error) {
4134
4456
  if (error?.code === 4001) throw error;
4135
4457
  if ([4200, -32601].includes(Number(error?.code)))
4136
- throw sponsorError("This account does not support sponsored calls.", true);
4137
- throw sponsorError("Wallet submission status is unknown. Check activity before retrying.", false);
4458
+ throw sponsorError(
4459
+ "This account does not support sponsored calls.",
4460
+ true,
4461
+ { phase: "send", failureCode: "WALLET_CALLS_UNSUPPORTED" }
4462
+ );
4463
+ throw sponsorError(
4464
+ "Wallet submission status is unknown. Check activity before retrying.",
4465
+ false,
4466
+ { callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
4467
+ );
4138
4468
  }
4139
4469
  const callsId = typeof sent === "string" ? sent : sent?.id;
4140
- if (!callsId) throw sponsorError("Wallet returned no calls ID; submission status is unknown.", false);
4141
- try {
4142
- input.onPending?.({ callsId });
4143
- } catch {
4144
- throw sponsorError("Submitted call status could not be saved.", false, { callsId });
4145
- }
4146
- for (let poll = 0; poll < 40; poll++) {
4470
+ if (typeof callsId !== "string" || callsId.length === 0 || callsId.length > 8194)
4471
+ throw sponsorError(
4472
+ "Wallet returned no calls ID; submission status is unknown.",
4473
+ false,
4474
+ { callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
4475
+ );
4476
+ savePending(input.onPending, { callsId });
4477
+ const deadline = Date.now() + OBSERVATION_WINDOW_MS;
4478
+ for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
4147
4479
  let status;
4148
4480
  try {
4149
4481
  status = await input.state.provider.request({
@@ -4151,33 +4483,40 @@ async function submitSmart(input) {
4151
4483
  params: [callsId]
4152
4484
  });
4153
4485
  } catch {
4154
- throw sponsorError("Sponsored withdrawal status is unknown.", false, { callsId });
4486
+ await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
4487
+ continue;
4155
4488
  }
4156
- if (status?.status === 400 || status?.status === 500)
4489
+ if (status?.id !== void 0 && status.id !== callsId || status?.chainId !== void 0 && Number(status.chainId) !== 8453)
4490
+ throw sponsorError(
4491
+ "Wallet returned a different sponsored operation status.",
4492
+ false,
4493
+ { callsId, phase: "observe", failureCode: "WALLET_STATUS_MISMATCH" }
4494
+ );
4495
+ if (Number(status?.status) === 400 || Number(status?.status) === 500)
4157
4496
  throw sponsorError(
4158
4497
  "Sponsored withdrawal failed in the wallet.",
4159
4498
  false,
4160
- { callsId, executionFailed: true }
4499
+ { callsId, executionFailed: true, phase: "observe", failureCode: "WALLET_EXECUTION_FAILED" }
4161
4500
  );
4162
- const txHash = status?.receipts?.[0]?.transactionHash;
4501
+ const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash).filter(isHash))];
4502
+ const txHash = hashes.length === 1 ? hashes[0] : void 0;
4163
4503
  if (txHash) {
4164
- try {
4165
- input.onPending?.({ callsId, txHash });
4166
- } catch {
4167
- throw sponsorError(
4168
- "Submitted call receipt could not be saved.",
4169
- false,
4170
- { callsId, txHash }
4504
+ savePending(input.onPending, { callsId, txHash });
4505
+ if (status?.receipts?.some((receipt) => receipt.transactionHash === txHash && /^(?:0x0+|0|failed|reverted)$/i.test(String(receipt.status))))
4506
+ throw new OwneyError(
4507
+ "AGENT_TRANSACTION_REVERTED",
4508
+ "Sponsored withdrawal execution reverted.",
4509
+ { callsId, transactionHash: txHash },
4510
+ "yieldseeker"
4171
4511
  );
4172
- }
4173
4512
  return { txHash, receipt: await waitForTransaction(input.client, txHash) };
4174
4513
  }
4175
- await new Promise((resolve) => setTimeout(resolve, 1500));
4514
+ await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
4176
4515
  }
4177
4516
  throw sponsorError(
4178
4517
  "Sponsored withdrawal is still pending; check status before retrying.",
4179
4518
  false,
4180
- { callsId }
4519
+ { callsId, phase: "observe", failureCode: "RECEIPT_UNRESOLVED" }
4181
4520
  );
4182
4521
  }
4183
4522
  async function submitYieldseekerSponsoredWithdrawal(input) {
@@ -4192,7 +4531,8 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
4192
4531
  agentWallet: input.agentWallet,
4193
4532
  asset: input.asset,
4194
4533
  callData: input.callData,
4195
- action: "withdraw"
4534
+ action: "withdraw",
4535
+ refreshYieldseekerSignature: input.refreshYieldseekerSignature
4196
4536
  });
4197
4537
  const args = { ...input, owner, rpcUrl };
4198
4538
  return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
@@ -4209,7 +4549,8 @@ async function submitYieldseekerSponsoredUpgrade(input) {
4209
4549
  agentWallet: input.agentWallet,
4210
4550
  asset: input.asset,
4211
4551
  callData: input.callData,
4212
- action: "upgrade"
4552
+ action: "upgrade",
4553
+ refreshYieldseekerSignature: input.refreshYieldseekerSignature
4213
4554
  });
4214
4555
  if (intent.ownerPath !== "smart")
4215
4556
  throw sponsorError("V1 upgrade sponsorship requires a Base Account owner.", true);
@@ -4231,6 +4572,7 @@ var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
4231
4572
  var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
4232
4573
  var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
4233
4574
  var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
4575
+ var YIELDSEEKER_MAX_POSITION_VERIFICATION_MS = 2e3;
4234
4576
  function generateYieldseekerUsername() {
4235
4577
  const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
4236
4578
  return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
@@ -4241,6 +4583,13 @@ function isUsernameConflict(error) {
4241
4583
  return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
4242
4584
  }
4243
4585
  var YIELDSEEKER_AGENT_WALLET_ABI = [
4586
+ {
4587
+ type: "function",
4588
+ name: "feeTracker",
4589
+ stateMutability: "view",
4590
+ inputs: [],
4591
+ outputs: [{ name: "", type: "address" }]
4592
+ },
4244
4593
  {
4245
4594
  type: "function",
4246
4595
  name: "withdrawAssetToUser",
@@ -4263,6 +4612,13 @@ var YIELDSEEKER_AGENT_WALLET_ABI = [
4263
4612
  outputs: []
4264
4613
  }
4265
4614
  ];
4615
+ var YIELDSEEKER_FEE_TRACKER_ABI = [{
4616
+ type: "function",
4617
+ name: "getFeesOwed",
4618
+ stateMutability: "view",
4619
+ inputs: [{ name: "wallet", type: "address" }],
4620
+ outputs: [{ name: "", type: "uint256" }]
4621
+ }];
4266
4622
  function query(params) {
4267
4623
  const search = new URLSearchParams();
4268
4624
  for (const [key2, value] of Object.entries(params)) {
@@ -4346,17 +4702,22 @@ var YieldseekerAgent = class {
4346
4702
  getAddress4(state.walletAddress),
4347
4703
  YIELDSEEKER_ASSET_METADATA[asset].address
4348
4704
  );
4705
+ this.rememberWalletStatus(key2, status);
4706
+ return status;
4707
+ }
4708
+ rememberWalletStatus(key2, status) {
4349
4709
  if (status.verified && status.version === "v2") {
4350
4710
  this.withdrawalVersionCache.set(key2, { at: Date.now(), status: { ...status } });
4351
4711
  if (this.withdrawalVersionCache.size > 128)
4352
4712
  this.withdrawalVersionCache.delete(this.withdrawalVersionCache.keys().next().value);
4353
4713
  } else this.withdrawalVersionCache.delete(key2);
4354
- return status;
4355
4714
  }
4356
4715
  /** Explicit owner action. This never runs as a side effect of withdrawal. */
4357
4716
  async upgradeWallet(state, chainId, asset, gasMode = "paid") {
4717
+ const generation = this.readGeneration;
4358
4718
  const before = await this.getWalletStatus(state, chainId, asset);
4359
- if (!before?.verified || !before.upgradeAvailable) {
4719
+ this.assertSession(generation, chainId);
4720
+ if (!before?.verified || before.version === "unknown") {
4360
4721
  throw new OwneyError(
4361
4722
  "WITHDRAW_FAILED",
4362
4723
  "This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
@@ -4364,48 +4725,77 @@ var YieldseekerAgent = class {
4364
4725
  this.id
4365
4726
  );
4366
4727
  }
4367
- const data = encodeFunctionData({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
4368
- let txHash;
4369
- if (gasMode === "sponsored") {
4370
- if (before.ownerPath !== "smart")
4371
- throw new OwneyError(
4372
- "SPONSOR_REQUEST_FAILED",
4373
- "EOA owners pay Base gas for the V1 upgrade.",
4374
- { safeToFallback: true },
4375
- this.id
4376
- );
4377
- let storage5;
4378
- try {
4379
- storage5 = typeof window === "undefined" ? void 0 : window.localStorage;
4380
- } catch {
4381
- storage5 = void 0;
4382
- }
4383
- if (!storage5)
4728
+ const versionKey = `${chainId}:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}:${asset}`;
4729
+ const pendingKey2 = `owney.yieldseeker.upgrade:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
4730
+ const confirmUpgrade = async (hash, receipt2) => {
4731
+ this.withdrawalVersionCache.delete(versionKey);
4732
+ const upgradeTopic = encodeEventTopics({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded" })[0];
4733
+ const lastUpgrade = receipt2?.logs.filter((log) => log.address.toLowerCase() === before.agentWallet.toLowerCase() && log.topics[0] === upgradeTopic).at(-1);
4734
+ const expectedUpgrade = (() => {
4735
+ if (!lastUpgrade || lastUpgrade.removed) return false;
4736
+ try {
4737
+ const event = decodeEventLog2({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded", data: lastUpgrade.data, topics: lastUpgrade.topics });
4738
+ return event.args.implementation.toLowerCase() === YIELDSEEKER_V2_IMPLEMENTATION.toLowerCase();
4739
+ } catch {
4740
+ return false;
4741
+ }
4742
+ })();
4743
+ if (!receipt2 || receipt2.status !== "success" || receipt2.transactionHash.toLowerCase() !== hash.toLowerCase() || typeof receipt2.blockNumber !== "bigint" || !expectedUpgrade)
4384
4744
  throw new OwneyError(
4385
- "SPONSOR_REQUEST_FAILED",
4386
- "Upgrade status cannot be saved in this browser.",
4387
- { safeToFallback: true },
4745
+ "WITHDRAW_FAILED",
4746
+ "Yieldseeker wallet upgrade receipt could not be verified.",
4747
+ { txHash: hash, reason: "UPGRADE_RECEIPT_MISMATCH" },
4388
4748
  this.id
4389
4749
  );
4390
- const pendingKey = `owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
4391
- let prior = null;
4392
- let saved;
4750
+ this.assertSession(generation, chainId);
4751
+ const status = {
4752
+ ...before,
4753
+ implementation: YIELDSEEKER_V2_IMPLEMENTATION,
4754
+ version: "v2",
4755
+ upgradeAvailable: false
4756
+ };
4757
+ this.rememberWalletStatus(versionKey, status);
4758
+ return { txHash: hash, status };
4759
+ };
4760
+ let storage5;
4761
+ try {
4762
+ storage5 = typeof window === "undefined" ? void 0 : window.localStorage;
4763
+ } catch {
4764
+ storage5 = void 0;
4765
+ }
4766
+ const clearConfirmedUpgrade = () => {
4393
4767
  try {
4394
- saved = storage5.getItem(pendingKey);
4768
+ storage5?.removeItem(pendingKey2);
4395
4769
  } catch {
4396
- throw new OwneyError(
4397
- "SPONSOR_REQUEST_FAILED",
4398
- "Upgrade status cannot be read in this browser.",
4399
- { safeToFallback: true },
4400
- this.id
4401
- );
4402
4770
  }
4771
+ };
4772
+ if (!storage5 && (gasMode === "sponsored" || before.ownerPath === "smart"))
4773
+ throw new OwneyError(
4774
+ "SPONSOR_REQUEST_FAILED",
4775
+ "Upgrade status cannot be saved in this browser.",
4776
+ { safeToFallback: false },
4777
+ this.id
4778
+ );
4779
+ let prior = null;
4780
+ try {
4781
+ const saved = storage5?.getItem(pendingKey2);
4403
4782
  try {
4404
4783
  prior = JSON.parse(saved ?? "null");
4405
4784
  } catch {
4406
4785
  prior = { callsId: void 0 };
4407
4786
  }
4408
- if (prior) {
4787
+ } catch {
4788
+ if (gasMode === "paid" && before.ownerPath === "eoa") storage5 = void 0;
4789
+ else throw new OwneyError(
4790
+ "SPONSOR_REQUEST_FAILED",
4791
+ "Upgrade status cannot be read in this browser.",
4792
+ { safeToFallback: false },
4793
+ this.id
4794
+ );
4795
+ }
4796
+ if (prior) {
4797
+ let earlierHash = this.isTransactionHash(prior.txHash) ? prior.txHash : void 0;
4798
+ if (!earlierHash) {
4409
4799
  if (!prior.callsId)
4410
4800
  throw new OwneyError(
4411
4801
  "SPONSOR_REQUEST_FAILED",
@@ -4415,10 +4805,7 @@ var YieldseekerAgent = class {
4415
4805
  );
4416
4806
  let callsStatus;
4417
4807
  try {
4418
- callsStatus = await state.provider.request({
4419
- method: "wallet_getCallsStatus",
4420
- params: [prior.callsId]
4421
- });
4808
+ callsStatus = await state.provider.request({ method: "wallet_getCallsStatus", params: [prior.callsId] });
4422
4809
  } catch {
4423
4810
  throw new OwneyError(
4424
4811
  "SPONSOR_REQUEST_FAILED",
@@ -4427,8 +4814,15 @@ var YieldseekerAgent = class {
4427
4814
  this.id
4428
4815
  );
4429
4816
  }
4430
- if (callsStatus?.status === 400 || callsStatus?.status === 500) {
4431
- storage5.removeItem(pendingKey);
4817
+ if (callsStatus?.id !== void 0 && callsStatus.id !== prior.callsId || callsStatus?.chainId !== void 0 && Number(callsStatus.chainId) !== chainId)
4818
+ throw new OwneyError(
4819
+ "SPONSOR_REQUEST_FAILED",
4820
+ "The earlier upgrade returned a different operation status.",
4821
+ { safeToFallback: false, callsId: prior.callsId },
4822
+ this.id
4823
+ );
4824
+ if (Number(callsStatus?.status) === 400 || Number(callsStatus?.status) === 500) {
4825
+ storage5.removeItem(pendingKey2);
4432
4826
  throw new OwneyError(
4433
4827
  "SPONSOR_REQUEST_FAILED",
4434
4828
  "The earlier sponsored upgrade failed.",
@@ -4436,22 +4830,39 @@ var YieldseekerAgent = class {
4436
4830
  this.id
4437
4831
  );
4438
4832
  }
4439
- const earlierHash = callsStatus?.receipts?.[0]?.transactionHash;
4440
- if (earlierHash) {
4441
- await this.waitForReceipt(state, chainId, earlierHash);
4442
- const earlierStatus = await this.getWalletStatus(state, chainId, asset);
4443
- if (earlierStatus?.version === "v2" && earlierStatus.verified) {
4444
- storage5.removeItem(pendingKey);
4445
- return { txHash: earlierHash, status: earlierStatus };
4446
- }
4447
- }
4833
+ const hashes = [...new Set((callsStatus?.receipts ?? []).map((value) => value.transactionHash).filter(this.isTransactionHash))];
4834
+ earlierHash = hashes.length === 1 ? hashes[0] : void 0;
4835
+ }
4836
+ if (earlierHash) {
4837
+ const recovered = await confirmUpgrade(earlierHash, await this.waitForReceipt(state, chainId, earlierHash));
4838
+ clearConfirmedUpgrade();
4839
+ return recovered;
4840
+ }
4841
+ throw new OwneyError(
4842
+ "SPONSOR_REQUEST_FAILED",
4843
+ "An earlier upgrade is still pending.",
4844
+ { safeToFallback: false, callsId: prior.callsId },
4845
+ this.id
4846
+ );
4847
+ }
4848
+ if (!before.upgradeAvailable)
4849
+ throw new OwneyError(
4850
+ "WITHDRAW_FAILED",
4851
+ "This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
4852
+ { version: before.version },
4853
+ this.id
4854
+ );
4855
+ const data = encodeFunctionData({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
4856
+ let txHash;
4857
+ let receipt;
4858
+ if (gasMode === "sponsored") {
4859
+ if (before.ownerPath !== "smart")
4448
4860
  throw new OwneyError(
4449
4861
  "SPONSOR_REQUEST_FAILED",
4450
- "An earlier upgrade is still pending.",
4451
- { safeToFallback: false, callsId: prior.callsId },
4862
+ "EOA owners pay Base gas for the V1 upgrade.",
4863
+ { safeToFallback: true },
4452
4864
  this.id
4453
4865
  );
4454
- }
4455
4866
  const context = await this.findAgent(state, chainId, asset);
4456
4867
  if (!context) throw new OwneyError("NOT_CONNECTED", "Yieldseeker agent is unavailable.");
4457
4868
  try {
@@ -4461,14 +4872,15 @@ var YieldseekerAgent = class {
4461
4872
  apiKey: this.owneyApiKey,
4462
4873
  routingApiBaseUrl: this.routingApiBaseUrl,
4463
4874
  yieldseekerSignature: await this.auth.getToken(state, chainId),
4875
+ refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
4464
4876
  userId: context.user.userId,
4465
4877
  agentId: context.agent.agentId,
4466
4878
  agentWallet: before.agentWallet,
4467
4879
  asset: YIELDSEEKER_ASSET_METADATA[asset].address,
4468
4880
  callData: data,
4469
4881
  onPending: (pending) => {
4470
- const current = storage5.getItem(pendingKey);
4471
- storage5.setItem(pendingKey, JSON.stringify({
4882
+ const current = storage5.getItem(pendingKey2);
4883
+ storage5.setItem(pendingKey2, JSON.stringify({
4472
4884
  startedAt: Date.now(),
4473
4885
  ...current ? JSON.parse(current) : {},
4474
4886
  ...pending
@@ -4476,8 +4888,9 @@ var YieldseekerAgent = class {
4476
4888
  }
4477
4889
  });
4478
4890
  txHash = upgraded.txHash;
4891
+ receipt = upgraded.receipt;
4479
4892
  } catch (error) {
4480
- if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey);
4893
+ if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey2);
4481
4894
  throw error;
4482
4895
  }
4483
4896
  } else {
@@ -4487,20 +4900,13 @@ var YieldseekerAgent = class {
4487
4900
  data,
4488
4901
  value: "0",
4489
4902
  chainId
4903
+ }, void 0, (value) => {
4904
+ receipt = value;
4490
4905
  });
4491
4906
  }
4492
- const status = await this.getWalletStatus(state, chainId, asset);
4493
- if (!status?.verified || status.version !== "v2") {
4494
- throw new OwneyError(
4495
- "WITHDRAW_FAILED",
4496
- "Yieldseeker wallet upgrade was not confirmed as V2.",
4497
- { txHash, version: status?.version ?? "missing" },
4498
- this.id
4499
- );
4500
- }
4501
- if (gasMode === "sponsored" && typeof window !== "undefined")
4502
- window.localStorage.removeItem(`owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`);
4503
- return { txHash, status };
4907
+ const confirmed = await confirmUpgrade(txHash, receipt ?? await this.waitForReceipt(state, chainId, txHash));
4908
+ if (gasMode === "sponsored") clearConfirmedUpgrade();
4909
+ return confirmed;
4504
4910
  }
4505
4911
  async disconnect() {
4506
4912
  this.withdrawalVersionCache.clear();
@@ -4573,19 +4979,37 @@ var YieldseekerAgent = class {
4573
4979
  submitted(txHash);
4574
4980
  receipt = await this.waitForReceipt(state, chainId, txHash);
4575
4981
  } else {
4576
- txHash = await this.submitTransaction(state, chainId, {
4577
- from: getAddress4(state.walletAddress),
4578
- to: YIELDSEEKER_ASSET_METADATA[asset].address,
4579
- data: encodeFunctionData({
4580
- abi: erc20Abi2,
4581
- functionName: "transfer",
4582
- args: [getAddress4(context.wallet.walletAddress), BigInt(amount)]
4583
- }),
4584
- value: "0",
4585
- chainId
4586
- }, submitted, (value) => {
4587
- receipt = value;
4588
- });
4982
+ try {
4983
+ txHash = await this.submitTransaction(state, chainId, {
4984
+ from: getAddress4(state.walletAddress),
4985
+ to: YIELDSEEKER_ASSET_METADATA[asset].address,
4986
+ data: encodeFunctionData({
4987
+ abi: erc20Abi3,
4988
+ functionName: "transfer",
4989
+ args: [getAddress4(context.wallet.walletAddress), BigInt(amount)]
4990
+ }),
4991
+ value: "0",
4992
+ chainId
4993
+ }, submitted, (value) => {
4994
+ receipt = value;
4995
+ });
4996
+ } catch (error) {
4997
+ if (error instanceof OwneyError && error.details?.ownerTransferNotExecuted === true && error.details.reason === "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL") {
4998
+ notifyFunding(onProgress, {
4999
+ agentId: this.id,
5000
+ chainId,
5001
+ token: asset,
5002
+ walletAddress: state.walletAddress,
5003
+ agentWallet: context.wallet.walletAddress,
5004
+ intendedAmount: amount,
5005
+ txHash: String(error.details.originalTransactionHash),
5006
+ transfer: "failed",
5007
+ registration: "not-applicable",
5008
+ delivery: "not-applicable"
5009
+ });
5010
+ }
5011
+ throw error;
5012
+ }
4589
5013
  }
4590
5014
  if (this.readGeneration === generation) {
4591
5015
  void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
@@ -4595,7 +5019,7 @@ var YieldseekerAgent = class {
4595
5019
  notifyFunding(onProgress, evidence);
4596
5020
  return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
4597
5021
  }
4598
- async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto") {
5022
+ async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
4599
5023
  let ownerTransferAttempted = false;
4600
5024
  let evidence = {
4601
5025
  agentId: this.id,
@@ -4613,10 +5037,10 @@ var YieldseekerAgent = class {
4613
5037
  notifyFunding(onProgress, evidence);
4614
5038
  }, () => {
4615
5039
  ownerTransferAttempted = true;
4616
- }, gasMode);
5040
+ }, gasMode, onBeforeSubmission);
4617
5041
  } catch (error) {
4618
5042
  if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
4619
- if (!ownerTransferAttempted || error?.code === 4001 || error instanceof OwneyError && (error.code === "AGENT_TRANSACTION_REVERTED" || error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)))
5043
+ if (!ownerTransferAttempted || error?.code === 4001 || error instanceof OwneyError && (error.code === "AGENT_TRANSACTION_REVERTED" || error.details?.ownerTransferNotExecuted === true || error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)))
4620
5044
  notifyFunding(onProgress, {
4621
5045
  ...evidence,
4622
5046
  ...error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true ? { userOpHash: void 0, callsId: void 0 } : {},
@@ -4626,7 +5050,7 @@ var YieldseekerAgent = class {
4626
5050
  throw error;
4627
5051
  }
4628
5052
  }
4629
- async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto") {
5053
+ async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
4630
5054
  this.assertChain(chainId);
4631
5055
  this.assertAsset(asset);
4632
5056
  if (amount !== void 0 && BigInt(amount) <= 0n) {
@@ -4671,37 +5095,121 @@ var YieldseekerAgent = class {
4671
5095
  let confirmedMovement = false;
4672
5096
  let settlement = null;
4673
5097
  try {
4674
- if (amount === void 0) this.advancePortfolioVersion(this.contextKey(state, chainId, asset));
4675
- const portfolio = await this.loadPortfolioContext(
4676
- state,
4677
- chainId,
4678
- context
4679
- );
5098
+ 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 }));
4680
5101
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
4681
- const readIdle = async () => {
4682
- const balance = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
4683
- address: metadata.address,
4684
- abi: erc20Abi2,
4685
- functionName: "balanceOf",
4686
- args: [getAddress4(context.wallet.walletAddress)]
4687
- }), chainId, "eth_call", this.id);
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;
4688
5110
  this.assertSession(generation, chainId, asset);
4689
- return balance;
5111
+ if (!isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker))
5112
+ throw this.invalidResponse("wallet fee tracker");
5113
+ const [idle2, fees] = await Promise.all([
5114
+ withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5115
+ address: metadata.address,
5116
+ abi: erc20Abi3,
5117
+ functionName: "balanceOf",
5118
+ args: [agentWallet]
5119
+ }), chainId, "eth_call", this.id),
5120
+ withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5121
+ address: getAddress4(feeTracker),
5122
+ abi: YIELDSEEKER_FEE_TRACKER_ABI,
5123
+ functionName: "getFeesOwed",
5124
+ args: [agentWallet]
5125
+ }), chainId, "eth_call", this.id)
5126
+ ]);
5127
+ this.assertSession(generation, chainId, asset);
5128
+ if (typeof idle2 !== "bigint" || idle2 < 0n || typeof fees !== "bigint" || fees < 0n)
5129
+ throw this.invalidResponse("wallet withdrawal balance");
5130
+ return { idle: idle2, fees, available: idle2 > fees ? idle2 - fees : 0n };
4690
5131
  };
4691
- const idle = await readIdle();
5132
+ const [portfolio, initialFunds] = await Promise.all([portfolioRead, readOwnerFunds()]);
5133
+ const idle = initialFunds.idle;
4692
5134
  let positions = portfolio.positions;
4693
5135
  let positionsStatus = "empty";
4694
5136
  const validatePositions = (values) => {
4695
5137
  const seen = /* @__PURE__ */ new Set();
4696
5138
  for (const value of values) {
4697
5139
  const address = value?.yieldOption?.address;
4698
- if (typeof address !== "string" || !isAddress2(address) || seen.has(address.toLowerCase()) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
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))
4699
5141
  throw this.invalidResponse("yield positions");
4700
5142
  seen.add(address.toLowerCase());
4701
5143
  }
4702
5144
  return values;
4703
5145
  };
4704
- const hasPositions = (values) => values.some((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
5146
+ const zeroValuePositions = /* @__PURE__ */ new WeakSet();
5147
+ const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
5148
+ const knownPositions = /* @__PURE__ */ new Map();
5149
+ const rememberPositions = (values) => {
5150
+ for (const position2 of values) knownPositions.set(position2.yieldOption.address.toLowerCase(), position2);
5151
+ };
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
+ const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
5163
+ const verifiedPositions = [];
5164
+ if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
5165
+ const reads = values.map(async (position2) => {
5166
+ const address = getAddress4(position2.yieldOption.address);
5167
+ 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
+ ]);
5173
+ if (typeof underlying !== "string" || !isAddress3(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
5174
+ const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: withdrawable.toString() };
5175
+ if (shares > 0n && withdrawable === 0n) {
5176
+ let positiveValue = false;
5177
+ const valuations = await Promise.all(["convertToAssets", "previewRedeem"].map(async (functionName) => {
5178
+ try {
5179
+ const value = await this.getReceiptClient().readContract({
5180
+ address,
5181
+ abi: erc4626Abi,
5182
+ functionName,
5183
+ args: [shares],
5184
+ blockNumber
5185
+ });
5186
+ if (typeof value !== "bigint" || value < 0n) return null;
5187
+ if (value > 0n && !positiveValue) {
5188
+ positiveValue = true;
5189
+ verifiedPositions.push(verified);
5190
+ }
5191
+ return value;
5192
+ } catch {
5193
+ return null;
5194
+ }
5195
+ }));
5196
+ if (positiveValue) return verified;
5197
+ if (blockNumber === void 0 || valuations.some((value) => value !== 0n)) return null;
5198
+ zeroValuePositions.add(verified);
5199
+ }
5200
+ verifiedPositions.push(verified);
5201
+ return verified;
5202
+ } catch {
5203
+ return null;
5204
+ }
5205
+ });
5206
+ const result = await boundedVerification(Promise.all(reads), timeoutMs);
5207
+ this.assertSession(generation, chainId, asset);
5208
+ return {
5209
+ verified: result !== void 0 && result.every((position2) => position2 !== null),
5210
+ positions: [...verifiedPositions]
5211
+ };
5212
+ };
4705
5213
  const refreshPositions = async () => {
4706
5214
  this.assertSession(generation, chainId, asset);
4707
5215
  const response = await this.walletRequest(
@@ -4715,21 +5223,40 @@ var YieldseekerAgent = class {
4715
5223
  };
4716
5224
  if (amount === void 0) {
4717
5225
  validatePositions(positions);
5226
+ rememberPositions(positions);
4718
5227
  positionsStatus = hasPositions(positions) ? "remaining" : "empty";
4719
5228
  }
4720
5229
  const deployed = positions.reduce(
4721
5230
  (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
4722
5231
  0n
4723
5232
  );
4724
- const totalAvailable = idle + deployed;
5233
+ const grossAvailable = idle + deployed;
5234
+ const totalAvailable = grossAvailable > initialFunds.fees ? grossAvailable - initialFunds.fees : 0n;
4725
5235
  const requested = amount === void 0 ? totalAvailable : BigInt(amount);
4726
5236
  if (requested === 0n) {
4727
5237
  this.invalidateFundingRead(state, chainId, asset);
4728
- if (amount === void 0 && positionsStatus === "remaining")
5238
+ const zeroPayoutCandidates = positions.filter((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
5239
+ if (amount === void 0 && zeroPayoutCandidates.length) {
5240
+ const deadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
5241
+ const blockNumber = await boundedVerification(
5242
+ Promise.resolve().then(() => this.getReceiptClient().getBlockNumber()).catch(() => void 0)
5243
+ );
5244
+ const proof = typeof blockNumber === "bigint" ? await verifyKnownPositions(blockNumber, zeroPayoutCandidates, deadline - Date.now()) : { verified: false, positions: [] };
5245
+ positions = proof.positions;
5246
+ positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
5247
+ if (positionsStatus !== "empty")
5248
+ throw new OwneyError(
5249
+ "WITHDRAW_FAILED",
5250
+ "Yieldseeker vault funds are not currently withdrawable.",
5251
+ { asset, positionsStatus, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
5252
+ this.id
5253
+ );
5254
+ }
5255
+ if (grossAvailable > 0n && totalAvailable === 0n)
4729
5256
  throw new OwneyError(
4730
- "WITHDRAW_FAILED",
4731
- "Yieldseeker vault funds are not currently withdrawable.",
4732
- { asset, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
5257
+ "WITHDRAW_INSUFFICIENT_BALANCE",
5258
+ "No owner-withdrawable Yieldseeker funds remain after reserved fees.",
5259
+ { asset, available: "0", reason: "YIELDSEEKER_FEES_ONLY", feesReserved: initialFunds.fees.toString() },
4733
5260
  this.id
4734
5261
  );
4735
5262
  throw new OwneyError(
@@ -4751,28 +5278,50 @@ var YieldseekerAgent = class {
4751
5278
  this.id
4752
5279
  );
4753
5280
  }
4754
- let remaining = requested > idle ? requested - idle : 0n;
5281
+ const requiredIdle = requested + initialFunds.fees;
5282
+ let remaining = requiredIdle > idle ? requiredIdle - idle : 0n;
4755
5283
  let grossUnwound = 0n;
5284
+ let latestUnwindBlock;
4756
5285
  const unwind = async (position2, assetsRaw) => {
4757
5286
  this.assertSession(generation, chainId, asset);
4758
- const response = await this.walletRequest(
4759
- state,
4760
- chainId,
4761
- this.agentPath(context, "withdraw-from-position"),
4762
- {
4763
- method: "POST",
4764
- body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
4765
- }
4766
- );
4767
- if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
4768
- await this.waitForReceipt(state, chainId, response.transactionHash);
5287
+ let unwindTransactionHash;
5288
+ try {
5289
+ const response = await this.walletRequest(
5290
+ state,
5291
+ chainId,
5292
+ this.agentPath(context, "withdraw-from-position"),
5293
+ {
5294
+ method: "POST",
5295
+ body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
5296
+ }
5297
+ );
5298
+ if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
5299
+ unwindTransactionHash = response.transactionHash;
5300
+ const unwindReceipt = await this.waitForReceipt(state, chainId, unwindTransactionHash);
5301
+ if (unwindReceipt) latestUnwindBlock = unwindReceipt.blockNumber;
5302
+ } catch (error) {
5303
+ this.invalidateFundingRead(state, chainId, asset);
5304
+ throw new OwneyError(
5305
+ error instanceof OwneyError ? error.code : "WITHDRAW_FAILED",
5306
+ "Could not confirm the Yieldseeker vault release. No owner transfer was submitted.",
5307
+ {
5308
+ ...error instanceof OwneyError ? error.details : {},
5309
+ withdrawalPhase: "unwind",
5310
+ reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
5311
+ vaultAddress: position2.yieldOption.address,
5312
+ unwindTransactionHash,
5313
+ ownerTransferSubmitted: false
5314
+ },
5315
+ this.id
5316
+ );
5317
+ }
4769
5318
  confirmedMovement = true;
4770
5319
  this.assertSession(generation, chainId, asset);
4771
5320
  grossUnwound += assetsRaw;
4772
5321
  };
4773
5322
  if (amount === void 0) {
4774
5323
  for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
4775
- const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.assetsRaw)]));
5324
+ const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
4776
5325
  let unwound = false;
4777
5326
  for (const position2 of positions) {
4778
5327
  const available = BigInt(position2.withdrawableAssetsRaw);
@@ -4781,15 +5330,22 @@ var YieldseekerAgent = class {
4781
5330
  unwound = true;
4782
5331
  }
4783
5332
  if (!unwound) break;
4784
- try {
4785
- positions = await refreshPositions();
4786
- positionsStatus = hasPositions(positions) ? "remaining" : "empty";
4787
- } catch {
4788
- this.assertSession(generation, chainId, asset);
5333
+ const knownBeforeRefresh = new Set(knownPositions.keys());
5334
+ const [catalog, proof] = await Promise.all([
5335
+ boundedVerification(refreshPositions().catch(() => void 0)),
5336
+ verifyKnownPositions(latestUnwindBlock)
5337
+ ]);
5338
+ if (catalog) rememberPositions(catalog);
5339
+ this.assertSession(generation, chainId, asset);
5340
+ if (proof.verified) {
5341
+ positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.yieldOption.address.toLowerCase()))];
5342
+ } else if (catalog) positions = catalog;
5343
+ else {
4789
5344
  positionsStatus = "unavailable";
4790
5345
  break;
4791
5346
  }
4792
- if (positions.some((position2) => BigInt(position2.assetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.assetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
5347
+ positionsStatus = hasPositions(positions) ? "remaining" : "empty";
5348
+ if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
4793
5349
  }
4794
5350
  } else {
4795
5351
  for (const position2 of positions) {
@@ -4807,8 +5363,8 @@ var YieldseekerAgent = class {
4807
5363
  remaining: remaining.toString()
4808
5364
  });
4809
5365
  }
4810
- const availableForOwner = confirmedMovement ? await readIdle() : idle;
4811
- const feeRate = portfolio.snapshot.feeRateBps;
5366
+ const availableForOwner = confirmedMovement ? (await readOwnerFunds()).available : initialFunds.available;
5367
+ const feeRate = portfolio.feeRateBps;
4812
5368
  const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
4813
5369
  const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
4814
5370
  if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
@@ -4828,7 +5384,11 @@ var YieldseekerAgent = class {
4828
5384
  );
4829
5385
  }
4830
5386
  const payout = amount === void 0 || availableForOwner < requested ? availableForOwner : requested;
4831
- const plannedMovement = this.snapshotMovement(state, chainId, context, -payout);
5387
+ const plannedMovement = amount === void 0 && maxSettlementBasis ? {
5388
+ ...maxSettlementBasis,
5389
+ delta: maxSettlementBasis.delta - payout,
5390
+ expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
5391
+ } : this.snapshotMovement(state, chainId, context, -payout);
4832
5392
  const account = getAddress4(state.walletAddress);
4833
5393
  let receipt;
4834
5394
  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" };
@@ -4841,7 +5401,6 @@ var YieldseekerAgent = class {
4841
5401
  functionName: "withdrawAssetToUser",
4842
5402
  args: [account, metadata.address, payout]
4843
5403
  });
4844
- onOwnerTransferAttempted?.();
4845
5404
  let txHash;
4846
5405
  if (sponsor) {
4847
5406
  const submitted = await submitYieldseekerSponsoredWithdrawal({
@@ -4850,16 +5409,22 @@ var YieldseekerAgent = class {
4850
5409
  apiKey: this.owneyApiKey,
4851
5410
  routingApiBaseUrl: this.routingApiBaseUrl,
4852
5411
  yieldseekerSignature: await this.auth.getToken(state, chainId),
5412
+ refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
4853
5413
  userId: context.user.userId,
4854
5414
  agentId: context.agent.agentId,
4855
5415
  agentWallet: getAddress4(context.wallet.walletAddress),
4856
5416
  asset: metadata.address,
4857
5417
  callData: data,
4858
- onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending })
5418
+ onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending }),
5419
+ onBeforeSubmission: async (pending) => {
5420
+ await onBeforeSubmission?.({ ...base6, ...pending });
5421
+ onOwnerTransferAttempted?.();
5422
+ }
4859
5423
  });
4860
5424
  txHash = submitted.txHash;
4861
5425
  receipt = submitted.receipt;
4862
5426
  } else {
5427
+ onOwnerTransferAttempted?.();
4863
5428
  txHash = await this.submitTransaction(state, chainId, {
4864
5429
  from: account,
4865
5430
  to: getAddress4(context.wallet.walletAddress),
@@ -4874,14 +5439,26 @@ var YieldseekerAgent = class {
4874
5439
  settlement = plannedMovement;
4875
5440
  const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
4876
5441
  notifyFunding(onProgress, evidence);
5442
+ let completion;
4877
5443
  if (amount === void 0) {
4878
- try {
4879
- positions = await refreshPositions();
4880
- positionsStatus = hasPositions(positions) ? "remaining" : "empty";
4881
- } catch {
4882
- positionsStatus = "unavailable";
4883
- }
4884
- if (positionsStatus !== "empty") {
5444
+ const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
5445
+ const knownAtPayout = new Set(knownPositions.keys());
5446
+ const [catalogVerified, knownProof] = await Promise.all([
5447
+ boundedVerification(refreshPositions().then((values) => {
5448
+ rememberPositions(values);
5449
+ return true;
5450
+ }).catch(() => false)),
5451
+ verifyKnownPositions(receipt?.blockNumber)
5452
+ ]);
5453
+ const newPositions = [...knownPositions].filter(([address]) => !knownAtPayout.has(address)).map(([, position2]) => position2);
5454
+ const newProof = newPositions.length ? await verifyKnownPositions(receipt?.blockNumber, newPositions, verificationDeadline - Date.now()) : { verified: true, positions: [] };
5455
+ const proof = {
5456
+ verified: catalogVerified === true && knownProof.verified && newProof.verified,
5457
+ positions: [...knownProof.positions, ...newProof.positions]
5458
+ };
5459
+ positions = proof.positions;
5460
+ positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
5461
+ if (positionsStatus === "remaining") {
4885
5462
  const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
4886
5463
  throw new OwneyError(
4887
5464
  "WITHDRAW_PARTIAL_FAILURE",
@@ -4890,7 +5467,7 @@ var YieldseekerAgent = class {
4890
5467
  reason: "YIELDSEEKER_MAX_INCOMPLETE",
4891
5468
  asset,
4892
5469
  positionsStatus,
4893
- remainingPositions: positionsStatus === "remaining" ? positions.map((position2) => ({
5470
+ remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
4894
5471
  vaultAddress: position2.yieldOption.address,
4895
5472
  sharesRaw: position2.assetsRaw,
4896
5473
  withdrawableAmountRaw: position2.withdrawableAssetsRaw
@@ -4900,6 +5477,7 @@ var YieldseekerAgent = class {
4900
5477
  this.id
4901
5478
  );
4902
5479
  }
5480
+ if (!proof.verified) completion = "unverified";
4903
5481
  }
4904
5482
  if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
4905
5483
  throw new OwneyError(
@@ -4908,7 +5486,13 @@ var YieldseekerAgent = class {
4908
5486
  { asset, available: "0", txHash },
4909
5487
  this.id
4910
5488
  );
4911
- return { txHash, type: amount === void 0 ? "full" : "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
5489
+ return {
5490
+ txHash,
5491
+ type: amount === void 0 && !completion ? "full" : "partial",
5492
+ ...completion ? { completion } : {},
5493
+ amount: evidence.confirmedAmount ?? payout.toString(),
5494
+ evidence
5495
+ };
4912
5496
  } finally {
4913
5497
  if (confirmedMovement && this.readGeneration === generation) {
4914
5498
  void this.refreshSnapshotAfterMovement(
@@ -4955,6 +5539,23 @@ var YieldseekerAgent = class {
4955
5539
  if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
4956
5540
  return context.wallet.walletAddress;
4957
5541
  }
5542
+ async hasExistingDepositFunds(state, chainId, asset) {
5543
+ this.assertChain(chainId);
5544
+ this.assertAsset(asset);
5545
+ const generation = this.readGeneration;
5546
+ const context = await this.findAgent(state, chainId, asset);
5547
+ this.assertSession(generation, chainId, asset);
5548
+ if (!context) return void 0;
5549
+ const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5550
+ address: YIELDSEEKER_ASSET_METADATA[asset].address,
5551
+ abi: erc20Abi3,
5552
+ functionName: "balanceOf",
5553
+ args: [getAddress4(context.wallet.walletAddress)]
5554
+ }), chainId, "eth_call", this.id);
5555
+ this.assertSession(generation, chainId, asset);
5556
+ if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
5557
+ return idle > 0n ? true : void 0;
5558
+ }
4958
5559
  async getEarnings(state, chainId) {
4959
5560
  this.assertChain(chainId);
4960
5561
  return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
@@ -4985,7 +5586,7 @@ var YieldseekerAgent = class {
4985
5586
  );
4986
5587
  const vaultAddresses = new Set(
4987
5588
  catalog.flat().filter(
4988
- (yieldOption) => yieldOption.chainId === chainId && isAddress2(yieldOption.address)
5589
+ (yieldOption) => yieldOption.chainId === chainId && isAddress3(yieldOption.address)
4989
5590
  ).map((yieldOption) => yieldOption.address.toLowerCase())
4990
5591
  );
4991
5592
  return mapYieldseekerHistory(contexts, {
@@ -5112,7 +5713,7 @@ var YieldseekerAgent = class {
5112
5713
  `/users/${user.userId}/agents/${agent.agentId}/wallet`
5113
5714
  ).then((walletResponse) => {
5114
5715
  this.assertSession(generation, chainId, asset);
5115
- if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
5716
+ if (!walletResponse?.agentWallet || !isAddress3(walletResponse.agentWallet.walletAddress)) {
5116
5717
  throw this.invalidResponse("agent wallet");
5117
5718
  }
5118
5719
  const context = {
@@ -5525,7 +6126,7 @@ var YieldseekerAgent = class {
5525
6126
  this.agentPath(context, "deploy"),
5526
6127
  { method: "POST", body: {} }
5527
6128
  );
5528
- if (!deployed?.agentWallet || !isAddress2(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
6129
+ if (!deployed?.agentWallet || !isAddress3(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
5529
6130
  throw this.invalidResponse("agent deployment", {
5530
6131
  reason: "Deploy did not return the expected Agent Wallet."
5531
6132
  });
@@ -5656,25 +6257,79 @@ var YieldseekerAgent = class {
5656
6257
  value: BigInt(transaction.value)
5657
6258
  });
5658
6259
  onSubmitted?.(hash);
6260
+ let replacement;
5659
6261
  const receipt = await withPaidRpcDiagnostics(
5660
6262
  () => this.getReceiptClient().waitForTransactionReceipt({
5661
6263
  hash,
5662
- confirmations: 1
6264
+ confirmations: 1,
6265
+ onReplaced: (value) => {
6266
+ replacement = value;
6267
+ }
5663
6268
  }),
5664
6269
  8453,
5665
6270
  "eth_getTransactionReceipt",
5666
6271
  this.id
5667
6272
  );
6273
+ const actualHash = receipt.transactionHash;
6274
+ if (!this.isTransactionHash(actualHash))
6275
+ throw new OwneyError(
6276
+ "WITHDRAW_FAILED",
6277
+ "The confirmed transaction hash could not be verified.",
6278
+ { originalTransactionHash: hash, ownerTransferNotExecuted: false },
6279
+ this.id
6280
+ );
6281
+ if (actualHash.toLowerCase() !== hash.toLowerCase()) {
6282
+ if (!replacement || replacement.transaction.hash.toLowerCase() !== actualHash.toLowerCase()) {
6283
+ try {
6284
+ const [original2, actual2] = await Promise.all([
6285
+ this.getReceiptClient().getTransaction({ hash }),
6286
+ this.getReceiptClient().getTransaction({ hash: actualHash })
6287
+ ]);
6288
+ replacement = { replacedTransaction: original2, transaction: actual2 };
6289
+ } catch {
6290
+ replacement = void 0;
6291
+ }
6292
+ }
6293
+ const original = replacement?.replacedTransaction;
6294
+ const actual = replacement?.transaction;
6295
+ const sameNonce = !!original && !!actual && original.hash.toLowerCase() === hash.toLowerCase() && actual.hash.toLowerCase() === actualHash.toLowerCase() && original.from.toLowerCase() === account.toLowerCase() && actual.from.toLowerCase() === account.toLowerCase() && original.nonce === actual.nonce && (actual.chainId === void 0 || actual.chainId === chainId) && receipt.blockNumber !== null && receipt.blockNumber !== void 0;
6296
+ if (!sameNonce)
6297
+ throw new OwneyError(
6298
+ "WITHDRAW_FAILED",
6299
+ "The replacement transaction could not be verified.",
6300
+ {
6301
+ originalTransactionHash: hash,
6302
+ replacementTransactionHash: actualHash,
6303
+ reason: "TRANSACTION_REPLACEMENT_UNRESOLVED",
6304
+ ownerTransferNotExecuted: false
6305
+ },
6306
+ this.id
6307
+ );
6308
+ const sameCall = actual.to?.toLowerCase() === transaction.to.toLowerCase() && actual.input?.toLowerCase() === transaction.data.toLowerCase() && actual.value === BigInt(transaction.value);
6309
+ if (!sameCall)
6310
+ throw new OwneyError(
6311
+ "WITHDRAW_FAILED",
6312
+ "The transaction was replaced without executing this withdrawal.",
6313
+ {
6314
+ originalTransactionHash: hash,
6315
+ replacementTransactionHash: actualHash,
6316
+ reason: "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL",
6317
+ ownerTransferNotExecuted: true
6318
+ },
6319
+ this.id
6320
+ );
6321
+ onSubmitted?.(actualHash);
6322
+ }
5668
6323
  if (receipt.status !== "success") {
5669
6324
  throw new OwneyError(
5670
6325
  "AGENT_TRANSACTION_REVERTED",
5671
- `Yieldseeker transaction reverted (${hash}).`,
5672
- { transactionHash: hash },
6326
+ `Yieldseeker transaction reverted (${actualHash}).`,
6327
+ { transactionHash: actualHash },
5673
6328
  this.id
5674
6329
  );
5675
6330
  }
5676
6331
  onReceipt?.(receipt);
5677
- return hash;
6332
+ return actualHash;
5678
6333
  }
5679
6334
  async waitForReceipt(state, chainId, transactionHash) {
5680
6335
  if (this.unwindReceiptWaiter) {
@@ -5701,12 +6356,12 @@ var YieldseekerAgent = class {
5701
6356
  return receipt;
5702
6357
  }
5703
6358
  assertTransaction(transaction, state, chainId) {
5704
- if (!transaction || typeof transaction.from !== "string" || !isAddress2(transaction.from) || typeof transaction.to !== "string" || !isAddress2(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || getAddress4(transaction.from) !== getAddress4(state.walletAddress)) {
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)) {
5705
6360
  throw this.invalidResponse("transaction");
5706
6361
  }
5707
6362
  }
5708
6363
  assertAgent(agent) {
5709
- if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress2(agent.assetAddress) || agent.chainId !== 8453) {
6364
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress3(agent.assetAddress) || agent.chainId !== 8453) {
5710
6365
  throw this.invalidResponse("agent");
5711
6366
  }
5712
6367
  }
@@ -6154,6 +6809,18 @@ var planOf = (transfers) => JSON.stringify(
6154
6809
  amount: BigInt(t.amount).toString()
6155
6810
  }))
6156
6811
  );
6812
+ var pendingKey = (i) => `owney.token-batch.v1:${keccak256(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
6813
+ function pendingTokenBatch(i) {
6814
+ try {
6815
+ const saved = read(pendingKey(i));
6816
+ if (!saved) return;
6817
+ const body = JSON.parse(saved);
6818
+ if (!body.serializedTransaction || body.chainId !== i.chainId || !isAddressEqual2(body.from, i.owner) || !isAddressEqual2(body.token, i.token) || !body.transfers?.length) return;
6819
+ return { txHash: keccak256(body.serializedTransaction), transfers: body.transfers.map(({ to, amount }) => ({ to, amount })) };
6820
+ } catch {
6821
+ return;
6822
+ }
6823
+ }
6157
6824
  function read(key2) {
6158
6825
  return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
6159
6826
  }
@@ -6170,11 +6837,11 @@ function clear(key2) {
6170
6837
  else window.localStorage.removeItem(key2);
6171
6838
  }
6172
6839
  function sponsorTokenBatch(i) {
6173
- const key2 = `owney.token-batch.v1:${keccak256(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
6840
+ const key2 = pendingKey(i);
6174
6841
  const plan = planOf(i.transfers);
6175
6842
  const active = inflight.get(key2);
6176
6843
  if (active) {
6177
- if (active.plan !== plan)
6844
+ if (active.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
6178
6845
  return Promise.reject(
6179
6846
  new Error(
6180
6847
  "A token deposit is already in progress. Wait for its result before depositing again."
@@ -6249,6 +6916,8 @@ async function execute(i, key2, plan) {
6249
6916
  }
6250
6917
  };
6251
6918
  const saved = read(key2);
6919
+ if (i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
6920
+ throw new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit changed. Check its status before depositing again.");
6252
6921
  if (saved) {
6253
6922
  const previous = JSON.parse(saved);
6254
6923
  if (previous.chainId !== i.chainId || !isAddressEqual2(previous.from, i.owner) || !isAddressEqual2(previous.token, i.token) || planOf(previous.transfers) !== plan)
@@ -6360,7 +7029,8 @@ function makeSponsoredTokenCallback(deps) {
6360
7029
  pub,
6361
7030
  wallet,
6362
7031
  onApproved: deps.onApproved,
6363
- onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
7032
+ onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers),
7033
+ resumeTxHash: deps.resumeTxHash
6364
7034
  });
6365
7035
  };
6366
7036
  const callback = makeVerificationAwareDepositCallback(
@@ -6446,7 +7116,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
6446
7116
  }
6447
7117
 
6448
7118
  // src/lib/sponsored-calls-deposit.ts
6449
- import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi3, toHex as toHex2 } from "viem";
7119
+ import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi4, toHex as toHex2 } from "viem";
6450
7120
  var DEFAULT_POLL_INTERVAL_MS = 1500;
6451
7121
  var DEFAULT_MAX_POLLS = 30;
6452
7122
  async function paymasterSupported(provider, owner, chainId) {
@@ -6492,7 +7162,7 @@ function makeSponsoredCallsCallback(deps) {
6492
7162
  to: token,
6493
7163
  value: "0x0",
6494
7164
  data: encodeFunctionData2({
6495
- abi: erc20Abi3,
7165
+ abi: erc20Abi4,
6496
7166
  functionName: "transfer",
6497
7167
  args: [transfer.to, BigInt(transfer.amount)]
6498
7168
  })
@@ -6524,50 +7194,73 @@ function makeSponsoredCallsCallback(deps) {
6524
7194
  url.searchParams.append("owneyIntent", intent);
6525
7195
  paymasterUrl = url.toString();
6526
7196
  }
6527
- const sendResult = await deps.provider.request({
6528
- method: "wallet_sendCalls",
6529
- params: [
6530
- {
6531
- version: "2.0.0",
6532
- from: deps.ownerAddress,
6533
- chainId: toHex2(chainId),
6534
- atomicRequired: transfers.length > 1,
6535
- calls,
6536
- capabilities: {
6537
- paymasterService: { url: paymasterUrl }
7197
+ const requestedId = toHex2(globalThis.crypto.getRandomValues(new Uint8Array(32)));
7198
+ const progress = (id, transfer) => {
7199
+ try {
7200
+ deps.onCallsProgress?.(id, transfers, transfer);
7201
+ } catch {
7202
+ }
7203
+ };
7204
+ progress(requestedId, "pending");
7205
+ let sendResult;
7206
+ try {
7207
+ sendResult = await deps.provider.request({
7208
+ method: "wallet_sendCalls",
7209
+ params: [
7210
+ {
7211
+ version: "2.0.0",
7212
+ id: requestedId,
7213
+ from: deps.ownerAddress,
7214
+ chainId: toHex2(chainId),
7215
+ atomicRequired: transfers.length > 1,
7216
+ calls,
7217
+ capabilities: {
7218
+ paymasterService: { url: paymasterUrl }
7219
+ }
6538
7220
  }
6539
- }
6540
- ]
6541
- });
6542
- deps.onApproved?.();
7221
+ ]
7222
+ });
7223
+ } catch (error) {
7224
+ if (error?.code === 4001 || error?.code === "ACTION_REJECTED")
7225
+ progress(requestedId, "failed");
7226
+ throw error;
7227
+ }
7228
+ try {
7229
+ deps.onApproved?.();
7230
+ } catch {
7231
+ }
6543
7232
  const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
6544
7233
  if (!callsId) {
6545
7234
  throw new OwneyError(
6546
7235
  "SPONSORED_CALLS_NO_ID",
6547
7236
  "Wallet accepted wallet_sendCalls but returned no calls id.",
6548
- { chainId }
7237
+ { chainId, callsId: requestedId }
6549
7238
  );
6550
7239
  }
7240
+ progress(callsId, "pending");
6551
7241
  for (let attempt = 0; attempt < maxPolls; attempt += 1) {
6552
7242
  const status = await deps.provider.request({
6553
7243
  method: "wallet_getCallsStatus",
6554
7244
  params: [callsId]
6555
7245
  });
7246
+ if (status?.id && status.id !== callsId || status?.chainId && Number(status.chainId) !== chainId)
7247
+ throw new OwneyError("SPONSORED_CALLS_NO_RECEIPT", "Wallet returned status for a different deposit batch.", { chainId, callsId });
6556
7248
  const txHash = status?.receipts?.[0]?.transactionHash;
6557
- if (txHash && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
7249
+ if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
6558
7250
  try {
6559
7251
  deps.onSubmitted?.(txHash, transfers);
6560
7252
  } catch {
6561
7253
  }
6562
7254
  }
6563
- if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
7255
+ if (status?.status === 400 || status?.status === 500) {
7256
+ progress(callsId, "failed");
6564
7257
  throw new OwneyError(
6565
7258
  "SPONSOR_REQUEST_FAILED",
6566
7259
  "The sponsored deposit did not complete successfully.",
6567
7260
  { chainId, callsId, safeToFallback: false }
6568
7261
  );
6569
7262
  }
6570
- if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
7263
+ if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
6571
7264
  if (status?.receipts?.some(
6572
7265
  (receipt) => receipt.transactionHash !== txHash
6573
7266
  ))
@@ -6783,8 +7476,8 @@ var OwneySDK = class {
6783
7476
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
6784
7477
  }
6785
7478
  /** Builds the default USDC batch callback for the connected wallet. */
6786
- getDefaultSponsoredCallback(onApproved, onSubmitted) {
6787
- if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
7479
+ getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
7480
+ if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedSponsoredCallback)
6788
7481
  return this.cachedSponsoredCallback;
6789
7482
  const provider = this.requireConnectedProvider();
6790
7483
  const owner = this.state.walletAddress;
@@ -6794,6 +7487,7 @@ var OwneySDK = class {
6794
7487
  ownerAddress: owner,
6795
7488
  onApproved,
6796
7489
  onSubmitted,
7490
+ resumeTxHash,
6797
7491
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
6798
7492
  // Casts work around viem's chain-narrowed Client vs the generic
6799
7493
  // PublicClient/WalletClient param types — structurally identical at
@@ -6809,13 +7503,13 @@ var OwneySDK = class {
6809
7503
  transport: custom4(provider)
6810
7504
  })
6811
7505
  });
6812
- if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
7506
+ if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedSponsoredCallback = callback;
6813
7507
  return callback;
6814
7508
  }
6815
7509
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
6816
- getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
7510
+ getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) {
6817
7511
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
6818
- if (!onApproved && !onSubmitted && cached) return cached;
7512
+ if (!onApproved && !onSubmitted && !onCallsProgress && cached) return cached;
6819
7513
  const provider = this.requireConnectedProvider();
6820
7514
  const callback = makeSponsoredCallsCallback({
6821
7515
  apiKey: this.apiKey,
@@ -6825,9 +7519,10 @@ var OwneySDK = class {
6825
7519
  paymasterServiceUrl: this.paymasterServiceUrl,
6826
7520
  onApproved,
6827
7521
  onSubmitted,
7522
+ onCallsProgress,
6828
7523
  tokenAddressByChain: sponsoredTokensFor(asset)
6829
7524
  });
6830
- if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
7525
+ if (!onApproved && !onSubmitted && !onCallsProgress) this.cachedSponsoredCallsCallbacks.set(asset, callback);
6831
7526
  return callback;
6832
7527
  }
6833
7528
  /**
@@ -6836,8 +7531,8 @@ var OwneySDK = class {
6836
7531
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
6837
7532
  * single-use batch authorization instead of an EIP-3009 authorization.
6838
7533
  */
6839
- getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
6840
- if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
7534
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
7535
+ if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedWethSponsoredCallback)
6841
7536
  return this.cachedWethSponsoredCallback;
6842
7537
  const provider = this.requireConnectedProvider();
6843
7538
  const owner = this.state.walletAddress;
@@ -6847,6 +7542,7 @@ var OwneySDK = class {
6847
7542
  ownerAddress: owner,
6848
7543
  onApproved,
6849
7544
  onSubmitted,
7545
+ resumeTxHash,
6850
7546
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
6851
7547
  // Casts work around viem's chain-narrowed Client vs the generic
6852
7548
  // PublicClient/WalletClient param types — structurally identical at
@@ -6862,7 +7558,7 @@ var OwneySDK = class {
6862
7558
  transport: custom4(provider)
6863
7559
  })
6864
7560
  });
6865
- if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
7561
+ if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedWethSponsoredCallback = callback;
6866
7562
  return callback;
6867
7563
  }
6868
7564
  getAgent(agentId) {
@@ -7171,6 +7867,7 @@ var OwneySDK = class {
7171
7867
  async deposit(options) {
7172
7868
  const { amount, asset, depositCallback, agentId, onApproved } = options;
7173
7869
  const prepared = /* @__PURE__ */ new Map();
7870
+ const paidFallbackAgents = /* @__PURE__ */ new Set();
7174
7871
  const onProgress = options.onProgress ? (evidence) => {
7175
7872
  if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
7176
7873
  notifyFunding(options.onProgress, evidence);
@@ -7181,152 +7878,184 @@ var OwneySDK = class {
7181
7878
  if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
7182
7879
  }
7183
7880
  } : void 0;
7881
+ const onCallsProgress = onProgress ? (callsId, destinations, transfer) => {
7882
+ for (const destination of destinations) {
7883
+ const evidence = prepared.get(destination.to.toLowerCase());
7884
+ if (evidence) notifyFunding(onProgress, { ...evidence, callsId, transfer });
7885
+ }
7886
+ } : void 0;
7184
7887
  if (BigInt(amount) === 0n) {
7185
7888
  return { agentResults: {} };
7186
7889
  }
7187
7890
  const state = this.requireState();
7188
7891
  const chainId = this.requireChainId();
7189
- const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted) : this.getDefaultSponsoredCallback(onApproved, onSubmitted));
7190
- if (agentId) {
7191
- if (this.isAgentDisabled(agentId)) {
7192
- throw new OwneyError(
7193
- "AGENT_DISABLED",
7194
- `Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
7195
- { agentId }
7196
- );
7197
- }
7198
- const agent = this.getAgent(agentId);
7199
- this.validateAssetSupport(agent, chainId, asset);
7200
- await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
7201
- return this.depositWithFallback(
7202
- agent,
7203
- state,
7204
- chainId,
7205
- amount,
7206
- asset,
7207
- effectiveCallback,
7208
- depositCallback,
7209
- onProgress
7210
- );
7211
- }
7212
- const totalAmount = BigInt(amount);
7213
- const eligibleAgents = this.getEligibleAgents(chainId, asset, {
7214
- excludeDisabled: true
7215
- });
7216
- const exemptFlags = await Promise.all(
7217
- eligibleAgents.map(
7218
- (a) => this.hasExistingBalance(a, state, chainId, asset, true)
7219
- )
7220
- );
7221
- const exempt = new Set(
7222
- eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
7223
- );
7224
- const agentAmounts = this.splitDepositAmount(
7225
- totalAmount,
7226
- eligibleAgents,
7227
- chainId,
7228
- asset,
7229
- exempt
7230
- );
7231
- if (agentAmounts.length === 0) {
7232
- const agentsRequiringActivation = eligibleAgents.filter(
7233
- (agent) => !exempt.has(agent.id)
7234
- );
7235
- const perAgentMinimum = agentsRequiringActivation.reduce(
7236
- (highest, agent) => {
7237
- const minimum = this.getMinDepositAmount(agent, chainId, asset);
7238
- return minimum > highest ? minimum : highest;
7239
- },
7240
- 0n
7241
- );
7242
- const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
7243
- throw new OwneyError(
7244
- "DEPOSIT_AMOUNT_BELOW_MINIMUM",
7245
- `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
7246
- {
7247
- amount,
7248
- asset,
7249
- chainId,
7250
- minDepositAmount: minimumRequired.toString(),
7251
- perAgentDepositAmount: perAgentMinimum.toString(),
7252
- agentMinimums: agentsRequiringActivation.map((agent) => ({
7253
- agentId: agent.id,
7254
- minDepositAmount: this.getMinDepositAmount(
7255
- agent,
7256
- chainId,
7257
- asset
7258
- ).toString()
7259
- }))
7260
- }
7261
- );
7262
- }
7263
- const batchTransfer = getDepositBatchTransfer(effectiveCallback);
7264
- if (!depositCallback && batchTransfer) {
7265
- return runAgentDepositBatch(
7266
- chainId,
7267
- agentAmounts.map(({ agent, amount: amount2 }) => ({
7268
- id: agent.id,
7269
- amount: amount2,
7270
- run: (callback) => withFailureReporting(
7271
- this.apiKey,
7272
- agent.id,
7273
- () => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
7274
- this.routingApiBaseUrl
7275
- )
7276
- })),
7277
- async (cid, transfers) => {
7278
- try {
7279
- return await batchTransfer(cid, transfers);
7280
- } catch (error) {
7281
- if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
7282
- throw error;
7283
- const requiredAmount = transfers.reduce(
7284
- (sum, transfer) => sum + BigInt(transfer.amount),
7285
- 0n
7286
- );
7287
- await this.approvePermit2(
7288
- asset,
7289
- requiredAmount,
7290
- cid
7291
- );
7292
- return batchTransfer(cid, transfers);
7293
- }
7892
+ if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
7893
+ throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
7894
+ 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));
7895
+ try {
7896
+ if (agentId) {
7897
+ if (this.isAgentDisabled(agentId)) {
7898
+ throw new OwneyError(
7899
+ "AGENT_DISABLED",
7900
+ `Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
7901
+ { agentId }
7902
+ );
7294
7903
  }
7295
- );
7296
- }
7297
- const agentResults = {};
7298
- for (const [
7299
- index,
7300
- { agent, amount: agentAmount }
7301
- ] of agentAmounts.entries()) {
7302
- try {
7303
- agentResults[agent.id] = await this.depositWithFallback(
7904
+ const agent = this.getAgent(agentId);
7905
+ this.validateAssetSupport(agent, chainId, asset);
7906
+ await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
7907
+ return await this.depositWithFallback(
7304
7908
  agent,
7305
7909
  state,
7306
7910
  chainId,
7307
- agentAmount,
7911
+ amount,
7308
7912
  asset,
7309
7913
  effectiveCallback,
7310
7914
  depositCallback,
7311
- onProgress
7915
+ onProgress,
7916
+ () => paidFallbackAgents.add(agent.id),
7917
+ !!options.resumeTxHash
7312
7918
  );
7313
- } catch (error) {
7314
- if (Object.keys(agentResults).length === 0) throw error;
7919
+ }
7920
+ const totalAmount = BigInt(amount);
7921
+ const eligibleAgents = this.getEligibleAgents(chainId, asset, {
7922
+ excludeDisabled: true
7923
+ });
7924
+ const prospectiveShare = totalAmount / BigInt(eligibleAgents.length);
7925
+ const meetsEveryMinimum = eligibleAgents.every((agent) => prospectiveShare >= this.getMinDepositAmount(agent, chainId, asset));
7926
+ const exemptFlags = meetsEveryMinimum ? eligibleAgents.map(() => false) : await Promise.all(eligibleAgents.map((agent) => this.hasExistingBalance(agent, state, chainId, asset, true)));
7927
+ const exempt = new Set(
7928
+ eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
7929
+ );
7930
+ const agentAmounts = this.splitDepositAmount(
7931
+ totalAmount,
7932
+ eligibleAgents,
7933
+ chainId,
7934
+ asset,
7935
+ exempt
7936
+ );
7937
+ if (agentAmounts.length === 0) {
7938
+ const agentsRequiringActivation = eligibleAgents.filter(
7939
+ (agent) => !exempt.has(agent.id)
7940
+ );
7941
+ const perAgentMinimum = agentsRequiringActivation.reduce(
7942
+ (highest, agent) => {
7943
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
7944
+ return minimum > highest ? minimum : highest;
7945
+ },
7946
+ 0n
7947
+ );
7948
+ const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
7315
7949
  throw new OwneyError(
7316
- "DEPOSIT_PARTIAL_FAILURE",
7317
- "Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
7950
+ "DEPOSIT_AMOUNT_BELOW_MINIMUM",
7951
+ `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
7318
7952
  {
7319
- agentResults,
7320
- failedAgentId: agent.id,
7321
- failedAmount: agentAmount,
7322
- unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
7323
- cause: error
7324
- },
7325
- agent.id
7953
+ amount,
7954
+ asset,
7955
+ chainId,
7956
+ minDepositAmount: minimumRequired.toString(),
7957
+ perAgentDepositAmount: perAgentMinimum.toString(),
7958
+ agentMinimums: agentsRequiringActivation.map((agent) => ({
7959
+ agentId: agent.id,
7960
+ minDepositAmount: this.getMinDepositAmount(
7961
+ agent,
7962
+ chainId,
7963
+ asset
7964
+ ).toString()
7965
+ }))
7966
+ }
7326
7967
  );
7327
7968
  }
7969
+ const batchTransfer = getDepositBatchTransfer(effectiveCallback);
7970
+ if (!depositCallback && batchTransfer) {
7971
+ return await runAgentDepositBatch(
7972
+ chainId,
7973
+ agentAmounts.map(({ agent, amount: amount2 }) => ({
7974
+ id: agent.id,
7975
+ amount: amount2,
7976
+ run: (callback) => withFailureReporting(
7977
+ this.apiKey,
7978
+ agent.id,
7979
+ () => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
7980
+ this.routingApiBaseUrl
7981
+ )
7982
+ })),
7983
+ async (cid, transfers) => {
7984
+ try {
7985
+ return await batchTransfer(cid, transfers);
7986
+ } catch (error) {
7987
+ if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
7988
+ throw error;
7989
+ const requiredAmount = transfers.reduce(
7990
+ (sum, transfer) => sum + BigInt(transfer.amount),
7991
+ 0n
7992
+ );
7993
+ await this.approvePermit2(
7994
+ asset,
7995
+ requiredAmount,
7996
+ cid
7997
+ );
7998
+ return batchTransfer(cid, transfers);
7999
+ }
8000
+ }
8001
+ );
8002
+ }
8003
+ const agentResults = {};
8004
+ for (const [
8005
+ index,
8006
+ { agent, amount: agentAmount }
8007
+ ] of agentAmounts.entries()) {
8008
+ try {
8009
+ agentResults[agent.id] = await this.depositWithFallback(
8010
+ agent,
8011
+ state,
8012
+ chainId,
8013
+ agentAmount,
8014
+ asset,
8015
+ effectiveCallback,
8016
+ depositCallback,
8017
+ onProgress,
8018
+ () => paidFallbackAgents.add(agent.id),
8019
+ !!options.resumeTxHash
8020
+ );
8021
+ } catch (error) {
8022
+ if (Object.keys(agentResults).length === 0) throw error;
8023
+ throw new OwneyError(
8024
+ "DEPOSIT_PARTIAL_FAILURE",
8025
+ "Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
8026
+ {
8027
+ agentResults,
8028
+ failedAgentId: agent.id,
8029
+ failedAmount: agentAmount,
8030
+ unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
8031
+ cause: error
8032
+ },
8033
+ agent.id
8034
+ );
8035
+ }
8036
+ }
8037
+ return { agentResults };
8038
+ } catch (error) {
8039
+ if (!depositCallback && onProgress && !options.resumeTxHash) {
8040
+ const ids = agentId ? [agentId] : this.getEligibleAgents(chainId, asset).map((agent) => agent.id);
8041
+ for (const id of ids) {
8042
+ const evidence = [...prepared.values()].find((value) => value.agentId === id);
8043
+ if (paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
8044
+ const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
8045
+ if (!rejected && (evidence?.txHash || evidence?.userOpHash || evidence?.callsId)) continue;
8046
+ notifyFunding(onProgress, { ...evidence ?? {
8047
+ agentId: id,
8048
+ chainId,
8049
+ token: asset,
8050
+ walletAddress: state.walletAddress,
8051
+ intendedAmount: amount,
8052
+ registration: "unknown",
8053
+ delivery: "not-applicable"
8054
+ }, transfer: "failed" });
8055
+ }
8056
+ }
8057
+ throw error;
7328
8058
  }
7329
- return { agentResults };
7330
8059
  }
7331
8060
  /**
7332
8061
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -7355,7 +8084,7 @@ var OwneySDK = class {
7355
8084
  * propagates unchanged. Scoped per agent invocation so one agent's failure
7356
8085
  * doesn't force a retry of sibling agents in a multi-agent split.
7357
8086
  */
7358
- async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
8087
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false) {
7359
8088
  const attempt = (callback) => withFailureReporting(
7360
8089
  this.apiKey,
7361
8090
  agent.id,
@@ -7367,6 +8096,7 @@ var OwneySDK = class {
7367
8096
  try {
7368
8097
  return await attempt(effectiveCallback);
7369
8098
  } catch (error) {
8099
+ if (resumeOnly) throw error;
7370
8100
  if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
7371
8101
  approvalAttempted = true;
7372
8102
  console.warn(
@@ -7384,6 +8114,7 @@ var OwneySDK = class {
7384
8114
  `[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
7385
8115
  error
7386
8116
  );
8117
+ onPaidFallback?.();
7387
8118
  return attempt(void 0);
7388
8119
  }
7389
8120
  }
@@ -7466,7 +8197,12 @@ var OwneySDK = class {
7466
8197
  */
7467
8198
  async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
7468
8199
  try {
7469
- const balance = await agent.getBalances(state, chainId);
8200
+ if (agent.hasExistingDepositFunds && await agent.hasExistingDepositFunds(
8201
+ state,
8202
+ chainId,
8203
+ asset
8204
+ ).catch(() => void 0) === true) return true;
8205
+ const balance = await agent.getBalances(state, chainId, asset);
7470
8206
  const target = asset.toLowerCase();
7471
8207
  const token = balance.tokens.find(
7472
8208
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
@@ -7592,7 +8328,8 @@ var OwneySDK = class {
7592
8328
  return verified;
7593
8329
  }
7594
8330
  /** Resolve a saved sponsored-withdrawal ID without opening a wallet prompt. */
7595
- async resolveSponsoredWithdrawalEvidence(evidence) {
8331
+ async resolveSponsoredWithdrawalEvidence(evidence, options = {}) {
8332
+ if (evidence.callsId && !evidence.userOpHash) return this.resolveWalletCallsFundingEvidence(evidence, "withdraw");
7596
8333
  const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
7597
8334
  if (evidence.agentId !== "yieldseeker" || chainId !== 8453 || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
7598
8335
  const agent = this.getAgent("yieldseeker");
@@ -7608,18 +8345,26 @@ var OwneySDK = class {
7608
8345
  owner: state.walletAddress,
7609
8346
  agentWallet: wallet,
7610
8347
  asset: SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token)?.address,
7611
- userOpHash: evidence.userOpHash
8348
+ userOpHash: evidence.userOpHash,
8349
+ ...evidence.userOperation ? { userOperation: Object.fromEntries([
8350
+ "sender",
8351
+ "nonce",
8352
+ "initCode",
8353
+ "callData",
8354
+ "callGasLimit",
8355
+ "verificationGasLimit",
8356
+ "preVerificationGas",
8357
+ "maxFeePerGas",
8358
+ "maxPriorityFeePerGas",
8359
+ "paymasterAndData"
8360
+ ].map((key2) => [key2, evidence.userOperation[key2]])) } : {},
8361
+ ...Number.isFinite(options.submittedAt) && options.submittedAt > 0 ? { submittedAt: options.submittedAt } : {}
7612
8362
  })
7613
8363
  });
7614
8364
  if (!response.ok) throw new OwneyError("AGENT_API_ERROR", "Could not resolve sponsored withdrawal status.");
7615
8365
  const payload = await response.json();
7616
8366
  txHash = payload.data?.txHash ?? void 0;
7617
8367
  executionFailed = payload.data?.executionFailed === true;
7618
- } else if (evidence.callsId) {
7619
- const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
7620
- if (status?.id && status.id !== evidence.callsId || status?.chainId && Number(status.chainId) !== chainId) return evidence;
7621
- executionFailed = status?.status === 400 || status?.status === 500;
7622
- txHash = status?.receipts?.[0]?.transactionHash;
7623
8368
  }
7624
8369
  assertCurrent();
7625
8370
  if (executionFailed) return {
@@ -7630,6 +8375,60 @@ var OwneySDK = class {
7630
8375
  };
7631
8376
  return txHash && /^0x[0-9a-fA-F]{64}$/.test(txHash) ? { ...evidence, txHash } : evidence;
7632
8377
  }
8378
+ /** Recover an EIP-5792 deposit or payout using this account's exact batch ID. */
8379
+ async resolveWalletCallsFundingEvidence(evidence, direction) {
8380
+ const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
8381
+ if (!evidence.callsId || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
8382
+ const agent = this.getAgent(evidence.agentId);
8383
+ const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
8384
+ if (!wallet || wallet.toLowerCase() !== evidence.agentWallet?.toLowerCase()) return evidence;
8385
+ const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
8386
+ assertCurrent();
8387
+ if (status?.id !== void 0 && status.id !== evidence.callsId || status?.chainId !== void 0 && Number(status.chainId) !== chainId) return evidence;
8388
+ const statusCode2 = Number(status?.status);
8389
+ if (statusCode2 === 400 || statusCode2 === 500) return {
8390
+ ...evidence,
8391
+ transfer: "failed",
8392
+ delivery: direction === "withdraw" ? "failed" : "not-applicable"
8393
+ };
8394
+ const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash?.toLowerCase()).filter((hash) => !!hash && /^0x[\da-f]{64}$/i.test(hash)))];
8395
+ if (hashes.length === 1) return { ...evidence, txHash: hashes[0] };
8396
+ if (hashes.length > 16) return evidence;
8397
+ const matches = [];
8398
+ for (const txHash of hashes) {
8399
+ const inspected = await this.inspectFunding({ ...evidence, txHash }, direction);
8400
+ assertCurrent();
8401
+ if (inspected.transfer === "confirmed") matches.push(inspected);
8402
+ }
8403
+ return matches.length === 1 ? matches[0] : evidence;
8404
+ }
8405
+ /** Whether a foreground deposit can resume the same signed batch, never a
8406
+ * fresh authorization. The batch rail independently rechecks the exact plan. */
8407
+ async canResumePendingDeposit(evidences, amount) {
8408
+ if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
8409
+ const first = evidences[0];
8410
+ const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
8411
+ const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
8412
+ if (!token) return false;
8413
+ const pending = pendingTokenBatch({
8414
+ apiKey: this.apiKey,
8415
+ baseUrl: this.routingApiBaseUrl,
8416
+ owner: state.walletAddress,
8417
+ chainId,
8418
+ token: token.address
8419
+ });
8420
+ if (!pending || pending.transfers.length !== evidences.length || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n).toString() !== amount) return false;
8421
+ const destinations = /* @__PURE__ */ new Set();
8422
+ for (const evidence of evidences) {
8423
+ if (evidence.chainId !== chainId || evidence.token !== first.token || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.callsId || evidence.userOpHash || evidence.txHash?.toLowerCase() !== pending.txHash.toLowerCase()) return false;
8424
+ const agent = this.getAgent(evidence.agentId);
8425
+ const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
8426
+ if (!wallet || destinations.has(wallet.toLowerCase()) || !pending.transfers.some((transfer) => transfer.to.toLowerCase() === wallet.toLowerCase() && transfer.amount === evidence.intendedAmount)) return false;
8427
+ destinations.add(wallet.toLowerCase());
8428
+ }
8429
+ assertCurrent();
8430
+ return true;
8431
+ }
7633
8432
  /** A display acknowledgement controls polling only; it never confirms a financial outcome. */
7634
8433
  endFundingBalanceRefresh(evidence, read2) {
7635
8434
  const { state, chainId } = this.captureReadScope(evidence.chainId);
@@ -7730,6 +8529,30 @@ var OwneySDK = class {
7730
8529
  }
7731
8530
  };
7732
8531
  const token = asset;
8532
+ const agentFailures = {};
8533
+ const recordAgentFailure = (id, error) => {
8534
+ if (!(error instanceof OwneyError)) return;
8535
+ const details = error.details ?? {};
8536
+ const codeValue = (value) => typeof value === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(value) ? value : void 0;
8537
+ const revertNames = ["InsufficientBalance", "InvalidAsset", "Unauthorized", "Error", "Panic", "FailedOp", "FailedOpWithRevert"];
8538
+ const panicCodes = [0, 1, 17, 18, 33, 34, 49, 50, 65, 81];
8539
+ agentFailures[id] = {
8540
+ code: error.code,
8541
+ phase: ["prepare", "send", "observe"].includes(String(details.phase)) ? details.phase : void 0,
8542
+ failureCode: codeValue(details.failureCode),
8543
+ reason: codeValue(details.reason),
8544
+ withdrawalPhase: details.withdrawalPhase === "unwind" ? "unwind" : void 0,
8545
+ statusCode: typeof details.statusCode === "number" ? details.statusCode : void 0,
8546
+ rpcCode: typeof details.rpcCode === "number" ? details.rpcCode : void 0,
8547
+ rpcMethod: typeof details.rpcMethod === "string" && /^[A-Za-z_]{1,80}$/.test(details.rpcMethod) ? details.rpcMethod : void 0,
8548
+ revertSelector: typeof details.revertSelector === "string" && /^0x[0-9a-f]{8}$/i.test(details.revertSelector) ? details.revertSelector : void 0,
8549
+ revertName: typeof details.revertName === "string" && revertNames.includes(details.revertName) ? details.revertName : void 0,
8550
+ aaCode: typeof details.aaCode === "string" && /^AA[0-9]{2}$/.test(details.aaCode) ? details.aaCode : void 0,
8551
+ panicCode: typeof details.panicCode === "string" && /^0x[0-9a-f]{1,2}$/i.test(details.panicCode) && panicCodes.includes(Number.parseInt(details.panicCode.slice(2), 16)) ? details.panicCode : void 0,
8552
+ safeToFallback: typeof details.safeToFallback === "boolean" ? details.safeToFallback : void 0,
8553
+ executionFailed: typeof details.executionFailed === "boolean" ? details.executionFailed : void 0
8554
+ };
8555
+ };
7733
8556
  const submissionEvidence = /* @__PURE__ */ new Map();
7734
8557
  const submit = async (agent, requested2) => {
7735
8558
  attempted.add(agent.id);
@@ -7750,9 +8573,19 @@ var OwneySDK = class {
7750
8573
  };
7751
8574
  progress(evidence);
7752
8575
  try {
8576
+ if (options.onBeforeSubmission)
8577
+ return await agent.withdraw(
8578
+ state,
8579
+ chainId,
8580
+ token,
8581
+ requested2,
8582
+ progress,
8583
+ options.gasMode ?? "auto",
8584
+ (next) => options.onBeforeSubmission({ ...evidence, ...next })
8585
+ );
7753
8586
  return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
7754
8587
  } catch (error) {
7755
- if (!evidence.txHash && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
8588
+ if (!evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
7756
8589
  progress({ ...evidence, transfer: "failed", delivery: "failed" });
7757
8590
  throw error;
7758
8591
  }
@@ -7802,6 +8635,7 @@ var OwneySDK = class {
7802
8635
  }
7803
8636
  console.error(`withdraw failed for agent "${agent.id}":`, err);
7804
8637
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
8638
+ recordAgentFailure(agent.id, err);
7805
8639
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
7806
8640
  void reportAgentFailure(
7807
8641
  this.apiKey,
@@ -7812,17 +8646,23 @@ var OwneySDK = class {
7812
8646
  }
7813
8647
  }
7814
8648
  if (Object.keys(results2).length === 0) {
8649
+ if (Object.keys(agentErrors2).length === 0)
8650
+ throw new OwneyError(
8651
+ "WITHDRAW_INSUFFICIENT_BALANCE",
8652
+ "No funds are currently available to withdraw for this asset.",
8653
+ { asset, available: "0" }
8654
+ );
7815
8655
  throw new OwneyError(
7816
8656
  "WITHDRAW_ALL_FAILED",
7817
8657
  `Failed to withdraw "${asset}" from all eligible agents.`,
7818
- { asset, agentErrors: agentErrors2 }
8658
+ { asset, agentErrors: agentErrors2, agentFailures }
7819
8659
  );
7820
8660
  }
7821
8661
  if (Object.keys(agentErrors2).length > 0) {
7822
8662
  throw new OwneyError(
7823
8663
  "WITHDRAW_PARTIAL_FAILURE",
7824
8664
  "Some agents could not complete the withdrawal.",
7825
- { asset, partialResults: results2, agentErrors: agentErrors2, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
8665
+ { asset, partialResults: results2, agentErrors: agentErrors2, agentFailures, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
7826
8666
  );
7827
8667
  }
7828
8668
  const amountsKnown = Object.values(results2).every(
@@ -7903,6 +8743,7 @@ var OwneySDK = class {
7903
8743
  if (this.isUserRejectedWithdrawal(err)) throw err;
7904
8744
  console.error(`withdraw failed for agent "${p.agent.id}":`, err);
7905
8745
  agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
8746
+ recordAgentFailure(p.agent.id, err);
7906
8747
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
7907
8748
  void reportAgentFailure(
7908
8749
  this.apiKey,
@@ -7923,7 +8764,8 @@ var OwneySDK = class {
7923
8764
  {
7924
8765
  asset,
7925
8766
  amount,
7926
- agentErrors
8767
+ agentErrors,
8768
+ agentFailures
7927
8769
  }
7928
8770
  );
7929
8771
  }
@@ -7938,6 +8780,7 @@ var OwneySDK = class {
7938
8780
  withdrawn: withdrawn.toString(),
7939
8781
  partialResults: results,
7940
8782
  agentErrors,
8783
+ agentFailures,
7941
8784
  ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
7942
8785
  }
7943
8786
  );