@owney/sdk 0.7.25-beta.13 → 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
@@ -34,6 +34,7 @@ module.exports = __toCommonJS(index_exports);
34
34
 
35
35
  // src/lib/funding-evidence.ts
36
36
  var import_viem = require("viem");
37
+ var import_account_abstraction = require("viem/account-abstraction");
37
38
  function notifyFunding(callback, evidence) {
38
39
  try {
39
40
  void Promise.resolve(callback?.({ ...evidence })).catch(() => {
@@ -43,11 +44,53 @@ function notifyFunding(callback, evidence) {
43
44
  }
44
45
  var fullWithdrawalAbi = (0, import_viem.parseAbi)(["function withdrawAllAssetToUser(address recipient, address asset)"]);
45
46
  var withdrawalEventAbi = (0, import_viem.parseAbi)(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
46
- function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress) {
47
- if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || transaction.to?.toLowerCase() !== evidence.agentWallet?.toLowerCase() || transaction.from.toLowerCase() !== evidence.walletAddress.toLowerCase()) return false;
47
+ var accountExecutionAbi = (0, import_viem.parseAbi)([
48
+ "function execute(address target,uint256 value,bytes data)",
49
+ "function executeBatch((address target,uint256 value,bytes data)[] calls)"
50
+ ]);
51
+ var entryPoint06 = "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789";
52
+ var entryPoint07 = "0x0000000071727de22e5e9d8baf0edac6f37da032";
53
+ function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receipt) {
54
+ if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || !evidence.agentWallet) return false;
55
+ const matchesFullCall = (data) => {
56
+ try {
57
+ const decoded = (0, import_viem.decodeFunctionData)({ abi: fullWithdrawalAbi, data });
58
+ return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
59
+ } catch {
60
+ return false;
61
+ }
62
+ };
63
+ if (transaction.to?.toLowerCase() === evidence.agentWallet.toLowerCase() && transaction.from.toLowerCase() === evidence.walletAddress.toLowerCase()) return matchesFullCall(transaction.input);
48
64
  try {
49
- const decoded = (0, import_viem.decodeFunctionData)({ abi: fullWithdrawalAbi, data: transaction.input });
50
- return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
65
+ const address = transaction.to?.toLowerCase();
66
+ if (address !== entryPoint06 && address !== entryPoint07) return false;
67
+ const version = address === entryPoint06 ? "0.6" : "0.7";
68
+ const decoded = (0, import_viem.decodeFunctionData)({ abi: version === "0.6" ? import_account_abstraction.entryPoint06Abi : import_account_abstraction.entryPoint07Abi, data: transaction.input });
69
+ if (decoded.functionName !== "handleOps") return false;
70
+ const operations = decoded.args[0].filter((operation2) => [evidence.agentWallet.toLowerCase(), evidence.walletAddress.toLowerCase()].includes(operation2.sender.toLowerCase()));
71
+ if (operations.length !== 1) return false;
72
+ const operation = operations[0];
73
+ const userOperation = (0, import_account_abstraction.toUserOperation)(operation);
74
+ const hash = (0, import_account_abstraction.getUserOperationHash)({
75
+ chainId: evidence.chainId,
76
+ entryPointAddress: transaction.to,
77
+ entryPointVersion: version,
78
+ userOperation
79
+ });
80
+ if (evidence.userOpHash && hash.toLowerCase() !== evidence.userOpHash.toLowerCase()) return false;
81
+ if (!receipt?.logs.some((log) => {
82
+ if (log.address.toLowerCase() !== address) return false;
83
+ try {
84
+ const event = (0, import_viem.decodeEventLog)({ abi: import_account_abstraction.entryPoint06Abi, data: log.data, topics: log.topics });
85
+ return event.eventName === "UserOperationEvent" && event.args.userOpHash.toLowerCase() === hash.toLowerCase() && event.args.sender.toLowerCase() === operation.sender.toLowerCase() && event.args.success;
86
+ } catch {
87
+ return false;
88
+ }
89
+ })) return false;
90
+ if (operation.sender.toLowerCase() === evidence.agentWallet.toLowerCase()) return matchesFullCall(operation.callData);
91
+ const execution = (0, import_viem.decodeFunctionData)({ abi: accountExecutionAbi, data: operation.callData });
92
+ const calls = execution.functionName === "execute" ? [{ target: execution.args[0], value: execution.args[1], data: execution.args[2] }] : execution.args[0];
93
+ return calls.length === 1 && calls[0].value === 0n && calls[0].target.toLowerCase() === evidence.agentWallet.toLowerCase() && matchesFullCall(calls[0].data);
51
94
  } catch {
52
95
  return false;
53
96
  }
@@ -59,6 +102,8 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
59
102
  const attributed = /* @__PURE__ */ new Map();
60
103
  const agent = evidence.agentWallet?.toLowerCase();
61
104
  const owner = evidence.walletAddress.toLowerCase();
105
+ const source = direction === "deposit" ? owner : agent;
106
+ const destination = direction === "deposit" ? agent : owner;
62
107
  const logIndexes = [];
63
108
  const transferLogs = [];
64
109
  const zeroTransferLogs = [];
@@ -73,17 +118,13 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
73
118
  if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
74
119
  zeroTransferLogs.push(log.logIndex);
75
120
  let matched = 0n;
76
- if (direction === "deposit") {
77
- if (to.toLowerCase() === agent) matched = value;
78
- } else {
79
- const sender = from.toLowerCase();
80
- const recipient = to.toLowerCase();
81
- const available = sender === agent ? value : attributed.get(sender) ?? 0n;
82
- const carried = available < value ? available : value;
83
- if (sender !== agent) attributed.set(sender, available - carried);
84
- if (recipient === owner) matched = carried;
85
- else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
86
- }
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);
87
128
  if (matched === 0n) continue;
88
129
  amount += matched;
89
130
  transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
@@ -375,8 +416,8 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
375
416
  return await original.apply(this, args);
376
417
  } catch (error) {
377
418
  const message = error instanceof Error ? error.message : "";
378
- request2.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : message === "Network error: Unable to reach the server" ? "network" : "other";
379
- if (["unauthorized", "forbidden", "rate-limited"].includes(request2.failure)) notify("rejected");
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");
380
421
  throw error;
381
422
  } finally {
382
423
  request2.elapsedMs = Math.max(0, Date.now() - startedAt);
@@ -399,9 +440,49 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
399
440
  } };
400
441
  }
401
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
+
402
483
  // src/agents/zyfai/zyfai.agent.ts
403
484
  var import_sdk = require("@zyfai/sdk");
404
- var import_viem3 = require("viem");
485
+ var import_viem4 = require("viem");
405
486
  var import_chains = require("viem/chains");
406
487
 
407
488
  // src/types/config.ts
@@ -488,14 +569,6 @@ function hexToDecimal(hex, decimals = 6) {
488
569
  const remainder = parsed % divisor;
489
570
  return Number(whole) + Number(remainder) / Number(divisor);
490
571
  }
491
- function normalizeDecimalPlace(value, decimals = 6) {
492
- const parsed = BigInt(value);
493
- if (decimals <= 0) return Number(parsed);
494
- const divisor = BigInt(10 ** decimals);
495
- const whole = parsed / divisor;
496
- const remainder = parsed % divisor;
497
- return Number(whole) + Number(remainder) / Number(divisor);
498
- }
499
572
  var DayFilterMapping = {
500
573
  "30D": 30,
501
574
  "14D": 14,
@@ -503,6 +576,8 @@ var DayFilterMapping = {
503
576
  };
504
577
 
505
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);
506
581
  var DECIMALS_BY_SYMBOL = {
507
582
  usdc: 6,
508
583
  weth: 18,
@@ -557,15 +632,15 @@ function mapPendingAllocations(raw2) {
557
632
  if (typeof e.balance !== "string") continue;
558
633
  let amount;
559
634
  try {
560
- 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));
561
637
  } catch {
562
638
  continue;
563
639
  }
564
- if (!Number.isFinite(amount) || amount <= 0) continue;
565
640
  pending.push({
566
641
  chainId: e.chainId,
567
642
  asset: e.tokenSymbol,
568
- amount: String(amount),
643
+ amount,
569
644
  isPending: e.isPending === true,
570
645
  ...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
571
646
  });
@@ -588,13 +663,13 @@ function mapBalances(raw2, _chainId, smartWallet) {
588
663
  { balance, balanceWithFee, decimals }
589
664
  ] of Object.entries(tokensBySymbol ?? {})) {
590
665
  if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
591
- const amount = hexToDecimal(balanceWithFee ?? balance, decimals);
592
- totalBalance += amount;
666
+ const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol, decimals));
667
+ totalBalance += Number(amount);
593
668
  tokens.push({
594
669
  chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
595
670
  chainId: positionChainId,
596
671
  asset: symbol,
597
- amount: String(amount)
672
+ amount
598
673
  });
599
674
  }
600
675
  }
@@ -608,19 +683,17 @@ function mapBalances(raw2, _chainId, smartWallet) {
608
683
  // legacy `amount` fallback is hex. Runtime
609
684
  // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
610
685
  // when present, otherwise fall back to the per-symbol lookup.
611
- amount: String(
612
- p.underlyingAmountWithFee ?? p.underlyingAmount ? normalizeDecimalPlace(
613
- p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0",
614
- tokenDecimals(
615
- p.token_symbol,
616
- p.decimals
617
- )
618
- ) : hexToDecimal(
619
- p.amount || "0",
620
- tokenDecimals(
621
- p.token_symbol,
622
- p.decimals
623
- )
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
624
697
  )
625
698
  ),
626
699
  apy: netApy(p.pool_apy_withFee, p.pool_apy, "pool_apy_withFee"),
@@ -1275,7 +1348,7 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
1275
1348
  }
1276
1349
 
1277
1350
  // src/lib/paid-rpc.ts
1278
- var import_viem2 = require("viem");
1351
+ var import_viem3 = require("viem");
1279
1352
  var PAID_RPC_RETRY_COUNT = 3;
1280
1353
  var PAID_RPC_RETRY_DELAY_MS = 1e3;
1281
1354
  function configuredRpcProxyBaseUrl(baseUrl) {
@@ -1311,9 +1384,9 @@ function createPaidRpcClient(chain, rpcUrls) {
1311
1384
  rpcUrls,
1312
1385
  chain.id
1313
1386
  );
1314
- return (0, import_viem2.createPublicClient)({
1387
+ return (0, import_viem3.createPublicClient)({
1315
1388
  chain,
1316
- transport: (0, import_viem2.http)(url, {
1389
+ transport: (0, import_viem3.http)(url, {
1317
1390
  retryCount: PAID_RPC_RETRY_COUNT,
1318
1391
  retryDelay: PAID_RPC_RETRY_DELAY_MS
1319
1392
  })
@@ -1410,7 +1483,7 @@ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
1410
1483
  }
1411
1484
 
1412
1485
  // src/agents/zyfai/zyfai.agent.ts
1413
- var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem3.parseAbi)([
1486
+ var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem4.parseAbi)([
1414
1487
  "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
1415
1488
  ]);
1416
1489
  var WETH_ADDRESS_BY_CHAIN = {
@@ -1561,7 +1634,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1561
1634
  }, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
1562
1635
  if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
1563
1636
  Object.assign(tx, {
1564
- amount: (0, import_viem3.formatUnits)(BigInt(evidence.confirmedAmount), asset.decimals),
1637
+ amount: (0, import_viem4.formatUnits)(BigInt(evidence.confirmedAmount), asset.decimals),
1565
1638
  amountRaw: evidence.confirmedAmount,
1566
1639
  blockNumber: evidence.blockNumber,
1567
1640
  logIndexes: evidence.logIndexes,
@@ -2288,6 +2361,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
2288
2361
  evidence2 = { ...evidence2, transfer: "unknown" };
2289
2362
  }
2290
2363
  notifyFunding(onProgress, evidence2);
2364
+ if (evidence2.transfer === "failed")
2365
+ throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
2291
2366
  try {
2292
2367
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
2293
2368
  await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
@@ -2326,12 +2401,77 @@ var ZyfaiAgent = class _ZyfaiAgent {
2326
2401
  } catch {
2327
2402
  }
2328
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);
2329
2406
  return { ...mapDeposit(raw2), evidence };
2330
2407
  } catch (error) {
2331
2408
  throw error;
2332
2409
  }
2333
2410
  }
2334
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
+ }
2335
2475
  async withdraw(state, chainId, token, amount, onProgress) {
2336
2476
  const validChainId = isValidChainId(chainId);
2337
2477
  try {
@@ -2349,11 +2489,35 @@ var ZyfaiAgent = class _ZyfaiAgent {
2349
2489
  });
2350
2490
  throw error;
2351
2491
  }
2352
- const observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2492
+ let observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2353
2493
  const authToken = this.sdk.httpClient?.authToken;
2494
+ const owner = this.getAddress();
2354
2495
  let raw2;
2355
2496
  try {
2356
- 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
+ );
2357
2521
  } catch (error) {
2358
2522
  this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
2359
2523
  if (observation.attempted() !== void 0) {
@@ -2632,7 +2796,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2632
2796
  };
2633
2797
 
2634
2798
  // src/agents/yieldseeker/yieldseeker.agent.ts
2635
- var import_viem10 = require("viem");
2799
+ var import_viem11 = require("viem");
2636
2800
  var import_chains4 = require("viem/chains");
2637
2801
 
2638
2802
  // src/lib/chain-guard.ts
@@ -2671,7 +2835,7 @@ async function ensureWalletOnChain(pub, wallet, expected) {
2671
2835
  }
2672
2836
 
2673
2837
  // src/lib/transfer-auth.ts
2674
- var import_viem4 = require("viem");
2838
+ var import_viem5 = require("viem");
2675
2839
 
2676
2840
  // src/lib/sponsor-client.ts
2677
2841
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
@@ -2787,7 +2951,7 @@ async function postSponsorBatchTransfer(input) {
2787
2951
  }
2788
2952
 
2789
2953
  // src/lib/permit2.ts
2790
- var import_viem5 = require("viem");
2954
+ var import_viem6 = require("viem");
2791
2955
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2792
2956
  var MAX_UINT256 = 2n ** 256n - 1n;
2793
2957
  function permit2ApprovalAmount(requiredAmount) {
@@ -2828,7 +2992,7 @@ var ERC20_ALLOWANCE_ABI = [
2828
2992
  function randomPermit2Nonce() {
2829
2993
  const bytes = new Uint8Array(32);
2830
2994
  globalThis.crypto.getRandomValues(bytes);
2831
- return BigInt((0, import_viem5.bytesToHex)(bytes));
2995
+ return BigInt((0, import_viem6.bytesToHex)(bytes));
2832
2996
  }
2833
2997
  async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
2834
2998
  return publicClient.readContract({
@@ -2871,7 +3035,7 @@ function makeVerificationAwareDepositCallback(implementation) {
2871
3035
 
2872
3036
  // src/agents/yieldseeker/yieldseeker.auth.ts
2873
3037
  var import_siwe = require("siwe");
2874
- var import_viem6 = require("viem");
3038
+ var import_viem7 = require("viem");
2875
3039
  var import_chains2 = require("viem/chains");
2876
3040
 
2877
3041
  // src/agents/yieldseeker/yieldseeker.auth-cache.ts
@@ -2984,7 +3148,7 @@ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
2984
3148
  return new import_siwe.SiweMessage({
2985
3149
  scheme: url.protocol.slice(0, -1),
2986
3150
  domain: url.host,
2987
- address: (0, import_viem6.getAddress)(address),
3151
+ address: (0, import_viem7.getAddress)(address),
2988
3152
  uri: url.origin,
2989
3153
  version: "1",
2990
3154
  chainId,
@@ -3070,15 +3234,15 @@ var YieldseekerAuth = class {
3070
3234
  clearYieldseekerSession(state.walletAddress, chainId);
3071
3235
  }
3072
3236
  async sign(state, chainId) {
3073
- const account = (0, import_viem6.getAddress)(state.walletAddress);
3074
- const publicClient = (0, import_viem6.createPublicClient)({
3237
+ const account = (0, import_viem7.getAddress)(state.walletAddress);
3238
+ const publicClient = (0, import_viem7.createPublicClient)({
3075
3239
  chain: import_chains2.base,
3076
- transport: (0, import_viem6.custom)(state.provider)
3240
+ transport: (0, import_viem7.custom)(state.provider)
3077
3241
  });
3078
- const walletClient = (0, import_viem6.createWalletClient)({
3242
+ const walletClient = (0, import_viem7.createWalletClient)({
3079
3243
  account,
3080
3244
  chain: import_chains2.base,
3081
- transport: (0, import_viem6.custom)(state.provider)
3245
+ transport: (0, import_viem7.custom)(state.provider)
3082
3246
  });
3083
3247
  await ensureWalletOnChain(
3084
3248
  publicClient,
@@ -3245,7 +3409,7 @@ var YieldseekerApiClient = class {
3245
3409
  };
3246
3410
 
3247
3411
  // src/agents/yieldseeker/yieldseeker.mapper.ts
3248
- var import_viem7 = require("viem");
3412
+ var import_viem8 = require("viem");
3249
3413
 
3250
3414
  // src/lib/helpers/snapshot-apy.ts
3251
3415
  var DAY_MS = 864e5;
@@ -3312,10 +3476,10 @@ function raw(value, endpoint) {
3312
3476
  return BigInt(value);
3313
3477
  }
3314
3478
  function decimal(value, decimals, endpoint) {
3315
- return (0, import_viem7.formatUnits)(raw(value, endpoint), decimals);
3479
+ return (0, import_viem8.formatUnits)(raw(value, endpoint), decimals);
3316
3480
  }
3317
3481
  function usd(rawAmount, decimals, price) {
3318
- return Number((0, import_viem7.formatUnits)(rawAmount, decimals)) * price;
3482
+ return Number((0, import_viem8.formatUnits)(rawAmount, decimals)) * price;
3319
3483
  }
3320
3484
  function percent(value) {
3321
3485
  const result = Number(value);
@@ -3341,7 +3505,7 @@ function assetAddressValue(record, address) {
3341
3505
  }
3342
3506
  function position(value, asset, baseAssetDecimals) {
3343
3507
  const option = value?.yieldOption;
3344
- 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)) {
3345
3509
  return invalid("yield positions", "missing vault metadata");
3346
3510
  }
3347
3511
  return {
@@ -3433,7 +3597,7 @@ function mapYieldseekerEarnings(contexts) {
3433
3597
  chain: "BASE",
3434
3598
  chainId: 8453,
3435
3599
  asset: context.asset,
3436
- amount: (0, import_viem7.formatUnits)(amount, context.snapshot.baseAssetDecimals)
3600
+ amount: (0, import_viem8.formatUnits)(amount, context.snapshot.baseAssetDecimals)
3437
3601
  });
3438
3602
  lifetimeEarnings += usd(
3439
3603
  amount,
@@ -3550,6 +3714,49 @@ function actionEntry(action) {
3550
3714
  rebalanceLog: []
3551
3715
  };
3552
3716
  }
3717
+ function allocationEntry(action, context) {
3718
+ if (action.actionType.toLowerCase() !== "autoseek") return void 0;
3719
+ const hash = action.details?.depositTransactionHash;
3720
+ const deposits = action.details?.deposits;
3721
+ if (typeof hash !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(hash) || !Array.isArray(deposits) || deposits.length === 0) return void 0;
3722
+ const positions = [];
3723
+ let amountRaw = 0n;
3724
+ for (const deposit of deposits) {
3725
+ if (!deposit || typeof deposit !== "object") return void 0;
3726
+ const leg = deposit;
3727
+ const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
3728
+ if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
3729
+ if (typeof leg.vaultAddress !== "string" || !(0, import_viem8.isAddress)(leg.vaultAddress)) return void 0;
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;
3731
+ if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
3732
+ const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
3733
+ positions.push({
3734
+ protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
3735
+ pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
3736
+ tokenSymbol: context.asset,
3737
+ amount: (0, import_viem8.formatUnits)(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3738
+ });
3739
+ amountRaw += rawAmount;
3740
+ }
3741
+ return {
3742
+ agent: "yieldseeker",
3743
+ action: "Deposit",
3744
+ date: action.createdDate,
3745
+ oldApy: null,
3746
+ newApy: null,
3747
+ positions,
3748
+ rebalanceLog: [],
3749
+ transactions: [{
3750
+ txHashes: [hash],
3751
+ chainId: context.agent.chainId,
3752
+ tokenAddress: context.agent.assetAddress,
3753
+ tokenSymbol: context.asset,
3754
+ agentWallet: context.wallet.walletAddress,
3755
+ amountRaw: String(amountRaw),
3756
+ amount: (0, import_viem8.formatUnits)(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3757
+ }]
3758
+ };
3759
+ }
3553
3760
  function depositDestination(context, movement) {
3554
3761
  const to = movement.toAddress.toLowerCase();
3555
3762
  const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
@@ -3617,20 +3824,30 @@ function mapYieldseekerHistory(contexts, options) {
3617
3824
  seenMovements.add(key2);
3618
3825
  return true;
3619
3826
  });
3620
- return [
3621
- ...movements.map(
3622
- (movement) => movementEntry(
3623
- movement,
3624
- context.wallet,
3625
- context.agent,
3626
- context.asset,
3627
- options.ownerAddress,
3628
- options.vaultAddresses,
3629
- depositDestination(context, movement)
3630
- )
3631
- ),
3632
- ...(context.actions ?? []).map(actionEntry)
3633
- ].filter((entry) => entry !== void 0);
3827
+ const movementEntries = movements.map(
3828
+ (movement) => movementEntry(
3829
+ movement,
3830
+ context.wallet,
3831
+ context.agent,
3832
+ context.asset,
3833
+ options.ownerAddress,
3834
+ options.vaultAddresses,
3835
+ depositDestination(context, movement)
3836
+ )
3837
+ ).filter((entry) => entry !== void 0);
3838
+ const deposits = new Set(movementEntries.filter((entry) => entry.action === "Deposit").map((entry) => entry.transactions[0]?.txHashes[0]?.toLowerCase()));
3839
+ const actions = (context.actions ?? []).flatMap((action) => {
3840
+ const allocation = allocationEntry(action, context);
3841
+ if (allocation) {
3842
+ const hash = allocation.transactions[0].txHashes[0].toLowerCase();
3843
+ if (deposits.has(hash)) return [];
3844
+ deposits.add(hash);
3845
+ return [allocation];
3846
+ }
3847
+ const entry = actionEntry(action);
3848
+ return entry ? [entry] : [];
3849
+ });
3850
+ return [...movementEntries, ...actions];
3634
3851
  });
3635
3852
  const grouped = /* @__PURE__ */ new Map();
3636
3853
  const ungrouped = [];
@@ -3660,7 +3877,7 @@ function mapYieldseekerHistory(contexts, options) {
3660
3877
  const known = new Set(target.logIndexes ?? []);
3661
3878
  if (tx.logIndexes?.some((index) => !known.has(index))) {
3662
3879
  target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
3663
- target.amount = (0, import_viem7.formatUnits)(
3880
+ target.amount = (0, import_viem8.formatUnits)(
3664
3881
  BigInt(target.amountRaw),
3665
3882
  YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
3666
3883
  );
@@ -3741,51 +3958,53 @@ function mapYieldseekerAgentApy(options, days) {
3741
3958
  }
3742
3959
 
3743
3960
  // src/agents/yieldseeker/yieldseeker.wallet-version.ts
3744
- var import_viem8 = require("viem");
3961
+ var import_viem9 = require("viem");
3745
3962
  var YIELDSEEKER_FACTORY = "0x9c7410a0faCAC60850C46eE5b58B518daec95130";
3746
3963
  var YIELDSEEKER_V1_IMPLEMENTATION = "0x32403455357589523435dB18b414057D7cE05051";
3747
3964
  var YIELDSEEKER_V2_IMPLEMENTATION = "0x613Db9c924ADB7A7A90c11b02Cce20c913E3Fe50";
3748
3965
  var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
3749
- var walletAbi = (0, import_viem8.parseAbi)([
3966
+ var walletAbi = (0, import_viem9.parseAbi)([
3750
3967
  "function owner() view returns (address)",
3751
3968
  "function ownerAgentIndex() view returns (uint256)",
3752
3969
  "function baseAsset() view returns (address)",
3753
3970
  "function FACTORY() view returns (address)",
3754
- "function upgradeToLatest()"
3971
+ "function upgradeToLatest()",
3972
+ "event Upgraded(address indexed implementation)"
3755
3973
  ]);
3756
- var factoryAbi = (0, import_viem8.parseAbi)([
3974
+ var factoryAbi = (0, import_viem9.parseAbi)([
3757
3975
  "function userWallets(address owner, uint256 index) view returns (address)",
3758
3976
  "function agentWalletImplementation() view returns (address)"
3759
3977
  ]);
3760
3978
  function yieldseekerOwnerPath(code) {
3761
3979
  return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
3762
3980
  }
3763
- async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
3764
- const agentWallet = (0, import_viem8.getAddress)(wallet);
3765
- const expectedOwner = (0, import_viem8.getAddress)(owner);
3766
- 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);
3767
3985
  const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
3768
- client.getBytecode({ address: agentWallet }),
3769
- client.getBytecode({ address: expectedOwner }),
3770
- client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT }),
3771
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner" }),
3772
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex" }),
3773
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset" }),
3774
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY" }),
3775
- 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 })
3776
3994
  ]);
3777
3995
  if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
3778
3996
  throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
3779
3997
  }
3780
- const implementation = (0, import_viem8.getAddress)(`0x${slot.slice(-40)}`);
3998
+ const implementation = (0, import_viem9.getAddress)(`0x${slot.slice(-40)}`);
3781
3999
  const mapped = await client.readContract({
3782
4000
  address: YIELDSEEKER_FACTORY,
3783
4001
  abi: factoryAbi,
3784
4002
  functionName: "userWallets",
3785
- args: [expectedOwner, index]
4003
+ args: [expectedOwner, index],
4004
+ blockNumber
3786
4005
  });
3787
- 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);
3788
- 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";
3789
4008
  return {
3790
4009
  agentWallet,
3791
4010
  owner: actualOwner,
@@ -3794,18 +4013,20 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
3794
4013
  implementation,
3795
4014
  version,
3796
4015
  verified,
3797
- upgradeAvailable: version === "v1" && (0, import_viem8.isAddressEqual)(latest, YIELDSEEKER_V2_IMPLEMENTATION)
4016
+ upgradeAvailable: version === "v1" && (0, import_viem9.isAddressEqual)(latest, YIELDSEEKER_V2_IMPLEMENTATION)
3798
4017
  };
3799
4018
  }
3800
4019
  var YIELDSEEKER_UPGRADE_ABI = walletAbi;
3801
4020
 
3802
4021
  // src/agents/yieldseeker/yieldseeker.sponsored-withdraw.ts
3803
- var import_viem9 = require("viem");
3804
- var import_account_abstraction = require("viem/account-abstraction");
4022
+ var import_viem10 = require("viem");
4023
+ var import_account_abstraction2 = require("viem/account-abstraction");
3805
4024
  var import_accounts = require("viem/accounts");
3806
4025
  var import_chains3 = require("viem/chains");
3807
4026
  var ENTRY_POINT_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
3808
4027
  var USER_OP_FEE_HEADROOM = 2n;
4028
+ var OBSERVATION_WINDOW_MS = 6e4;
4029
+ var OBSERVATION_POLL_MS = 1500;
3809
4030
  var ENTRY_POINT_NONCE_ABI = [
3810
4031
  { type: "function", name: "getNonce", stateMutability: "view", inputs: [
3811
4032
  { name: "sender", type: "address" },
@@ -3815,7 +4036,7 @@ var ENTRY_POINT_NONCE_ABI = [
3815
4036
  var DEFAULT_ROUTING_API = "https://owney-routing-api-243946518160.europe-west4.run.app";
3816
4037
  var SIMULATION_ACCOUNT = (0, import_accounts.privateKeyToAccount)((0, import_accounts.generatePrivateKey)());
3817
4038
  function operationHash(operation) {
3818
- return (0, import_account_abstraction.getUserOperationHash)({
4039
+ return (0, import_account_abstraction2.getUserOperationHash)({
3819
4040
  chainId: 8453,
3820
4041
  entryPointAddress: ENTRY_POINT_V06,
3821
4042
  entryPointVersion: "0.6",
@@ -3838,10 +4059,119 @@ function sponsorError(message, safeToFallback, details = {}) {
3838
4059
  return new OwneyError(
3839
4060
  "SPONSOR_REQUEST_FAILED",
3840
4061
  message,
3841
- { 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
+ },
3842
4069
  "yieldseeker"
3843
4070
  );
3844
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
+ }
3845
4175
  async function requestIntent(input) {
3846
4176
  const baseUrl = (input.routingApiBaseUrl ?? DEFAULT_ROUTING_API).replace(/\/$/, "");
3847
4177
  let response;
@@ -3863,18 +4193,35 @@ async function requestIntent(input) {
3863
4193
  action: input.action,
3864
4194
  yieldseekerUserId: input.userId,
3865
4195
  yieldseekerAgentId: input.agentId
3866
- })
4196
+ }),
4197
+ signal: AbortSignal.timeout(15e3)
3867
4198
  });
3868
4199
  } catch {
3869
- 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
+ );
3870
4205
  }
3871
4206
  const envelope = await response.json().catch(() => null);
3872
4207
  const intent = envelope?.data;
3873
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
+ }
3874
4215
  throw sponsorError(
3875
4216
  "Withdrawal sponsorship is currently unavailable.",
3876
4217
  true,
3877
- { 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
+ }
3878
4225
  );
3879
4226
  }
3880
4227
  const url = new URL(`${baseUrl}/api/v1/sponsor/yieldseeker-withdraw/rpc`);
@@ -3887,19 +4234,55 @@ async function rpc(url, method, params, phase) {
3887
4234
  response = await fetch(url, {
3888
4235
  method: "POST",
3889
4236
  headers: { "content-type": "application/json" },
3890
- 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)
3891
4239
  });
3892
4240
  } catch {
3893
- 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
+ );
3894
4251
  }
3895
4252
  const payload = await response.json().catch(() => null);
3896
4253
  if (!response.ok || payload?.error || payload?.result === void 0) {
3897
4254
  const rejection = payload?.error;
3898
- 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 ?? ""));
3899
4279
  throw sponsorError(
3900
4280
  phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
3901
4281
  phase === "prepare" || definitiveRejection,
3902
- { statusCode: response.status, rpcCode: payload?.error?.code, rpcMethod: method }
4282
+ {
4283
+ ...details,
4284
+ ...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
4285
+ }
3903
4286
  );
3904
4287
  }
3905
4288
  return payload.result;
@@ -3919,13 +4302,15 @@ async function submitEoa(input) {
3919
4302
  let nonce;
3920
4303
  let fees;
3921
4304
  try {
3922
- nonce = await input.client.readContract({
3923
- address: ENTRY_POINT_V06,
3924
- abi: ENTRY_POINT_NONCE_ABI,
3925
- functionName: "getNonce",
3926
- args: [input.agentWallet, 0n]
3927
- });
3928
- 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
+ ]);
3929
4314
  } catch {
3930
4315
  throw sponsorError("Could not prepare the sponsored withdrawal.", true);
3931
4316
  }
@@ -3933,14 +4318,14 @@ async function submitEoa(input) {
3933
4318
  throw sponsorError("Base did not return EIP-1559 gas fees.", true);
3934
4319
  let operation = {
3935
4320
  sender: input.agentWallet,
3936
- nonce: (0, import_viem9.toHex)(nonce),
4321
+ nonce: (0, import_viem10.toHex)(nonce),
3937
4322
  initCode: "0x",
3938
4323
  callData: input.callData,
3939
- callGasLimit: (0, import_viem9.toHex)(3e5),
3940
- verificationGasLimit: (0, import_viem9.toHex)(3e5),
3941
- preVerificationGas: (0, import_viem9.toHex)(6e4),
3942
- maxFeePerGas: (0, import_viem9.toHex)(fees.maxFeePerGas * USER_OP_FEE_HEADROOM),
3943
- 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),
3944
4329
  paymasterAndData: "0x",
3945
4330
  signature: "0x"
3946
4331
  };
@@ -3948,7 +4333,7 @@ async function submitEoa(input) {
3948
4333
  const stub = await rpc(
3949
4334
  input.rpcUrl,
3950
4335
  "pm_getPaymasterStubData",
3951
- [operation, ENTRY_POINT_V06, (0, import_viem9.toHex)(8453), {}],
4336
+ [operation, ENTRY_POINT_V06, (0, import_viem10.toHex)(8453), {}],
3952
4337
  "prepare"
3953
4338
  );
3954
4339
  if (!stub.paymasterAndData?.startsWith("0x"))
@@ -3963,39 +4348,63 @@ async function submitEoa(input) {
3963
4348
  const final = await rpc(
3964
4349
  input.rpcUrl,
3965
4350
  "pm_getPaymasterData",
3966
- [operation, ENTRY_POINT_V06, (0, import_viem9.toHex)(8453), {}],
4351
+ [operation, ENTRY_POINT_V06, (0, import_viem10.toHex)(8453), {}],
3967
4352
  "prepare"
3968
4353
  );
3969
4354
  if (!final.paymasterAndData?.startsWith("0x"))
3970
4355
  throw sponsorError("CDP did not return final paymaster data.", true);
3971
- operation = { ...operation, paymasterAndData: final.paymasterAndData };
4356
+ operation = { ...operation, ...finalPaymasterFields(final) };
3972
4357
  }
3973
4358
  const userOpHash = operationHash(operation);
3974
- const wallet = (0, import_viem9.createWalletClient)({
4359
+ await assertOwnerSession(input.state, input.owner);
4360
+ const wallet = (0, import_viem10.createWalletClient)({
3975
4361
  account: input.owner,
3976
4362
  chain: import_chains3.base,
3977
- transport: (0, import_viem9.custom)(input.state.provider)
4363
+ transport: (0, import_viem10.custom)(input.state.provider)
3978
4364
  });
3979
4365
  const signature = await wallet.signMessage({ message: { raw: userOpHash } });
3980
4366
  operation = { ...operation, signature };
3981
- 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 });
3982
4382
  const submittedHash = await rpc(
3983
4383
  input.rpcUrl,
3984
4384
  "eth_sendUserOperation",
3985
4385
  [operation, ENTRY_POINT_V06],
3986
4386
  "send"
3987
4387
  );
3988
- if (submittedHash.toLowerCase() !== userOpHash.toLowerCase())
4388
+ if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
3989
4389
  throw sponsorError(
3990
4390
  "Bundler returned a different UserOperation hash.",
3991
4391
  false,
3992
- { userOpHash, submittedHash }
4392
+ { userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
3993
4393
  );
3994
- for (let poll = 0; poll < 40; poll++) {
3995
- 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
+ }
3996
4405
  const txHash = response?.receipt?.transactionHash;
3997
- if (txHash) {
3998
- input.onPending?.({ userOpHash, txHash });
4406
+ if (isHash(txHash)) {
4407
+ savePending(input.onPending, { userOpHash, txHash });
3999
4408
  if (response?.success === false)
4000
4409
  throw new OwneyError(
4001
4410
  "AGENT_TRANSACTION_REVERTED",
@@ -4005,12 +4414,17 @@ async function submitEoa(input) {
4005
4414
  );
4006
4415
  return { txHash, receipt: await waitForTransaction(input.client, txHash) };
4007
4416
  }
4008
- await new Promise((resolve) => setTimeout(resolve, 1500));
4417
+ await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
4009
4418
  }
4010
4419
  throw sponsorError(
4011
4420
  "Sponsored withdrawal is still pending; check status before retrying.",
4012
4421
  false,
4013
- { userOpHash }
4422
+ {
4423
+ userOpHash,
4424
+ phase: "observe",
4425
+ failureCode: "RECEIPT_UNRESOLVED",
4426
+ ...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
4427
+ }
4014
4428
  );
4015
4429
  }
4016
4430
  async function submitSmart(input) {
@@ -4023,21 +4437,22 @@ async function submitSmart(input) {
4023
4437
  } catch {
4024
4438
  throw sponsorError("This account cannot check paymaster support on Base.", true);
4025
4439
  }
4026
- 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)
4027
4442
  throw sponsorError("This account cannot use a paymaster on Base.", true);
4443
+ await assertOwnerSession(input.state, input.owner);
4028
4444
  let sent;
4029
- try {
4030
- input.onPending?.({});
4031
- } catch {
4032
- throw sponsorError("Upgrade or withdrawal status cannot be saved.", true);
4033
- }
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 });
4034
4448
  try {
4035
4449
  sent = await input.state.provider.request({
4036
4450
  method: "wallet_sendCalls",
4037
4451
  params: [{
4038
4452
  version: "2.0.0",
4039
4453
  from: input.owner,
4040
- chainId: (0, import_viem9.toHex)(8453),
4454
+ chainId: (0, import_viem10.toHex)(8453),
4455
+ id: requestedId,
4041
4456
  atomicRequired: false,
4042
4457
  calls: [{ to: input.agentWallet, value: "0x0", data: input.callData }],
4043
4458
  capabilities: { paymasterService: { url: input.rpcUrl } }
@@ -4046,17 +4461,27 @@ async function submitSmart(input) {
4046
4461
  } catch (error) {
4047
4462
  if (error?.code === 4001) throw error;
4048
4463
  if ([4200, -32601].includes(Number(error?.code)))
4049
- throw sponsorError("This account does not support sponsored calls.", true);
4050
- 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
+ );
4051
4474
  }
4052
4475
  const callsId = typeof sent === "string" ? sent : sent?.id;
4053
- if (!callsId) throw sponsorError("Wallet returned no calls ID; submission status is unknown.", false);
4054
- try {
4055
- input.onPending?.({ callsId });
4056
- } catch {
4057
- throw sponsorError("Submitted call status could not be saved.", false, { callsId });
4058
- }
4059
- 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++) {
4060
4485
  let status;
4061
4486
  try {
4062
4487
  status = await input.state.provider.request({
@@ -4064,37 +4489,44 @@ async function submitSmart(input) {
4064
4489
  params: [callsId]
4065
4490
  });
4066
4491
  } catch {
4067
- 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;
4068
4494
  }
4069
- 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)
4070
4502
  throw sponsorError(
4071
4503
  "Sponsored withdrawal failed in the wallet.",
4072
4504
  false,
4073
- { callsId, executionFailed: true }
4505
+ { callsId, executionFailed: true, phase: "observe", failureCode: "WALLET_EXECUTION_FAILED" }
4074
4506
  );
4075
- 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;
4076
4509
  if (txHash) {
4077
- try {
4078
- input.onPending?.({ callsId, txHash });
4079
- } catch {
4080
- throw sponsorError(
4081
- "Submitted call receipt could not be saved.",
4082
- false,
4083
- { 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"
4084
4517
  );
4085
- }
4086
4518
  return { txHash, receipt: await waitForTransaction(input.client, txHash) };
4087
4519
  }
4088
- await new Promise((resolve) => setTimeout(resolve, 1500));
4520
+ await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
4089
4521
  }
4090
4522
  throw sponsorError(
4091
4523
  "Sponsored withdrawal is still pending; check status before retrying.",
4092
4524
  false,
4093
- { callsId }
4525
+ { callsId, phase: "observe", failureCode: "RECEIPT_UNRESOLVED" }
4094
4526
  );
4095
4527
  }
4096
4528
  async function submitYieldseekerSponsoredWithdrawal(input) {
4097
- const owner = (0, import_viem9.getAddress)(input.state.walletAddress);
4529
+ const owner = (0, import_viem10.getAddress)(input.state.walletAddress);
4098
4530
  const { intent, rpcUrl } = await requestIntent({
4099
4531
  apiKey: input.apiKey,
4100
4532
  routingApiBaseUrl: input.routingApiBaseUrl,
@@ -4105,13 +4537,14 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
4105
4537
  agentWallet: input.agentWallet,
4106
4538
  asset: input.asset,
4107
4539
  callData: input.callData,
4108
- action: "withdraw"
4540
+ action: "withdraw",
4541
+ refreshYieldseekerSignature: input.refreshYieldseekerSignature
4109
4542
  });
4110
4543
  const args = { ...input, owner, rpcUrl };
4111
4544
  return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
4112
4545
  }
4113
4546
  async function submitYieldseekerSponsoredUpgrade(input) {
4114
- const owner = (0, import_viem9.getAddress)(input.state.walletAddress);
4547
+ const owner = (0, import_viem10.getAddress)(input.state.walletAddress);
4115
4548
  const { intent, rpcUrl } = await requestIntent({
4116
4549
  apiKey: input.apiKey,
4117
4550
  routingApiBaseUrl: input.routingApiBaseUrl,
@@ -4122,7 +4555,8 @@ async function submitYieldseekerSponsoredUpgrade(input) {
4122
4555
  agentWallet: input.agentWallet,
4123
4556
  asset: input.asset,
4124
4557
  callData: input.callData,
4125
- action: "upgrade"
4558
+ action: "upgrade",
4559
+ refreshYieldseekerSignature: input.refreshYieldseekerSignature
4126
4560
  });
4127
4561
  if (intent.ownerPath !== "smart")
4128
4562
  throw sponsorError("V1 upgrade sponsorship requires a Base Account owner.", true);
@@ -4143,6 +4577,8 @@ var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
4143
4577
  var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
4144
4578
  var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
4145
4579
  var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
4580
+ var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
4581
+ var YIELDSEEKER_MAX_POSITION_VERIFICATION_MS = 2e3;
4146
4582
  function generateYieldseekerUsername() {
4147
4583
  const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
4148
4584
  return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
@@ -4153,6 +4589,13 @@ function isUsernameConflict(error) {
4153
4589
  return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
4154
4590
  }
4155
4591
  var YIELDSEEKER_AGENT_WALLET_ABI = [
4592
+ {
4593
+ type: "function",
4594
+ name: "feeTracker",
4595
+ stateMutability: "view",
4596
+ inputs: [],
4597
+ outputs: [{ name: "", type: "address" }]
4598
+ },
4156
4599
  {
4157
4600
  type: "function",
4158
4601
  name: "withdrawAssetToUser",
@@ -4175,6 +4618,13 @@ var YIELDSEEKER_AGENT_WALLET_ABI = [
4175
4618
  outputs: []
4176
4619
  }
4177
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
+ }];
4178
4628
  function query(params) {
4179
4629
  const search = new URLSearchParams();
4180
4630
  for (const [key2, value] of Object.entries(params)) {
@@ -4254,21 +4704,26 @@ var YieldseekerAgent = class {
4254
4704
  if (useCachedV2 && cached && Date.now() - cached.at < 15 * 6e4) return { ...cached.status };
4255
4705
  const status = await readYieldseekerWalletStatus(
4256
4706
  this.getReceiptClient(),
4257
- (0, import_viem10.getAddress)(context.wallet.walletAddress),
4258
- (0, import_viem10.getAddress)(state.walletAddress),
4707
+ (0, import_viem11.getAddress)(context.wallet.walletAddress),
4708
+ (0, import_viem11.getAddress)(state.walletAddress),
4259
4709
  YIELDSEEKER_ASSET_METADATA[asset].address
4260
4710
  );
4711
+ this.rememberWalletStatus(key2, status);
4712
+ return status;
4713
+ }
4714
+ rememberWalletStatus(key2, status) {
4261
4715
  if (status.verified && status.version === "v2") {
4262
4716
  this.withdrawalVersionCache.set(key2, { at: Date.now(), status: { ...status } });
4263
4717
  if (this.withdrawalVersionCache.size > 128)
4264
4718
  this.withdrawalVersionCache.delete(this.withdrawalVersionCache.keys().next().value);
4265
4719
  } else this.withdrawalVersionCache.delete(key2);
4266
- return status;
4267
4720
  }
4268
4721
  /** Explicit owner action. This never runs as a side effect of withdrawal. */
4269
4722
  async upgradeWallet(state, chainId, asset, gasMode = "paid") {
4723
+ const generation = this.readGeneration;
4270
4724
  const before = await this.getWalletStatus(state, chainId, asset);
4271
- if (!before?.verified || !before.upgradeAvailable) {
4725
+ this.assertSession(generation, chainId);
4726
+ if (!before?.verified || before.version === "unknown") {
4272
4727
  throw new OwneyError(
4273
4728
  "WITHDRAW_FAILED",
4274
4729
  "This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
@@ -4276,48 +4731,77 @@ var YieldseekerAgent = class {
4276
4731
  this.id
4277
4732
  );
4278
4733
  }
4279
- const data = (0, import_viem10.encodeFunctionData)({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
4280
- let txHash;
4281
- if (gasMode === "sponsored") {
4282
- if (before.ownerPath !== "smart")
4283
- throw new OwneyError(
4284
- "SPONSOR_REQUEST_FAILED",
4285
- "EOA owners pay Base gas for the V1 upgrade.",
4286
- { safeToFallback: true },
4287
- this.id
4288
- );
4289
- let storage5;
4290
- try {
4291
- storage5 = typeof window === "undefined" ? void 0 : window.localStorage;
4292
- } catch {
4293
- storage5 = void 0;
4294
- }
4295
- 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)
4296
4750
  throw new OwneyError(
4297
- "SPONSOR_REQUEST_FAILED",
4298
- "Upgrade status cannot be saved in this browser.",
4299
- { safeToFallback: true },
4751
+ "WITHDRAW_FAILED",
4752
+ "Yieldseeker wallet upgrade receipt could not be verified.",
4753
+ { txHash: hash, reason: "UPGRADE_RECEIPT_MISMATCH" },
4300
4754
  this.id
4301
4755
  );
4302
- const pendingKey = `owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
4303
- let prior = null;
4304
- 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 = () => {
4305
4773
  try {
4306
- saved = storage5.getItem(pendingKey);
4774
+ storage5?.removeItem(pendingKey2);
4307
4775
  } catch {
4308
- throw new OwneyError(
4309
- "SPONSOR_REQUEST_FAILED",
4310
- "Upgrade status cannot be read in this browser.",
4311
- { safeToFallback: true },
4312
- this.id
4313
- );
4314
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);
4315
4788
  try {
4316
4789
  prior = JSON.parse(saved ?? "null");
4317
4790
  } catch {
4318
4791
  prior = { callsId: void 0 };
4319
4792
  }
4320
- 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) {
4321
4805
  if (!prior.callsId)
4322
4806
  throw new OwneyError(
4323
4807
  "SPONSOR_REQUEST_FAILED",
@@ -4327,10 +4811,7 @@ var YieldseekerAgent = class {
4327
4811
  );
4328
4812
  let callsStatus;
4329
4813
  try {
4330
- callsStatus = await state.provider.request({
4331
- method: "wallet_getCallsStatus",
4332
- params: [prior.callsId]
4333
- });
4814
+ callsStatus = await state.provider.request({ method: "wallet_getCallsStatus", params: [prior.callsId] });
4334
4815
  } catch {
4335
4816
  throw new OwneyError(
4336
4817
  "SPONSOR_REQUEST_FAILED",
@@ -4339,8 +4820,15 @@ var YieldseekerAgent = class {
4339
4820
  this.id
4340
4821
  );
4341
4822
  }
4342
- if (callsStatus?.status === 400 || callsStatus?.status === 500) {
4343
- 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);
4344
4832
  throw new OwneyError(
4345
4833
  "SPONSOR_REQUEST_FAILED",
4346
4834
  "The earlier sponsored upgrade failed.",
@@ -4348,22 +4836,39 @@ var YieldseekerAgent = class {
4348
4836
  this.id
4349
4837
  );
4350
4838
  }
4351
- const earlierHash = callsStatus?.receipts?.[0]?.transactionHash;
4352
- if (earlierHash) {
4353
- await this.waitForReceipt(state, chainId, earlierHash);
4354
- const earlierStatus = await this.getWalletStatus(state, chainId, asset);
4355
- if (earlierStatus?.version === "v2" && earlierStatus.verified) {
4356
- storage5.removeItem(pendingKey);
4357
- return { txHash: earlierHash, status: earlierStatus };
4358
- }
4359
- }
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")
4360
4866
  throw new OwneyError(
4361
4867
  "SPONSOR_REQUEST_FAILED",
4362
- "An earlier upgrade is still pending.",
4363
- { safeToFallback: false, callsId: prior.callsId },
4868
+ "EOA owners pay Base gas for the V1 upgrade.",
4869
+ { safeToFallback: true },
4364
4870
  this.id
4365
4871
  );
4366
- }
4367
4872
  const context = await this.findAgent(state, chainId, asset);
4368
4873
  if (!context) throw new OwneyError("NOT_CONNECTED", "Yieldseeker agent is unavailable.");
4369
4874
  try {
@@ -4373,14 +4878,15 @@ var YieldseekerAgent = class {
4373
4878
  apiKey: this.owneyApiKey,
4374
4879
  routingApiBaseUrl: this.routingApiBaseUrl,
4375
4880
  yieldseekerSignature: await this.auth.getToken(state, chainId),
4881
+ refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
4376
4882
  userId: context.user.userId,
4377
4883
  agentId: context.agent.agentId,
4378
4884
  agentWallet: before.agentWallet,
4379
4885
  asset: YIELDSEEKER_ASSET_METADATA[asset].address,
4380
4886
  callData: data,
4381
4887
  onPending: (pending) => {
4382
- const current = storage5.getItem(pendingKey);
4383
- storage5.setItem(pendingKey, JSON.stringify({
4888
+ const current = storage5.getItem(pendingKey2);
4889
+ storage5.setItem(pendingKey2, JSON.stringify({
4384
4890
  startedAt: Date.now(),
4385
4891
  ...current ? JSON.parse(current) : {},
4386
4892
  ...pending
@@ -4388,31 +4894,25 @@ var YieldseekerAgent = class {
4388
4894
  }
4389
4895
  });
4390
4896
  txHash = upgraded.txHash;
4897
+ receipt = upgraded.receipt;
4391
4898
  } catch (error) {
4392
- 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);
4393
4900
  throw error;
4394
4901
  }
4395
4902
  } else {
4396
4903
  txHash = await this.submitTransaction(state, chainId, {
4397
- from: (0, import_viem10.getAddress)(state.walletAddress),
4904
+ from: (0, import_viem11.getAddress)(state.walletAddress),
4398
4905
  to: before.agentWallet,
4399
4906
  data,
4400
4907
  value: "0",
4401
4908
  chainId
4909
+ }, void 0, (value) => {
4910
+ receipt = value;
4402
4911
  });
4403
4912
  }
4404
- const status = await this.getWalletStatus(state, chainId, asset);
4405
- if (!status?.verified || status.version !== "v2") {
4406
- throw new OwneyError(
4407
- "WITHDRAW_FAILED",
4408
- "Yieldseeker wallet upgrade was not confirmed as V2.",
4409
- { txHash, version: status?.version ?? "missing" },
4410
- this.id
4411
- );
4412
- }
4413
- if (gasMode === "sponsored" && typeof window !== "undefined")
4414
- window.localStorage.removeItem(`owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`);
4415
- return { txHash, status };
4913
+ const confirmed = await confirmUpgrade(txHash, receipt ?? await this.waitForReceipt(state, chainId, txHash));
4914
+ if (gasMode === "sponsored") clearConfirmedUpgrade();
4915
+ return confirmed;
4416
4916
  }
4417
4917
  async disconnect() {
4418
4918
  this.withdrawalVersionCache.clear();
@@ -4485,19 +4985,37 @@ var YieldseekerAgent = class {
4485
4985
  submitted(txHash);
4486
4986
  receipt = await this.waitForReceipt(state, chainId, txHash);
4487
4987
  } else {
4488
- txHash = await this.submitTransaction(state, chainId, {
4489
- from: (0, import_viem10.getAddress)(state.walletAddress),
4490
- to: YIELDSEEKER_ASSET_METADATA[asset].address,
4491
- data: (0, import_viem10.encodeFunctionData)({
4492
- abi: import_viem10.erc20Abi,
4493
- functionName: "transfer",
4494
- args: [(0, import_viem10.getAddress)(context.wallet.walletAddress), BigInt(amount)]
4495
- }),
4496
- value: "0",
4497
- chainId
4498
- }, submitted, (value) => {
4499
- receipt = value;
4500
- });
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
+ }
4501
5019
  }
4502
5020
  if (this.readGeneration === generation) {
4503
5021
  void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
@@ -4507,7 +5025,7 @@ var YieldseekerAgent = class {
4507
5025
  notifyFunding(onProgress, evidence);
4508
5026
  return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
4509
5027
  }
4510
- async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto") {
5028
+ async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
4511
5029
  let ownerTransferAttempted = false;
4512
5030
  let evidence = {
4513
5031
  agentId: this.id,
@@ -4525,10 +5043,10 @@ var YieldseekerAgent = class {
4525
5043
  notifyFunding(onProgress, evidence);
4526
5044
  }, () => {
4527
5045
  ownerTransferAttempted = true;
4528
- }, gasMode);
5046
+ }, gasMode, onBeforeSubmission);
4529
5047
  } catch (error) {
4530
5048
  if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
4531
- 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)))
4532
5050
  notifyFunding(onProgress, {
4533
5051
  ...evidence,
4534
5052
  ...error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true ? { userOpHash: void 0, callsId: void 0 } : {},
@@ -4538,7 +5056,7 @@ var YieldseekerAgent = class {
4538
5056
  throw error;
4539
5057
  }
4540
5058
  }
4541
- async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto") {
5059
+ async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
4542
5060
  this.assertChain(chainId);
4543
5061
  this.assertAsset(asset);
4544
5062
  if (amount !== void 0 && BigInt(amount) <= 0n) {
@@ -4583,31 +5101,170 @@ var YieldseekerAgent = class {
4583
5101
  let confirmedMovement = false;
4584
5102
  let settlement = null;
4585
5103
  try {
4586
- const portfolio = await this.loadPortfolioContext(
4587
- state,
4588
- chainId,
4589
- context
4590
- );
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 }));
4591
5107
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
4592
- const readIdle = async () => {
4593
- const balance = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
4594
- address: metadata.address,
4595
- abi: import_viem10.erc20Abi,
4596
- functionName: "balanceOf",
4597
- args: [(0, import_viem10.getAddress)(context.wallet.walletAddress)]
4598
- }), 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;
5116
+ this.assertSession(generation, chainId, asset);
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 };
5137
+ };
5138
+ const [portfolio, initialFunds] = await Promise.all([portfolioRead, readOwnerFunds()]);
5139
+ const idle = initialFunds.idle;
5140
+ let positions = portfolio.positions;
5141
+ let positionsStatus = "empty";
5142
+ const validatePositions = (values) => {
5143
+ const seen = /* @__PURE__ */ new Set();
5144
+ for (const value of values) {
5145
+ const address = value?.yieldOption?.address;
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))
5147
+ throw this.invalidResponse("yield positions");
5148
+ seen.add(address.toLowerCase());
5149
+ }
5150
+ return values;
5151
+ };
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
+ };
5219
+ const refreshPositions = async () => {
5220
+ this.assertSession(generation, chainId, asset);
5221
+ const response = await this.walletRequest(
5222
+ state,
5223
+ chainId,
5224
+ this.agentPath(context, "yield-positions")
5225
+ );
4599
5226
  this.assertSession(generation, chainId, asset);
4600
- return balance;
5227
+ if (!Array.isArray(response?.yieldPositions)) throw this.invalidResponse("yield positions");
5228
+ return validatePositions(response.yieldPositions);
4601
5229
  };
4602
- const idle = await readIdle();
4603
- const deployed = portfolio.positions.reduce(
5230
+ if (amount === void 0) {
5231
+ validatePositions(positions);
5232
+ rememberPositions(positions);
5233
+ positionsStatus = hasPositions(positions) ? "remaining" : "empty";
5234
+ }
5235
+ const deployed = positions.reduce(
4604
5236
  (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
4605
5237
  0n
4606
5238
  );
4607
- const totalAvailable = idle + deployed;
5239
+ const grossAvailable = idle + deployed;
5240
+ const totalAvailable = grossAvailable > initialFunds.fees ? grossAvailable - initialFunds.fees : 0n;
4608
5241
  const requested = amount === void 0 ? totalAvailable : BigInt(amount);
4609
5242
  if (requested === 0n) {
4610
5243
  this.invalidateFundingRead(state, chainId, asset);
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)
5262
+ throw new OwneyError(
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() },
5266
+ this.id
5267
+ );
4611
5268
  throw new OwneyError(
4612
5269
  "WITHDRAW_INSUFFICIENT_BALANCE",
4613
5270
  "No Yieldseeker funds are available to withdraw.",
@@ -4627,46 +5284,104 @@ var YieldseekerAgent = class {
4627
5284
  this.id
4628
5285
  );
4629
5286
  }
4630
- let remaining = requested > idle ? requested - idle : 0n;
5287
+ const requiredIdle = requested + initialFunds.fees;
5288
+ let remaining = requiredIdle > idle ? requiredIdle - idle : 0n;
4631
5289
  let grossUnwound = 0n;
4632
- for (const position2 of portfolio.positions) {
4633
- if (remaining === 0n) break;
4634
- const available = BigInt(position2.withdrawableAssetsRaw);
4635
- if (available <= 0n) continue;
4636
- const assetsRaw = available < remaining ? available : remaining;
4637
- const response = await this.walletRequest(
4638
- state,
4639
- chainId,
4640
- this.agentPath(context, "withdraw-from-position"),
4641
- {
4642
- method: "POST",
4643
- body: {
4644
- chainId,
4645
- vaultAddress: position2.yieldOption.address,
4646
- assetsRaw: assetsRaw.toString()
5290
+ let latestUnwindBlock;
5291
+ const unwind = async (position2, assetsRaw) => {
5292
+ this.assertSession(generation, chainId, asset);
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() }
4647
5302
  }
4648
- }
4649
- );
4650
- if (!this.isTransactionHash(response?.transactionHash)) {
4651
- throw this.invalidResponse("position withdrawal");
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
+ );
4652
5323
  }
4653
- await this.waitForReceipt(state, chainId, response.transactionHash);
4654
5324
  confirmedMovement = true;
5325
+ this.assertSession(generation, chainId, asset);
4655
5326
  grossUnwound += assetsRaw;
4656
- remaining -= assetsRaw;
5327
+ };
5328
+ if (amount === void 0) {
5329
+ for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
5330
+ const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
5331
+ let unwound = false;
5332
+ for (const position2 of positions) {
5333
+ const available = BigInt(position2.withdrawableAssetsRaw);
5334
+ if (available <= 0n) continue;
5335
+ await unwind(position2, available);
5336
+ unwound = true;
5337
+ }
5338
+ if (!unwound) break;
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 {
5350
+ positionsStatus = "unavailable";
5351
+ break;
5352
+ }
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;
5355
+ }
5356
+ } else {
5357
+ for (const position2 of positions) {
5358
+ if (remaining === 0n) break;
5359
+ const available = BigInt(position2.withdrawableAssetsRaw);
5360
+ if (available <= 0n) continue;
5361
+ const assetsRaw = available < remaining ? available : remaining;
5362
+ await unwind(position2, assetsRaw);
5363
+ remaining -= assetsRaw;
5364
+ }
4657
5365
  }
4658
- if (remaining > 0n) {
5366
+ if (amount !== void 0 && remaining > 0n) {
4659
5367
  throw this.invalidResponse("yield positions", {
4660
5368
  reason: "Withdrawable positions could not cover the request.",
4661
5369
  remaining: remaining.toString()
4662
5370
  });
4663
5371
  }
4664
- const availableForOwner = confirmedMovement ? await readIdle() : idle;
4665
- const feeRate = portfolio.snapshot.feeRateBps;
5372
+ const availableForOwner = confirmedMovement ? (await readOwnerFunds()).available : initialFunds.available;
5373
+ const feeRate = portfolio.feeRateBps;
4666
5374
  const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
4667
5375
  const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
4668
5376
  if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
4669
5377
  this.invalidateFundingRead(state, chainId, asset);
5378
+ if (amount === void 0 && positionsStatus !== "empty")
5379
+ throw new OwneyError(
5380
+ "WITHDRAW_FAILED",
5381
+ "Yieldseeker vault funds could not be cleared for the owner transfer.",
5382
+ { asset, positionsStatus },
5383
+ this.id
5384
+ );
4670
5385
  throw new OwneyError(
4671
5386
  "WITHDRAW_INSUFFICIENT_BALANCE",
4672
5387
  "Yieldseeker funds are not available for the owner transfer.",
@@ -4675,20 +5390,23 @@ var YieldseekerAgent = class {
4675
5390
  );
4676
5391
  }
4677
5392
  const payout = amount === void 0 || availableForOwner < requested ? availableForOwner : requested;
4678
- const plannedMovement = this.snapshotMovement(state, chainId, context, -payout);
4679
- 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);
4680
5399
  let receipt;
4681
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" };
4682
- const data = amount === void 0 ? (0, import_viem10.encodeFunctionData)({
5401
+ const data = amount === void 0 ? (0, import_viem11.encodeFunctionData)({
4683
5402
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
4684
5403
  functionName: "withdrawAllAssetToUser",
4685
5404
  args: [account, metadata.address]
4686
- }) : (0, import_viem10.encodeFunctionData)({
5405
+ }) : (0, import_viem11.encodeFunctionData)({
4687
5406
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
4688
5407
  functionName: "withdrawAssetToUser",
4689
5408
  args: [account, metadata.address, payout]
4690
5409
  });
4691
- onOwnerTransferAttempted?.();
4692
5410
  let txHash;
4693
5411
  if (sponsor) {
4694
5412
  const submitted = await submitYieldseekerSponsoredWithdrawal({
@@ -4697,19 +5415,25 @@ var YieldseekerAgent = class {
4697
5415
  apiKey: this.owneyApiKey,
4698
5416
  routingApiBaseUrl: this.routingApiBaseUrl,
4699
5417
  yieldseekerSignature: await this.auth.getToken(state, chainId),
5418
+ refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
4700
5419
  userId: context.user.userId,
4701
5420
  agentId: context.agent.agentId,
4702
- agentWallet: (0, import_viem10.getAddress)(context.wallet.walletAddress),
5421
+ agentWallet: (0, import_viem11.getAddress)(context.wallet.walletAddress),
4703
5422
  asset: metadata.address,
4704
5423
  callData: data,
4705
- 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
+ }
4706
5429
  });
4707
5430
  txHash = submitted.txHash;
4708
5431
  receipt = submitted.receipt;
4709
5432
  } else {
5433
+ onOwnerTransferAttempted?.();
4710
5434
  txHash = await this.submitTransaction(state, chainId, {
4711
5435
  from: account,
4712
- to: (0, import_viem10.getAddress)(context.wallet.walletAddress),
5436
+ to: (0, import_viem11.getAddress)(context.wallet.walletAddress),
4713
5437
  data,
4714
5438
  value: "0",
4715
5439
  chainId
@@ -4721,6 +5445,46 @@ var YieldseekerAgent = class {
4721
5445
  settlement = plannedMovement;
4722
5446
  const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
4723
5447
  notifyFunding(onProgress, evidence);
5448
+ let completion;
5449
+ if (amount === void 0) {
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") {
5468
+ const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
5469
+ throw new OwneyError(
5470
+ "WITHDRAW_PARTIAL_FAILURE",
5471
+ "Yieldseeker MAX could not confirm that all vault positions were cleared.",
5472
+ {
5473
+ reason: "YIELDSEEKER_MAX_INCOMPLETE",
5474
+ asset,
5475
+ positionsStatus,
5476
+ remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
5477
+ vaultAddress: position2.yieldOption.address,
5478
+ sharesRaw: position2.assetsRaw,
5479
+ withdrawableAmountRaw: position2.withdrawableAssetsRaw
5480
+ })) : void 0,
5481
+ partialResults: { [this.id]: result }
5482
+ },
5483
+ this.id
5484
+ );
5485
+ }
5486
+ if (!proof.verified) completion = "unverified";
5487
+ }
4724
5488
  if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
4725
5489
  throw new OwneyError(
4726
5490
  "WITHDRAW_INSUFFICIENT_BALANCE",
@@ -4728,7 +5492,13 @@ var YieldseekerAgent = class {
4728
5492
  { asset, available: "0", txHash },
4729
5493
  this.id
4730
5494
  );
4731
- 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
+ };
4732
5502
  } finally {
4733
5503
  if (confirmedMovement && this.readGeneration === generation) {
4734
5504
  void this.refreshSnapshotAfterMovement(
@@ -4775,6 +5545,23 @@ var YieldseekerAgent = class {
4775
5545
  if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
4776
5546
  return context.wallet.walletAddress;
4777
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
+ }
4778
5565
  async getEarnings(state, chainId) {
4779
5566
  this.assertChain(chainId);
4780
5567
  return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
@@ -4805,7 +5592,7 @@ var YieldseekerAgent = class {
4805
5592
  );
4806
5593
  const vaultAddresses = new Set(
4807
5594
  catalog.flat().filter(
4808
- (yieldOption) => yieldOption.chainId === chainId && (0, import_viem10.isAddress)(yieldOption.address)
5595
+ (yieldOption) => yieldOption.chainId === chainId && (0, import_viem11.isAddress)(yieldOption.address)
4809
5596
  ).map((yieldOption) => yieldOption.address.toLowerCase())
4810
5597
  );
4811
5598
  return mapYieldseekerHistory(contexts, {
@@ -4932,7 +5719,7 @@ var YieldseekerAgent = class {
4932
5719
  `/users/${user.userId}/agents/${agent.agentId}/wallet`
4933
5720
  ).then((walletResponse) => {
4934
5721
  this.assertSession(generation, chainId, asset);
4935
- if (!walletResponse?.agentWallet || !(0, import_viem10.isAddress)(walletResponse.agentWallet.walletAddress)) {
5722
+ if (!walletResponse?.agentWallet || !(0, import_viem11.isAddress)(walletResponse.agentWallet.walletAddress)) {
4936
5723
  throw this.invalidResponse("agent wallet");
4937
5724
  }
4938
5725
  const context = {
@@ -4962,7 +5749,7 @@ var YieldseekerAgent = class {
4962
5749
  this.users.set(key2, persisted);
4963
5750
  return persisted;
4964
5751
  }
4965
- const walletAddress = (0, import_viem10.getAddress)(state.walletAddress);
5752
+ const walletAddress = (0, import_viem11.getAddress)(state.walletAddress);
4966
5753
  const generation = this.readGeneration;
4967
5754
  let user = null;
4968
5755
  try {
@@ -5345,7 +6132,7 @@ var YieldseekerAgent = class {
5345
6132
  this.agentPath(context, "deploy"),
5346
6133
  { method: "POST", body: {} }
5347
6134
  );
5348
- 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) {
5349
6136
  throw this.invalidResponse("agent deployment", {
5350
6137
  reason: "Deploy did not return the expected Agent Wallet."
5351
6138
  });
@@ -5453,15 +6240,15 @@ var YieldseekerAgent = class {
5453
6240
  return hash2;
5454
6241
  }
5455
6242
  this.assertTransaction(transaction, state, chainId);
5456
- const account = (0, import_viem10.getAddress)(state.walletAddress);
5457
- const walletClient = (0, import_viem10.createWalletClient)({
6243
+ const account = (0, import_viem11.getAddress)(state.walletAddress);
6244
+ const walletClient = (0, import_viem11.createWalletClient)({
5458
6245
  account,
5459
6246
  chain: import_chains4.base,
5460
- transport: (0, import_viem10.custom)(state.provider)
6247
+ transport: (0, import_viem11.custom)(state.provider)
5461
6248
  });
5462
- const walletChainClient = (0, import_viem10.createPublicClient)({
6249
+ const walletChainClient = (0, import_viem11.createPublicClient)({
5463
6250
  chain: import_chains4.base,
5464
- transport: (0, import_viem10.custom)(state.provider)
6251
+ transport: (0, import_viem11.custom)(state.provider)
5465
6252
  });
5466
6253
  await ensureWalletOnChain(
5467
6254
  walletChainClient,
@@ -5471,30 +6258,84 @@ var YieldseekerAgent = class {
5471
6258
  const hash = await walletClient.sendTransaction({
5472
6259
  account,
5473
6260
  chain: import_chains4.base,
5474
- to: (0, import_viem10.getAddress)(transaction.to),
6261
+ to: (0, import_viem11.getAddress)(transaction.to),
5475
6262
  data: transaction.data,
5476
6263
  value: BigInt(transaction.value)
5477
6264
  });
5478
6265
  onSubmitted?.(hash);
6266
+ let replacement;
5479
6267
  const receipt = await withPaidRpcDiagnostics(
5480
6268
  () => this.getReceiptClient().waitForTransactionReceipt({
5481
6269
  hash,
5482
- confirmations: 1
6270
+ confirmations: 1,
6271
+ onReplaced: (value) => {
6272
+ replacement = value;
6273
+ }
5483
6274
  }),
5484
6275
  8453,
5485
6276
  "eth_getTransactionReceipt",
5486
6277
  this.id
5487
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
+ }
5488
6329
  if (receipt.status !== "success") {
5489
6330
  throw new OwneyError(
5490
6331
  "AGENT_TRANSACTION_REVERTED",
5491
- `Yieldseeker transaction reverted (${hash}).`,
5492
- { transactionHash: hash },
6332
+ `Yieldseeker transaction reverted (${actualHash}).`,
6333
+ { transactionHash: actualHash },
5493
6334
  this.id
5494
6335
  );
5495
6336
  }
5496
6337
  onReceipt?.(receipt);
5497
- return hash;
6338
+ return actualHash;
5498
6339
  }
5499
6340
  async waitForReceipt(state, chainId, transactionHash) {
5500
6341
  if (this.unwindReceiptWaiter) {
@@ -5521,12 +6362,12 @@ var YieldseekerAgent = class {
5521
6362
  return receipt;
5522
6363
  }
5523
6364
  assertTransaction(transaction, state, chainId) {
5524
- 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)) {
5525
6366
  throw this.invalidResponse("transaction");
5526
6367
  }
5527
6368
  }
5528
6369
  assertAgent(agent) {
5529
- 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) {
5530
6371
  throw this.invalidResponse("agent");
5531
6372
  }
5532
6373
  }
@@ -5680,7 +6521,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
5680
6521
  }
5681
6522
 
5682
6523
  // src/lib/helpers/withdraw-helper.ts
5683
- var import_viem11 = require("viem");
6524
+ var import_viem12 = require("viem");
5684
6525
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
5685
6526
  const target = asset.toUpperCase();
5686
6527
  return agents.map((agent) => {
@@ -5688,7 +6529,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
5688
6529
  const tokenBalance = agentBalance?.tokens.find(
5689
6530
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
5690
6531
  );
5691
- let balance = tokenBalance ? (0, import_viem11.parseUnits)(tokenBalance.amount, decimals) : 0n;
6532
+ let balance = tokenBalance ? (0, import_viem12.parseUnits)(tokenBalance.amount, decimals) : 0n;
5692
6533
  if (agent.balanceComposition === "tokens-plus-positions") {
5693
6534
  const chainNameById = {
5694
6535
  1: "ETHEREUM",
@@ -5700,14 +6541,14 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
5700
6541
  const positionChain = position2.chain.trim().toUpperCase();
5701
6542
  const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
5702
6543
  if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
5703
- 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);
5704
6545
  }
5705
6546
  }
5706
6547
  const assetTotal = agentBalance?.assetBalances?.find(
5707
6548
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
5708
6549
  );
5709
6550
  if (assetTotal) {
5710
- const total = (0, import_viem11.parseUnits)(assetTotal.amount, decimals);
6551
+ const total = (0, import_viem12.parseUnits)(assetTotal.amount, decimals);
5711
6552
  if (total < balance) balance = total > 0n ? total : 0n;
5712
6553
  }
5713
6554
  return { agent, balance };
@@ -5866,14 +6707,14 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
5866
6707
  }
5867
6708
 
5868
6709
  // src/client.ts
5869
- var import_viem15 = require("viem");
6710
+ var import_viem16 = require("viem");
5870
6711
  var import_chains5 = require("viem/chains");
5871
6712
 
5872
6713
  // src/lib/sponsored-token-batch.ts
5873
- var import_viem13 = require("viem");
6714
+ var import_viem14 = require("viem");
5874
6715
 
5875
6716
  // src/lib/permit2-batch.ts
5876
- var import_viem12 = require("viem");
6717
+ var import_viem13 = require("viem");
5877
6718
  var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
5878
6719
  var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
5879
6720
  var ERC2612_PERMIT_TYPES = {
@@ -5885,7 +6726,7 @@ var ERC2612_PERMIT_TYPES = {
5885
6726
  { name: "deadline", type: "uint256" }
5886
6727
  ]
5887
6728
  };
5888
- var ERC2612_READ_ABI = (0, import_viem12.parseAbi)([
6729
+ var ERC2612_READ_ABI = (0, import_viem13.parseAbi)([
5889
6730
  "function name() view returns (string)",
5890
6731
  "function version() view returns (string)",
5891
6732
  "function nonces(address owner) view returns (uint256)"
@@ -5923,7 +6764,7 @@ var PERMIT_BATCH_TYPES = {
5923
6764
  { name: "amount", type: "uint256" }
5924
6765
  ]
5925
6766
  };
5926
- var PERMIT2_BATCH_ABI = (0, import_viem12.parseAbi)([
6767
+ var PERMIT2_BATCH_ABI = (0, import_viem13.parseAbi)([
5927
6768
  "struct TokenPermissions { address token; uint256 amount; }",
5928
6769
  "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
5929
6770
  "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
@@ -5966,6 +6807,18 @@ var planOf = (transfers) => JSON.stringify(
5966
6807
  amount: BigInt(t.amount).toString()
5967
6808
  }))
5968
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
+ }
5969
6822
  function read(key2) {
5970
6823
  return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
5971
6824
  }
@@ -5982,11 +6835,11 @@ function clear(key2) {
5982
6835
  else window.localStorage.removeItem(key2);
5983
6836
  }
5984
6837
  function sponsorTokenBatch(i) {
5985
- 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);
5986
6839
  const plan = planOf(i.transfers);
5987
6840
  const active = inflight.get(key2);
5988
6841
  if (active) {
5989
- if (active.plan !== plan)
6842
+ if (active.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
5990
6843
  return Promise.reject(
5991
6844
  new Error(
5992
6845
  "A token deposit is already in progress. Wait for its result before depositing again."
@@ -6013,7 +6866,7 @@ async function execute(i, key2, plan) {
6013
6866
  baseUrl: i.baseUrl,
6014
6867
  body
6015
6868
  });
6016
- 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)
6017
6870
  throw new Error(
6018
6871
  "Sponsorship API did not return a valid prepared permit transaction."
6019
6872
  );
@@ -6029,7 +6882,7 @@ async function execute(i, key2, plan) {
6029
6882
  baseUrl: i.baseUrl,
6030
6883
  body
6031
6884
  });
6032
- if (!prepared.serializedTransaction || (0, import_viem13.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
6885
+ if (!prepared.serializedTransaction || (0, import_viem14.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
6033
6886
  throw new Error(
6034
6887
  "Sponsorship API did not return a valid prepared transaction."
6035
6888
  );
@@ -6040,7 +6893,7 @@ async function execute(i, key2, plan) {
6040
6893
  save(key2, body);
6041
6894
  }
6042
6895
  try {
6043
- i.onSubmitted?.((0, import_viem13.keccak256)(body.serializedTransaction));
6896
+ i.onSubmitted?.((0, import_viem14.keccak256)(body.serializedTransaction));
6044
6897
  } catch {
6045
6898
  }
6046
6899
  const result = await postSponsorBatchTransfer({
@@ -6048,7 +6901,7 @@ async function execute(i, key2, plan) {
6048
6901
  baseUrl: i.baseUrl,
6049
6902
  body
6050
6903
  });
6051
- if (result.txHash !== (0, import_viem13.keccak256)(body.serializedTransaction))
6904
+ if (result.txHash !== (0, import_viem14.keccak256)(body.serializedTransaction))
6052
6905
  throw new Error(
6053
6906
  "Sponsorship receipt does not match the pending transaction."
6054
6907
  );
@@ -6061,9 +6914,11 @@ async function execute(i, key2, plan) {
6061
6914
  }
6062
6915
  };
6063
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.");
6064
6919
  if (saved) {
6065
6920
  const previous = JSON.parse(saved);
6066
- 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)
6067
6922
  throw new Error(
6068
6923
  "Retry the previous token deposit and agent split first to reconcile its status."
6069
6924
  );
@@ -6080,7 +6935,7 @@ async function execute(i, key2, plan) {
6080
6935
  "DEPOSIT_INSUFFICIENT_BALANCE",
6081
6936
  "Insufficient token balance for this deposit."
6082
6937
  );
6083
- 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)))
6084
6939
  throw new OwneyError(
6085
6940
  "PERMIT2_APPROVAL_REQUIRED",
6086
6941
  "token deposits need a one-time Permit2 approval."
@@ -6172,7 +7027,8 @@ function makeSponsoredTokenCallback(deps) {
6172
7027
  pub,
6173
7028
  wallet,
6174
7029
  onApproved: deps.onApproved,
6175
- onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
7030
+ onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers),
7031
+ resumeTxHash: deps.resumeTxHash
6176
7032
  });
6177
7033
  };
6178
7034
  const callback = makeVerificationAwareDepositCallback(
@@ -6258,7 +7114,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
6258
7114
  }
6259
7115
 
6260
7116
  // src/lib/sponsored-calls-deposit.ts
6261
- var import_viem14 = require("viem");
7117
+ var import_viem15 = require("viem");
6262
7118
  var DEFAULT_POLL_INTERVAL_MS = 1500;
6263
7119
  var DEFAULT_MAX_POLLS = 30;
6264
7120
  async function paymasterSupported(provider, owner, chainId) {
@@ -6266,7 +7122,7 @@ async function paymasterSupported(provider, owner, chainId) {
6266
7122
  method: "wallet_getCapabilities",
6267
7123
  params: [owner]
6268
7124
  });
6269
- const forChain = caps?.[(0, import_viem14.toHex)(chainId)] ?? caps?.[String(chainId)];
7125
+ const forChain = caps?.[(0, import_viem15.toHex)(chainId)] ?? caps?.[String(chainId)];
6270
7126
  return Boolean(forChain?.paymasterService?.supported);
6271
7127
  }
6272
7128
  function makeSponsoredCallsCallback(deps) {
@@ -6303,8 +7159,8 @@ function makeSponsoredCallsCallback(deps) {
6303
7159
  const calls = transfers.map((transfer) => ({
6304
7160
  to: token,
6305
7161
  value: "0x0",
6306
- data: (0, import_viem14.encodeFunctionData)({
6307
- abi: import_viem14.erc20Abi,
7162
+ data: (0, import_viem15.encodeFunctionData)({
7163
+ abi: import_viem15.erc20Abi,
6308
7164
  functionName: "transfer",
6309
7165
  args: [transfer.to, BigInt(transfer.amount)]
6310
7166
  })
@@ -6336,50 +7192,73 @@ function makeSponsoredCallsCallback(deps) {
6336
7192
  url.searchParams.append("owneyIntent", intent);
6337
7193
  paymasterUrl = url.toString();
6338
7194
  }
6339
- const sendResult = await deps.provider.request({
6340
- method: "wallet_sendCalls",
6341
- params: [
6342
- {
6343
- version: "2.0.0",
6344
- from: deps.ownerAddress,
6345
- chainId: (0, import_viem14.toHex)(chainId),
6346
- atomicRequired: transfers.length > 1,
6347
- calls,
6348
- capabilities: {
6349
- 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
+ }
6350
7218
  }
6351
- }
6352
- ]
6353
- });
6354
- 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
+ }
6355
7230
  const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
6356
7231
  if (!callsId) {
6357
7232
  throw new OwneyError(
6358
7233
  "SPONSORED_CALLS_NO_ID",
6359
7234
  "Wallet accepted wallet_sendCalls but returned no calls id.",
6360
- { chainId }
7235
+ { chainId, callsId: requestedId }
6361
7236
  );
6362
7237
  }
7238
+ progress(callsId, "pending");
6363
7239
  for (let attempt = 0; attempt < maxPolls; attempt += 1) {
6364
7240
  const status = await deps.provider.request({
6365
7241
  method: "wallet_getCallsStatus",
6366
7242
  params: [callsId]
6367
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 });
6368
7246
  const txHash = status?.receipts?.[0]?.transactionHash;
6369
- 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)) {
6370
7248
  try {
6371
7249
  deps.onSubmitted?.(txHash, transfers);
6372
7250
  } catch {
6373
7251
  }
6374
7252
  }
6375
- 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");
6376
7255
  throw new OwneyError(
6377
7256
  "SPONSOR_REQUEST_FAILED",
6378
7257
  "The sponsored deposit did not complete successfully.",
6379
7258
  { chainId, callsId, safeToFallback: false }
6380
7259
  );
6381
7260
  }
6382
- 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")) {
6383
7262
  if (status?.receipts?.some(
6384
7263
  (receipt) => receipt.transactionHash !== txHash
6385
7264
  ))
@@ -6595,8 +7474,8 @@ var OwneySDK = class {
6595
7474
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
6596
7475
  }
6597
7476
  /** Builds the default USDC batch callback for the connected wallet. */
6598
- getDefaultSponsoredCallback(onApproved, onSubmitted) {
6599
- if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
7477
+ getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
7478
+ if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedSponsoredCallback)
6600
7479
  return this.cachedSponsoredCallback;
6601
7480
  const provider = this.requireConnectedProvider();
6602
7481
  const owner = this.state.walletAddress;
@@ -6606,28 +7485,29 @@ var OwneySDK = class {
6606
7485
  ownerAddress: owner,
6607
7486
  onApproved,
6608
7487
  onSubmitted,
7488
+ resumeTxHash,
6609
7489
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
6610
7490
  // Casts work around viem's chain-narrowed Client vs the generic
6611
7491
  // PublicClient/WalletClient param types — structurally identical at
6612
7492
  // runtime, but the two share a name TS treats as unrelated.
6613
7493
  getPublicClient: (cid) => this.getPaidRpcClient(cid),
6614
- getWalletChainClient: (cid) => (0, import_viem15.createPublicClient)({
7494
+ getWalletChainClient: (cid) => (0, import_viem16.createPublicClient)({
6615
7495
  chain: VIEM_CHAIN2[cid],
6616
- transport: (0, import_viem15.custom)(provider)
7496
+ transport: (0, import_viem16.custom)(provider)
6617
7497
  }),
6618
- getWalletClient: (cid) => (0, import_viem15.createWalletClient)({
7498
+ getWalletClient: (cid) => (0, import_viem16.createWalletClient)({
6619
7499
  account: owner,
6620
7500
  chain: VIEM_CHAIN2[cid],
6621
- transport: (0, import_viem15.custom)(provider)
7501
+ transport: (0, import_viem16.custom)(provider)
6622
7502
  })
6623
7503
  });
6624
- if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
7504
+ if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedSponsoredCallback = callback;
6625
7505
  return callback;
6626
7506
  }
6627
7507
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
6628
- getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
7508
+ getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) {
6629
7509
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
6630
- if (!onApproved && !onSubmitted && cached) return cached;
7510
+ if (!onApproved && !onSubmitted && !onCallsProgress && cached) return cached;
6631
7511
  const provider = this.requireConnectedProvider();
6632
7512
  const callback = makeSponsoredCallsCallback({
6633
7513
  apiKey: this.apiKey,
@@ -6637,9 +7517,10 @@ var OwneySDK = class {
6637
7517
  paymasterServiceUrl: this.paymasterServiceUrl,
6638
7518
  onApproved,
6639
7519
  onSubmitted,
7520
+ onCallsProgress,
6640
7521
  tokenAddressByChain: sponsoredTokensFor(asset)
6641
7522
  });
6642
- if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
7523
+ if (!onApproved && !onSubmitted && !onCallsProgress) this.cachedSponsoredCallsCallbacks.set(asset, callback);
6643
7524
  return callback;
6644
7525
  }
6645
7526
  /**
@@ -6648,8 +7529,8 @@ var OwneySDK = class {
6648
7529
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
6649
7530
  * single-use batch authorization instead of an EIP-3009 authorization.
6650
7531
  */
6651
- getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
6652
- if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
7532
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
7533
+ if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedWethSponsoredCallback)
6653
7534
  return this.cachedWethSponsoredCallback;
6654
7535
  const provider = this.requireConnectedProvider();
6655
7536
  const owner = this.state.walletAddress;
@@ -6659,22 +7540,23 @@ var OwneySDK = class {
6659
7540
  ownerAddress: owner,
6660
7541
  onApproved,
6661
7542
  onSubmitted,
7543
+ resumeTxHash,
6662
7544
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
6663
7545
  // Casts work around viem's chain-narrowed Client vs the generic
6664
7546
  // PublicClient/WalletClient param types — structurally identical at
6665
7547
  // runtime, but the two share a name TS treats as unrelated.
6666
7548
  getPublicClient: (cid) => this.getPaidRpcClient(cid),
6667
- getWalletChainClient: (cid) => (0, import_viem15.createPublicClient)({
7549
+ getWalletChainClient: (cid) => (0, import_viem16.createPublicClient)({
6668
7550
  chain: VIEM_CHAIN2[cid],
6669
- transport: (0, import_viem15.custom)(provider)
7551
+ transport: (0, import_viem16.custom)(provider)
6670
7552
  }),
6671
- getWalletClient: (cid) => (0, import_viem15.createWalletClient)({
7553
+ getWalletClient: (cid) => (0, import_viem16.createWalletClient)({
6672
7554
  account: owner,
6673
7555
  chain: VIEM_CHAIN2[cid],
6674
- transport: (0, import_viem15.custom)(provider)
7556
+ transport: (0, import_viem16.custom)(provider)
6675
7557
  })
6676
7558
  });
6677
- if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
7559
+ if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedWethSponsoredCallback = callback;
6678
7560
  return callback;
6679
7561
  }
6680
7562
  getAgent(agentId) {
@@ -6983,6 +7865,7 @@ var OwneySDK = class {
6983
7865
  async deposit(options) {
6984
7866
  const { amount, asset, depositCallback, agentId, onApproved } = options;
6985
7867
  const prepared = /* @__PURE__ */ new Map();
7868
+ const paidFallbackAgents = /* @__PURE__ */ new Set();
6986
7869
  const onProgress = options.onProgress ? (evidence) => {
6987
7870
  if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
6988
7871
  notifyFunding(options.onProgress, evidence);
@@ -6993,152 +7876,184 @@ var OwneySDK = class {
6993
7876
  if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
6994
7877
  }
6995
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;
6996
7885
  if (BigInt(amount) === 0n) {
6997
7886
  return { agentResults: {} };
6998
7887
  }
6999
7888
  const state = this.requireState();
7000
7889
  const chainId = this.requireChainId();
7001
- const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted) : this.getDefaultSponsoredCallback(onApproved, onSubmitted));
7002
- if (agentId) {
7003
- if (this.isAgentDisabled(agentId)) {
7004
- throw new OwneyError(
7005
- "AGENT_DISABLED",
7006
- `Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
7007
- { agentId }
7008
- );
7009
- }
7010
- const agent = this.getAgent(agentId);
7011
- this.validateAssetSupport(agent, chainId, asset);
7012
- await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
7013
- return this.depositWithFallback(
7014
- agent,
7015
- state,
7016
- chainId,
7017
- amount,
7018
- asset,
7019
- effectiveCallback,
7020
- depositCallback,
7021
- onProgress
7022
- );
7023
- }
7024
- const totalAmount = BigInt(amount);
7025
- const eligibleAgents = this.getEligibleAgents(chainId, asset, {
7026
- excludeDisabled: true
7027
- });
7028
- const exemptFlags = await Promise.all(
7029
- eligibleAgents.map(
7030
- (a) => this.hasExistingBalance(a, state, chainId, asset, true)
7031
- )
7032
- );
7033
- const exempt = new Set(
7034
- eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
7035
- );
7036
- const agentAmounts = this.splitDepositAmount(
7037
- totalAmount,
7038
- eligibleAgents,
7039
- chainId,
7040
- asset,
7041
- exempt
7042
- );
7043
- if (agentAmounts.length === 0) {
7044
- const agentsRequiringActivation = eligibleAgents.filter(
7045
- (agent) => !exempt.has(agent.id)
7046
- );
7047
- const perAgentMinimum = agentsRequiringActivation.reduce(
7048
- (highest, agent) => {
7049
- const minimum = this.getMinDepositAmount(agent, chainId, asset);
7050
- return minimum > highest ? minimum : highest;
7051
- },
7052
- 0n
7053
- );
7054
- const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
7055
- throw new OwneyError(
7056
- "DEPOSIT_AMOUNT_BELOW_MINIMUM",
7057
- `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
7058
- {
7059
- amount,
7060
- asset,
7061
- chainId,
7062
- minDepositAmount: minimumRequired.toString(),
7063
- perAgentDepositAmount: perAgentMinimum.toString(),
7064
- agentMinimums: agentsRequiringActivation.map((agent) => ({
7065
- agentId: agent.id,
7066
- minDepositAmount: this.getMinDepositAmount(
7067
- agent,
7068
- chainId,
7069
- asset
7070
- ).toString()
7071
- }))
7072
- }
7073
- );
7074
- }
7075
- const batchTransfer = getDepositBatchTransfer(effectiveCallback);
7076
- if (!depositCallback && batchTransfer) {
7077
- return runAgentDepositBatch(
7078
- chainId,
7079
- agentAmounts.map(({ agent, amount: amount2 }) => ({
7080
- id: agent.id,
7081
- amount: amount2,
7082
- run: (callback) => withFailureReporting(
7083
- this.apiKey,
7084
- agent.id,
7085
- () => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
7086
- this.routingApiBaseUrl
7087
- )
7088
- })),
7089
- async (cid, transfers) => {
7090
- try {
7091
- return await batchTransfer(cid, transfers);
7092
- } catch (error) {
7093
- if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
7094
- throw error;
7095
- const requiredAmount = transfers.reduce(
7096
- (sum, transfer) => sum + BigInt(transfer.amount),
7097
- 0n
7098
- );
7099
- await this.approvePermit2(
7100
- asset,
7101
- requiredAmount,
7102
- cid
7103
- );
7104
- return batchTransfer(cid, transfers);
7105
- }
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
+ );
7106
7901
  }
7107
- );
7108
- }
7109
- const agentResults = {};
7110
- for (const [
7111
- index,
7112
- { agent, amount: agentAmount }
7113
- ] of agentAmounts.entries()) {
7114
- try {
7115
- 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(
7116
7906
  agent,
7117
7907
  state,
7118
7908
  chainId,
7119
- agentAmount,
7909
+ amount,
7120
7910
  asset,
7121
7911
  effectiveCallback,
7122
7912
  depositCallback,
7123
- onProgress
7913
+ onProgress,
7914
+ () => paidFallbackAgents.add(agent.id),
7915
+ !!options.resumeTxHash
7124
7916
  );
7125
- } catch (error) {
7126
- 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);
7127
7947
  throw new OwneyError(
7128
- "DEPOSIT_PARTIAL_FAILURE",
7129
- "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}.`,
7130
7950
  {
7131
- agentResults,
7132
- failedAgentId: agent.id,
7133
- failedAmount: agentAmount,
7134
- unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
7135
- cause: error
7136
- },
7137
- 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
+ }
7138
7999
  );
7139
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;
7140
8056
  }
7141
- return { agentResults };
7142
8057
  }
7143
8058
  /**
7144
8059
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -7167,7 +8082,7 @@ var OwneySDK = class {
7167
8082
  * propagates unchanged. Scoped per agent invocation so one agent's failure
7168
8083
  * doesn't force a retry of sibling agents in a multi-agent split.
7169
8084
  */
7170
- async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
8085
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false) {
7171
8086
  const attempt = (callback) => withFailureReporting(
7172
8087
  this.apiKey,
7173
8088
  agent.id,
@@ -7179,6 +8094,7 @@ var OwneySDK = class {
7179
8094
  try {
7180
8095
  return await attempt(effectiveCallback);
7181
8096
  } catch (error) {
8097
+ if (resumeOnly) throw error;
7182
8098
  if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
7183
8099
  approvalAttempted = true;
7184
8100
  console.warn(
@@ -7196,6 +8112,7 @@ var OwneySDK = class {
7196
8112
  `[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
7197
8113
  error
7198
8114
  );
8115
+ onPaidFallback?.();
7199
8116
  return attempt(void 0);
7200
8117
  }
7201
8118
  }
@@ -7278,7 +8195,12 @@ var OwneySDK = class {
7278
8195
  */
7279
8196
  async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
7280
8197
  try {
7281
- 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);
7282
8204
  const target = asset.toLowerCase();
7283
8205
  const token = balance.tokens.find(
7284
8206
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
@@ -7391,7 +8313,7 @@ var OwneySDK = class {
7391
8313
  if (direction === "withdraw" && evidence.agentId === "yieldseeker" && verified.transfer === "unknown" && verified.confirmedAmount && BigInt(verified.confirmedAmount) > 0n) {
7392
8314
  const transaction = await rpc2.getTransaction({ hash: evidence.txHash });
7393
8315
  assertCurrent();
7394
- if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address))
8316
+ if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address, receipt))
7395
8317
  verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
7396
8318
  }
7397
8319
  const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
@@ -7404,7 +8326,8 @@ var OwneySDK = class {
7404
8326
  return verified;
7405
8327
  }
7406
8328
  /** Resolve a saved sponsored-withdrawal ID without opening a wallet prompt. */
7407
- async resolveSponsoredWithdrawalEvidence(evidence) {
8329
+ async resolveSponsoredWithdrawalEvidence(evidence, options = {}) {
8330
+ if (evidence.callsId && !evidence.userOpHash) return this.resolveWalletCallsFundingEvidence(evidence, "withdraw");
7408
8331
  const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
7409
8332
  if (evidence.agentId !== "yieldseeker" || chainId !== 8453 || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
7410
8333
  const agent = this.getAgent("yieldseeker");
@@ -7420,18 +8343,26 @@ var OwneySDK = class {
7420
8343
  owner: state.walletAddress,
7421
8344
  agentWallet: wallet,
7422
8345
  asset: SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token)?.address,
7423
- 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 } : {}
7424
8360
  })
7425
8361
  });
7426
8362
  if (!response.ok) throw new OwneyError("AGENT_API_ERROR", "Could not resolve sponsored withdrawal status.");
7427
8363
  const payload = await response.json();
7428
8364
  txHash = payload.data?.txHash ?? void 0;
7429
8365
  executionFailed = payload.data?.executionFailed === true;
7430
- } else if (evidence.callsId) {
7431
- const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
7432
- if (status?.id && status.id !== evidence.callsId || status?.chainId && Number(status.chainId) !== chainId) return evidence;
7433
- executionFailed = status?.status === 400 || status?.status === 500;
7434
- txHash = status?.receipts?.[0]?.transactionHash;
7435
8366
  }
7436
8367
  assertCurrent();
7437
8368
  if (executionFailed) return {
@@ -7442,6 +8373,60 @@ var OwneySDK = class {
7442
8373
  };
7443
8374
  return txHash && /^0x[0-9a-fA-F]{64}$/.test(txHash) ? { ...evidence, txHash } : evidence;
7444
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
+ }
7445
8430
  /** A display acknowledgement controls polling only; it never confirms a financial outcome. */
7446
8431
  endFundingBalanceRefresh(evidence, read2) {
7447
8432
  const { state, chainId } = this.captureReadScope(evidence.chainId);
@@ -7542,6 +8527,30 @@ var OwneySDK = class {
7542
8527
  }
7543
8528
  };
7544
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
+ };
7545
8554
  const submissionEvidence = /* @__PURE__ */ new Map();
7546
8555
  const submit = async (agent, requested2) => {
7547
8556
  attempted.add(agent.id);
@@ -7562,9 +8571,19 @@ var OwneySDK = class {
7562
8571
  };
7563
8572
  progress(evidence);
7564
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
+ );
7565
8584
  return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
7566
8585
  } catch (error) {
7567
- 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 : "")))
7568
8587
  progress({ ...evidence, transfer: "failed", delivery: "failed" });
7569
8588
  throw error;
7570
8589
  }
@@ -7595,6 +8614,7 @@ var OwneySDK = class {
7595
8614
  if (!amount) {
7596
8615
  const results2 = {};
7597
8616
  const agentErrors2 = {};
8617
+ const incompleteWithdrawals = {};
7598
8618
  for (const agent of withdrawalAgents) {
7599
8619
  try {
7600
8620
  results2[agent.id] = await submit(agent);
@@ -7603,8 +8623,17 @@ var OwneySDK = class {
7603
8623
  if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
7604
8624
  if (this.isUserRejectedWithdrawal(err)) throw err;
7605
8625
  if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
8626
+ if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
8627
+ incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
8628
+ const evidence = submissionEvidence.get(agent.id);
8629
+ if (evidence?.agentId === agent.id && evidence.chainId === chainId && evidence.token === token && evidence.walletAddress.toLowerCase() === state.walletAddress.toLowerCase() && evidence.intendedAmount === "MAX" && evidence.transfer === "confirmed" && evidence.delivery === "confirmed" && evidence.txHash && /^0x[\da-f]{64}$/i.test(evidence.txHash) && evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) && BigInt(evidence.confirmedAmount) > 0n) {
8630
+ results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
8631
+ notifyAgentResult(agent.id, results2[agent.id]);
8632
+ }
8633
+ }
7606
8634
  console.error(`withdraw failed for agent "${agent.id}":`, err);
7607
8635
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
8636
+ recordAgentFailure(agent.id, err);
7608
8637
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
7609
8638
  void reportAgentFailure(
7610
8639
  this.apiKey,
@@ -7615,17 +8644,23 @@ var OwneySDK = class {
7615
8644
  }
7616
8645
  }
7617
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
+ );
7618
8653
  throw new OwneyError(
7619
8654
  "WITHDRAW_ALL_FAILED",
7620
8655
  `Failed to withdraw "${asset}" from all eligible agents.`,
7621
- { asset, agentErrors: agentErrors2 }
8656
+ { asset, agentErrors: agentErrors2, agentFailures }
7622
8657
  );
7623
8658
  }
7624
8659
  if (Object.keys(agentErrors2).length > 0) {
7625
8660
  throw new OwneyError(
7626
8661
  "WITHDRAW_PARTIAL_FAILURE",
7627
8662
  "Some agents could not complete the withdrawal.",
7628
- { asset, partialResults: results2, agentErrors: agentErrors2 }
8663
+ { asset, partialResults: results2, agentErrors: agentErrors2, agentFailures, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
7629
8664
  );
7630
8665
  }
7631
8666
  const amountsKnown = Object.values(results2).every(
@@ -7706,6 +8741,7 @@ var OwneySDK = class {
7706
8741
  if (this.isUserRejectedWithdrawal(err)) throw err;
7707
8742
  console.error(`withdraw failed for agent "${p.agent.id}":`, err);
7708
8743
  agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
8744
+ recordAgentFailure(p.agent.id, err);
7709
8745
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
7710
8746
  void reportAgentFailure(
7711
8747
  this.apiKey,
@@ -7726,7 +8762,8 @@ var OwneySDK = class {
7726
8762
  {
7727
8763
  asset,
7728
8764
  amount,
7729
- agentErrors
8765
+ agentErrors,
8766
+ agentFailures
7730
8767
  }
7731
8768
  );
7732
8769
  }
@@ -7741,6 +8778,7 @@ var OwneySDK = class {
7741
8778
  withdrawn: withdrawn.toString(),
7742
8779
  partialResults: results,
7743
8780
  agentErrors,
8781
+ agentFailures,
7744
8782
  ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
7745
8783
  }
7746
8784
  );
@@ -8200,16 +9238,16 @@ var OwneySDK = class {
8200
9238
  );
8201
9239
  }
8202
9240
  const provider = this.requireConnectedProvider();
8203
- const walletChainClient = (0, import_viem15.createPublicClient)({
9241
+ const walletChainClient = (0, import_viem16.createPublicClient)({
8204
9242
  chain: VIEM_CHAIN2[chainId],
8205
- transport: (0, import_viem15.custom)(provider)
9243
+ transport: (0, import_viem16.custom)(provider)
8206
9244
  });
8207
9245
  const publicClient = this.getPaidRpcClient(chainId);
8208
9246
  const approvalAmount = permit2ApprovalAmount(requiredAmount);
8209
- const wallet = (0, import_viem15.createWalletClient)({
9247
+ const wallet = (0, import_viem16.createWalletClient)({
8210
9248
  account: state.walletAddress,
8211
9249
  chain: VIEM_CHAIN2[chainId],
8212
- transport: (0, import_viem15.custom)(provider)
9250
+ transport: (0, import_viem16.custom)(provider)
8213
9251
  });
8214
9252
  await ensureWalletOnChain(walletChainClient, wallet, chainId);
8215
9253
  const hash = await wallet.writeContract({
@@ -8371,7 +9409,7 @@ var OwneySDK = class {
8371
9409
  };
8372
9410
 
8373
9411
  // src/agents/zyfai/zyfai.siwx.ts
8374
- var import_viem16 = require("viem");
9412
+ var import_viem17 = require("viem");
8375
9413
  var import_siwe2 = require("siwe");
8376
9414
  var import_sdk2 = require("@zyfai/sdk");
8377
9415
 
@@ -8498,7 +9536,7 @@ function buildSIWXConfig(deps) {
8498
9536
  issuedAt,
8499
9537
  toString() {
8500
9538
  return new import_siwe2.SiweMessage({
8501
- address: (0, import_viem16.getAddress)(accountAddress),
9539
+ address: (0, import_viem17.getAddress)(accountAddress),
8502
9540
  chainId: numericChainId(chainId),
8503
9541
  domain,
8504
9542
  uri,