@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.cjs CHANGED
@@ -102,6 +102,8 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
102
102
  const attributed = /* @__PURE__ */ new Map();
103
103
  const agent = evidence.agentWallet?.toLowerCase();
104
104
  const owner = evidence.walletAddress.toLowerCase();
105
+ const source = direction === "deposit" ? owner : agent;
106
+ const destination = direction === "deposit" ? agent : owner;
105
107
  const logIndexes = [];
106
108
  const transferLogs = [];
107
109
  const zeroTransferLogs = [];
@@ -116,17 +118,13 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
116
118
  if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
117
119
  zeroTransferLogs.push(log.logIndex);
118
120
  let matched = 0n;
119
- if (direction === "deposit") {
120
- if (to.toLowerCase() === agent) matched = value;
121
- } else {
122
- const sender = from.toLowerCase();
123
- const recipient = to.toLowerCase();
124
- const available = sender === agent ? value : attributed.get(sender) ?? 0n;
125
- const carried = available < value ? available : value;
126
- if (sender !== agent) attributed.set(sender, available - carried);
127
- if (recipient === owner) matched = carried;
128
- else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
129
- }
121
+ const sender = from.toLowerCase();
122
+ const recipient = to.toLowerCase();
123
+ const available = sender === source ? value : attributed.get(sender) ?? 0n;
124
+ const carried = available < value ? available : value;
125
+ if (sender !== source) attributed.set(sender, available - carried);
126
+ if (recipient === destination) matched = carried;
127
+ else if (recipient !== source) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
130
128
  if (matched === 0n) continue;
131
129
  amount += matched;
132
130
  transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
@@ -418,8 +416,8 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
418
416
  return await original.apply(this, args);
419
417
  } catch (error) {
420
418
  const message = error instanceof Error ? error.message : "";
421
- 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";
422
- if (["unauthorized", "forbidden", "rate-limited"].includes(request2.failure)) notify("rejected");
419
+ 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";
420
+ if (["unauthorized", "forbidden", "rate-limited", "insufficient-balance", "session-key-missing"].includes(request2.failure)) notify("rejected");
423
421
  throw error;
424
422
  } finally {
425
423
  request2.elapsedMs = Math.max(0, Date.now() - startedAt);
@@ -442,9 +440,49 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
442
440
  } };
443
441
  }
444
442
 
443
+ // src/agents/zyfai/zyfai.withdraw-empty.ts
444
+ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
445
+ const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
446
+ const response = record(raw2);
447
+ const portfolio = record(response?.portfolio);
448
+ const chains = record(portfolio?.portfolioByChain);
449
+ if (response?.success === false || !chains || !Array.isArray(portfolio?.positions) || !Array.isArray(portfolio.staleBalances)) return false;
450
+ if (portfolio.pendingAsyncWithdrawals !== void 0 && (!Array.isArray(portfolio.pendingAsyncWithdrawals) || portfolio.pendingAsyncWithdrawals.length > 0)) return false;
451
+ const symbol = (value) => typeof value === "string" ? value.toUpperCase() === "ETH" ? "WETH" : value.toUpperCase() : void 0;
452
+ const chain = (value) => {
453
+ if (typeof value === "number") return value;
454
+ if (typeof value !== "string") return void 0;
455
+ return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
456
+ };
457
+ const zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
458
+ for (const [id, assets] of Object.entries(chains)) {
459
+ const idNumber = chain(id);
460
+ if (idNumber === void 0) return false;
461
+ if (idNumber !== chainId) continue;
462
+ const entries = record(assets);
463
+ if (!entries) return false;
464
+ for (const [asset, value] of Object.entries(entries)) {
465
+ if (symbol(asset) !== token) continue;
466
+ const balance = record(value);
467
+ if (!balance || !zero(balance.balance)) return false;
468
+ }
469
+ }
470
+ for (const value of [...portfolio.positions, ...portfolio.staleBalances]) {
471
+ const entry = record(value);
472
+ if (!entry) return false;
473
+ const id = chain(entry.chainId ?? entry.chain);
474
+ const asset = symbol(entry.tokenSymbol ?? entry.token_symbol);
475
+ if (id === void 0 || asset === void 0) return false;
476
+ if (id !== chainId || asset !== token) continue;
477
+ const amounts = [entry.balance, entry.amount, entry.underlyingAmount].filter((value2) => value2 !== void 0);
478
+ if (!amounts.length || !amounts.every(zero)) return false;
479
+ }
480
+ return true;
481
+ }
482
+
445
483
  // src/agents/zyfai/zyfai.agent.ts
446
484
  var import_sdk = require("@zyfai/sdk");
447
- var import_viem3 = require("viem");
485
+ var import_viem4 = require("viem");
448
486
  var import_chains = require("viem/chains");
449
487
 
450
488
  // src/types/config.ts
@@ -531,14 +569,6 @@ function hexToDecimal(hex, decimals = 6) {
531
569
  const remainder = parsed % divisor;
532
570
  return Number(whole) + Number(remainder) / Number(divisor);
533
571
  }
534
- function normalizeDecimalPlace(value, decimals = 6) {
535
- const parsed = BigInt(value);
536
- if (decimals <= 0) return Number(parsed);
537
- const divisor = BigInt(10 ** decimals);
538
- const whole = parsed / divisor;
539
- const remainder = parsed % divisor;
540
- return Number(whole) + Number(remainder) / Number(divisor);
541
- }
542
572
  var DayFilterMapping = {
543
573
  "30D": 30,
544
574
  "14D": 14,
@@ -546,6 +576,8 @@ var DayFilterMapping = {
546
576
  };
547
577
 
548
578
  // src/agents/zyfai/zyfai.mapper.ts
579
+ var import_viem2 = require("viem");
580
+ var exactHexAmount = (raw2, decimals) => (0, import_viem2.formatUnits)(BigInt(/^0x/i.test(raw2) ? raw2 : `0x${raw2}`), decimals);
549
581
  var DECIMALS_BY_SYMBOL = {
550
582
  usdc: 6,
551
583
  weth: 18,
@@ -600,15 +632,15 @@ function mapPendingAllocations(raw2) {
600
632
  if (typeof e.balance !== "string") continue;
601
633
  let amount;
602
634
  try {
603
- amount = hexToDecimal(e.balance, tokenDecimals(e.tokenSymbol));
635
+ if (BigInt(/^0x/i.test(e.balance) ? e.balance : `0x${e.balance}`) <= 0n) continue;
636
+ amount = exactHexAmount(e.balance, tokenDecimals(e.tokenSymbol));
604
637
  } catch {
605
638
  continue;
606
639
  }
607
- if (!Number.isFinite(amount) || amount <= 0) continue;
608
640
  pending.push({
609
641
  chainId: e.chainId,
610
642
  asset: e.tokenSymbol,
611
- amount: String(amount),
643
+ amount,
612
644
  isPending: e.isPending === true,
613
645
  ...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
614
646
  });
@@ -631,13 +663,13 @@ function mapBalances(raw2, _chainId, smartWallet) {
631
663
  { balance, balanceWithFee, decimals }
632
664
  ] of Object.entries(tokensBySymbol ?? {})) {
633
665
  if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
634
- const amount = hexToDecimal(balanceWithFee ?? balance, decimals);
635
- totalBalance += amount;
666
+ const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol, decimals));
667
+ totalBalance += Number(amount);
636
668
  tokens.push({
637
669
  chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
638
670
  chainId: positionChainId,
639
671
  asset: symbol,
640
- amount: String(amount)
672
+ amount
641
673
  });
642
674
  }
643
675
  }
@@ -651,19 +683,17 @@ function mapBalances(raw2, _chainId, smartWallet) {
651
683
  // legacy `amount` fallback is hex. Runtime
652
684
  // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
653
685
  // when present, otherwise fall back to the per-symbol lookup.
654
- amount: String(
655
- p.underlyingAmountWithFee ?? p.underlyingAmount ? normalizeDecimalPlace(
656
- p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0",
657
- tokenDecimals(
658
- p.token_symbol,
659
- p.decimals
660
- )
661
- ) : hexToDecimal(
662
- p.amount || "0",
663
- tokenDecimals(
664
- p.token_symbol,
665
- p.decimals
666
- )
686
+ amount: p.underlyingAmountWithFee ?? p.underlyingAmount ? (0, import_viem2.formatUnits)(
687
+ BigInt(p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0"),
688
+ tokenDecimals(
689
+ p.token_symbol,
690
+ p.decimals
691
+ )
692
+ ) : exactHexAmount(
693
+ p.amount || "0",
694
+ tokenDecimals(
695
+ p.token_symbol,
696
+ p.decimals
667
697
  )
668
698
  ),
669
699
  apy: netApy(p.pool_apy_withFee, p.pool_apy, "pool_apy_withFee"),
@@ -1318,7 +1348,7 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
1318
1348
  }
1319
1349
 
1320
1350
  // src/lib/paid-rpc.ts
1321
- var import_viem2 = require("viem");
1351
+ var import_viem3 = require("viem");
1322
1352
  var PAID_RPC_RETRY_COUNT = 3;
1323
1353
  var PAID_RPC_RETRY_DELAY_MS = 1e3;
1324
1354
  function configuredRpcProxyBaseUrl(baseUrl) {
@@ -1354,9 +1384,9 @@ function createPaidRpcClient(chain, rpcUrls) {
1354
1384
  rpcUrls,
1355
1385
  chain.id
1356
1386
  );
1357
- return (0, import_viem2.createPublicClient)({
1387
+ return (0, import_viem3.createPublicClient)({
1358
1388
  chain,
1359
- transport: (0, import_viem2.http)(url, {
1389
+ transport: (0, import_viem3.http)(url, {
1360
1390
  retryCount: PAID_RPC_RETRY_COUNT,
1361
1391
  retryDelay: PAID_RPC_RETRY_DELAY_MS
1362
1392
  })
@@ -1453,7 +1483,7 @@ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
1453
1483
  }
1454
1484
 
1455
1485
  // src/agents/zyfai/zyfai.agent.ts
1456
- var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem3.parseAbi)([
1486
+ var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem4.parseAbi)([
1457
1487
  "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
1458
1488
  ]);
1459
1489
  var WETH_ADDRESS_BY_CHAIN = {
@@ -1604,7 +1634,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1604
1634
  }, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
1605
1635
  if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
1606
1636
  Object.assign(tx, {
1607
- amount: (0, import_viem3.formatUnits)(BigInt(evidence.confirmedAmount), asset.decimals),
1637
+ amount: (0, import_viem4.formatUnits)(BigInt(evidence.confirmedAmount), asset.decimals),
1608
1638
  amountRaw: evidence.confirmedAmount,
1609
1639
  blockNumber: evidence.blockNumber,
1610
1640
  logIndexes: evidence.logIndexes,
@@ -2331,6 +2361,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
2331
2361
  evidence2 = { ...evidence2, transfer: "unknown" };
2332
2362
  }
2333
2363
  notifyFunding(onProgress, evidence2);
2364
+ if (evidence2.transfer === "failed")
2365
+ throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
2334
2366
  try {
2335
2367
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
2336
2368
  await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
@@ -2369,12 +2401,77 @@ var ZyfaiAgent = class _ZyfaiAgent {
2369
2401
  } catch {
2370
2402
  }
2371
2403
  notifyFunding(onProgress, evidence);
2404
+ if (evidence.transfer === "failed")
2405
+ throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash: raw2.txHash }, this.id);
2372
2406
  return { ...mapDeposit(raw2), evidence };
2373
2407
  } catch (error) {
2374
2408
  throw error;
2375
2409
  }
2376
2410
  }
2377
2411
  // --- IAgent: Fund operations ---
2412
+ /** Called only after the execution endpoint explicitly rejected a request.
2413
+ * A timeout or an accepted request never authorizes repair and resubmission. */
2414
+ async recoverWithdrawalSession(state, chainId, token, owner) {
2415
+ const internals = this.sdk;
2416
+ const assertScope = () => {
2417
+ const account = internals.walletClient?.account?.address;
2418
+ if (state.walletAddress.toLowerCase() !== owner.toLowerCase() || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
2419
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
2420
+ };
2421
+ assertScope();
2422
+ const [wallet, registered, portfolio] = await Promise.all([
2423
+ this.sdk.getSmartWalletAddress(owner, chainId),
2424
+ this.sdk.getSmartWalletByEOA(owner),
2425
+ this.sdk.getPortfolio(owner).catch(() => void 0)
2426
+ ]);
2427
+ assertScope();
2428
+ if (!(0, import_viem4.isAddress)(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
2429
+ throw new OwneyError(
2430
+ "AGENT_AUTH_FAILED",
2431
+ "Could not verify Zyfai's withdrawal wallet.",
2432
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2433
+ this.id
2434
+ );
2435
+ if (hasEmptyWithdrawalSnapshot(portfolio, chainId, token)) {
2436
+ const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
2437
+ const idle = asset ? await this.getPublicClient(chainId).readContract({
2438
+ address: asset.address,
2439
+ abi: import_viem4.erc20Abi,
2440
+ functionName: "balanceOf",
2441
+ args: [wallet.address]
2442
+ }).catch(() => void 0) : void 0;
2443
+ assertScope();
2444
+ if (idle === 0n) throw new OwneyError(
2445
+ "WITHDRAW_INSUFFICIENT_BALANCE",
2446
+ "No funds remain in Zyfai for this asset.",
2447
+ { available: "0", chainId, token },
2448
+ this.id
2449
+ );
2450
+ }
2451
+ const walletState = await this.readWalletState(owner);
2452
+ assertScope();
2453
+ const unavailable = () => new OwneyError(
2454
+ "AGENT_AUTH_FAILED",
2455
+ "Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
2456
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2457
+ this.id
2458
+ );
2459
+ if (walletState.predeployed !== false || internals.isPredeployed === true) throw unavailable();
2460
+ internals.hasActiveSessionKey = false;
2461
+ const key2 = `${owner.toLowerCase()}:${chainId}`;
2462
+ this.sessionKeyAttempts.delete(key2);
2463
+ try {
2464
+ const result = await this.sdk.createSessionKey(owner, chainId);
2465
+ assertScope();
2466
+ if (result.success === false || result.alreadyActive === true) throw unavailable();
2467
+ this.sessionKeyAttempts.add(key2);
2468
+ } catch (error) {
2469
+ assertScope();
2470
+ const code = error?.code;
2471
+ if (code === 4001 || code === "ACTION_REJECTED" || error instanceof Error && /user (rejected|denied)|request rejected|action rejected/i.test(error.message)) throw error;
2472
+ throw unavailable();
2473
+ }
2474
+ }
2378
2475
  async withdraw(state, chainId, token, amount, onProgress) {
2379
2476
  const validChainId = isValidChainId(chainId);
2380
2477
  try {
@@ -2392,11 +2489,35 @@ var ZyfaiAgent = class _ZyfaiAgent {
2392
2489
  });
2393
2490
  throw error;
2394
2491
  }
2395
- const observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2492
+ let observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2396
2493
  const authToken = this.sdk.httpClient?.authToken;
2494
+ const owner = this.getAddress();
2397
2495
  let raw2;
2398
2496
  try {
2399
- raw2 = await this.sdk.withdrawFunds(this.getAddress(), validChainId, amount, token);
2497
+ for (let attempt = 0; attempt < 2; attempt++) {
2498
+ try {
2499
+ raw2 = await this.sdk.withdrawFunds(owner, validChainId, amount, token);
2500
+ break;
2501
+ } catch (error) {
2502
+ if (!observation.rejected() || observation.request()?.failure !== "session-key-missing") throw error;
2503
+ if (attempt === 1)
2504
+ throw new OwneyError(
2505
+ "AGENT_AUTH_FAILED",
2506
+ "Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
2507
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2508
+ this.id
2509
+ );
2510
+ await this.recoverWithdrawalSession(state, validChainId, token, owner);
2511
+ observation.stop();
2512
+ observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2513
+ }
2514
+ }
2515
+ if (!raw2) throw new OwneyError(
2516
+ "AGENT_INVALID_RESPONSE",
2517
+ "Zyfai returned no withdrawal result.",
2518
+ { chainId, token, phase: "observe" },
2519
+ this.id
2520
+ );
2400
2521
  } catch (error) {
2401
2522
  this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
2402
2523
  if (observation.attempted() !== void 0) {
@@ -2675,7 +2796,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2675
2796
  };
2676
2797
 
2677
2798
  // src/agents/yieldseeker/yieldseeker.agent.ts
2678
- var import_viem10 = require("viem");
2799
+ var import_viem11 = require("viem");
2679
2800
  var import_chains4 = require("viem/chains");
2680
2801
 
2681
2802
  // src/lib/chain-guard.ts
@@ -2714,7 +2835,7 @@ async function ensureWalletOnChain(pub, wallet, expected) {
2714
2835
  }
2715
2836
 
2716
2837
  // src/lib/transfer-auth.ts
2717
- var import_viem4 = require("viem");
2838
+ var import_viem5 = require("viem");
2718
2839
 
2719
2840
  // src/lib/sponsor-client.ts
2720
2841
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
@@ -2830,7 +2951,7 @@ async function postSponsorBatchTransfer(input) {
2830
2951
  }
2831
2952
 
2832
2953
  // src/lib/permit2.ts
2833
- var import_viem5 = require("viem");
2954
+ var import_viem6 = require("viem");
2834
2955
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2835
2956
  var MAX_UINT256 = 2n ** 256n - 1n;
2836
2957
  function permit2ApprovalAmount(requiredAmount) {
@@ -2871,7 +2992,7 @@ var ERC20_ALLOWANCE_ABI = [
2871
2992
  function randomPermit2Nonce() {
2872
2993
  const bytes = new Uint8Array(32);
2873
2994
  globalThis.crypto.getRandomValues(bytes);
2874
- return BigInt((0, import_viem5.bytesToHex)(bytes));
2995
+ return BigInt((0, import_viem6.bytesToHex)(bytes));
2875
2996
  }
2876
2997
  async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
2877
2998
  return publicClient.readContract({
@@ -2914,7 +3035,7 @@ function makeVerificationAwareDepositCallback(implementation) {
2914
3035
 
2915
3036
  // src/agents/yieldseeker/yieldseeker.auth.ts
2916
3037
  var import_siwe = require("siwe");
2917
- var import_viem6 = require("viem");
3038
+ var import_viem7 = require("viem");
2918
3039
  var import_chains2 = require("viem/chains");
2919
3040
 
2920
3041
  // src/agents/yieldseeker/yieldseeker.auth-cache.ts
@@ -3027,7 +3148,7 @@ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
3027
3148
  return new import_siwe.SiweMessage({
3028
3149
  scheme: url.protocol.slice(0, -1),
3029
3150
  domain: url.host,
3030
- address: (0, import_viem6.getAddress)(address),
3151
+ address: (0, import_viem7.getAddress)(address),
3031
3152
  uri: url.origin,
3032
3153
  version: "1",
3033
3154
  chainId,
@@ -3113,15 +3234,15 @@ var YieldseekerAuth = class {
3113
3234
  clearYieldseekerSession(state.walletAddress, chainId);
3114
3235
  }
3115
3236
  async sign(state, chainId) {
3116
- const account = (0, import_viem6.getAddress)(state.walletAddress);
3117
- const publicClient = (0, import_viem6.createPublicClient)({
3237
+ const account = (0, import_viem7.getAddress)(state.walletAddress);
3238
+ const publicClient = (0, import_viem7.createPublicClient)({
3118
3239
  chain: import_chains2.base,
3119
- transport: (0, import_viem6.custom)(state.provider)
3240
+ transport: (0, import_viem7.custom)(state.provider)
3120
3241
  });
3121
- const walletClient = (0, import_viem6.createWalletClient)({
3242
+ const walletClient = (0, import_viem7.createWalletClient)({
3122
3243
  account,
3123
3244
  chain: import_chains2.base,
3124
- transport: (0, import_viem6.custom)(state.provider)
3245
+ transport: (0, import_viem7.custom)(state.provider)
3125
3246
  });
3126
3247
  await ensureWalletOnChain(
3127
3248
  publicClient,
@@ -3288,7 +3409,7 @@ var YieldseekerApiClient = class {
3288
3409
  };
3289
3410
 
3290
3411
  // src/agents/yieldseeker/yieldseeker.mapper.ts
3291
- var import_viem7 = require("viem");
3412
+ var import_viem8 = require("viem");
3292
3413
 
3293
3414
  // src/lib/helpers/snapshot-apy.ts
3294
3415
  var DAY_MS = 864e5;
@@ -3355,10 +3476,10 @@ function raw(value, endpoint) {
3355
3476
  return BigInt(value);
3356
3477
  }
3357
3478
  function decimal(value, decimals, endpoint) {
3358
- return (0, import_viem7.formatUnits)(raw(value, endpoint), decimals);
3479
+ return (0, import_viem8.formatUnits)(raw(value, endpoint), decimals);
3359
3480
  }
3360
3481
  function usd(rawAmount, decimals, price) {
3361
- return Number((0, import_viem7.formatUnits)(rawAmount, decimals)) * price;
3482
+ return Number((0, import_viem8.formatUnits)(rawAmount, decimals)) * price;
3362
3483
  }
3363
3484
  function percent(value) {
3364
3485
  const result = Number(value);
@@ -3384,7 +3505,7 @@ function assetAddressValue(record, address) {
3384
3505
  }
3385
3506
  function position(value, asset, baseAssetDecimals) {
3386
3507
  const option = value?.yieldOption;
3387
- if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem7.isAddress)(option.address)) {
3508
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem8.isAddress)(option.address)) {
3388
3509
  return invalid("yield positions", "missing vault metadata");
3389
3510
  }
3390
3511
  return {
@@ -3476,7 +3597,7 @@ function mapYieldseekerEarnings(contexts) {
3476
3597
  chain: "BASE",
3477
3598
  chainId: 8453,
3478
3599
  asset: context.asset,
3479
- amount: (0, import_viem7.formatUnits)(amount, context.snapshot.baseAssetDecimals)
3600
+ amount: (0, import_viem8.formatUnits)(amount, context.snapshot.baseAssetDecimals)
3480
3601
  });
3481
3602
  lifetimeEarnings += usd(
3482
3603
  amount,
@@ -3605,7 +3726,7 @@ function allocationEntry(action, context) {
3605
3726
  const leg = deposit;
3606
3727
  const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
3607
3728
  if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
3608
- if (typeof leg.vaultAddress !== "string" || !(0, import_viem7.isAddress)(leg.vaultAddress)) return void 0;
3729
+ if (typeof leg.vaultAddress !== "string" || !(0, import_viem8.isAddress)(leg.vaultAddress)) return void 0;
3609
3730
  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;
3610
3731
  if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
3611
3732
  const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
@@ -3613,7 +3734,7 @@ function allocationEntry(action, context) {
3613
3734
  protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
3614
3735
  pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
3615
3736
  tokenSymbol: context.asset,
3616
- amount: (0, import_viem7.formatUnits)(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3737
+ amount: (0, import_viem8.formatUnits)(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3617
3738
  });
3618
3739
  amountRaw += rawAmount;
3619
3740
  }
@@ -3632,7 +3753,7 @@ function allocationEntry(action, context) {
3632
3753
  tokenSymbol: context.asset,
3633
3754
  agentWallet: context.wallet.walletAddress,
3634
3755
  amountRaw: String(amountRaw),
3635
- amount: (0, import_viem7.formatUnits)(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3756
+ amount: (0, import_viem8.formatUnits)(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3636
3757
  }]
3637
3758
  };
3638
3759
  }
@@ -3756,7 +3877,7 @@ function mapYieldseekerHistory(contexts, options) {
3756
3877
  const known = new Set(target.logIndexes ?? []);
3757
3878
  if (tx.logIndexes?.some((index) => !known.has(index))) {
3758
3879
  target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
3759
- target.amount = (0, import_viem7.formatUnits)(
3880
+ target.amount = (0, import_viem8.formatUnits)(
3760
3881
  BigInt(target.amountRaw),
3761
3882
  YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
3762
3883
  );
@@ -3837,51 +3958,53 @@ function mapYieldseekerAgentApy(options, days) {
3837
3958
  }
3838
3959
 
3839
3960
  // src/agents/yieldseeker/yieldseeker.wallet-version.ts
3840
- var import_viem8 = require("viem");
3961
+ var import_viem9 = require("viem");
3841
3962
  var YIELDSEEKER_FACTORY = "0x9c7410a0faCAC60850C46eE5b58B518daec95130";
3842
3963
  var YIELDSEEKER_V1_IMPLEMENTATION = "0x32403455357589523435dB18b414057D7cE05051";
3843
3964
  var YIELDSEEKER_V2_IMPLEMENTATION = "0x613Db9c924ADB7A7A90c11b02Cce20c913E3Fe50";
3844
3965
  var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
3845
- var walletAbi = (0, import_viem8.parseAbi)([
3966
+ var walletAbi = (0, import_viem9.parseAbi)([
3846
3967
  "function owner() view returns (address)",
3847
3968
  "function ownerAgentIndex() view returns (uint256)",
3848
3969
  "function baseAsset() view returns (address)",
3849
3970
  "function FACTORY() view returns (address)",
3850
- "function upgradeToLatest()"
3971
+ "function upgradeToLatest()",
3972
+ "event Upgraded(address indexed implementation)"
3851
3973
  ]);
3852
- var factoryAbi = (0, import_viem8.parseAbi)([
3974
+ var factoryAbi = (0, import_viem9.parseAbi)([
3853
3975
  "function userWallets(address owner, uint256 index) view returns (address)",
3854
3976
  "function agentWalletImplementation() view returns (address)"
3855
3977
  ]);
3856
3978
  function yieldseekerOwnerPath(code) {
3857
3979
  return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
3858
3980
  }
3859
- async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
3860
- const agentWallet = (0, import_viem8.getAddress)(wallet);
3861
- const expectedOwner = (0, import_viem8.getAddress)(owner);
3862
- const expectedAsset = (0, import_viem8.getAddress)(asset);
3981
+ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
3982
+ const agentWallet = (0, import_viem9.getAddress)(wallet);
3983
+ const expectedOwner = (0, import_viem9.getAddress)(owner);
3984
+ const expectedAsset = (0, import_viem9.getAddress)(asset);
3863
3985
  const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
3864
- client.getBytecode({ address: agentWallet }),
3865
- client.getBytecode({ address: expectedOwner }),
3866
- client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT }),
3867
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner" }),
3868
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex" }),
3869
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset" }),
3870
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY" }),
3871
- client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation" })
3986
+ client.getBytecode({ address: agentWallet, blockNumber }),
3987
+ client.getBytecode({ address: expectedOwner, blockNumber }),
3988
+ client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT, blockNumber }),
3989
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner", blockNumber }),
3990
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex", blockNumber }),
3991
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset", blockNumber }),
3992
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY", blockNumber }),
3993
+ client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation", blockNumber })
3872
3994
  ]);
3873
3995
  if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
3874
3996
  throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
3875
3997
  }
3876
- const implementation = (0, import_viem8.getAddress)(`0x${slot.slice(-40)}`);
3998
+ const implementation = (0, import_viem9.getAddress)(`0x${slot.slice(-40)}`);
3877
3999
  const mapped = await client.readContract({
3878
4000
  address: YIELDSEEKER_FACTORY,
3879
4001
  abi: factoryAbi,
3880
4002
  functionName: "userWallets",
3881
- args: [expectedOwner, index]
4003
+ args: [expectedOwner, index],
4004
+ blockNumber
3882
4005
  });
3883
- const verified = (0, import_viem8.isAddressEqual)(actualOwner, expectedOwner) && (0, import_viem8.isAddressEqual)(actualAsset, expectedAsset) && (0, import_viem8.isAddressEqual)(actualFactory, YIELDSEEKER_FACTORY) && (0, import_viem8.isAddressEqual)(mapped, agentWallet);
3884
- const version = !verified ? "unknown" : (0, import_viem8.isAddressEqual)(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : (0, import_viem8.isAddressEqual)(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
4006
+ const verified = (0, import_viem9.isAddressEqual)(actualOwner, expectedOwner) && (0, import_viem9.isAddressEqual)(actualAsset, expectedAsset) && (0, import_viem9.isAddressEqual)(actualFactory, YIELDSEEKER_FACTORY) && (0, import_viem9.isAddressEqual)(mapped, agentWallet);
4007
+ const version = !verified ? "unknown" : (0, import_viem9.isAddressEqual)(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : (0, import_viem9.isAddressEqual)(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
3885
4008
  return {
3886
4009
  agentWallet,
3887
4010
  owner: actualOwner,
@@ -3890,18 +4013,20 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
3890
4013
  implementation,
3891
4014
  version,
3892
4015
  verified,
3893
- upgradeAvailable: version === "v1" && (0, import_viem8.isAddressEqual)(latest, YIELDSEEKER_V2_IMPLEMENTATION)
4016
+ upgradeAvailable: version === "v1" && (0, import_viem9.isAddressEqual)(latest, YIELDSEEKER_V2_IMPLEMENTATION)
3894
4017
  };
3895
4018
  }
3896
4019
  var YIELDSEEKER_UPGRADE_ABI = walletAbi;
3897
4020
 
3898
4021
  // src/agents/yieldseeker/yieldseeker.sponsored-withdraw.ts
3899
- var import_viem9 = require("viem");
4022
+ var import_viem10 = require("viem");
3900
4023
  var import_account_abstraction2 = require("viem/account-abstraction");
3901
4024
  var import_accounts = require("viem/accounts");
3902
4025
  var import_chains3 = require("viem/chains");
3903
4026
  var ENTRY_POINT_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
3904
4027
  var USER_OP_FEE_HEADROOM = 2n;
4028
+ var OBSERVATION_WINDOW_MS = 6e4;
4029
+ var OBSERVATION_POLL_MS = 1500;
3905
4030
  var ENTRY_POINT_NONCE_ABI = [
3906
4031
  { type: "function", name: "getNonce", stateMutability: "view", inputs: [
3907
4032
  { name: "sender", type: "address" },
@@ -3934,10 +4059,119 @@ function sponsorError(message, safeToFallback, details = {}) {
3934
4059
  return new OwneyError(
3935
4060
  "SPONSOR_REQUEST_FAILED",
3936
4061
  message,
3937
- { safeToFallback, ...details },
4062
+ {
4063
+ safeToFallback,
4064
+ phase: safeToFallback ? "prepare" : "observe",
4065
+ failureCode: safeToFallback ? "SPONSOR_PREPARATION_FAILED" : "SPONSOR_OUTCOME_UNRESOLVED",
4066
+ ...safeToFallback ? { rejectedBeforeSubmission: true } : {},
4067
+ ...details
4068
+ },
3938
4069
  "yieldseeker"
3939
4070
  );
3940
4071
  }
4072
+ function providerReason(value) {
4073
+ if (typeof value !== "string") return void 0;
4074
+ 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);
4075
+ }
4076
+ function rpcDiagnostics(data) {
4077
+ const details = {};
4078
+ if (typeof data?.failureCode === "string" && [
4079
+ "SPONSOR_RATE_LIMIT",
4080
+ "SPONSOR_UPSTREAM_UNAVAILABLE",
4081
+ "SPONSOR_RPC_UNSUPPORTED",
4082
+ "SPONSOR_POLICY_REJECTED",
4083
+ "SPONSOR_BUDGET_REJECTED",
4084
+ "SPONSOR_EXECUTION_REVERTED"
4085
+ ].includes(data.failureCode))
4086
+ details.failureCode = data.failureCode;
4087
+ if (typeof data?.revertSelector === "string" && /^0x[0-9a-fA-F]{8}$/.test(data.revertSelector))
4088
+ details.revertSelector = data.revertSelector;
4089
+ if (typeof data?.revertName === "string" && [
4090
+ "InsufficientBalance",
4091
+ "InvalidAsset",
4092
+ "Unauthorized",
4093
+ "Error",
4094
+ "Panic",
4095
+ "FailedOp",
4096
+ "FailedOpWithRevert"
4097
+ ].includes(data.revertName))
4098
+ details.revertName = data.revertName;
4099
+ if (typeof data?.aaCode === "string" && /^AA[0-9]{2}$/.test(data.aaCode)) details.aaCode = data.aaCode;
4100
+ if (typeof data?.panicCode === "string" && /^0x[0-9a-fA-F]{1,2}$/.test(data.panicCode)) {
4101
+ const panicCode = (0, import_viem10.toHex)(BigInt(data.panicCode));
4102
+ if (["0x0", "0x1", "0x11", "0x12", "0x21", "0x22", "0x31", "0x32", "0x41", "0x51"].includes(panicCode))
4103
+ details.panicCode = panicCode;
4104
+ }
4105
+ return details;
4106
+ }
4107
+ function isHash(value) {
4108
+ return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
4109
+ }
4110
+ async function assertOwnerSession(state, owner) {
4111
+ const [chain, accounts] = await Promise.all([
4112
+ state.provider.request({ method: "eth_chainId" }),
4113
+ state.provider.request({ method: "eth_accounts" })
4114
+ ]);
4115
+ if (Number(chain) !== 8453)
4116
+ throw new OwneyError(
4117
+ "CHAIN_MISMATCH",
4118
+ "Switch back to Base before withdrawing.",
4119
+ { rejectedBeforeSubmission: true, phase: "prepare" },
4120
+ "yieldseeker"
4121
+ );
4122
+ if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase())
4123
+ throw new OwneyError(
4124
+ "NOT_CONNECTED",
4125
+ "The connected withdrawal account changed.",
4126
+ { rejectedBeforeSubmission: true, phase: "prepare" },
4127
+ "yieldseeker"
4128
+ );
4129
+ }
4130
+ function savePending(onPending, evidence, beforeSubmission = false) {
4131
+ try {
4132
+ onPending?.(evidence);
4133
+ } catch {
4134
+ if (beforeSubmission)
4135
+ throw sponsorError(
4136
+ "Withdrawal status cannot be saved before submission.",
4137
+ true,
4138
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
4139
+ );
4140
+ }
4141
+ }
4142
+ async function checkpoint(callback, pending) {
4143
+ try {
4144
+ await callback?.(pending);
4145
+ } catch {
4146
+ throw sponsorError(
4147
+ "Withdrawal status cannot be saved before submission.",
4148
+ true,
4149
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
4150
+ );
4151
+ }
4152
+ }
4153
+ function finalPaymasterFields(value) {
4154
+ const result = {};
4155
+ for (const key2 of [
4156
+ "paymasterAndData",
4157
+ "callGasLimit",
4158
+ "verificationGasLimit",
4159
+ "preVerificationGas",
4160
+ "maxFeePerGas",
4161
+ "maxPriorityFeePerGas"
4162
+ ]) {
4163
+ const field = value[key2];
4164
+ if (field === void 0) continue;
4165
+ if (typeof field !== "string" || !/^0x[0-9a-fA-F]+$/.test(field))
4166
+ throw sponsorError(
4167
+ "Paymaster returned an invalid final operation.",
4168
+ true,
4169
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INVALID_PAYMASTER_QUOTE" }
4170
+ );
4171
+ result[key2] = field;
4172
+ }
4173
+ return result;
4174
+ }
3941
4175
  async function requestIntent(input) {
3942
4176
  const baseUrl = (input.routingApiBaseUrl ?? DEFAULT_ROUTING_API).replace(/\/$/, "");
3943
4177
  let response;
@@ -3959,18 +4193,35 @@ async function requestIntent(input) {
3959
4193
  action: input.action,
3960
4194
  yieldseekerUserId: input.userId,
3961
4195
  yieldseekerAgentId: input.agentId
3962
- })
4196
+ }),
4197
+ signal: AbortSignal.timeout(15e3)
3963
4198
  });
3964
4199
  } catch {
3965
- throw sponsorError("Withdrawal sponsorship could not be requested.", true);
4200
+ throw sponsorError(
4201
+ "Withdrawal sponsorship could not be requested.",
4202
+ true,
4203
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INTENT_NETWORK_ERROR" }
4204
+ );
3966
4205
  }
3967
4206
  const envelope = await response.json().catch(() => null);
3968
4207
  const intent = envelope?.data;
3969
4208
  if (!response.ok || !envelope?.success || !intent?.intent || !["eoa", "smart"].includes(intent.ownerPath)) {
4209
+ const rawCode = typeof envelope?.failureCode === "string" ? envelope.failureCode : envelope?.code;
4210
+ const failureCode = typeof rawCode === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(rawCode) ? rawCode : void 0;
4211
+ if (input.refreshYieldseekerSignature && (failureCode === "YIELDSEEKER_AUTH_REJECTED" || failureCode === "AUTH_REJECTED" || envelope?.sponsorshipStage === "provider-auth" && (envelope.providerStatus === 401 || envelope.providerStatus === 403))) {
4212
+ const signature = await input.refreshYieldseekerSignature(input.yieldseekerSignature);
4213
+ return requestIntent({ ...input, yieldseekerSignature: signature, refreshYieldseekerSignature: void 0 });
4214
+ }
3970
4215
  throw sponsorError(
3971
4216
  "Withdrawal sponsorship is currently unavailable.",
3972
4217
  true,
3973
- { statusCode: response.status }
4218
+ {
4219
+ statusCode: response.status,
4220
+ phase: "prepare",
4221
+ rejectedBeforeSubmission: true,
4222
+ failureCode: failureCode ?? "INTENT_REJECTED",
4223
+ providerReason: providerReason(envelope?.message)
4224
+ }
3974
4225
  );
3975
4226
  }
3976
4227
  const url = new URL(`${baseUrl}/api/v1/sponsor/yieldseeker-withdraw/rpc`);
@@ -3983,19 +4234,55 @@ async function rpc(url, method, params, phase) {
3983
4234
  response = await fetch(url, {
3984
4235
  method: "POST",
3985
4236
  headers: { "content-type": "application/json" },
3986
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
4237
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
4238
+ signal: AbortSignal.timeout(15e3)
3987
4239
  });
3988
4240
  } catch {
3989
- throw sponsorError("Withdrawal sponsorship request status is unknown.", phase === "prepare");
4241
+ throw sponsorError(
4242
+ "Withdrawal sponsorship request status is unknown.",
4243
+ phase === "prepare",
4244
+ {
4245
+ phase,
4246
+ rpcMethod: method,
4247
+ ...phase === "prepare" ? { rejectedBeforeSubmission: true } : {},
4248
+ failureCode: "RPC_NETWORK_ERROR"
4249
+ }
4250
+ );
3990
4251
  }
3991
4252
  const payload = await response.json().catch(() => null);
3992
4253
  if (!response.ok || payload?.error || payload?.result === void 0) {
3993
4254
  const rejection = payload?.error;
3994
- 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 ?? ""));
4255
+ const diagnostics = rpcDiagnostics(rejection?.data);
4256
+ const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
4257
+ const details = {
4258
+ statusCode: response.status,
4259
+ rpcCode: rejection?.code,
4260
+ rpcMethod: method,
4261
+ phase,
4262
+ providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
4263
+ failureCode: "RPC_REJECTED",
4264
+ ...diagnostics
4265
+ };
4266
+ if (phase === "prepare" && executionReverted)
4267
+ throw new OwneyError(
4268
+ "WITHDRAW_FAILED",
4269
+ "Yieldseeker withdrawal simulation reverted before submission.",
4270
+ {
4271
+ ...details,
4272
+ failureCode: "SPONSOR_EXECUTION_REVERTED",
4273
+ safeToFallback: false,
4274
+ rejectedBeforeSubmission: true
4275
+ },
4276
+ "yieldseeker"
4277
+ );
4278
+ 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 ?? ""));
3995
4279
  throw sponsorError(
3996
4280
  phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
3997
4281
  phase === "prepare" || definitiveRejection,
3998
- { statusCode: response.status, rpcCode: payload?.error?.code, rpcMethod: method }
4282
+ {
4283
+ ...details,
4284
+ ...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
4285
+ }
3999
4286
  );
4000
4287
  }
4001
4288
  return payload.result;
@@ -4015,13 +4302,15 @@ async function submitEoa(input) {
4015
4302
  let nonce;
4016
4303
  let fees;
4017
4304
  try {
4018
- nonce = await input.client.readContract({
4019
- address: ENTRY_POINT_V06,
4020
- abi: ENTRY_POINT_NONCE_ABI,
4021
- functionName: "getNonce",
4022
- args: [input.agentWallet, 0n]
4023
- });
4024
- fees = await input.client.estimateFeesPerGas();
4305
+ [nonce, fees] = await Promise.all([
4306
+ input.client.readContract({
4307
+ address: ENTRY_POINT_V06,
4308
+ abi: ENTRY_POINT_NONCE_ABI,
4309
+ functionName: "getNonce",
4310
+ args: [input.agentWallet, 0n]
4311
+ }),
4312
+ input.client.estimateFeesPerGas()
4313
+ ]);
4025
4314
  } catch {
4026
4315
  throw sponsorError("Could not prepare the sponsored withdrawal.", true);
4027
4316
  }
@@ -4029,14 +4318,14 @@ async function submitEoa(input) {
4029
4318
  throw sponsorError("Base did not return EIP-1559 gas fees.", true);
4030
4319
  let operation = {
4031
4320
  sender: input.agentWallet,
4032
- nonce: (0, import_viem9.toHex)(nonce),
4321
+ nonce: (0, import_viem10.toHex)(nonce),
4033
4322
  initCode: "0x",
4034
4323
  callData: input.callData,
4035
- callGasLimit: (0, import_viem9.toHex)(3e5),
4036
- verificationGasLimit: (0, import_viem9.toHex)(3e5),
4037
- preVerificationGas: (0, import_viem9.toHex)(6e4),
4038
- maxFeePerGas: (0, import_viem9.toHex)(fees.maxFeePerGas * USER_OP_FEE_HEADROOM),
4039
- maxPriorityFeePerGas: (0, import_viem9.toHex)(fees.maxPriorityFeePerGas),
4324
+ callGasLimit: (0, import_viem10.toHex)(3e5),
4325
+ verificationGasLimit: (0, import_viem10.toHex)(3e5),
4326
+ preVerificationGas: (0, import_viem10.toHex)(6e4),
4327
+ maxFeePerGas: (0, import_viem10.toHex)(fees.maxFeePerGas * USER_OP_FEE_HEADROOM),
4328
+ maxPriorityFeePerGas: (0, import_viem10.toHex)(fees.maxPriorityFeePerGas),
4040
4329
  paymasterAndData: "0x",
4041
4330
  signature: "0x"
4042
4331
  };
@@ -4044,7 +4333,7 @@ async function submitEoa(input) {
4044
4333
  const stub = await rpc(
4045
4334
  input.rpcUrl,
4046
4335
  "pm_getPaymasterStubData",
4047
- [operation, ENTRY_POINT_V06, (0, import_viem9.toHex)(8453), {}],
4336
+ [operation, ENTRY_POINT_V06, (0, import_viem10.toHex)(8453), {}],
4048
4337
  "prepare"
4049
4338
  );
4050
4339
  if (!stub.paymasterAndData?.startsWith("0x"))
@@ -4059,39 +4348,63 @@ async function submitEoa(input) {
4059
4348
  const final = await rpc(
4060
4349
  input.rpcUrl,
4061
4350
  "pm_getPaymasterData",
4062
- [operation, ENTRY_POINT_V06, (0, import_viem9.toHex)(8453), {}],
4351
+ [operation, ENTRY_POINT_V06, (0, import_viem10.toHex)(8453), {}],
4063
4352
  "prepare"
4064
4353
  );
4065
4354
  if (!final.paymasterAndData?.startsWith("0x"))
4066
4355
  throw sponsorError("CDP did not return final paymaster data.", true);
4067
- operation = { ...operation, paymasterAndData: final.paymasterAndData };
4356
+ operation = { ...operation, ...finalPaymasterFields(final) };
4068
4357
  }
4069
4358
  const userOpHash = operationHash(operation);
4070
- const wallet = (0, import_viem9.createWalletClient)({
4359
+ await assertOwnerSession(input.state, input.owner);
4360
+ const wallet = (0, import_viem10.createWalletClient)({
4071
4361
  account: input.owner,
4072
4362
  chain: import_chains3.base,
4073
- transport: (0, import_viem9.custom)(input.state.provider)
4363
+ transport: (0, import_viem10.custom)(input.state.provider)
4074
4364
  });
4075
4365
  const signature = await wallet.signMessage({ message: { raw: userOpHash } });
4076
4366
  operation = { ...operation, signature };
4077
- input.onPending?.({ userOpHash });
4367
+ await assertOwnerSession(input.state, input.owner);
4368
+ const userOperation = {
4369
+ sender: operation.sender,
4370
+ nonce: operation.nonce,
4371
+ initCode: operation.initCode,
4372
+ callData: operation.callData,
4373
+ callGasLimit: operation.callGasLimit,
4374
+ verificationGasLimit: operation.verificationGasLimit,
4375
+ preVerificationGas: operation.preVerificationGas,
4376
+ maxFeePerGas: operation.maxFeePerGas,
4377
+ maxPriorityFeePerGas: operation.maxPriorityFeePerGas,
4378
+ paymasterAndData: operation.paymasterAndData
4379
+ };
4380
+ savePending(input.onPending, { userOpHash, userOperation }, true);
4381
+ await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
4078
4382
  const submittedHash = await rpc(
4079
4383
  input.rpcUrl,
4080
4384
  "eth_sendUserOperation",
4081
4385
  [operation, ENTRY_POINT_V06],
4082
4386
  "send"
4083
4387
  );
4084
- if (submittedHash.toLowerCase() !== userOpHash.toLowerCase())
4388
+ if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
4085
4389
  throw sponsorError(
4086
4390
  "Bundler returned a different UserOperation hash.",
4087
4391
  false,
4088
- { userOpHash, submittedHash }
4392
+ { userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
4089
4393
  );
4090
- for (let poll = 0; poll < 40; poll++) {
4091
- const response = await rpc(input.rpcUrl, "eth_getUserOperationReceipt", [userOpHash], "observe");
4394
+ const deadline = Date.now() + OBSERVATION_WINDOW_MS;
4395
+ let lastReadError;
4396
+ for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
4397
+ let response;
4398
+ try {
4399
+ response = await rpc(input.rpcUrl, "eth_getUserOperationReceipt", [userOpHash], "observe");
4400
+ } catch (error) {
4401
+ lastReadError = error;
4402
+ await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
4403
+ continue;
4404
+ }
4092
4405
  const txHash = response?.receipt?.transactionHash;
4093
- if (txHash) {
4094
- input.onPending?.({ userOpHash, txHash });
4406
+ if (isHash(txHash)) {
4407
+ savePending(input.onPending, { userOpHash, txHash });
4095
4408
  if (response?.success === false)
4096
4409
  throw new OwneyError(
4097
4410
  "AGENT_TRANSACTION_REVERTED",
@@ -4101,12 +4414,17 @@ async function submitEoa(input) {
4101
4414
  );
4102
4415
  return { txHash, receipt: await waitForTransaction(input.client, txHash) };
4103
4416
  }
4104
- await new Promise((resolve) => setTimeout(resolve, 1500));
4417
+ await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
4105
4418
  }
4106
4419
  throw sponsorError(
4107
4420
  "Sponsored withdrawal is still pending; check status before retrying.",
4108
4421
  false,
4109
- { userOpHash }
4422
+ {
4423
+ userOpHash,
4424
+ phase: "observe",
4425
+ failureCode: "RECEIPT_UNRESOLVED",
4426
+ ...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
4427
+ }
4110
4428
  );
4111
4429
  }
4112
4430
  async function submitSmart(input) {
@@ -4119,21 +4437,22 @@ async function submitSmart(input) {
4119
4437
  } catch {
4120
4438
  throw sponsorError("This account cannot check paymaster support on Base.", true);
4121
4439
  }
4122
- if (!capabilities?.[(0, import_viem9.toHex)(8453)]?.paymasterService?.supported && !capabilities?.["8453"]?.paymasterService?.supported)
4440
+ const supportsPaymaster = capabilities?.[(0, import_viem10.toHex)(8453)]?.paymasterService?.supported ?? capabilities?.["8453"]?.paymasterService?.supported ?? capabilities?.["0x0"]?.paymasterService?.supported;
4441
+ if (supportsPaymaster !== true)
4123
4442
  throw sponsorError("This account cannot use a paymaster on Base.", true);
4443
+ await assertOwnerSession(input.state, input.owner);
4124
4444
  let sent;
4125
- try {
4126
- input.onPending?.({});
4127
- } catch {
4128
- throw sponsorError("Upgrade or withdrawal status cannot be saved.", true);
4129
- }
4445
+ const requestedId = (0, import_viem10.toHex)(globalThis.crypto.getRandomValues(new Uint8Array(32)));
4446
+ savePending(input.onPending, { callsId: requestedId }, true);
4447
+ await checkpoint(input.onBeforeSubmission, { callsId: requestedId });
4130
4448
  try {
4131
4449
  sent = await input.state.provider.request({
4132
4450
  method: "wallet_sendCalls",
4133
4451
  params: [{
4134
4452
  version: "2.0.0",
4135
4453
  from: input.owner,
4136
- chainId: (0, import_viem9.toHex)(8453),
4454
+ chainId: (0, import_viem10.toHex)(8453),
4455
+ id: requestedId,
4137
4456
  atomicRequired: false,
4138
4457
  calls: [{ to: input.agentWallet, value: "0x0", data: input.callData }],
4139
4458
  capabilities: { paymasterService: { url: input.rpcUrl } }
@@ -4142,17 +4461,27 @@ async function submitSmart(input) {
4142
4461
  } catch (error) {
4143
4462
  if (error?.code === 4001) throw error;
4144
4463
  if ([4200, -32601].includes(Number(error?.code)))
4145
- throw sponsorError("This account does not support sponsored calls.", true);
4146
- throw sponsorError("Wallet submission status is unknown. Check activity before retrying.", false);
4464
+ throw sponsorError(
4465
+ "This account does not support sponsored calls.",
4466
+ true,
4467
+ { phase: "send", failureCode: "WALLET_CALLS_UNSUPPORTED" }
4468
+ );
4469
+ throw sponsorError(
4470
+ "Wallet submission status is unknown. Check activity before retrying.",
4471
+ false,
4472
+ { callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
4473
+ );
4147
4474
  }
4148
4475
  const callsId = typeof sent === "string" ? sent : sent?.id;
4149
- if (!callsId) throw sponsorError("Wallet returned no calls ID; submission status is unknown.", false);
4150
- try {
4151
- input.onPending?.({ callsId });
4152
- } catch {
4153
- throw sponsorError("Submitted call status could not be saved.", false, { callsId });
4154
- }
4155
- for (let poll = 0; poll < 40; poll++) {
4476
+ if (typeof callsId !== "string" || callsId.length === 0 || callsId.length > 8194)
4477
+ throw sponsorError(
4478
+ "Wallet returned no calls ID; submission status is unknown.",
4479
+ false,
4480
+ { callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
4481
+ );
4482
+ savePending(input.onPending, { callsId });
4483
+ const deadline = Date.now() + OBSERVATION_WINDOW_MS;
4484
+ for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
4156
4485
  let status;
4157
4486
  try {
4158
4487
  status = await input.state.provider.request({
@@ -4160,37 +4489,44 @@ async function submitSmart(input) {
4160
4489
  params: [callsId]
4161
4490
  });
4162
4491
  } catch {
4163
- throw sponsorError("Sponsored withdrawal status is unknown.", false, { callsId });
4492
+ await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
4493
+ continue;
4164
4494
  }
4165
- if (status?.status === 400 || status?.status === 500)
4495
+ if (status?.id !== void 0 && status.id !== callsId || status?.chainId !== void 0 && Number(status.chainId) !== 8453)
4496
+ throw sponsorError(
4497
+ "Wallet returned a different sponsored operation status.",
4498
+ false,
4499
+ { callsId, phase: "observe", failureCode: "WALLET_STATUS_MISMATCH" }
4500
+ );
4501
+ if (Number(status?.status) === 400 || Number(status?.status) === 500)
4166
4502
  throw sponsorError(
4167
4503
  "Sponsored withdrawal failed in the wallet.",
4168
4504
  false,
4169
- { callsId, executionFailed: true }
4505
+ { callsId, executionFailed: true, phase: "observe", failureCode: "WALLET_EXECUTION_FAILED" }
4170
4506
  );
4171
- const txHash = status?.receipts?.[0]?.transactionHash;
4507
+ const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash).filter(isHash))];
4508
+ const txHash = hashes.length === 1 ? hashes[0] : void 0;
4172
4509
  if (txHash) {
4173
- try {
4174
- input.onPending?.({ callsId, txHash });
4175
- } catch {
4176
- throw sponsorError(
4177
- "Submitted call receipt could not be saved.",
4178
- false,
4179
- { callsId, txHash }
4510
+ savePending(input.onPending, { callsId, txHash });
4511
+ if (status?.receipts?.some((receipt) => receipt.transactionHash === txHash && /^(?:0x0+|0|failed|reverted)$/i.test(String(receipt.status))))
4512
+ throw new OwneyError(
4513
+ "AGENT_TRANSACTION_REVERTED",
4514
+ "Sponsored withdrawal execution reverted.",
4515
+ { callsId, transactionHash: txHash },
4516
+ "yieldseeker"
4180
4517
  );
4181
- }
4182
4518
  return { txHash, receipt: await waitForTransaction(input.client, txHash) };
4183
4519
  }
4184
- await new Promise((resolve) => setTimeout(resolve, 1500));
4520
+ await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
4185
4521
  }
4186
4522
  throw sponsorError(
4187
4523
  "Sponsored withdrawal is still pending; check status before retrying.",
4188
4524
  false,
4189
- { callsId }
4525
+ { callsId, phase: "observe", failureCode: "RECEIPT_UNRESOLVED" }
4190
4526
  );
4191
4527
  }
4192
4528
  async function submitYieldseekerSponsoredWithdrawal(input) {
4193
- const owner = (0, import_viem9.getAddress)(input.state.walletAddress);
4529
+ const owner = (0, import_viem10.getAddress)(input.state.walletAddress);
4194
4530
  const { intent, rpcUrl } = await requestIntent({
4195
4531
  apiKey: input.apiKey,
4196
4532
  routingApiBaseUrl: input.routingApiBaseUrl,
@@ -4201,13 +4537,14 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
4201
4537
  agentWallet: input.agentWallet,
4202
4538
  asset: input.asset,
4203
4539
  callData: input.callData,
4204
- action: "withdraw"
4540
+ action: "withdraw",
4541
+ refreshYieldseekerSignature: input.refreshYieldseekerSignature
4205
4542
  });
4206
4543
  const args = { ...input, owner, rpcUrl };
4207
4544
  return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
4208
4545
  }
4209
4546
  async function submitYieldseekerSponsoredUpgrade(input) {
4210
- const owner = (0, import_viem9.getAddress)(input.state.walletAddress);
4547
+ const owner = (0, import_viem10.getAddress)(input.state.walletAddress);
4211
4548
  const { intent, rpcUrl } = await requestIntent({
4212
4549
  apiKey: input.apiKey,
4213
4550
  routingApiBaseUrl: input.routingApiBaseUrl,
@@ -4218,7 +4555,8 @@ async function submitYieldseekerSponsoredUpgrade(input) {
4218
4555
  agentWallet: input.agentWallet,
4219
4556
  asset: input.asset,
4220
4557
  callData: input.callData,
4221
- action: "upgrade"
4558
+ action: "upgrade",
4559
+ refreshYieldseekerSignature: input.refreshYieldseekerSignature
4222
4560
  });
4223
4561
  if (intent.ownerPath !== "smart")
4224
4562
  throw sponsorError("V1 upgrade sponsorship requires a Base Account owner.", true);
@@ -4240,6 +4578,7 @@ var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
4240
4578
  var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
4241
4579
  var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
4242
4580
  var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
4581
+ var YIELDSEEKER_MAX_POSITION_VERIFICATION_MS = 2e3;
4243
4582
  function generateYieldseekerUsername() {
4244
4583
  const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
4245
4584
  return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
@@ -4250,6 +4589,13 @@ function isUsernameConflict(error) {
4250
4589
  return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
4251
4590
  }
4252
4591
  var YIELDSEEKER_AGENT_WALLET_ABI = [
4592
+ {
4593
+ type: "function",
4594
+ name: "feeTracker",
4595
+ stateMutability: "view",
4596
+ inputs: [],
4597
+ outputs: [{ name: "", type: "address" }]
4598
+ },
4253
4599
  {
4254
4600
  type: "function",
4255
4601
  name: "withdrawAssetToUser",
@@ -4272,6 +4618,13 @@ var YIELDSEEKER_AGENT_WALLET_ABI = [
4272
4618
  outputs: []
4273
4619
  }
4274
4620
  ];
4621
+ var YIELDSEEKER_FEE_TRACKER_ABI = [{
4622
+ type: "function",
4623
+ name: "getFeesOwed",
4624
+ stateMutability: "view",
4625
+ inputs: [{ name: "wallet", type: "address" }],
4626
+ outputs: [{ name: "", type: "uint256" }]
4627
+ }];
4275
4628
  function query(params) {
4276
4629
  const search = new URLSearchParams();
4277
4630
  for (const [key2, value] of Object.entries(params)) {
@@ -4351,21 +4704,26 @@ var YieldseekerAgent = class {
4351
4704
  if (useCachedV2 && cached && Date.now() - cached.at < 15 * 6e4) return { ...cached.status };
4352
4705
  const status = await readYieldseekerWalletStatus(
4353
4706
  this.getReceiptClient(),
4354
- (0, import_viem10.getAddress)(context.wallet.walletAddress),
4355
- (0, import_viem10.getAddress)(state.walletAddress),
4707
+ (0, import_viem11.getAddress)(context.wallet.walletAddress),
4708
+ (0, import_viem11.getAddress)(state.walletAddress),
4356
4709
  YIELDSEEKER_ASSET_METADATA[asset].address
4357
4710
  );
4711
+ this.rememberWalletStatus(key2, status);
4712
+ return status;
4713
+ }
4714
+ rememberWalletStatus(key2, status) {
4358
4715
  if (status.verified && status.version === "v2") {
4359
4716
  this.withdrawalVersionCache.set(key2, { at: Date.now(), status: { ...status } });
4360
4717
  if (this.withdrawalVersionCache.size > 128)
4361
4718
  this.withdrawalVersionCache.delete(this.withdrawalVersionCache.keys().next().value);
4362
4719
  } else this.withdrawalVersionCache.delete(key2);
4363
- return status;
4364
4720
  }
4365
4721
  /** Explicit owner action. This never runs as a side effect of withdrawal. */
4366
4722
  async upgradeWallet(state, chainId, asset, gasMode = "paid") {
4723
+ const generation = this.readGeneration;
4367
4724
  const before = await this.getWalletStatus(state, chainId, asset);
4368
- if (!before?.verified || !before.upgradeAvailable) {
4725
+ this.assertSession(generation, chainId);
4726
+ if (!before?.verified || before.version === "unknown") {
4369
4727
  throw new OwneyError(
4370
4728
  "WITHDRAW_FAILED",
4371
4729
  "This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
@@ -4373,48 +4731,77 @@ var YieldseekerAgent = class {
4373
4731
  this.id
4374
4732
  );
4375
4733
  }
4376
- const data = (0, import_viem10.encodeFunctionData)({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
4377
- let txHash;
4378
- if (gasMode === "sponsored") {
4379
- if (before.ownerPath !== "smart")
4380
- throw new OwneyError(
4381
- "SPONSOR_REQUEST_FAILED",
4382
- "EOA owners pay Base gas for the V1 upgrade.",
4383
- { safeToFallback: true },
4384
- this.id
4385
- );
4386
- let storage5;
4387
- try {
4388
- storage5 = typeof window === "undefined" ? void 0 : window.localStorage;
4389
- } catch {
4390
- storage5 = void 0;
4391
- }
4392
- if (!storage5)
4734
+ const versionKey = `${chainId}:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}:${asset}`;
4735
+ const pendingKey2 = `owney.yieldseeker.upgrade:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
4736
+ const confirmUpgrade = async (hash, receipt2) => {
4737
+ this.withdrawalVersionCache.delete(versionKey);
4738
+ const upgradeTopic = (0, import_viem11.encodeEventTopics)({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded" })[0];
4739
+ const lastUpgrade = receipt2?.logs.filter((log) => log.address.toLowerCase() === before.agentWallet.toLowerCase() && log.topics[0] === upgradeTopic).at(-1);
4740
+ const expectedUpgrade = (() => {
4741
+ if (!lastUpgrade || lastUpgrade.removed) return false;
4742
+ try {
4743
+ const event = (0, import_viem11.decodeEventLog)({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded", data: lastUpgrade.data, topics: lastUpgrade.topics });
4744
+ return event.args.implementation.toLowerCase() === YIELDSEEKER_V2_IMPLEMENTATION.toLowerCase();
4745
+ } catch {
4746
+ return false;
4747
+ }
4748
+ })();
4749
+ if (!receipt2 || receipt2.status !== "success" || receipt2.transactionHash.toLowerCase() !== hash.toLowerCase() || typeof receipt2.blockNumber !== "bigint" || !expectedUpgrade)
4393
4750
  throw new OwneyError(
4394
- "SPONSOR_REQUEST_FAILED",
4395
- "Upgrade status cannot be saved in this browser.",
4396
- { safeToFallback: true },
4751
+ "WITHDRAW_FAILED",
4752
+ "Yieldseeker wallet upgrade receipt could not be verified.",
4753
+ { txHash: hash, reason: "UPGRADE_RECEIPT_MISMATCH" },
4397
4754
  this.id
4398
4755
  );
4399
- const pendingKey = `owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
4400
- let prior = null;
4401
- let saved;
4756
+ this.assertSession(generation, chainId);
4757
+ const status = {
4758
+ ...before,
4759
+ implementation: YIELDSEEKER_V2_IMPLEMENTATION,
4760
+ version: "v2",
4761
+ upgradeAvailable: false
4762
+ };
4763
+ this.rememberWalletStatus(versionKey, status);
4764
+ return { txHash: hash, status };
4765
+ };
4766
+ let storage5;
4767
+ try {
4768
+ storage5 = typeof window === "undefined" ? void 0 : window.localStorage;
4769
+ } catch {
4770
+ storage5 = void 0;
4771
+ }
4772
+ const clearConfirmedUpgrade = () => {
4402
4773
  try {
4403
- saved = storage5.getItem(pendingKey);
4774
+ storage5?.removeItem(pendingKey2);
4404
4775
  } catch {
4405
- throw new OwneyError(
4406
- "SPONSOR_REQUEST_FAILED",
4407
- "Upgrade status cannot be read in this browser.",
4408
- { safeToFallback: true },
4409
- this.id
4410
- );
4411
4776
  }
4777
+ };
4778
+ if (!storage5 && (gasMode === "sponsored" || before.ownerPath === "smart"))
4779
+ throw new OwneyError(
4780
+ "SPONSOR_REQUEST_FAILED",
4781
+ "Upgrade status cannot be saved in this browser.",
4782
+ { safeToFallback: false },
4783
+ this.id
4784
+ );
4785
+ let prior = null;
4786
+ try {
4787
+ const saved = storage5?.getItem(pendingKey2);
4412
4788
  try {
4413
4789
  prior = JSON.parse(saved ?? "null");
4414
4790
  } catch {
4415
4791
  prior = { callsId: void 0 };
4416
4792
  }
4417
- if (prior) {
4793
+ } catch {
4794
+ if (gasMode === "paid" && before.ownerPath === "eoa") storage5 = void 0;
4795
+ else throw new OwneyError(
4796
+ "SPONSOR_REQUEST_FAILED",
4797
+ "Upgrade status cannot be read in this browser.",
4798
+ { safeToFallback: false },
4799
+ this.id
4800
+ );
4801
+ }
4802
+ if (prior) {
4803
+ let earlierHash = this.isTransactionHash(prior.txHash) ? prior.txHash : void 0;
4804
+ if (!earlierHash) {
4418
4805
  if (!prior.callsId)
4419
4806
  throw new OwneyError(
4420
4807
  "SPONSOR_REQUEST_FAILED",
@@ -4424,10 +4811,7 @@ var YieldseekerAgent = class {
4424
4811
  );
4425
4812
  let callsStatus;
4426
4813
  try {
4427
- callsStatus = await state.provider.request({
4428
- method: "wallet_getCallsStatus",
4429
- params: [prior.callsId]
4430
- });
4814
+ callsStatus = await state.provider.request({ method: "wallet_getCallsStatus", params: [prior.callsId] });
4431
4815
  } catch {
4432
4816
  throw new OwneyError(
4433
4817
  "SPONSOR_REQUEST_FAILED",
@@ -4436,8 +4820,15 @@ var YieldseekerAgent = class {
4436
4820
  this.id
4437
4821
  );
4438
4822
  }
4439
- if (callsStatus?.status === 400 || callsStatus?.status === 500) {
4440
- storage5.removeItem(pendingKey);
4823
+ if (callsStatus?.id !== void 0 && callsStatus.id !== prior.callsId || callsStatus?.chainId !== void 0 && Number(callsStatus.chainId) !== chainId)
4824
+ throw new OwneyError(
4825
+ "SPONSOR_REQUEST_FAILED",
4826
+ "The earlier upgrade returned a different operation status.",
4827
+ { safeToFallback: false, callsId: prior.callsId },
4828
+ this.id
4829
+ );
4830
+ if (Number(callsStatus?.status) === 400 || Number(callsStatus?.status) === 500) {
4831
+ storage5.removeItem(pendingKey2);
4441
4832
  throw new OwneyError(
4442
4833
  "SPONSOR_REQUEST_FAILED",
4443
4834
  "The earlier sponsored upgrade failed.",
@@ -4445,22 +4836,39 @@ var YieldseekerAgent = class {
4445
4836
  this.id
4446
4837
  );
4447
4838
  }
4448
- const earlierHash = callsStatus?.receipts?.[0]?.transactionHash;
4449
- if (earlierHash) {
4450
- await this.waitForReceipt(state, chainId, earlierHash);
4451
- const earlierStatus = await this.getWalletStatus(state, chainId, asset);
4452
- if (earlierStatus?.version === "v2" && earlierStatus.verified) {
4453
- storage5.removeItem(pendingKey);
4454
- return { txHash: earlierHash, status: earlierStatus };
4455
- }
4456
- }
4839
+ const hashes = [...new Set((callsStatus?.receipts ?? []).map((value) => value.transactionHash).filter(this.isTransactionHash))];
4840
+ earlierHash = hashes.length === 1 ? hashes[0] : void 0;
4841
+ }
4842
+ if (earlierHash) {
4843
+ const recovered = await confirmUpgrade(earlierHash, await this.waitForReceipt(state, chainId, earlierHash));
4844
+ clearConfirmedUpgrade();
4845
+ return recovered;
4846
+ }
4847
+ throw new OwneyError(
4848
+ "SPONSOR_REQUEST_FAILED",
4849
+ "An earlier upgrade is still pending.",
4850
+ { safeToFallback: false, callsId: prior.callsId },
4851
+ this.id
4852
+ );
4853
+ }
4854
+ if (!before.upgradeAvailable)
4855
+ throw new OwneyError(
4856
+ "WITHDRAW_FAILED",
4857
+ "This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
4858
+ { version: before.version },
4859
+ this.id
4860
+ );
4861
+ const data = (0, import_viem11.encodeFunctionData)({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
4862
+ let txHash;
4863
+ let receipt;
4864
+ if (gasMode === "sponsored") {
4865
+ if (before.ownerPath !== "smart")
4457
4866
  throw new OwneyError(
4458
4867
  "SPONSOR_REQUEST_FAILED",
4459
- "An earlier upgrade is still pending.",
4460
- { safeToFallback: false, callsId: prior.callsId },
4868
+ "EOA owners pay Base gas for the V1 upgrade.",
4869
+ { safeToFallback: true },
4461
4870
  this.id
4462
4871
  );
4463
- }
4464
4872
  const context = await this.findAgent(state, chainId, asset);
4465
4873
  if (!context) throw new OwneyError("NOT_CONNECTED", "Yieldseeker agent is unavailable.");
4466
4874
  try {
@@ -4470,14 +4878,15 @@ var YieldseekerAgent = class {
4470
4878
  apiKey: this.owneyApiKey,
4471
4879
  routingApiBaseUrl: this.routingApiBaseUrl,
4472
4880
  yieldseekerSignature: await this.auth.getToken(state, chainId),
4881
+ refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
4473
4882
  userId: context.user.userId,
4474
4883
  agentId: context.agent.agentId,
4475
4884
  agentWallet: before.agentWallet,
4476
4885
  asset: YIELDSEEKER_ASSET_METADATA[asset].address,
4477
4886
  callData: data,
4478
4887
  onPending: (pending) => {
4479
- const current = storage5.getItem(pendingKey);
4480
- storage5.setItem(pendingKey, JSON.stringify({
4888
+ const current = storage5.getItem(pendingKey2);
4889
+ storage5.setItem(pendingKey2, JSON.stringify({
4481
4890
  startedAt: Date.now(),
4482
4891
  ...current ? JSON.parse(current) : {},
4483
4892
  ...pending
@@ -4485,31 +4894,25 @@ var YieldseekerAgent = class {
4485
4894
  }
4486
4895
  });
4487
4896
  txHash = upgraded.txHash;
4897
+ receipt = upgraded.receipt;
4488
4898
  } catch (error) {
4489
- if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey);
4899
+ if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey2);
4490
4900
  throw error;
4491
4901
  }
4492
4902
  } else {
4493
4903
  txHash = await this.submitTransaction(state, chainId, {
4494
- from: (0, import_viem10.getAddress)(state.walletAddress),
4904
+ from: (0, import_viem11.getAddress)(state.walletAddress),
4495
4905
  to: before.agentWallet,
4496
4906
  data,
4497
4907
  value: "0",
4498
4908
  chainId
4909
+ }, void 0, (value) => {
4910
+ receipt = value;
4499
4911
  });
4500
4912
  }
4501
- const status = await this.getWalletStatus(state, chainId, asset);
4502
- if (!status?.verified || status.version !== "v2") {
4503
- throw new OwneyError(
4504
- "WITHDRAW_FAILED",
4505
- "Yieldseeker wallet upgrade was not confirmed as V2.",
4506
- { txHash, version: status?.version ?? "missing" },
4507
- this.id
4508
- );
4509
- }
4510
- if (gasMode === "sponsored" && typeof window !== "undefined")
4511
- window.localStorage.removeItem(`owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`);
4512
- return { txHash, status };
4913
+ const confirmed = await confirmUpgrade(txHash, receipt ?? await this.waitForReceipt(state, chainId, txHash));
4914
+ if (gasMode === "sponsored") clearConfirmedUpgrade();
4915
+ return confirmed;
4513
4916
  }
4514
4917
  async disconnect() {
4515
4918
  this.withdrawalVersionCache.clear();
@@ -4582,19 +4985,37 @@ var YieldseekerAgent = class {
4582
4985
  submitted(txHash);
4583
4986
  receipt = await this.waitForReceipt(state, chainId, txHash);
4584
4987
  } else {
4585
- txHash = await this.submitTransaction(state, chainId, {
4586
- from: (0, import_viem10.getAddress)(state.walletAddress),
4587
- to: YIELDSEEKER_ASSET_METADATA[asset].address,
4588
- data: (0, import_viem10.encodeFunctionData)({
4589
- abi: import_viem10.erc20Abi,
4590
- functionName: "transfer",
4591
- args: [(0, import_viem10.getAddress)(context.wallet.walletAddress), BigInt(amount)]
4592
- }),
4593
- value: "0",
4594
- chainId
4595
- }, submitted, (value) => {
4596
- receipt = value;
4597
- });
4988
+ try {
4989
+ txHash = await this.submitTransaction(state, chainId, {
4990
+ from: (0, import_viem11.getAddress)(state.walletAddress),
4991
+ to: YIELDSEEKER_ASSET_METADATA[asset].address,
4992
+ data: (0, import_viem11.encodeFunctionData)({
4993
+ abi: import_viem11.erc20Abi,
4994
+ functionName: "transfer",
4995
+ args: [(0, import_viem11.getAddress)(context.wallet.walletAddress), BigInt(amount)]
4996
+ }),
4997
+ value: "0",
4998
+ chainId
4999
+ }, submitted, (value) => {
5000
+ receipt = value;
5001
+ });
5002
+ } catch (error) {
5003
+ if (error instanceof OwneyError && error.details?.ownerTransferNotExecuted === true && error.details.reason === "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL") {
5004
+ notifyFunding(onProgress, {
5005
+ agentId: this.id,
5006
+ chainId,
5007
+ token: asset,
5008
+ walletAddress: state.walletAddress,
5009
+ agentWallet: context.wallet.walletAddress,
5010
+ intendedAmount: amount,
5011
+ txHash: String(error.details.originalTransactionHash),
5012
+ transfer: "failed",
5013
+ registration: "not-applicable",
5014
+ delivery: "not-applicable"
5015
+ });
5016
+ }
5017
+ throw error;
5018
+ }
4598
5019
  }
4599
5020
  if (this.readGeneration === generation) {
4600
5021
  void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
@@ -4604,7 +5025,7 @@ var YieldseekerAgent = class {
4604
5025
  notifyFunding(onProgress, evidence);
4605
5026
  return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
4606
5027
  }
4607
- async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto") {
5028
+ async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
4608
5029
  let ownerTransferAttempted = false;
4609
5030
  let evidence = {
4610
5031
  agentId: this.id,
@@ -4622,10 +5043,10 @@ var YieldseekerAgent = class {
4622
5043
  notifyFunding(onProgress, evidence);
4623
5044
  }, () => {
4624
5045
  ownerTransferAttempted = true;
4625
- }, gasMode);
5046
+ }, gasMode, onBeforeSubmission);
4626
5047
  } catch (error) {
4627
5048
  if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
4628
- 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)))
5049
+ 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)))
4629
5050
  notifyFunding(onProgress, {
4630
5051
  ...evidence,
4631
5052
  ...error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true ? { userOpHash: void 0, callsId: void 0 } : {},
@@ -4635,7 +5056,7 @@ var YieldseekerAgent = class {
4635
5056
  throw error;
4636
5057
  }
4637
5058
  }
4638
- async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto") {
5059
+ async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
4639
5060
  this.assertChain(chainId);
4640
5061
  this.assertAsset(asset);
4641
5062
  if (amount !== void 0 && BigInt(amount) <= 0n) {
@@ -4680,37 +5101,121 @@ var YieldseekerAgent = class {
4680
5101
  let confirmedMovement = false;
4681
5102
  let settlement = null;
4682
5103
  try {
4683
- if (amount === void 0) this.advancePortfolioVersion(this.contextKey(state, chainId, asset));
4684
- const portfolio = await this.loadPortfolioContext(
4685
- state,
4686
- chainId,
4687
- context
4688
- );
5104
+ const maxSettlementBasis = amount === void 0 ? this.snapshotMovement(state, chainId, context, 0n) : null;
5105
+ this.advancePortfolioVersion(this.contextKey(state, chainId, asset));
5106
+ 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 }));
4689
5107
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
4690
- const readIdle = async () => {
4691
- const balance = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
4692
- address: metadata.address,
4693
- abi: import_viem10.erc20Abi,
4694
- functionName: "balanceOf",
4695
- args: [(0, import_viem10.getAddress)(context.wallet.walletAddress)]
4696
- }), chainId, "eth_call", this.id);
5108
+ const agentWallet = (0, import_viem11.getAddress)(context.wallet.walletAddress);
5109
+ const feeTrackerRead = withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5110
+ address: agentWallet,
5111
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
5112
+ functionName: "feeTracker"
5113
+ }), chainId, "eth_call", this.id);
5114
+ const readOwnerFunds = async () => {
5115
+ const feeTracker = await feeTrackerRead;
4697
5116
  this.assertSession(generation, chainId, asset);
4698
- return balance;
5117
+ if (!(0, import_viem11.isAddress)(feeTracker) || /^0x0{40}$/i.test(feeTracker))
5118
+ throw this.invalidResponse("wallet fee tracker");
5119
+ const [idle2, fees] = await Promise.all([
5120
+ withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5121
+ address: metadata.address,
5122
+ abi: import_viem11.erc20Abi,
5123
+ functionName: "balanceOf",
5124
+ args: [agentWallet]
5125
+ }), chainId, "eth_call", this.id),
5126
+ withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5127
+ address: (0, import_viem11.getAddress)(feeTracker),
5128
+ abi: YIELDSEEKER_FEE_TRACKER_ABI,
5129
+ functionName: "getFeesOwed",
5130
+ args: [agentWallet]
5131
+ }), chainId, "eth_call", this.id)
5132
+ ]);
5133
+ this.assertSession(generation, chainId, asset);
5134
+ if (typeof idle2 !== "bigint" || idle2 < 0n || typeof fees !== "bigint" || fees < 0n)
5135
+ throw this.invalidResponse("wallet withdrawal balance");
5136
+ return { idle: idle2, fees, available: idle2 > fees ? idle2 - fees : 0n };
4699
5137
  };
4700
- const idle = await readIdle();
5138
+ const [portfolio, initialFunds] = await Promise.all([portfolioRead, readOwnerFunds()]);
5139
+ const idle = initialFunds.idle;
4701
5140
  let positions = portfolio.positions;
4702
5141
  let positionsStatus = "empty";
4703
5142
  const validatePositions = (values) => {
4704
5143
  const seen = /* @__PURE__ */ new Set();
4705
5144
  for (const value of values) {
4706
5145
  const address = value?.yieldOption?.address;
4707
- if (typeof address !== "string" || !(0, import_viem10.isAddress)(address) || seen.has(address.toLowerCase()) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
5146
+ if (typeof address !== "string" || !(0, import_viem11.isAddress)(address) || seen.has(address.toLowerCase()) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
4708
5147
  throw this.invalidResponse("yield positions");
4709
5148
  seen.add(address.toLowerCase());
4710
5149
  }
4711
5150
  return values;
4712
5151
  };
4713
- const hasPositions = (values) => values.some((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
5152
+ const zeroValuePositions = /* @__PURE__ */ new WeakSet();
5153
+ const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
5154
+ const knownPositions = /* @__PURE__ */ new Map();
5155
+ const rememberPositions = (values) => {
5156
+ for (const position2 of values) knownPositions.set(position2.yieldOption.address.toLowerCase(), position2);
5157
+ };
5158
+ const boundedVerification = async (read2, timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
5159
+ let timer;
5160
+ try {
5161
+ return await Promise.race([read2, new Promise((resolve) => {
5162
+ timer = setTimeout(() => resolve(void 0), Math.max(0, timeoutMs));
5163
+ })]);
5164
+ } finally {
5165
+ if (timer) clearTimeout(timer);
5166
+ }
5167
+ };
5168
+ const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
5169
+ const verifiedPositions = [];
5170
+ if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
5171
+ const reads = values.map(async (position2) => {
5172
+ const address = (0, import_viem11.getAddress)(position2.yieldOption.address);
5173
+ try {
5174
+ const [underlying, shares, withdrawable] = await Promise.all([
5175
+ this.getReceiptClient().readContract({ address, abi: import_viem11.erc4626Abi, functionName: "asset", blockNumber }),
5176
+ this.getReceiptClient().readContract({ address, abi: import_viem11.erc20Abi, functionName: "balanceOf", args: [agentWallet], blockNumber }),
5177
+ this.getReceiptClient().readContract({ address, abi: import_viem11.erc4626Abi, functionName: "maxWithdraw", args: [agentWallet], blockNumber })
5178
+ ]);
5179
+ if (typeof underlying !== "string" || !(0, import_viem11.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
5180
+ const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: withdrawable.toString() };
5181
+ if (shares > 0n && withdrawable === 0n) {
5182
+ let positiveValue = false;
5183
+ const valuations = await Promise.all(["convertToAssets", "previewRedeem"].map(async (functionName) => {
5184
+ try {
5185
+ const value = await this.getReceiptClient().readContract({
5186
+ address,
5187
+ abi: import_viem11.erc4626Abi,
5188
+ functionName,
5189
+ args: [shares],
5190
+ blockNumber
5191
+ });
5192
+ if (typeof value !== "bigint" || value < 0n) return null;
5193
+ if (value > 0n && !positiveValue) {
5194
+ positiveValue = true;
5195
+ verifiedPositions.push(verified);
5196
+ }
5197
+ return value;
5198
+ } catch {
5199
+ return null;
5200
+ }
5201
+ }));
5202
+ if (positiveValue) return verified;
5203
+ if (blockNumber === void 0 || valuations.some((value) => value !== 0n)) return null;
5204
+ zeroValuePositions.add(verified);
5205
+ }
5206
+ verifiedPositions.push(verified);
5207
+ return verified;
5208
+ } catch {
5209
+ return null;
5210
+ }
5211
+ });
5212
+ const result = await boundedVerification(Promise.all(reads), timeoutMs);
5213
+ this.assertSession(generation, chainId, asset);
5214
+ return {
5215
+ verified: result !== void 0 && result.every((position2) => position2 !== null),
5216
+ positions: [...verifiedPositions]
5217
+ };
5218
+ };
4714
5219
  const refreshPositions = async () => {
4715
5220
  this.assertSession(generation, chainId, asset);
4716
5221
  const response = await this.walletRequest(
@@ -4724,21 +5229,40 @@ var YieldseekerAgent = class {
4724
5229
  };
4725
5230
  if (amount === void 0) {
4726
5231
  validatePositions(positions);
5232
+ rememberPositions(positions);
4727
5233
  positionsStatus = hasPositions(positions) ? "remaining" : "empty";
4728
5234
  }
4729
5235
  const deployed = positions.reduce(
4730
5236
  (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
4731
5237
  0n
4732
5238
  );
4733
- const totalAvailable = idle + deployed;
5239
+ const grossAvailable = idle + deployed;
5240
+ const totalAvailable = grossAvailable > initialFunds.fees ? grossAvailable - initialFunds.fees : 0n;
4734
5241
  const requested = amount === void 0 ? totalAvailable : BigInt(amount);
4735
5242
  if (requested === 0n) {
4736
5243
  this.invalidateFundingRead(state, chainId, asset);
4737
- if (amount === void 0 && positionsStatus === "remaining")
5244
+ const zeroPayoutCandidates = positions.filter((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
5245
+ if (amount === void 0 && zeroPayoutCandidates.length) {
5246
+ const deadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
5247
+ const blockNumber = await boundedVerification(
5248
+ Promise.resolve().then(() => this.getReceiptClient().getBlockNumber()).catch(() => void 0)
5249
+ );
5250
+ const proof = typeof blockNumber === "bigint" ? await verifyKnownPositions(blockNumber, zeroPayoutCandidates, deadline - Date.now()) : { verified: false, positions: [] };
5251
+ positions = proof.positions;
5252
+ positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
5253
+ if (positionsStatus !== "empty")
5254
+ throw new OwneyError(
5255
+ "WITHDRAW_FAILED",
5256
+ "Yieldseeker vault funds are not currently withdrawable.",
5257
+ { asset, positionsStatus, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
5258
+ this.id
5259
+ );
5260
+ }
5261
+ if (grossAvailable > 0n && totalAvailable === 0n)
4738
5262
  throw new OwneyError(
4739
- "WITHDRAW_FAILED",
4740
- "Yieldseeker vault funds are not currently withdrawable.",
4741
- { asset, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
5263
+ "WITHDRAW_INSUFFICIENT_BALANCE",
5264
+ "No owner-withdrawable Yieldseeker funds remain after reserved fees.",
5265
+ { asset, available: "0", reason: "YIELDSEEKER_FEES_ONLY", feesReserved: initialFunds.fees.toString() },
4742
5266
  this.id
4743
5267
  );
4744
5268
  throw new OwneyError(
@@ -4760,28 +5284,50 @@ var YieldseekerAgent = class {
4760
5284
  this.id
4761
5285
  );
4762
5286
  }
4763
- let remaining = requested > idle ? requested - idle : 0n;
5287
+ const requiredIdle = requested + initialFunds.fees;
5288
+ let remaining = requiredIdle > idle ? requiredIdle - idle : 0n;
4764
5289
  let grossUnwound = 0n;
5290
+ let latestUnwindBlock;
4765
5291
  const unwind = async (position2, assetsRaw) => {
4766
5292
  this.assertSession(generation, chainId, asset);
4767
- const response = await this.walletRequest(
4768
- state,
4769
- chainId,
4770
- this.agentPath(context, "withdraw-from-position"),
4771
- {
4772
- method: "POST",
4773
- body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
4774
- }
4775
- );
4776
- if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
4777
- await this.waitForReceipt(state, chainId, response.transactionHash);
5293
+ let unwindTransactionHash;
5294
+ try {
5295
+ const response = await this.walletRequest(
5296
+ state,
5297
+ chainId,
5298
+ this.agentPath(context, "withdraw-from-position"),
5299
+ {
5300
+ method: "POST",
5301
+ body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
5302
+ }
5303
+ );
5304
+ if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
5305
+ unwindTransactionHash = response.transactionHash;
5306
+ const unwindReceipt = await this.waitForReceipt(state, chainId, unwindTransactionHash);
5307
+ if (unwindReceipt) latestUnwindBlock = unwindReceipt.blockNumber;
5308
+ } catch (error) {
5309
+ this.invalidateFundingRead(state, chainId, asset);
5310
+ throw new OwneyError(
5311
+ error instanceof OwneyError ? error.code : "WITHDRAW_FAILED",
5312
+ "Could not confirm the Yieldseeker vault release. No owner transfer was submitted.",
5313
+ {
5314
+ ...error instanceof OwneyError ? error.details : {},
5315
+ withdrawalPhase: "unwind",
5316
+ reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
5317
+ vaultAddress: position2.yieldOption.address,
5318
+ unwindTransactionHash,
5319
+ ownerTransferSubmitted: false
5320
+ },
5321
+ this.id
5322
+ );
5323
+ }
4778
5324
  confirmedMovement = true;
4779
5325
  this.assertSession(generation, chainId, asset);
4780
5326
  grossUnwound += assetsRaw;
4781
5327
  };
4782
5328
  if (amount === void 0) {
4783
5329
  for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
4784
- const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.assetsRaw)]));
5330
+ const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
4785
5331
  let unwound = false;
4786
5332
  for (const position2 of positions) {
4787
5333
  const available = BigInt(position2.withdrawableAssetsRaw);
@@ -4790,15 +5336,22 @@ var YieldseekerAgent = class {
4790
5336
  unwound = true;
4791
5337
  }
4792
5338
  if (!unwound) break;
4793
- try {
4794
- positions = await refreshPositions();
4795
- positionsStatus = hasPositions(positions) ? "remaining" : "empty";
4796
- } catch {
4797
- this.assertSession(generation, chainId, asset);
5339
+ const knownBeforeRefresh = new Set(knownPositions.keys());
5340
+ const [catalog, proof] = await Promise.all([
5341
+ boundedVerification(refreshPositions().catch(() => void 0)),
5342
+ verifyKnownPositions(latestUnwindBlock)
5343
+ ]);
5344
+ if (catalog) rememberPositions(catalog);
5345
+ this.assertSession(generation, chainId, asset);
5346
+ if (proof.verified) {
5347
+ positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.yieldOption.address.toLowerCase()))];
5348
+ } else if (catalog) positions = catalog;
5349
+ else {
4798
5350
  positionsStatus = "unavailable";
4799
5351
  break;
4800
5352
  }
4801
- if (positions.some((position2) => BigInt(position2.assetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.assetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
5353
+ positionsStatus = hasPositions(positions) ? "remaining" : "empty";
5354
+ if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
4802
5355
  }
4803
5356
  } else {
4804
5357
  for (const position2 of positions) {
@@ -4816,8 +5369,8 @@ var YieldseekerAgent = class {
4816
5369
  remaining: remaining.toString()
4817
5370
  });
4818
5371
  }
4819
- const availableForOwner = confirmedMovement ? await readIdle() : idle;
4820
- const feeRate = portfolio.snapshot.feeRateBps;
5372
+ const availableForOwner = confirmedMovement ? (await readOwnerFunds()).available : initialFunds.available;
5373
+ const feeRate = portfolio.feeRateBps;
4821
5374
  const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
4822
5375
  const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
4823
5376
  if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
@@ -4837,20 +5390,23 @@ var YieldseekerAgent = class {
4837
5390
  );
4838
5391
  }
4839
5392
  const payout = amount === void 0 || availableForOwner < requested ? availableForOwner : requested;
4840
- const plannedMovement = this.snapshotMovement(state, chainId, context, -payout);
4841
- const account = (0, import_viem10.getAddress)(state.walletAddress);
5393
+ const plannedMovement = amount === void 0 && maxSettlementBasis ? {
5394
+ ...maxSettlementBasis,
5395
+ delta: maxSettlementBasis.delta - payout,
5396
+ expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
5397
+ } : this.snapshotMovement(state, chainId, context, -payout);
5398
+ const account = (0, import_viem11.getAddress)(state.walletAddress);
4842
5399
  let receipt;
4843
5400
  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" };
4844
- const data = amount === void 0 ? (0, import_viem10.encodeFunctionData)({
5401
+ const data = amount === void 0 ? (0, import_viem11.encodeFunctionData)({
4845
5402
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
4846
5403
  functionName: "withdrawAllAssetToUser",
4847
5404
  args: [account, metadata.address]
4848
- }) : (0, import_viem10.encodeFunctionData)({
5405
+ }) : (0, import_viem11.encodeFunctionData)({
4849
5406
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
4850
5407
  functionName: "withdrawAssetToUser",
4851
5408
  args: [account, metadata.address, payout]
4852
5409
  });
4853
- onOwnerTransferAttempted?.();
4854
5410
  let txHash;
4855
5411
  if (sponsor) {
4856
5412
  const submitted = await submitYieldseekerSponsoredWithdrawal({
@@ -4859,19 +5415,25 @@ var YieldseekerAgent = class {
4859
5415
  apiKey: this.owneyApiKey,
4860
5416
  routingApiBaseUrl: this.routingApiBaseUrl,
4861
5417
  yieldseekerSignature: await this.auth.getToken(state, chainId),
5418
+ refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
4862
5419
  userId: context.user.userId,
4863
5420
  agentId: context.agent.agentId,
4864
- agentWallet: (0, import_viem10.getAddress)(context.wallet.walletAddress),
5421
+ agentWallet: (0, import_viem11.getAddress)(context.wallet.walletAddress),
4865
5422
  asset: metadata.address,
4866
5423
  callData: data,
4867
- onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending })
5424
+ onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending }),
5425
+ onBeforeSubmission: async (pending) => {
5426
+ await onBeforeSubmission?.({ ...base6, ...pending });
5427
+ onOwnerTransferAttempted?.();
5428
+ }
4868
5429
  });
4869
5430
  txHash = submitted.txHash;
4870
5431
  receipt = submitted.receipt;
4871
5432
  } else {
5433
+ onOwnerTransferAttempted?.();
4872
5434
  txHash = await this.submitTransaction(state, chainId, {
4873
5435
  from: account,
4874
- to: (0, import_viem10.getAddress)(context.wallet.walletAddress),
5436
+ to: (0, import_viem11.getAddress)(context.wallet.walletAddress),
4875
5437
  data,
4876
5438
  value: "0",
4877
5439
  chainId
@@ -4883,14 +5445,26 @@ var YieldseekerAgent = class {
4883
5445
  settlement = plannedMovement;
4884
5446
  const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
4885
5447
  notifyFunding(onProgress, evidence);
5448
+ let completion;
4886
5449
  if (amount === void 0) {
4887
- try {
4888
- positions = await refreshPositions();
4889
- positionsStatus = hasPositions(positions) ? "remaining" : "empty";
4890
- } catch {
4891
- positionsStatus = "unavailable";
4892
- }
4893
- if (positionsStatus !== "empty") {
5450
+ const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
5451
+ const knownAtPayout = new Set(knownPositions.keys());
5452
+ const [catalogVerified, knownProof] = await Promise.all([
5453
+ boundedVerification(refreshPositions().then((values) => {
5454
+ rememberPositions(values);
5455
+ return true;
5456
+ }).catch(() => false)),
5457
+ verifyKnownPositions(receipt?.blockNumber)
5458
+ ]);
5459
+ const newPositions = [...knownPositions].filter(([address]) => !knownAtPayout.has(address)).map(([, position2]) => position2);
5460
+ const newProof = newPositions.length ? await verifyKnownPositions(receipt?.blockNumber, newPositions, verificationDeadline - Date.now()) : { verified: true, positions: [] };
5461
+ const proof = {
5462
+ verified: catalogVerified === true && knownProof.verified && newProof.verified,
5463
+ positions: [...knownProof.positions, ...newProof.positions]
5464
+ };
5465
+ positions = proof.positions;
5466
+ positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
5467
+ if (positionsStatus === "remaining") {
4894
5468
  const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
4895
5469
  throw new OwneyError(
4896
5470
  "WITHDRAW_PARTIAL_FAILURE",
@@ -4899,7 +5473,7 @@ var YieldseekerAgent = class {
4899
5473
  reason: "YIELDSEEKER_MAX_INCOMPLETE",
4900
5474
  asset,
4901
5475
  positionsStatus,
4902
- remainingPositions: positionsStatus === "remaining" ? positions.map((position2) => ({
5476
+ remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
4903
5477
  vaultAddress: position2.yieldOption.address,
4904
5478
  sharesRaw: position2.assetsRaw,
4905
5479
  withdrawableAmountRaw: position2.withdrawableAssetsRaw
@@ -4909,6 +5483,7 @@ var YieldseekerAgent = class {
4909
5483
  this.id
4910
5484
  );
4911
5485
  }
5486
+ if (!proof.verified) completion = "unverified";
4912
5487
  }
4913
5488
  if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
4914
5489
  throw new OwneyError(
@@ -4917,7 +5492,13 @@ var YieldseekerAgent = class {
4917
5492
  { asset, available: "0", txHash },
4918
5493
  this.id
4919
5494
  );
4920
- return { txHash, type: amount === void 0 ? "full" : "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
5495
+ return {
5496
+ txHash,
5497
+ type: amount === void 0 && !completion ? "full" : "partial",
5498
+ ...completion ? { completion } : {},
5499
+ amount: evidence.confirmedAmount ?? payout.toString(),
5500
+ evidence
5501
+ };
4921
5502
  } finally {
4922
5503
  if (confirmedMovement && this.readGeneration === generation) {
4923
5504
  void this.refreshSnapshotAfterMovement(
@@ -4964,6 +5545,23 @@ var YieldseekerAgent = class {
4964
5545
  if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
4965
5546
  return context.wallet.walletAddress;
4966
5547
  }
5548
+ async hasExistingDepositFunds(state, chainId, asset) {
5549
+ this.assertChain(chainId);
5550
+ this.assertAsset(asset);
5551
+ const generation = this.readGeneration;
5552
+ const context = await this.findAgent(state, chainId, asset);
5553
+ this.assertSession(generation, chainId, asset);
5554
+ if (!context) return void 0;
5555
+ const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5556
+ address: YIELDSEEKER_ASSET_METADATA[asset].address,
5557
+ abi: import_viem11.erc20Abi,
5558
+ functionName: "balanceOf",
5559
+ args: [(0, import_viem11.getAddress)(context.wallet.walletAddress)]
5560
+ }), chainId, "eth_call", this.id);
5561
+ this.assertSession(generation, chainId, asset);
5562
+ if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
5563
+ return idle > 0n ? true : void 0;
5564
+ }
4967
5565
  async getEarnings(state, chainId) {
4968
5566
  this.assertChain(chainId);
4969
5567
  return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
@@ -4994,7 +5592,7 @@ var YieldseekerAgent = class {
4994
5592
  );
4995
5593
  const vaultAddresses = new Set(
4996
5594
  catalog.flat().filter(
4997
- (yieldOption) => yieldOption.chainId === chainId && (0, import_viem10.isAddress)(yieldOption.address)
5595
+ (yieldOption) => yieldOption.chainId === chainId && (0, import_viem11.isAddress)(yieldOption.address)
4998
5596
  ).map((yieldOption) => yieldOption.address.toLowerCase())
4999
5597
  );
5000
5598
  return mapYieldseekerHistory(contexts, {
@@ -5121,7 +5719,7 @@ var YieldseekerAgent = class {
5121
5719
  `/users/${user.userId}/agents/${agent.agentId}/wallet`
5122
5720
  ).then((walletResponse) => {
5123
5721
  this.assertSession(generation, chainId, asset);
5124
- if (!walletResponse?.agentWallet || !(0, import_viem10.isAddress)(walletResponse.agentWallet.walletAddress)) {
5722
+ if (!walletResponse?.agentWallet || !(0, import_viem11.isAddress)(walletResponse.agentWallet.walletAddress)) {
5125
5723
  throw this.invalidResponse("agent wallet");
5126
5724
  }
5127
5725
  const context = {
@@ -5151,7 +5749,7 @@ var YieldseekerAgent = class {
5151
5749
  this.users.set(key2, persisted);
5152
5750
  return persisted;
5153
5751
  }
5154
- const walletAddress = (0, import_viem10.getAddress)(state.walletAddress);
5752
+ const walletAddress = (0, import_viem11.getAddress)(state.walletAddress);
5155
5753
  const generation = this.readGeneration;
5156
5754
  let user = null;
5157
5755
  try {
@@ -5534,7 +6132,7 @@ var YieldseekerAgent = class {
5534
6132
  this.agentPath(context, "deploy"),
5535
6133
  { method: "POST", body: {} }
5536
6134
  );
5537
- if (!deployed?.agentWallet || !(0, import_viem10.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
6135
+ if (!deployed?.agentWallet || !(0, import_viem11.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
5538
6136
  throw this.invalidResponse("agent deployment", {
5539
6137
  reason: "Deploy did not return the expected Agent Wallet."
5540
6138
  });
@@ -5642,15 +6240,15 @@ var YieldseekerAgent = class {
5642
6240
  return hash2;
5643
6241
  }
5644
6242
  this.assertTransaction(transaction, state, chainId);
5645
- const account = (0, import_viem10.getAddress)(state.walletAddress);
5646
- const walletClient = (0, import_viem10.createWalletClient)({
6243
+ const account = (0, import_viem11.getAddress)(state.walletAddress);
6244
+ const walletClient = (0, import_viem11.createWalletClient)({
5647
6245
  account,
5648
6246
  chain: import_chains4.base,
5649
- transport: (0, import_viem10.custom)(state.provider)
6247
+ transport: (0, import_viem11.custom)(state.provider)
5650
6248
  });
5651
- const walletChainClient = (0, import_viem10.createPublicClient)({
6249
+ const walletChainClient = (0, import_viem11.createPublicClient)({
5652
6250
  chain: import_chains4.base,
5653
- transport: (0, import_viem10.custom)(state.provider)
6251
+ transport: (0, import_viem11.custom)(state.provider)
5654
6252
  });
5655
6253
  await ensureWalletOnChain(
5656
6254
  walletChainClient,
@@ -5660,30 +6258,84 @@ var YieldseekerAgent = class {
5660
6258
  const hash = await walletClient.sendTransaction({
5661
6259
  account,
5662
6260
  chain: import_chains4.base,
5663
- to: (0, import_viem10.getAddress)(transaction.to),
6261
+ to: (0, import_viem11.getAddress)(transaction.to),
5664
6262
  data: transaction.data,
5665
6263
  value: BigInt(transaction.value)
5666
6264
  });
5667
6265
  onSubmitted?.(hash);
6266
+ let replacement;
5668
6267
  const receipt = await withPaidRpcDiagnostics(
5669
6268
  () => this.getReceiptClient().waitForTransactionReceipt({
5670
6269
  hash,
5671
- confirmations: 1
6270
+ confirmations: 1,
6271
+ onReplaced: (value) => {
6272
+ replacement = value;
6273
+ }
5672
6274
  }),
5673
6275
  8453,
5674
6276
  "eth_getTransactionReceipt",
5675
6277
  this.id
5676
6278
  );
6279
+ const actualHash = receipt.transactionHash;
6280
+ if (!this.isTransactionHash(actualHash))
6281
+ throw new OwneyError(
6282
+ "WITHDRAW_FAILED",
6283
+ "The confirmed transaction hash could not be verified.",
6284
+ { originalTransactionHash: hash, ownerTransferNotExecuted: false },
6285
+ this.id
6286
+ );
6287
+ if (actualHash.toLowerCase() !== hash.toLowerCase()) {
6288
+ if (!replacement || replacement.transaction.hash.toLowerCase() !== actualHash.toLowerCase()) {
6289
+ try {
6290
+ const [original2, actual2] = await Promise.all([
6291
+ this.getReceiptClient().getTransaction({ hash }),
6292
+ this.getReceiptClient().getTransaction({ hash: actualHash })
6293
+ ]);
6294
+ replacement = { replacedTransaction: original2, transaction: actual2 };
6295
+ } catch {
6296
+ replacement = void 0;
6297
+ }
6298
+ }
6299
+ const original = replacement?.replacedTransaction;
6300
+ const actual = replacement?.transaction;
6301
+ 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;
6302
+ if (!sameNonce)
6303
+ throw new OwneyError(
6304
+ "WITHDRAW_FAILED",
6305
+ "The replacement transaction could not be verified.",
6306
+ {
6307
+ originalTransactionHash: hash,
6308
+ replacementTransactionHash: actualHash,
6309
+ reason: "TRANSACTION_REPLACEMENT_UNRESOLVED",
6310
+ ownerTransferNotExecuted: false
6311
+ },
6312
+ this.id
6313
+ );
6314
+ const sameCall = actual.to?.toLowerCase() === transaction.to.toLowerCase() && actual.input?.toLowerCase() === transaction.data.toLowerCase() && actual.value === BigInt(transaction.value);
6315
+ if (!sameCall)
6316
+ throw new OwneyError(
6317
+ "WITHDRAW_FAILED",
6318
+ "The transaction was replaced without executing this withdrawal.",
6319
+ {
6320
+ originalTransactionHash: hash,
6321
+ replacementTransactionHash: actualHash,
6322
+ reason: "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL",
6323
+ ownerTransferNotExecuted: true
6324
+ },
6325
+ this.id
6326
+ );
6327
+ onSubmitted?.(actualHash);
6328
+ }
5677
6329
  if (receipt.status !== "success") {
5678
6330
  throw new OwneyError(
5679
6331
  "AGENT_TRANSACTION_REVERTED",
5680
- `Yieldseeker transaction reverted (${hash}).`,
5681
- { transactionHash: hash },
6332
+ `Yieldseeker transaction reverted (${actualHash}).`,
6333
+ { transactionHash: actualHash },
5682
6334
  this.id
5683
6335
  );
5684
6336
  }
5685
6337
  onReceipt?.(receipt);
5686
- return hash;
6338
+ return actualHash;
5687
6339
  }
5688
6340
  async waitForReceipt(state, chainId, transactionHash) {
5689
6341
  if (this.unwindReceiptWaiter) {
@@ -5710,12 +6362,12 @@ var YieldseekerAgent = class {
5710
6362
  return receipt;
5711
6363
  }
5712
6364
  assertTransaction(transaction, state, chainId) {
5713
- if (!transaction || typeof transaction.from !== "string" || !(0, import_viem10.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem10.isAddress)(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || (0, import_viem10.getAddress)(transaction.from) !== (0, import_viem10.getAddress)(state.walletAddress)) {
6365
+ if (!transaction || typeof transaction.from !== "string" || !(0, import_viem11.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem11.isAddress)(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || (0, import_viem11.getAddress)(transaction.from) !== (0, import_viem11.getAddress)(state.walletAddress)) {
5714
6366
  throw this.invalidResponse("transaction");
5715
6367
  }
5716
6368
  }
5717
6369
  assertAgent(agent) {
5718
- if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem10.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
6370
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem11.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
5719
6371
  throw this.invalidResponse("agent");
5720
6372
  }
5721
6373
  }
@@ -5869,7 +6521,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
5869
6521
  }
5870
6522
 
5871
6523
  // src/lib/helpers/withdraw-helper.ts
5872
- var import_viem11 = require("viem");
6524
+ var import_viem12 = require("viem");
5873
6525
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
5874
6526
  const target = asset.toUpperCase();
5875
6527
  return agents.map((agent) => {
@@ -5877,7 +6529,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
5877
6529
  const tokenBalance = agentBalance?.tokens.find(
5878
6530
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
5879
6531
  );
5880
- let balance = tokenBalance ? (0, import_viem11.parseUnits)(tokenBalance.amount, decimals) : 0n;
6532
+ let balance = tokenBalance ? (0, import_viem12.parseUnits)(tokenBalance.amount, decimals) : 0n;
5881
6533
  if (agent.balanceComposition === "tokens-plus-positions") {
5882
6534
  const chainNameById = {
5883
6535
  1: "ETHEREUM",
@@ -5889,14 +6541,14 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
5889
6541
  const positionChain = position2.chain.trim().toUpperCase();
5890
6542
  const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
5891
6543
  if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
5892
- balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : (0, import_viem11.parseUnits)(position2.amount, decimals);
6544
+ balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : (0, import_viem12.parseUnits)(position2.amount, decimals);
5893
6545
  }
5894
6546
  }
5895
6547
  const assetTotal = agentBalance?.assetBalances?.find(
5896
6548
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
5897
6549
  );
5898
6550
  if (assetTotal) {
5899
- const total = (0, import_viem11.parseUnits)(assetTotal.amount, decimals);
6551
+ const total = (0, import_viem12.parseUnits)(assetTotal.amount, decimals);
5900
6552
  if (total < balance) balance = total > 0n ? total : 0n;
5901
6553
  }
5902
6554
  return { agent, balance };
@@ -6055,14 +6707,14 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
6055
6707
  }
6056
6708
 
6057
6709
  // src/client.ts
6058
- var import_viem15 = require("viem");
6710
+ var import_viem16 = require("viem");
6059
6711
  var import_chains5 = require("viem/chains");
6060
6712
 
6061
6713
  // src/lib/sponsored-token-batch.ts
6062
- var import_viem13 = require("viem");
6714
+ var import_viem14 = require("viem");
6063
6715
 
6064
6716
  // src/lib/permit2-batch.ts
6065
- var import_viem12 = require("viem");
6717
+ var import_viem13 = require("viem");
6066
6718
  var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
6067
6719
  var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
6068
6720
  var ERC2612_PERMIT_TYPES = {
@@ -6074,7 +6726,7 @@ var ERC2612_PERMIT_TYPES = {
6074
6726
  { name: "deadline", type: "uint256" }
6075
6727
  ]
6076
6728
  };
6077
- var ERC2612_READ_ABI = (0, import_viem12.parseAbi)([
6729
+ var ERC2612_READ_ABI = (0, import_viem13.parseAbi)([
6078
6730
  "function name() view returns (string)",
6079
6731
  "function version() view returns (string)",
6080
6732
  "function nonces(address owner) view returns (uint256)"
@@ -6112,7 +6764,7 @@ var PERMIT_BATCH_TYPES = {
6112
6764
  { name: "amount", type: "uint256" }
6113
6765
  ]
6114
6766
  };
6115
- var PERMIT2_BATCH_ABI = (0, import_viem12.parseAbi)([
6767
+ var PERMIT2_BATCH_ABI = (0, import_viem13.parseAbi)([
6116
6768
  "struct TokenPermissions { address token; uint256 amount; }",
6117
6769
  "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
6118
6770
  "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
@@ -6155,6 +6807,18 @@ var planOf = (transfers) => JSON.stringify(
6155
6807
  amount: BigInt(t.amount).toString()
6156
6808
  }))
6157
6809
  );
6810
+ var pendingKey = (i) => `owney.token-batch.v1:${(0, import_viem14.keccak256)((0, import_viem14.toBytes)(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
6811
+ function pendingTokenBatch(i) {
6812
+ try {
6813
+ const saved = read(pendingKey(i));
6814
+ if (!saved) return;
6815
+ const body = JSON.parse(saved);
6816
+ if (!body.serializedTransaction || body.chainId !== i.chainId || !(0, import_viem14.isAddressEqual)(body.from, i.owner) || !(0, import_viem14.isAddressEqual)(body.token, i.token) || !body.transfers?.length) return;
6817
+ return { txHash: (0, import_viem14.keccak256)(body.serializedTransaction), transfers: body.transfers.map(({ to, amount }) => ({ to, amount })) };
6818
+ } catch {
6819
+ return;
6820
+ }
6821
+ }
6158
6822
  function read(key2) {
6159
6823
  return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
6160
6824
  }
@@ -6171,11 +6835,11 @@ function clear(key2) {
6171
6835
  else window.localStorage.removeItem(key2);
6172
6836
  }
6173
6837
  function sponsorTokenBatch(i) {
6174
- const key2 = `owney.token-batch.v1:${(0, import_viem13.keccak256)((0, import_viem13.toBytes)(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
6838
+ const key2 = pendingKey(i);
6175
6839
  const plan = planOf(i.transfers);
6176
6840
  const active = inflight.get(key2);
6177
6841
  if (active) {
6178
- if (active.plan !== plan)
6842
+ if (active.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
6179
6843
  return Promise.reject(
6180
6844
  new Error(
6181
6845
  "A token deposit is already in progress. Wait for its result before depositing again."
@@ -6202,7 +6866,7 @@ async function execute(i, key2, plan) {
6202
6866
  baseUrl: i.baseUrl,
6203
6867
  body
6204
6868
  });
6205
- if (!preparedPermit.serializedPermitTransaction || !preparedPermit.permitTxHash || (0, import_viem13.keccak256)(preparedPermit.serializedPermitTransaction) !== preparedPermit.permitTxHash)
6869
+ if (!preparedPermit.serializedPermitTransaction || !preparedPermit.permitTxHash || (0, import_viem14.keccak256)(preparedPermit.serializedPermitTransaction) !== preparedPermit.permitTxHash)
6206
6870
  throw new Error(
6207
6871
  "Sponsorship API did not return a valid prepared permit transaction."
6208
6872
  );
@@ -6218,7 +6882,7 @@ async function execute(i, key2, plan) {
6218
6882
  baseUrl: i.baseUrl,
6219
6883
  body
6220
6884
  });
6221
- if (!prepared.serializedTransaction || (0, import_viem13.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
6885
+ if (!prepared.serializedTransaction || (0, import_viem14.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
6222
6886
  throw new Error(
6223
6887
  "Sponsorship API did not return a valid prepared transaction."
6224
6888
  );
@@ -6229,7 +6893,7 @@ async function execute(i, key2, plan) {
6229
6893
  save(key2, body);
6230
6894
  }
6231
6895
  try {
6232
- i.onSubmitted?.((0, import_viem13.keccak256)(body.serializedTransaction));
6896
+ i.onSubmitted?.((0, import_viem14.keccak256)(body.serializedTransaction));
6233
6897
  } catch {
6234
6898
  }
6235
6899
  const result = await postSponsorBatchTransfer({
@@ -6237,7 +6901,7 @@ async function execute(i, key2, plan) {
6237
6901
  baseUrl: i.baseUrl,
6238
6902
  body
6239
6903
  });
6240
- if (result.txHash !== (0, import_viem13.keccak256)(body.serializedTransaction))
6904
+ if (result.txHash !== (0, import_viem14.keccak256)(body.serializedTransaction))
6241
6905
  throw new Error(
6242
6906
  "Sponsorship receipt does not match the pending transaction."
6243
6907
  );
@@ -6250,9 +6914,11 @@ async function execute(i, key2, plan) {
6250
6914
  }
6251
6915
  };
6252
6916
  const saved = read(key2);
6917
+ if (i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
6918
+ throw new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit changed. Check its status before depositing again.");
6253
6919
  if (saved) {
6254
6920
  const previous = JSON.parse(saved);
6255
- if (previous.chainId !== i.chainId || !(0, import_viem13.isAddressEqual)(previous.from, i.owner) || !(0, import_viem13.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
6921
+ if (previous.chainId !== i.chainId || !(0, import_viem14.isAddressEqual)(previous.from, i.owner) || !(0, import_viem14.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
6256
6922
  throw new Error(
6257
6923
  "Retry the previous token deposit and agent split first to reconcile its status."
6258
6924
  );
@@ -6269,7 +6935,7 @@ async function execute(i, key2, plan) {
6269
6935
  "DEPOSIT_INSUFFICIENT_BALANCE",
6270
6936
  "Insufficient token balance for this deposit."
6271
6937
  );
6272
- if (allowance < total && (i.chainId !== 8453 || !(0, import_viem13.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
6938
+ if (allowance < total && (i.chainId !== 8453 || !(0, import_viem14.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
6273
6939
  throw new OwneyError(
6274
6940
  "PERMIT2_APPROVAL_REQUIRED",
6275
6941
  "token deposits need a one-time Permit2 approval."
@@ -6361,7 +7027,8 @@ function makeSponsoredTokenCallback(deps) {
6361
7027
  pub,
6362
7028
  wallet,
6363
7029
  onApproved: deps.onApproved,
6364
- onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
7030
+ onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers),
7031
+ resumeTxHash: deps.resumeTxHash
6365
7032
  });
6366
7033
  };
6367
7034
  const callback = makeVerificationAwareDepositCallback(
@@ -6447,7 +7114,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
6447
7114
  }
6448
7115
 
6449
7116
  // src/lib/sponsored-calls-deposit.ts
6450
- var import_viem14 = require("viem");
7117
+ var import_viem15 = require("viem");
6451
7118
  var DEFAULT_POLL_INTERVAL_MS = 1500;
6452
7119
  var DEFAULT_MAX_POLLS = 30;
6453
7120
  async function paymasterSupported(provider, owner, chainId) {
@@ -6455,7 +7122,7 @@ async function paymasterSupported(provider, owner, chainId) {
6455
7122
  method: "wallet_getCapabilities",
6456
7123
  params: [owner]
6457
7124
  });
6458
- const forChain = caps?.[(0, import_viem14.toHex)(chainId)] ?? caps?.[String(chainId)];
7125
+ const forChain = caps?.[(0, import_viem15.toHex)(chainId)] ?? caps?.[String(chainId)];
6459
7126
  return Boolean(forChain?.paymasterService?.supported);
6460
7127
  }
6461
7128
  function makeSponsoredCallsCallback(deps) {
@@ -6492,8 +7159,8 @@ function makeSponsoredCallsCallback(deps) {
6492
7159
  const calls = transfers.map((transfer) => ({
6493
7160
  to: token,
6494
7161
  value: "0x0",
6495
- data: (0, import_viem14.encodeFunctionData)({
6496
- abi: import_viem14.erc20Abi,
7162
+ data: (0, import_viem15.encodeFunctionData)({
7163
+ abi: import_viem15.erc20Abi,
6497
7164
  functionName: "transfer",
6498
7165
  args: [transfer.to, BigInt(transfer.amount)]
6499
7166
  })
@@ -6525,50 +7192,73 @@ function makeSponsoredCallsCallback(deps) {
6525
7192
  url.searchParams.append("owneyIntent", intent);
6526
7193
  paymasterUrl = url.toString();
6527
7194
  }
6528
- const sendResult = await deps.provider.request({
6529
- method: "wallet_sendCalls",
6530
- params: [
6531
- {
6532
- version: "2.0.0",
6533
- from: deps.ownerAddress,
6534
- chainId: (0, import_viem14.toHex)(chainId),
6535
- atomicRequired: transfers.length > 1,
6536
- calls,
6537
- capabilities: {
6538
- paymasterService: { url: paymasterUrl }
7195
+ const requestedId = (0, import_viem15.toHex)(globalThis.crypto.getRandomValues(new Uint8Array(32)));
7196
+ const progress = (id, transfer) => {
7197
+ try {
7198
+ deps.onCallsProgress?.(id, transfers, transfer);
7199
+ } catch {
7200
+ }
7201
+ };
7202
+ progress(requestedId, "pending");
7203
+ let sendResult;
7204
+ try {
7205
+ sendResult = await deps.provider.request({
7206
+ method: "wallet_sendCalls",
7207
+ params: [
7208
+ {
7209
+ version: "2.0.0",
7210
+ id: requestedId,
7211
+ from: deps.ownerAddress,
7212
+ chainId: (0, import_viem15.toHex)(chainId),
7213
+ atomicRequired: transfers.length > 1,
7214
+ calls,
7215
+ capabilities: {
7216
+ paymasterService: { url: paymasterUrl }
7217
+ }
6539
7218
  }
6540
- }
6541
- ]
6542
- });
6543
- deps.onApproved?.();
7219
+ ]
7220
+ });
7221
+ } catch (error) {
7222
+ if (error?.code === 4001 || error?.code === "ACTION_REJECTED")
7223
+ progress(requestedId, "failed");
7224
+ throw error;
7225
+ }
7226
+ try {
7227
+ deps.onApproved?.();
7228
+ } catch {
7229
+ }
6544
7230
  const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
6545
7231
  if (!callsId) {
6546
7232
  throw new OwneyError(
6547
7233
  "SPONSORED_CALLS_NO_ID",
6548
7234
  "Wallet accepted wallet_sendCalls but returned no calls id.",
6549
- { chainId }
7235
+ { chainId, callsId: requestedId }
6550
7236
  );
6551
7237
  }
7238
+ progress(callsId, "pending");
6552
7239
  for (let attempt = 0; attempt < maxPolls; attempt += 1) {
6553
7240
  const status = await deps.provider.request({
6554
7241
  method: "wallet_getCallsStatus",
6555
7242
  params: [callsId]
6556
7243
  });
7244
+ if (status?.id && status.id !== callsId || status?.chainId && Number(status.chainId) !== chainId)
7245
+ throw new OwneyError("SPONSORED_CALLS_NO_RECEIPT", "Wallet returned status for a different deposit batch.", { chainId, callsId });
6557
7246
  const txHash = status?.receipts?.[0]?.transactionHash;
6558
- if (txHash && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
7247
+ if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
6559
7248
  try {
6560
7249
  deps.onSubmitted?.(txHash, transfers);
6561
7250
  } catch {
6562
7251
  }
6563
7252
  }
6564
- if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
7253
+ if (status?.status === 400 || status?.status === 500) {
7254
+ progress(callsId, "failed");
6565
7255
  throw new OwneyError(
6566
7256
  "SPONSOR_REQUEST_FAILED",
6567
7257
  "The sponsored deposit did not complete successfully.",
6568
7258
  { chainId, callsId, safeToFallback: false }
6569
7259
  );
6570
7260
  }
6571
- if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
7261
+ if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
6572
7262
  if (status?.receipts?.some(
6573
7263
  (receipt) => receipt.transactionHash !== txHash
6574
7264
  ))
@@ -6784,8 +7474,8 @@ var OwneySDK = class {
6784
7474
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
6785
7475
  }
6786
7476
  /** Builds the default USDC batch callback for the connected wallet. */
6787
- getDefaultSponsoredCallback(onApproved, onSubmitted) {
6788
- if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
7477
+ getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
7478
+ if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedSponsoredCallback)
6789
7479
  return this.cachedSponsoredCallback;
6790
7480
  const provider = this.requireConnectedProvider();
6791
7481
  const owner = this.state.walletAddress;
@@ -6795,28 +7485,29 @@ var OwneySDK = class {
6795
7485
  ownerAddress: owner,
6796
7486
  onApproved,
6797
7487
  onSubmitted,
7488
+ resumeTxHash,
6798
7489
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
6799
7490
  // Casts work around viem's chain-narrowed Client vs the generic
6800
7491
  // PublicClient/WalletClient param types — structurally identical at
6801
7492
  // runtime, but the two share a name TS treats as unrelated.
6802
7493
  getPublicClient: (cid) => this.getPaidRpcClient(cid),
6803
- getWalletChainClient: (cid) => (0, import_viem15.createPublicClient)({
7494
+ getWalletChainClient: (cid) => (0, import_viem16.createPublicClient)({
6804
7495
  chain: VIEM_CHAIN2[cid],
6805
- transport: (0, import_viem15.custom)(provider)
7496
+ transport: (0, import_viem16.custom)(provider)
6806
7497
  }),
6807
- getWalletClient: (cid) => (0, import_viem15.createWalletClient)({
7498
+ getWalletClient: (cid) => (0, import_viem16.createWalletClient)({
6808
7499
  account: owner,
6809
7500
  chain: VIEM_CHAIN2[cid],
6810
- transport: (0, import_viem15.custom)(provider)
7501
+ transport: (0, import_viem16.custom)(provider)
6811
7502
  })
6812
7503
  });
6813
- if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
7504
+ if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedSponsoredCallback = callback;
6814
7505
  return callback;
6815
7506
  }
6816
7507
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
6817
- getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
7508
+ getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) {
6818
7509
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
6819
- if (!onApproved && !onSubmitted && cached) return cached;
7510
+ if (!onApproved && !onSubmitted && !onCallsProgress && cached) return cached;
6820
7511
  const provider = this.requireConnectedProvider();
6821
7512
  const callback = makeSponsoredCallsCallback({
6822
7513
  apiKey: this.apiKey,
@@ -6826,9 +7517,10 @@ var OwneySDK = class {
6826
7517
  paymasterServiceUrl: this.paymasterServiceUrl,
6827
7518
  onApproved,
6828
7519
  onSubmitted,
7520
+ onCallsProgress,
6829
7521
  tokenAddressByChain: sponsoredTokensFor(asset)
6830
7522
  });
6831
- if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
7523
+ if (!onApproved && !onSubmitted && !onCallsProgress) this.cachedSponsoredCallsCallbacks.set(asset, callback);
6832
7524
  return callback;
6833
7525
  }
6834
7526
  /**
@@ -6837,8 +7529,8 @@ var OwneySDK = class {
6837
7529
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
6838
7530
  * single-use batch authorization instead of an EIP-3009 authorization.
6839
7531
  */
6840
- getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
6841
- if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
7532
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
7533
+ if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedWethSponsoredCallback)
6842
7534
  return this.cachedWethSponsoredCallback;
6843
7535
  const provider = this.requireConnectedProvider();
6844
7536
  const owner = this.state.walletAddress;
@@ -6848,22 +7540,23 @@ var OwneySDK = class {
6848
7540
  ownerAddress: owner,
6849
7541
  onApproved,
6850
7542
  onSubmitted,
7543
+ resumeTxHash,
6851
7544
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
6852
7545
  // Casts work around viem's chain-narrowed Client vs the generic
6853
7546
  // PublicClient/WalletClient param types — structurally identical at
6854
7547
  // runtime, but the two share a name TS treats as unrelated.
6855
7548
  getPublicClient: (cid) => this.getPaidRpcClient(cid),
6856
- getWalletChainClient: (cid) => (0, import_viem15.createPublicClient)({
7549
+ getWalletChainClient: (cid) => (0, import_viem16.createPublicClient)({
6857
7550
  chain: VIEM_CHAIN2[cid],
6858
- transport: (0, import_viem15.custom)(provider)
7551
+ transport: (0, import_viem16.custom)(provider)
6859
7552
  }),
6860
- getWalletClient: (cid) => (0, import_viem15.createWalletClient)({
7553
+ getWalletClient: (cid) => (0, import_viem16.createWalletClient)({
6861
7554
  account: owner,
6862
7555
  chain: VIEM_CHAIN2[cid],
6863
- transport: (0, import_viem15.custom)(provider)
7556
+ transport: (0, import_viem16.custom)(provider)
6864
7557
  })
6865
7558
  });
6866
- if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
7559
+ if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedWethSponsoredCallback = callback;
6867
7560
  return callback;
6868
7561
  }
6869
7562
  getAgent(agentId) {
@@ -7172,6 +7865,7 @@ var OwneySDK = class {
7172
7865
  async deposit(options) {
7173
7866
  const { amount, asset, depositCallback, agentId, onApproved } = options;
7174
7867
  const prepared = /* @__PURE__ */ new Map();
7868
+ const paidFallbackAgents = /* @__PURE__ */ new Set();
7175
7869
  const onProgress = options.onProgress ? (evidence) => {
7176
7870
  if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
7177
7871
  notifyFunding(options.onProgress, evidence);
@@ -7182,152 +7876,184 @@ var OwneySDK = class {
7182
7876
  if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
7183
7877
  }
7184
7878
  } : void 0;
7879
+ const onCallsProgress = onProgress ? (callsId, destinations, transfer) => {
7880
+ for (const destination of destinations) {
7881
+ const evidence = prepared.get(destination.to.toLowerCase());
7882
+ if (evidence) notifyFunding(onProgress, { ...evidence, callsId, transfer });
7883
+ }
7884
+ } : void 0;
7185
7885
  if (BigInt(amount) === 0n) {
7186
7886
  return { agentResults: {} };
7187
7887
  }
7188
7888
  const state = this.requireState();
7189
7889
  const chainId = this.requireChainId();
7190
- const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted) : this.getDefaultSponsoredCallback(onApproved, onSubmitted));
7191
- if (agentId) {
7192
- if (this.isAgentDisabled(agentId)) {
7193
- throw new OwneyError(
7194
- "AGENT_DISABLED",
7195
- `Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
7196
- { agentId }
7197
- );
7198
- }
7199
- const agent = this.getAgent(agentId);
7200
- this.validateAssetSupport(agent, chainId, asset);
7201
- await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
7202
- return this.depositWithFallback(
7203
- agent,
7204
- state,
7205
- chainId,
7206
- amount,
7207
- asset,
7208
- effectiveCallback,
7209
- depositCallback,
7210
- onProgress
7211
- );
7212
- }
7213
- const totalAmount = BigInt(amount);
7214
- const eligibleAgents = this.getEligibleAgents(chainId, asset, {
7215
- excludeDisabled: true
7216
- });
7217
- const exemptFlags = await Promise.all(
7218
- eligibleAgents.map(
7219
- (a) => this.hasExistingBalance(a, state, chainId, asset, true)
7220
- )
7221
- );
7222
- const exempt = new Set(
7223
- eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
7224
- );
7225
- const agentAmounts = this.splitDepositAmount(
7226
- totalAmount,
7227
- eligibleAgents,
7228
- chainId,
7229
- asset,
7230
- exempt
7231
- );
7232
- if (agentAmounts.length === 0) {
7233
- const agentsRequiringActivation = eligibleAgents.filter(
7234
- (agent) => !exempt.has(agent.id)
7235
- );
7236
- const perAgentMinimum = agentsRequiringActivation.reduce(
7237
- (highest, agent) => {
7238
- const minimum = this.getMinDepositAmount(agent, chainId, asset);
7239
- return minimum > highest ? minimum : highest;
7240
- },
7241
- 0n
7242
- );
7243
- const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
7244
- throw new OwneyError(
7245
- "DEPOSIT_AMOUNT_BELOW_MINIMUM",
7246
- `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
7247
- {
7248
- amount,
7249
- asset,
7250
- chainId,
7251
- minDepositAmount: minimumRequired.toString(),
7252
- perAgentDepositAmount: perAgentMinimum.toString(),
7253
- agentMinimums: agentsRequiringActivation.map((agent) => ({
7254
- agentId: agent.id,
7255
- minDepositAmount: this.getMinDepositAmount(
7256
- agent,
7257
- chainId,
7258
- asset
7259
- ).toString()
7260
- }))
7261
- }
7262
- );
7263
- }
7264
- const batchTransfer = getDepositBatchTransfer(effectiveCallback);
7265
- if (!depositCallback && batchTransfer) {
7266
- return runAgentDepositBatch(
7267
- chainId,
7268
- agentAmounts.map(({ agent, amount: amount2 }) => ({
7269
- id: agent.id,
7270
- amount: amount2,
7271
- run: (callback) => withFailureReporting(
7272
- this.apiKey,
7273
- agent.id,
7274
- () => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
7275
- this.routingApiBaseUrl
7276
- )
7277
- })),
7278
- async (cid, transfers) => {
7279
- try {
7280
- return await batchTransfer(cid, transfers);
7281
- } catch (error) {
7282
- if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
7283
- throw error;
7284
- const requiredAmount = transfers.reduce(
7285
- (sum, transfer) => sum + BigInt(transfer.amount),
7286
- 0n
7287
- );
7288
- await this.approvePermit2(
7289
- asset,
7290
- requiredAmount,
7291
- cid
7292
- );
7293
- return batchTransfer(cid, transfers);
7294
- }
7890
+ if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
7891
+ throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
7892
+ 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));
7893
+ try {
7894
+ if (agentId) {
7895
+ if (this.isAgentDisabled(agentId)) {
7896
+ throw new OwneyError(
7897
+ "AGENT_DISABLED",
7898
+ `Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
7899
+ { agentId }
7900
+ );
7295
7901
  }
7296
- );
7297
- }
7298
- const agentResults = {};
7299
- for (const [
7300
- index,
7301
- { agent, amount: agentAmount }
7302
- ] of agentAmounts.entries()) {
7303
- try {
7304
- agentResults[agent.id] = await this.depositWithFallback(
7902
+ const agent = this.getAgent(agentId);
7903
+ this.validateAssetSupport(agent, chainId, asset);
7904
+ await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
7905
+ return await this.depositWithFallback(
7305
7906
  agent,
7306
7907
  state,
7307
7908
  chainId,
7308
- agentAmount,
7909
+ amount,
7309
7910
  asset,
7310
7911
  effectiveCallback,
7311
7912
  depositCallback,
7312
- onProgress
7913
+ onProgress,
7914
+ () => paidFallbackAgents.add(agent.id),
7915
+ !!options.resumeTxHash
7313
7916
  );
7314
- } catch (error) {
7315
- if (Object.keys(agentResults).length === 0) throw error;
7917
+ }
7918
+ const totalAmount = BigInt(amount);
7919
+ const eligibleAgents = this.getEligibleAgents(chainId, asset, {
7920
+ excludeDisabled: true
7921
+ });
7922
+ const prospectiveShare = totalAmount / BigInt(eligibleAgents.length);
7923
+ const meetsEveryMinimum = eligibleAgents.every((agent) => prospectiveShare >= this.getMinDepositAmount(agent, chainId, asset));
7924
+ const exemptFlags = meetsEveryMinimum ? eligibleAgents.map(() => false) : await Promise.all(eligibleAgents.map((agent) => this.hasExistingBalance(agent, state, chainId, asset, true)));
7925
+ const exempt = new Set(
7926
+ eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
7927
+ );
7928
+ const agentAmounts = this.splitDepositAmount(
7929
+ totalAmount,
7930
+ eligibleAgents,
7931
+ chainId,
7932
+ asset,
7933
+ exempt
7934
+ );
7935
+ if (agentAmounts.length === 0) {
7936
+ const agentsRequiringActivation = eligibleAgents.filter(
7937
+ (agent) => !exempt.has(agent.id)
7938
+ );
7939
+ const perAgentMinimum = agentsRequiringActivation.reduce(
7940
+ (highest, agent) => {
7941
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
7942
+ return minimum > highest ? minimum : highest;
7943
+ },
7944
+ 0n
7945
+ );
7946
+ const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
7316
7947
  throw new OwneyError(
7317
- "DEPOSIT_PARTIAL_FAILURE",
7318
- "Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
7948
+ "DEPOSIT_AMOUNT_BELOW_MINIMUM",
7949
+ `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
7319
7950
  {
7320
- agentResults,
7321
- failedAgentId: agent.id,
7322
- failedAmount: agentAmount,
7323
- unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
7324
- cause: error
7325
- },
7326
- agent.id
7951
+ amount,
7952
+ asset,
7953
+ chainId,
7954
+ minDepositAmount: minimumRequired.toString(),
7955
+ perAgentDepositAmount: perAgentMinimum.toString(),
7956
+ agentMinimums: agentsRequiringActivation.map((agent) => ({
7957
+ agentId: agent.id,
7958
+ minDepositAmount: this.getMinDepositAmount(
7959
+ agent,
7960
+ chainId,
7961
+ asset
7962
+ ).toString()
7963
+ }))
7964
+ }
7965
+ );
7966
+ }
7967
+ const batchTransfer = getDepositBatchTransfer(effectiveCallback);
7968
+ if (!depositCallback && batchTransfer) {
7969
+ return await runAgentDepositBatch(
7970
+ chainId,
7971
+ agentAmounts.map(({ agent, amount: amount2 }) => ({
7972
+ id: agent.id,
7973
+ amount: amount2,
7974
+ run: (callback) => withFailureReporting(
7975
+ this.apiKey,
7976
+ agent.id,
7977
+ () => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
7978
+ this.routingApiBaseUrl
7979
+ )
7980
+ })),
7981
+ async (cid, transfers) => {
7982
+ try {
7983
+ return await batchTransfer(cid, transfers);
7984
+ } catch (error) {
7985
+ if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
7986
+ throw error;
7987
+ const requiredAmount = transfers.reduce(
7988
+ (sum, transfer) => sum + BigInt(transfer.amount),
7989
+ 0n
7990
+ );
7991
+ await this.approvePermit2(
7992
+ asset,
7993
+ requiredAmount,
7994
+ cid
7995
+ );
7996
+ return batchTransfer(cid, transfers);
7997
+ }
7998
+ }
7327
7999
  );
7328
8000
  }
8001
+ const agentResults = {};
8002
+ for (const [
8003
+ index,
8004
+ { agent, amount: agentAmount }
8005
+ ] of agentAmounts.entries()) {
8006
+ try {
8007
+ agentResults[agent.id] = await this.depositWithFallback(
8008
+ agent,
8009
+ state,
8010
+ chainId,
8011
+ agentAmount,
8012
+ asset,
8013
+ effectiveCallback,
8014
+ depositCallback,
8015
+ onProgress,
8016
+ () => paidFallbackAgents.add(agent.id),
8017
+ !!options.resumeTxHash
8018
+ );
8019
+ } catch (error) {
8020
+ if (Object.keys(agentResults).length === 0) throw error;
8021
+ throw new OwneyError(
8022
+ "DEPOSIT_PARTIAL_FAILURE",
8023
+ "Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
8024
+ {
8025
+ agentResults,
8026
+ failedAgentId: agent.id,
8027
+ failedAmount: agentAmount,
8028
+ unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
8029
+ cause: error
8030
+ },
8031
+ agent.id
8032
+ );
8033
+ }
8034
+ }
8035
+ return { agentResults };
8036
+ } catch (error) {
8037
+ if (!depositCallback && onProgress && !options.resumeTxHash) {
8038
+ const ids = agentId ? [agentId] : this.getEligibleAgents(chainId, asset).map((agent) => agent.id);
8039
+ for (const id of ids) {
8040
+ const evidence = [...prepared.values()].find((value) => value.agentId === id);
8041
+ if (paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
8042
+ const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
8043
+ if (!rejected && (evidence?.txHash || evidence?.userOpHash || evidence?.callsId)) continue;
8044
+ notifyFunding(onProgress, { ...evidence ?? {
8045
+ agentId: id,
8046
+ chainId,
8047
+ token: asset,
8048
+ walletAddress: state.walletAddress,
8049
+ intendedAmount: amount,
8050
+ registration: "unknown",
8051
+ delivery: "not-applicable"
8052
+ }, transfer: "failed" });
8053
+ }
8054
+ }
8055
+ throw error;
7329
8056
  }
7330
- return { agentResults };
7331
8057
  }
7332
8058
  /**
7333
8059
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -7356,7 +8082,7 @@ var OwneySDK = class {
7356
8082
  * propagates unchanged. Scoped per agent invocation so one agent's failure
7357
8083
  * doesn't force a retry of sibling agents in a multi-agent split.
7358
8084
  */
7359
- async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
8085
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false) {
7360
8086
  const attempt = (callback) => withFailureReporting(
7361
8087
  this.apiKey,
7362
8088
  agent.id,
@@ -7368,6 +8094,7 @@ var OwneySDK = class {
7368
8094
  try {
7369
8095
  return await attempt(effectiveCallback);
7370
8096
  } catch (error) {
8097
+ if (resumeOnly) throw error;
7371
8098
  if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
7372
8099
  approvalAttempted = true;
7373
8100
  console.warn(
@@ -7385,6 +8112,7 @@ var OwneySDK = class {
7385
8112
  `[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
7386
8113
  error
7387
8114
  );
8115
+ onPaidFallback?.();
7388
8116
  return attempt(void 0);
7389
8117
  }
7390
8118
  }
@@ -7467,7 +8195,12 @@ var OwneySDK = class {
7467
8195
  */
7468
8196
  async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
7469
8197
  try {
7470
- const balance = await agent.getBalances(state, chainId);
8198
+ if (agent.hasExistingDepositFunds && await agent.hasExistingDepositFunds(
8199
+ state,
8200
+ chainId,
8201
+ asset
8202
+ ).catch(() => void 0) === true) return true;
8203
+ const balance = await agent.getBalances(state, chainId, asset);
7471
8204
  const target = asset.toLowerCase();
7472
8205
  const token = balance.tokens.find(
7473
8206
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
@@ -7593,7 +8326,8 @@ var OwneySDK = class {
7593
8326
  return verified;
7594
8327
  }
7595
8328
  /** Resolve a saved sponsored-withdrawal ID without opening a wallet prompt. */
7596
- async resolveSponsoredWithdrawalEvidence(evidence) {
8329
+ async resolveSponsoredWithdrawalEvidence(evidence, options = {}) {
8330
+ if (evidence.callsId && !evidence.userOpHash) return this.resolveWalletCallsFundingEvidence(evidence, "withdraw");
7597
8331
  const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
7598
8332
  if (evidence.agentId !== "yieldseeker" || chainId !== 8453 || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
7599
8333
  const agent = this.getAgent("yieldseeker");
@@ -7609,18 +8343,26 @@ var OwneySDK = class {
7609
8343
  owner: state.walletAddress,
7610
8344
  agentWallet: wallet,
7611
8345
  asset: SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token)?.address,
7612
- userOpHash: evidence.userOpHash
8346
+ userOpHash: evidence.userOpHash,
8347
+ ...evidence.userOperation ? { userOperation: Object.fromEntries([
8348
+ "sender",
8349
+ "nonce",
8350
+ "initCode",
8351
+ "callData",
8352
+ "callGasLimit",
8353
+ "verificationGasLimit",
8354
+ "preVerificationGas",
8355
+ "maxFeePerGas",
8356
+ "maxPriorityFeePerGas",
8357
+ "paymasterAndData"
8358
+ ].map((key2) => [key2, evidence.userOperation[key2]])) } : {},
8359
+ ...Number.isFinite(options.submittedAt) && options.submittedAt > 0 ? { submittedAt: options.submittedAt } : {}
7613
8360
  })
7614
8361
  });
7615
8362
  if (!response.ok) throw new OwneyError("AGENT_API_ERROR", "Could not resolve sponsored withdrawal status.");
7616
8363
  const payload = await response.json();
7617
8364
  txHash = payload.data?.txHash ?? void 0;
7618
8365
  executionFailed = payload.data?.executionFailed === true;
7619
- } else if (evidence.callsId) {
7620
- const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
7621
- if (status?.id && status.id !== evidence.callsId || status?.chainId && Number(status.chainId) !== chainId) return evidence;
7622
- executionFailed = status?.status === 400 || status?.status === 500;
7623
- txHash = status?.receipts?.[0]?.transactionHash;
7624
8366
  }
7625
8367
  assertCurrent();
7626
8368
  if (executionFailed) return {
@@ -7631,6 +8373,60 @@ var OwneySDK = class {
7631
8373
  };
7632
8374
  return txHash && /^0x[0-9a-fA-F]{64}$/.test(txHash) ? { ...evidence, txHash } : evidence;
7633
8375
  }
8376
+ /** Recover an EIP-5792 deposit or payout using this account's exact batch ID. */
8377
+ async resolveWalletCallsFundingEvidence(evidence, direction) {
8378
+ const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
8379
+ if (!evidence.callsId || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
8380
+ const agent = this.getAgent(evidence.agentId);
8381
+ const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
8382
+ if (!wallet || wallet.toLowerCase() !== evidence.agentWallet?.toLowerCase()) return evidence;
8383
+ const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
8384
+ assertCurrent();
8385
+ if (status?.id !== void 0 && status.id !== evidence.callsId || status?.chainId !== void 0 && Number(status.chainId) !== chainId) return evidence;
8386
+ const statusCode2 = Number(status?.status);
8387
+ if (statusCode2 === 400 || statusCode2 === 500) return {
8388
+ ...evidence,
8389
+ transfer: "failed",
8390
+ delivery: direction === "withdraw" ? "failed" : "not-applicable"
8391
+ };
8392
+ const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash?.toLowerCase()).filter((hash) => !!hash && /^0x[\da-f]{64}$/i.test(hash)))];
8393
+ if (hashes.length === 1) return { ...evidence, txHash: hashes[0] };
8394
+ if (hashes.length > 16) return evidence;
8395
+ const matches = [];
8396
+ for (const txHash of hashes) {
8397
+ const inspected = await this.inspectFunding({ ...evidence, txHash }, direction);
8398
+ assertCurrent();
8399
+ if (inspected.transfer === "confirmed") matches.push(inspected);
8400
+ }
8401
+ return matches.length === 1 ? matches[0] : evidence;
8402
+ }
8403
+ /** Whether a foreground deposit can resume the same signed batch, never a
8404
+ * fresh authorization. The batch rail independently rechecks the exact plan. */
8405
+ async canResumePendingDeposit(evidences, amount) {
8406
+ if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
8407
+ const first = evidences[0];
8408
+ const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
8409
+ const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
8410
+ if (!token) return false;
8411
+ const pending = pendingTokenBatch({
8412
+ apiKey: this.apiKey,
8413
+ baseUrl: this.routingApiBaseUrl,
8414
+ owner: state.walletAddress,
8415
+ chainId,
8416
+ token: token.address
8417
+ });
8418
+ if (!pending || pending.transfers.length !== evidences.length || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n).toString() !== amount) return false;
8419
+ const destinations = /* @__PURE__ */ new Set();
8420
+ for (const evidence of evidences) {
8421
+ 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;
8422
+ const agent = this.getAgent(evidence.agentId);
8423
+ const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
8424
+ if (!wallet || destinations.has(wallet.toLowerCase()) || !pending.transfers.some((transfer) => transfer.to.toLowerCase() === wallet.toLowerCase() && transfer.amount === evidence.intendedAmount)) return false;
8425
+ destinations.add(wallet.toLowerCase());
8426
+ }
8427
+ assertCurrent();
8428
+ return true;
8429
+ }
7634
8430
  /** A display acknowledgement controls polling only; it never confirms a financial outcome. */
7635
8431
  endFundingBalanceRefresh(evidence, read2) {
7636
8432
  const { state, chainId } = this.captureReadScope(evidence.chainId);
@@ -7731,6 +8527,30 @@ var OwneySDK = class {
7731
8527
  }
7732
8528
  };
7733
8529
  const token = asset;
8530
+ const agentFailures = {};
8531
+ const recordAgentFailure = (id, error) => {
8532
+ if (!(error instanceof OwneyError)) return;
8533
+ const details = error.details ?? {};
8534
+ const codeValue = (value) => typeof value === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(value) ? value : void 0;
8535
+ const revertNames = ["InsufficientBalance", "InvalidAsset", "Unauthorized", "Error", "Panic", "FailedOp", "FailedOpWithRevert"];
8536
+ const panicCodes = [0, 1, 17, 18, 33, 34, 49, 50, 65, 81];
8537
+ agentFailures[id] = {
8538
+ code: error.code,
8539
+ phase: ["prepare", "send", "observe"].includes(String(details.phase)) ? details.phase : void 0,
8540
+ failureCode: codeValue(details.failureCode),
8541
+ reason: codeValue(details.reason),
8542
+ withdrawalPhase: details.withdrawalPhase === "unwind" ? "unwind" : void 0,
8543
+ statusCode: typeof details.statusCode === "number" ? details.statusCode : void 0,
8544
+ rpcCode: typeof details.rpcCode === "number" ? details.rpcCode : void 0,
8545
+ rpcMethod: typeof details.rpcMethod === "string" && /^[A-Za-z_]{1,80}$/.test(details.rpcMethod) ? details.rpcMethod : void 0,
8546
+ revertSelector: typeof details.revertSelector === "string" && /^0x[0-9a-f]{8}$/i.test(details.revertSelector) ? details.revertSelector : void 0,
8547
+ revertName: typeof details.revertName === "string" && revertNames.includes(details.revertName) ? details.revertName : void 0,
8548
+ aaCode: typeof details.aaCode === "string" && /^AA[0-9]{2}$/.test(details.aaCode) ? details.aaCode : void 0,
8549
+ 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,
8550
+ safeToFallback: typeof details.safeToFallback === "boolean" ? details.safeToFallback : void 0,
8551
+ executionFailed: typeof details.executionFailed === "boolean" ? details.executionFailed : void 0
8552
+ };
8553
+ };
7734
8554
  const submissionEvidence = /* @__PURE__ */ new Map();
7735
8555
  const submit = async (agent, requested2) => {
7736
8556
  attempted.add(agent.id);
@@ -7751,9 +8571,19 @@ var OwneySDK = class {
7751
8571
  };
7752
8572
  progress(evidence);
7753
8573
  try {
8574
+ if (options.onBeforeSubmission)
8575
+ return await agent.withdraw(
8576
+ state,
8577
+ chainId,
8578
+ token,
8579
+ requested2,
8580
+ progress,
8581
+ options.gasMode ?? "auto",
8582
+ (next) => options.onBeforeSubmission({ ...evidence, ...next })
8583
+ );
7754
8584
  return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
7755
8585
  } catch (error) {
7756
- if (!evidence.txHash && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
8586
+ if (!evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
7757
8587
  progress({ ...evidence, transfer: "failed", delivery: "failed" });
7758
8588
  throw error;
7759
8589
  }
@@ -7803,6 +8633,7 @@ var OwneySDK = class {
7803
8633
  }
7804
8634
  console.error(`withdraw failed for agent "${agent.id}":`, err);
7805
8635
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
8636
+ recordAgentFailure(agent.id, err);
7806
8637
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
7807
8638
  void reportAgentFailure(
7808
8639
  this.apiKey,
@@ -7813,17 +8644,23 @@ var OwneySDK = class {
7813
8644
  }
7814
8645
  }
7815
8646
  if (Object.keys(results2).length === 0) {
8647
+ if (Object.keys(agentErrors2).length === 0)
8648
+ throw new OwneyError(
8649
+ "WITHDRAW_INSUFFICIENT_BALANCE",
8650
+ "No funds are currently available to withdraw for this asset.",
8651
+ { asset, available: "0" }
8652
+ );
7816
8653
  throw new OwneyError(
7817
8654
  "WITHDRAW_ALL_FAILED",
7818
8655
  `Failed to withdraw "${asset}" from all eligible agents.`,
7819
- { asset, agentErrors: agentErrors2 }
8656
+ { asset, agentErrors: agentErrors2, agentFailures }
7820
8657
  );
7821
8658
  }
7822
8659
  if (Object.keys(agentErrors2).length > 0) {
7823
8660
  throw new OwneyError(
7824
8661
  "WITHDRAW_PARTIAL_FAILURE",
7825
8662
  "Some agents could not complete the withdrawal.",
7826
- { asset, partialResults: results2, agentErrors: agentErrors2, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
8663
+ { asset, partialResults: results2, agentErrors: agentErrors2, agentFailures, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
7827
8664
  );
7828
8665
  }
7829
8666
  const amountsKnown = Object.values(results2).every(
@@ -7904,6 +8741,7 @@ var OwneySDK = class {
7904
8741
  if (this.isUserRejectedWithdrawal(err)) throw err;
7905
8742
  console.error(`withdraw failed for agent "${p.agent.id}":`, err);
7906
8743
  agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
8744
+ recordAgentFailure(p.agent.id, err);
7907
8745
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
7908
8746
  void reportAgentFailure(
7909
8747
  this.apiKey,
@@ -7924,7 +8762,8 @@ var OwneySDK = class {
7924
8762
  {
7925
8763
  asset,
7926
8764
  amount,
7927
- agentErrors
8765
+ agentErrors,
8766
+ agentFailures
7928
8767
  }
7929
8768
  );
7930
8769
  }
@@ -7939,6 +8778,7 @@ var OwneySDK = class {
7939
8778
  withdrawn: withdrawn.toString(),
7940
8779
  partialResults: results,
7941
8780
  agentErrors,
8781
+ agentFailures,
7942
8782
  ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
7943
8783
  }
7944
8784
  );
@@ -8398,16 +9238,16 @@ var OwneySDK = class {
8398
9238
  );
8399
9239
  }
8400
9240
  const provider = this.requireConnectedProvider();
8401
- const walletChainClient = (0, import_viem15.createPublicClient)({
9241
+ const walletChainClient = (0, import_viem16.createPublicClient)({
8402
9242
  chain: VIEM_CHAIN2[chainId],
8403
- transport: (0, import_viem15.custom)(provider)
9243
+ transport: (0, import_viem16.custom)(provider)
8404
9244
  });
8405
9245
  const publicClient = this.getPaidRpcClient(chainId);
8406
9246
  const approvalAmount = permit2ApprovalAmount(requiredAmount);
8407
- const wallet = (0, import_viem15.createWalletClient)({
9247
+ const wallet = (0, import_viem16.createWalletClient)({
8408
9248
  account: state.walletAddress,
8409
9249
  chain: VIEM_CHAIN2[chainId],
8410
- transport: (0, import_viem15.custom)(provider)
9250
+ transport: (0, import_viem16.custom)(provider)
8411
9251
  });
8412
9252
  await ensureWalletOnChain(walletChainClient, wallet, chainId);
8413
9253
  const hash = await wallet.writeContract({
@@ -8569,7 +9409,7 @@ var OwneySDK = class {
8569
9409
  };
8570
9410
 
8571
9411
  // src/agents/zyfai/zyfai.siwx.ts
8572
- var import_viem16 = require("viem");
9412
+ var import_viem17 = require("viem");
8573
9413
  var import_siwe2 = require("siwe");
8574
9414
  var import_sdk2 = require("@zyfai/sdk");
8575
9415
 
@@ -8696,7 +9536,7 @@ function buildSIWXConfig(deps) {
8696
9536
  issuedAt,
8697
9537
  toString() {
8698
9538
  return new import_siwe2.SiweMessage({
8699
- address: (0, import_viem16.getAddress)(accountAddress),
9539
+ address: (0, import_viem17.getAddress)(accountAddress),
8700
9540
  chainId: numericChainId(chainId),
8701
9541
  domain,
8702
9542
  uri,