@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.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/lib/funding-evidence.ts
2
2
  import { decodeEventLog, decodeFunctionData, erc20Abi, parseAbi } from "viem";
3
+ import { entryPoint06Abi, entryPoint07Abi, getUserOperationHash, toUserOperation } from "viem/account-abstraction";
3
4
  function notifyFunding(callback, evidence) {
4
5
  try {
5
6
  void Promise.resolve(callback?.({ ...evidence })).catch(() => {
@@ -9,11 +10,53 @@ function notifyFunding(callback, evidence) {
9
10
  }
10
11
  var fullWithdrawalAbi = parseAbi(["function withdrawAllAssetToUser(address recipient, address asset)"]);
11
12
  var withdrawalEventAbi = parseAbi(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
12
- function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress) {
13
- 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;
13
+ var accountExecutionAbi = parseAbi([
14
+ "function execute(address target,uint256 value,bytes data)",
15
+ "function executeBatch((address target,uint256 value,bytes data)[] calls)"
16
+ ]);
17
+ var entryPoint06 = "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789";
18
+ var entryPoint07 = "0x0000000071727de22e5e9d8baf0edac6f37da032";
19
+ function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receipt) {
20
+ if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || !evidence.agentWallet) return false;
21
+ const matchesFullCall = (data) => {
22
+ try {
23
+ const decoded = decodeFunctionData({ abi: fullWithdrawalAbi, data });
24
+ return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
25
+ } catch {
26
+ return false;
27
+ }
28
+ };
29
+ if (transaction.to?.toLowerCase() === evidence.agentWallet.toLowerCase() && transaction.from.toLowerCase() === evidence.walletAddress.toLowerCase()) return matchesFullCall(transaction.input);
14
30
  try {
15
- const decoded = decodeFunctionData({ abi: fullWithdrawalAbi, data: transaction.input });
16
- return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
31
+ const address = transaction.to?.toLowerCase();
32
+ if (address !== entryPoint06 && address !== entryPoint07) return false;
33
+ const version = address === entryPoint06 ? "0.6" : "0.7";
34
+ const decoded = decodeFunctionData({ abi: version === "0.6" ? entryPoint06Abi : entryPoint07Abi, data: transaction.input });
35
+ if (decoded.functionName !== "handleOps") return false;
36
+ const operations = decoded.args[0].filter((operation2) => [evidence.agentWallet.toLowerCase(), evidence.walletAddress.toLowerCase()].includes(operation2.sender.toLowerCase()));
37
+ if (operations.length !== 1) return false;
38
+ const operation = operations[0];
39
+ const userOperation = toUserOperation(operation);
40
+ const hash = getUserOperationHash({
41
+ chainId: evidence.chainId,
42
+ entryPointAddress: transaction.to,
43
+ entryPointVersion: version,
44
+ userOperation
45
+ });
46
+ if (evidence.userOpHash && hash.toLowerCase() !== evidence.userOpHash.toLowerCase()) return false;
47
+ if (!receipt?.logs.some((log) => {
48
+ if (log.address.toLowerCase() !== address) return false;
49
+ try {
50
+ const event = decodeEventLog({ abi: entryPoint06Abi, data: log.data, topics: log.topics });
51
+ return event.eventName === "UserOperationEvent" && event.args.userOpHash.toLowerCase() === hash.toLowerCase() && event.args.sender.toLowerCase() === operation.sender.toLowerCase() && event.args.success;
52
+ } catch {
53
+ return false;
54
+ }
55
+ })) return false;
56
+ if (operation.sender.toLowerCase() === evidence.agentWallet.toLowerCase()) return matchesFullCall(operation.callData);
57
+ const execution = decodeFunctionData({ abi: accountExecutionAbi, data: operation.callData });
58
+ const calls = execution.functionName === "execute" ? [{ target: execution.args[0], value: execution.args[1], data: execution.args[2] }] : execution.args[0];
59
+ return calls.length === 1 && calls[0].value === 0n && calls[0].target.toLowerCase() === evidence.agentWallet.toLowerCase() && matchesFullCall(calls[0].data);
17
60
  } catch {
18
61
  return false;
19
62
  }
@@ -25,6 +68,8 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
25
68
  const attributed = /* @__PURE__ */ new Map();
26
69
  const agent = evidence.agentWallet?.toLowerCase();
27
70
  const owner = evidence.walletAddress.toLowerCase();
71
+ const source = direction === "deposit" ? owner : agent;
72
+ const destination = direction === "deposit" ? agent : owner;
28
73
  const logIndexes = [];
29
74
  const transferLogs = [];
30
75
  const zeroTransferLogs = [];
@@ -39,17 +84,13 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction) {
39
84
  if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
40
85
  zeroTransferLogs.push(log.logIndex);
41
86
  let matched = 0n;
42
- if (direction === "deposit") {
43
- if (to.toLowerCase() === agent) matched = value;
44
- } else {
45
- const sender = from.toLowerCase();
46
- const recipient = to.toLowerCase();
47
- const available = sender === agent ? value : attributed.get(sender) ?? 0n;
48
- const carried = available < value ? available : value;
49
- if (sender !== agent) attributed.set(sender, available - carried);
50
- if (recipient === owner) matched = carried;
51
- else if (recipient !== agent) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
52
- }
87
+ const sender = from.toLowerCase();
88
+ const recipient = to.toLowerCase();
89
+ const available = sender === source ? value : attributed.get(sender) ?? 0n;
90
+ const carried = available < value ? available : value;
91
+ if (sender !== source) attributed.set(sender, available - carried);
92
+ if (recipient === destination) matched = carried;
93
+ else if (recipient !== source) attributed.set(recipient, (attributed.get(recipient) ?? 0n) + carried);
53
94
  if (matched === 0n) continue;
54
95
  amount += matched;
55
96
  transferLogs.push({ logIndex: log.logIndex, amountRaw: matched.toString() });
@@ -341,8 +382,8 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
341
382
  return await original.apply(this, args);
342
383
  } catch (error) {
343
384
  const message = error instanceof Error ? error.message : "";
344
- 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";
345
- if (["unauthorized", "forbidden", "rate-limited"].includes(request2.failure)) notify("rejected");
385
+ request2.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : /^Idle balance \d+(?:\.\d+)? (USDC|WETH|ETH|EURC) is below the minimum withdrawal of \d+(?:\.\d+)? \1\.?$/.test(message) ? "insufficient-balance" : /^User doesn't have any active session key\.?$/.test(message) ? "session-key-missing" : message === "Network error: Unable to reach the server" ? "network" : "other";
386
+ if (["unauthorized", "forbidden", "rate-limited", "insufficient-balance", "session-key-missing"].includes(request2.failure)) notify("rejected");
346
387
  throw error;
347
388
  } finally {
348
389
  request2.elapsedMs = Math.max(0, Date.now() - startedAt);
@@ -365,9 +406,49 @@ function observeWithdrawalSubmission(sdk, chainId, token) {
365
406
  } };
366
407
  }
367
408
 
409
+ // src/agents/zyfai/zyfai.withdraw-empty.ts
410
+ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
411
+ const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
412
+ const response = record(raw2);
413
+ const portfolio = record(response?.portfolio);
414
+ const chains = record(portfolio?.portfolioByChain);
415
+ if (response?.success === false || !chains || !Array.isArray(portfolio?.positions) || !Array.isArray(portfolio.staleBalances)) return false;
416
+ if (portfolio.pendingAsyncWithdrawals !== void 0 && (!Array.isArray(portfolio.pendingAsyncWithdrawals) || portfolio.pendingAsyncWithdrawals.length > 0)) return false;
417
+ const symbol = (value) => typeof value === "string" ? value.toUpperCase() === "ETH" ? "WETH" : value.toUpperCase() : void 0;
418
+ const chain = (value) => {
419
+ if (typeof value === "number") return value;
420
+ if (typeof value !== "string") return void 0;
421
+ return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
422
+ };
423
+ const zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
424
+ for (const [id, assets] of Object.entries(chains)) {
425
+ const idNumber = chain(id);
426
+ if (idNumber === void 0) return false;
427
+ if (idNumber !== chainId) continue;
428
+ const entries = record(assets);
429
+ if (!entries) return false;
430
+ for (const [asset, value] of Object.entries(entries)) {
431
+ if (symbol(asset) !== token) continue;
432
+ const balance = record(value);
433
+ if (!balance || !zero(balance.balance)) return false;
434
+ }
435
+ }
436
+ for (const value of [...portfolio.positions, ...portfolio.staleBalances]) {
437
+ const entry = record(value);
438
+ if (!entry) return false;
439
+ const id = chain(entry.chainId ?? entry.chain);
440
+ const asset = symbol(entry.tokenSymbol ?? entry.token_symbol);
441
+ if (id === void 0 || asset === void 0) return false;
442
+ if (id !== chainId || asset !== token) continue;
443
+ const amounts = [entry.balance, entry.amount, entry.underlyingAmount].filter((value2) => value2 !== void 0);
444
+ if (!amounts.length || !amounts.every(zero)) return false;
445
+ }
446
+ return true;
447
+ }
448
+
368
449
  // src/agents/zyfai/zyfai.agent.ts
369
450
  import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
370
- import { formatUnits, parseAbi as parseAbi2 } from "viem";
451
+ import { erc20Abi as erc20Abi2, formatUnits as formatUnits2, isAddress, parseAbi as parseAbi2 } from "viem";
371
452
  import { base, arbitrum, mainnet } from "viem/chains";
372
453
 
373
454
  // src/types/config.ts
@@ -454,14 +535,6 @@ function hexToDecimal(hex, decimals = 6) {
454
535
  const remainder = parsed % divisor;
455
536
  return Number(whole) + Number(remainder) / Number(divisor);
456
537
  }
457
- function normalizeDecimalPlace(value, decimals = 6) {
458
- const parsed = BigInt(value);
459
- if (decimals <= 0) return Number(parsed);
460
- const divisor = BigInt(10 ** decimals);
461
- const whole = parsed / divisor;
462
- const remainder = parsed % divisor;
463
- return Number(whole) + Number(remainder) / Number(divisor);
464
- }
465
538
  var DayFilterMapping = {
466
539
  "30D": 30,
467
540
  "14D": 14,
@@ -469,6 +542,8 @@ var DayFilterMapping = {
469
542
  };
470
543
 
471
544
  // src/agents/zyfai/zyfai.mapper.ts
545
+ import { formatUnits } from "viem";
546
+ var exactHexAmount = (raw2, decimals) => formatUnits(BigInt(/^0x/i.test(raw2) ? raw2 : `0x${raw2}`), decimals);
472
547
  var DECIMALS_BY_SYMBOL = {
473
548
  usdc: 6,
474
549
  weth: 18,
@@ -523,15 +598,15 @@ function mapPendingAllocations(raw2) {
523
598
  if (typeof e.balance !== "string") continue;
524
599
  let amount;
525
600
  try {
526
- amount = hexToDecimal(e.balance, tokenDecimals(e.tokenSymbol));
601
+ if (BigInt(/^0x/i.test(e.balance) ? e.balance : `0x${e.balance}`) <= 0n) continue;
602
+ amount = exactHexAmount(e.balance, tokenDecimals(e.tokenSymbol));
527
603
  } catch {
528
604
  continue;
529
605
  }
530
- if (!Number.isFinite(amount) || amount <= 0) continue;
531
606
  pending.push({
532
607
  chainId: e.chainId,
533
608
  asset: e.tokenSymbol,
534
- amount: String(amount),
609
+ amount,
535
610
  isPending: e.isPending === true,
536
611
  ...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
537
612
  });
@@ -554,13 +629,13 @@ function mapBalances(raw2, _chainId, smartWallet) {
554
629
  { balance, balanceWithFee, decimals }
555
630
  ] of Object.entries(tokensBySymbol ?? {})) {
556
631
  if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
557
- const amount = hexToDecimal(balanceWithFee ?? balance, decimals);
558
- totalBalance += amount;
632
+ const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol, decimals));
633
+ totalBalance += Number(amount);
559
634
  tokens.push({
560
635
  chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
561
636
  chainId: positionChainId,
562
637
  asset: symbol,
563
- amount: String(amount)
638
+ amount
564
639
  });
565
640
  }
566
641
  }
@@ -574,19 +649,17 @@ function mapBalances(raw2, _chainId, smartWallet) {
574
649
  // legacy `amount` fallback is hex. Runtime
575
650
  // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
576
651
  // when present, otherwise fall back to the per-symbol lookup.
577
- amount: String(
578
- p.underlyingAmountWithFee ?? p.underlyingAmount ? normalizeDecimalPlace(
579
- p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0",
580
- tokenDecimals(
581
- p.token_symbol,
582
- p.decimals
583
- )
584
- ) : hexToDecimal(
585
- p.amount || "0",
586
- tokenDecimals(
587
- p.token_symbol,
588
- p.decimals
589
- )
652
+ amount: p.underlyingAmountWithFee ?? p.underlyingAmount ? formatUnits(
653
+ BigInt(p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0"),
654
+ tokenDecimals(
655
+ p.token_symbol,
656
+ p.decimals
657
+ )
658
+ ) : exactHexAmount(
659
+ p.amount || "0",
660
+ tokenDecimals(
661
+ p.token_symbol,
662
+ p.decimals
590
663
  )
591
664
  ),
592
665
  apy: netApy(p.pool_apy_withFee, p.pool_apy, "pool_apy_withFee"),
@@ -1530,7 +1603,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1530
1603
  }, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
1531
1604
  if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
1532
1605
  Object.assign(tx, {
1533
- amount: formatUnits(BigInt(evidence.confirmedAmount), asset.decimals),
1606
+ amount: formatUnits2(BigInt(evidence.confirmedAmount), asset.decimals),
1534
1607
  amountRaw: evidence.confirmedAmount,
1535
1608
  blockNumber: evidence.blockNumber,
1536
1609
  logIndexes: evidence.logIndexes,
@@ -2257,6 +2330,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
2257
2330
  evidence2 = { ...evidence2, transfer: "unknown" };
2258
2331
  }
2259
2332
  notifyFunding(onProgress, evidence2);
2333
+ if (evidence2.transfer === "failed")
2334
+ throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
2260
2335
  try {
2261
2336
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
2262
2337
  await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
@@ -2295,12 +2370,77 @@ var ZyfaiAgent = class _ZyfaiAgent {
2295
2370
  } catch {
2296
2371
  }
2297
2372
  notifyFunding(onProgress, evidence);
2373
+ if (evidence.transfer === "failed")
2374
+ throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash: raw2.txHash }, this.id);
2298
2375
  return { ...mapDeposit(raw2), evidence };
2299
2376
  } catch (error) {
2300
2377
  throw error;
2301
2378
  }
2302
2379
  }
2303
2380
  // --- IAgent: Fund operations ---
2381
+ /** Called only after the execution endpoint explicitly rejected a request.
2382
+ * A timeout or an accepted request never authorizes repair and resubmission. */
2383
+ async recoverWithdrawalSession(state, chainId, token, owner) {
2384
+ const internals = this.sdk;
2385
+ const assertScope = () => {
2386
+ const account = internals.walletClient?.account?.address;
2387
+ if (state.walletAddress.toLowerCase() !== owner.toLowerCase() || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
2388
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai withdrawal.", void 0, this.id);
2389
+ };
2390
+ assertScope();
2391
+ const [wallet, registered, portfolio] = await Promise.all([
2392
+ this.sdk.getSmartWalletAddress(owner, chainId),
2393
+ this.sdk.getSmartWalletByEOA(owner),
2394
+ this.sdk.getPortfolio(owner).catch(() => void 0)
2395
+ ]);
2396
+ assertScope();
2397
+ if (!isAddress(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
2398
+ throw new OwneyError(
2399
+ "AGENT_AUTH_FAILED",
2400
+ "Could not verify Zyfai's withdrawal wallet.",
2401
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2402
+ this.id
2403
+ );
2404
+ if (hasEmptyWithdrawalSnapshot(portfolio, chainId, token)) {
2405
+ const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
2406
+ const idle = asset ? await this.getPublicClient(chainId).readContract({
2407
+ address: asset.address,
2408
+ abi: erc20Abi2,
2409
+ functionName: "balanceOf",
2410
+ args: [wallet.address]
2411
+ }).catch(() => void 0) : void 0;
2412
+ assertScope();
2413
+ if (idle === 0n) throw new OwneyError(
2414
+ "WITHDRAW_INSUFFICIENT_BALANCE",
2415
+ "No funds remain in Zyfai for this asset.",
2416
+ { available: "0", chainId, token },
2417
+ this.id
2418
+ );
2419
+ }
2420
+ const walletState = await this.readWalletState(owner);
2421
+ assertScope();
2422
+ const unavailable = () => new OwneyError(
2423
+ "AGENT_AUTH_FAILED",
2424
+ "Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
2425
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2426
+ this.id
2427
+ );
2428
+ if (walletState.predeployed !== false || internals.isPredeployed === true) throw unavailable();
2429
+ internals.hasActiveSessionKey = false;
2430
+ const key2 = `${owner.toLowerCase()}:${chainId}`;
2431
+ this.sessionKeyAttempts.delete(key2);
2432
+ try {
2433
+ const result = await this.sdk.createSessionKey(owner, chainId);
2434
+ assertScope();
2435
+ if (result.success === false || result.alreadyActive === true) throw unavailable();
2436
+ this.sessionKeyAttempts.add(key2);
2437
+ } catch (error) {
2438
+ assertScope();
2439
+ const code = error?.code;
2440
+ if (code === 4001 || code === "ACTION_REJECTED" || error instanceof Error && /user (rejected|denied)|request rejected|action rejected/i.test(error.message)) throw error;
2441
+ throw unavailable();
2442
+ }
2443
+ }
2304
2444
  async withdraw(state, chainId, token, amount, onProgress) {
2305
2445
  const validChainId = isValidChainId(chainId);
2306
2446
  try {
@@ -2318,11 +2458,35 @@ var ZyfaiAgent = class _ZyfaiAgent {
2318
2458
  });
2319
2459
  throw error;
2320
2460
  }
2321
- const observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2461
+ let observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2322
2462
  const authToken = this.sdk.httpClient?.authToken;
2463
+ const owner = this.getAddress();
2323
2464
  let raw2;
2324
2465
  try {
2325
- raw2 = await this.sdk.withdrawFunds(this.getAddress(), validChainId, amount, token);
2466
+ for (let attempt = 0; attempt < 2; attempt++) {
2467
+ try {
2468
+ raw2 = await this.sdk.withdrawFunds(owner, validChainId, amount, token);
2469
+ break;
2470
+ } catch (error) {
2471
+ if (!observation.rejected() || observation.request()?.failure !== "session-key-missing") throw error;
2472
+ if (attempt === 1)
2473
+ throw new OwneyError(
2474
+ "AGENT_AUTH_FAILED",
2475
+ "Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
2476
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2477
+ this.id
2478
+ );
2479
+ await this.recoverWithdrawalSession(state, validChainId, token, owner);
2480
+ observation.stop();
2481
+ observation = observeWithdrawalSubmission(this.sdk, validChainId, token);
2482
+ }
2483
+ }
2484
+ if (!raw2) throw new OwneyError(
2485
+ "AGENT_INVALID_RESPONSE",
2486
+ "Zyfai returned no withdrawal result.",
2487
+ { chainId, token, phase: "observe" },
2488
+ this.id
2489
+ );
2326
2490
  } catch (error) {
2327
2491
  this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
2328
2492
  if (observation.attempted() !== void 0) {
@@ -2605,10 +2769,13 @@ import {
2605
2769
  createPublicClient as createPublicClient3,
2606
2770
  createWalletClient as createWalletClient3,
2607
2771
  custom as custom3,
2772
+ decodeEventLog as decodeEventLog2,
2773
+ encodeEventTopics,
2608
2774
  encodeFunctionData,
2609
- erc20Abi as erc20Abi2,
2775
+ erc20Abi as erc20Abi3,
2776
+ erc4626Abi,
2610
2777
  getAddress as getAddress4,
2611
- isAddress as isAddress2
2778
+ isAddress as isAddress3
2612
2779
  } from "viem";
2613
2780
  import { base as base4 } from "viem/chains";
2614
2781
 
@@ -3227,7 +3394,7 @@ var YieldseekerApiClient = class {
3227
3394
  };
3228
3395
 
3229
3396
  // src/agents/yieldseeker/yieldseeker.mapper.ts
3230
- import { formatUnits as formatUnits2, isAddress } from "viem";
3397
+ import { formatUnits as formatUnits3, isAddress as isAddress2 } from "viem";
3231
3398
 
3232
3399
  // src/lib/helpers/snapshot-apy.ts
3233
3400
  var DAY_MS = 864e5;
@@ -3294,10 +3461,10 @@ function raw(value, endpoint) {
3294
3461
  return BigInt(value);
3295
3462
  }
3296
3463
  function decimal(value, decimals, endpoint) {
3297
- return formatUnits2(raw(value, endpoint), decimals);
3464
+ return formatUnits3(raw(value, endpoint), decimals);
3298
3465
  }
3299
3466
  function usd(rawAmount, decimals, price) {
3300
- return Number(formatUnits2(rawAmount, decimals)) * price;
3467
+ return Number(formatUnits3(rawAmount, decimals)) * price;
3301
3468
  }
3302
3469
  function percent(value) {
3303
3470
  const result = Number(value);
@@ -3323,7 +3490,7 @@ function assetAddressValue(record, address) {
3323
3490
  }
3324
3491
  function position(value, asset, baseAssetDecimals) {
3325
3492
  const option = value?.yieldOption;
3326
- if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress(option.address)) {
3493
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress2(option.address)) {
3327
3494
  return invalid("yield positions", "missing vault metadata");
3328
3495
  }
3329
3496
  return {
@@ -3415,7 +3582,7 @@ function mapYieldseekerEarnings(contexts) {
3415
3582
  chain: "BASE",
3416
3583
  chainId: 8453,
3417
3584
  asset: context.asset,
3418
- amount: formatUnits2(amount, context.snapshot.baseAssetDecimals)
3585
+ amount: formatUnits3(amount, context.snapshot.baseAssetDecimals)
3419
3586
  });
3420
3587
  lifetimeEarnings += usd(
3421
3588
  amount,
@@ -3532,6 +3699,49 @@ function actionEntry(action) {
3532
3699
  rebalanceLog: []
3533
3700
  };
3534
3701
  }
3702
+ function allocationEntry(action, context) {
3703
+ if (action.actionType.toLowerCase() !== "autoseek") return void 0;
3704
+ const hash = action.details?.depositTransactionHash;
3705
+ const deposits = action.details?.deposits;
3706
+ if (typeof hash !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(hash) || !Array.isArray(deposits) || deposits.length === 0) return void 0;
3707
+ const positions = [];
3708
+ let amountRaw = 0n;
3709
+ for (const deposit of deposits) {
3710
+ if (!deposit || typeof deposit !== "object") return void 0;
3711
+ const leg = deposit;
3712
+ const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
3713
+ if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
3714
+ if (typeof leg.vaultAddress !== "string" || !isAddress2(leg.vaultAddress)) return void 0;
3715
+ const rawAmount = typeof leg.rawAmount === "string" && /^\d+$/.test(leg.rawAmount) ? BigInt(leg.rawAmount) : typeof leg.rawAmount === "number" && Number.isSafeInteger(leg.rawAmount) ? BigInt(leg.rawAmount) : void 0;
3716
+ if (rawAmount === void 0 || rawAmount <= 0n) return void 0;
3717
+ const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
3718
+ positions.push({
3719
+ protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
3720
+ pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
3721
+ tokenSymbol: context.asset,
3722
+ amount: formatUnits3(rawAmount, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3723
+ });
3724
+ amountRaw += rawAmount;
3725
+ }
3726
+ return {
3727
+ agent: "yieldseeker",
3728
+ action: "Deposit",
3729
+ date: action.createdDate,
3730
+ oldApy: null,
3731
+ newApy: null,
3732
+ positions,
3733
+ rebalanceLog: [],
3734
+ transactions: [{
3735
+ txHashes: [hash],
3736
+ chainId: context.agent.chainId,
3737
+ tokenAddress: context.agent.assetAddress,
3738
+ tokenSymbol: context.asset,
3739
+ agentWallet: context.wallet.walletAddress,
3740
+ amountRaw: String(amountRaw),
3741
+ amount: formatUnits3(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
3742
+ }]
3743
+ };
3744
+ }
3535
3745
  function depositDestination(context, movement) {
3536
3746
  const to = movement.toAddress.toLowerCase();
3537
3747
  const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
@@ -3599,20 +3809,30 @@ function mapYieldseekerHistory(contexts, options) {
3599
3809
  seenMovements.add(key2);
3600
3810
  return true;
3601
3811
  });
3602
- return [
3603
- ...movements.map(
3604
- (movement) => movementEntry(
3605
- movement,
3606
- context.wallet,
3607
- context.agent,
3608
- context.asset,
3609
- options.ownerAddress,
3610
- options.vaultAddresses,
3611
- depositDestination(context, movement)
3612
- )
3613
- ),
3614
- ...(context.actions ?? []).map(actionEntry)
3615
- ].filter((entry) => entry !== void 0);
3812
+ const movementEntries = movements.map(
3813
+ (movement) => movementEntry(
3814
+ movement,
3815
+ context.wallet,
3816
+ context.agent,
3817
+ context.asset,
3818
+ options.ownerAddress,
3819
+ options.vaultAddresses,
3820
+ depositDestination(context, movement)
3821
+ )
3822
+ ).filter((entry) => entry !== void 0);
3823
+ const deposits = new Set(movementEntries.filter((entry) => entry.action === "Deposit").map((entry) => entry.transactions[0]?.txHashes[0]?.toLowerCase()));
3824
+ const actions = (context.actions ?? []).flatMap((action) => {
3825
+ const allocation = allocationEntry(action, context);
3826
+ if (allocation) {
3827
+ const hash = allocation.transactions[0].txHashes[0].toLowerCase();
3828
+ if (deposits.has(hash)) return [];
3829
+ deposits.add(hash);
3830
+ return [allocation];
3831
+ }
3832
+ const entry = actionEntry(action);
3833
+ return entry ? [entry] : [];
3834
+ });
3835
+ return [...movementEntries, ...actions];
3616
3836
  });
3617
3837
  const grouped = /* @__PURE__ */ new Map();
3618
3838
  const ungrouped = [];
@@ -3642,7 +3862,7 @@ function mapYieldseekerHistory(contexts, options) {
3642
3862
  const known = new Set(target.logIndexes ?? []);
3643
3863
  if (tx.logIndexes?.some((index) => !known.has(index))) {
3644
3864
  target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
3645
- target.amount = formatUnits2(
3865
+ target.amount = formatUnits3(
3646
3866
  BigInt(target.amountRaw),
3647
3867
  YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
3648
3868
  );
@@ -3737,7 +3957,8 @@ var walletAbi = parseAbi3([
3737
3957
  "function ownerAgentIndex() view returns (uint256)",
3738
3958
  "function baseAsset() view returns (address)",
3739
3959
  "function FACTORY() view returns (address)",
3740
- "function upgradeToLatest()"
3960
+ "function upgradeToLatest()",
3961
+ "event Upgraded(address indexed implementation)"
3741
3962
  ]);
3742
3963
  var factoryAbi = parseAbi3([
3743
3964
  "function userWallets(address owner, uint256 index) view returns (address)",
@@ -3746,19 +3967,19 @@ var factoryAbi = parseAbi3([
3746
3967
  function yieldseekerOwnerPath(code) {
3747
3968
  return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
3748
3969
  }
3749
- async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
3970
+ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
3750
3971
  const agentWallet = getAddress2(wallet);
3751
3972
  const expectedOwner = getAddress2(owner);
3752
3973
  const expectedAsset = getAddress2(asset);
3753
3974
  const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
3754
- client.getBytecode({ address: agentWallet }),
3755
- client.getBytecode({ address: expectedOwner }),
3756
- client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT }),
3757
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner" }),
3758
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex" }),
3759
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset" }),
3760
- client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY" }),
3761
- client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation" })
3975
+ client.getBytecode({ address: agentWallet, blockNumber }),
3976
+ client.getBytecode({ address: expectedOwner, blockNumber }),
3977
+ client.getStorageAt({ address: agentWallet, slot: ERC1967_IMPLEMENTATION_SLOT, blockNumber }),
3978
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "owner", blockNumber }),
3979
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "ownerAgentIndex", blockNumber }),
3980
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "baseAsset", blockNumber }),
3981
+ client.readContract({ address: agentWallet, abi: walletAbi, functionName: "FACTORY", blockNumber }),
3982
+ client.readContract({ address: YIELDSEEKER_FACTORY, abi: factoryAbi, functionName: "agentWalletImplementation", blockNumber })
3762
3983
  ]);
3763
3984
  if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
3764
3985
  throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
@@ -3768,7 +3989,8 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset) {
3768
3989
  address: YIELDSEEKER_FACTORY,
3769
3990
  abi: factoryAbi,
3770
3991
  functionName: "userWallets",
3771
- args: [expectedOwner, index]
3992
+ args: [expectedOwner, index],
3993
+ blockNumber
3772
3994
  });
3773
3995
  const verified = isAddressEqual(actualOwner, expectedOwner) && isAddressEqual(actualAsset, expectedAsset) && isAddressEqual(actualFactory, YIELDSEEKER_FACTORY) && isAddressEqual(mapped, agentWallet);
3774
3996
  const version = !verified ? "unknown" : isAddressEqual(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : isAddressEqual(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
@@ -3792,11 +4014,13 @@ import {
3792
4014
  getAddress as getAddress3,
3793
4015
  toHex
3794
4016
  } from "viem";
3795
- import { getUserOperationHash } from "viem/account-abstraction";
4017
+ import { getUserOperationHash as getUserOperationHash2 } from "viem/account-abstraction";
3796
4018
  import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
3797
4019
  import { base as base3 } from "viem/chains";
3798
4020
  var ENTRY_POINT_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
3799
4021
  var USER_OP_FEE_HEADROOM = 2n;
4022
+ var OBSERVATION_WINDOW_MS = 6e4;
4023
+ var OBSERVATION_POLL_MS = 1500;
3800
4024
  var ENTRY_POINT_NONCE_ABI = [
3801
4025
  { type: "function", name: "getNonce", stateMutability: "view", inputs: [
3802
4026
  { name: "sender", type: "address" },
@@ -3806,7 +4030,7 @@ var ENTRY_POINT_NONCE_ABI = [
3806
4030
  var DEFAULT_ROUTING_API = "https://owney-routing-api-243946518160.europe-west4.run.app";
3807
4031
  var SIMULATION_ACCOUNT = privateKeyToAccount(generatePrivateKey());
3808
4032
  function operationHash(operation) {
3809
- return getUserOperationHash({
4033
+ return getUserOperationHash2({
3810
4034
  chainId: 8453,
3811
4035
  entryPointAddress: ENTRY_POINT_V06,
3812
4036
  entryPointVersion: "0.6",
@@ -3829,10 +4053,119 @@ function sponsorError(message, safeToFallback, details = {}) {
3829
4053
  return new OwneyError(
3830
4054
  "SPONSOR_REQUEST_FAILED",
3831
4055
  message,
3832
- { safeToFallback, ...details },
4056
+ {
4057
+ safeToFallback,
4058
+ phase: safeToFallback ? "prepare" : "observe",
4059
+ failureCode: safeToFallback ? "SPONSOR_PREPARATION_FAILED" : "SPONSOR_OUTCOME_UNRESOLVED",
4060
+ ...safeToFallback ? { rejectedBeforeSubmission: true } : {},
4061
+ ...details
4062
+ },
3833
4063
  "yieldseeker"
3834
4064
  );
3835
4065
  }
4066
+ function providerReason(value) {
4067
+ if (typeof value !== "string") return void 0;
4068
+ return value.replace(/https?:\/\/\S+/gi, "[url]").replace(/0x[0-9a-fA-F]{40,}/g, "[hex]").replace(/[A-Za-z0-9_+=\/-]{48,}/g, "[token]").slice(0, 300);
4069
+ }
4070
+ function rpcDiagnostics(data) {
4071
+ const details = {};
4072
+ if (typeof data?.failureCode === "string" && [
4073
+ "SPONSOR_RATE_LIMIT",
4074
+ "SPONSOR_UPSTREAM_UNAVAILABLE",
4075
+ "SPONSOR_RPC_UNSUPPORTED",
4076
+ "SPONSOR_POLICY_REJECTED",
4077
+ "SPONSOR_BUDGET_REJECTED",
4078
+ "SPONSOR_EXECUTION_REVERTED"
4079
+ ].includes(data.failureCode))
4080
+ details.failureCode = data.failureCode;
4081
+ if (typeof data?.revertSelector === "string" && /^0x[0-9a-fA-F]{8}$/.test(data.revertSelector))
4082
+ details.revertSelector = data.revertSelector;
4083
+ if (typeof data?.revertName === "string" && [
4084
+ "InsufficientBalance",
4085
+ "InvalidAsset",
4086
+ "Unauthorized",
4087
+ "Error",
4088
+ "Panic",
4089
+ "FailedOp",
4090
+ "FailedOpWithRevert"
4091
+ ].includes(data.revertName))
4092
+ details.revertName = data.revertName;
4093
+ if (typeof data?.aaCode === "string" && /^AA[0-9]{2}$/.test(data.aaCode)) details.aaCode = data.aaCode;
4094
+ if (typeof data?.panicCode === "string" && /^0x[0-9a-fA-F]{1,2}$/.test(data.panicCode)) {
4095
+ const panicCode = toHex(BigInt(data.panicCode));
4096
+ if (["0x0", "0x1", "0x11", "0x12", "0x21", "0x22", "0x31", "0x32", "0x41", "0x51"].includes(panicCode))
4097
+ details.panicCode = panicCode;
4098
+ }
4099
+ return details;
4100
+ }
4101
+ function isHash(value) {
4102
+ return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
4103
+ }
4104
+ async function assertOwnerSession(state, owner) {
4105
+ const [chain, accounts] = await Promise.all([
4106
+ state.provider.request({ method: "eth_chainId" }),
4107
+ state.provider.request({ method: "eth_accounts" })
4108
+ ]);
4109
+ if (Number(chain) !== 8453)
4110
+ throw new OwneyError(
4111
+ "CHAIN_MISMATCH",
4112
+ "Switch back to Base before withdrawing.",
4113
+ { rejectedBeforeSubmission: true, phase: "prepare" },
4114
+ "yieldseeker"
4115
+ );
4116
+ if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase())
4117
+ throw new OwneyError(
4118
+ "NOT_CONNECTED",
4119
+ "The connected withdrawal account changed.",
4120
+ { rejectedBeforeSubmission: true, phase: "prepare" },
4121
+ "yieldseeker"
4122
+ );
4123
+ }
4124
+ function savePending(onPending, evidence, beforeSubmission = false) {
4125
+ try {
4126
+ onPending?.(evidence);
4127
+ } catch {
4128
+ if (beforeSubmission)
4129
+ throw sponsorError(
4130
+ "Withdrawal status cannot be saved before submission.",
4131
+ true,
4132
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
4133
+ );
4134
+ }
4135
+ }
4136
+ async function checkpoint(callback, pending) {
4137
+ try {
4138
+ await callback?.(pending);
4139
+ } catch {
4140
+ throw sponsorError(
4141
+ "Withdrawal status cannot be saved before submission.",
4142
+ true,
4143
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "RECOVERY_STORAGE_UNAVAILABLE" }
4144
+ );
4145
+ }
4146
+ }
4147
+ function finalPaymasterFields(value) {
4148
+ const result = {};
4149
+ for (const key2 of [
4150
+ "paymasterAndData",
4151
+ "callGasLimit",
4152
+ "verificationGasLimit",
4153
+ "preVerificationGas",
4154
+ "maxFeePerGas",
4155
+ "maxPriorityFeePerGas"
4156
+ ]) {
4157
+ const field = value[key2];
4158
+ if (field === void 0) continue;
4159
+ if (typeof field !== "string" || !/^0x[0-9a-fA-F]+$/.test(field))
4160
+ throw sponsorError(
4161
+ "Paymaster returned an invalid final operation.",
4162
+ true,
4163
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INVALID_PAYMASTER_QUOTE" }
4164
+ );
4165
+ result[key2] = field;
4166
+ }
4167
+ return result;
4168
+ }
3836
4169
  async function requestIntent(input) {
3837
4170
  const baseUrl = (input.routingApiBaseUrl ?? DEFAULT_ROUTING_API).replace(/\/$/, "");
3838
4171
  let response;
@@ -3854,18 +4187,35 @@ async function requestIntent(input) {
3854
4187
  action: input.action,
3855
4188
  yieldseekerUserId: input.userId,
3856
4189
  yieldseekerAgentId: input.agentId
3857
- })
4190
+ }),
4191
+ signal: AbortSignal.timeout(15e3)
3858
4192
  });
3859
4193
  } catch {
3860
- throw sponsorError("Withdrawal sponsorship could not be requested.", true);
4194
+ throw sponsorError(
4195
+ "Withdrawal sponsorship could not be requested.",
4196
+ true,
4197
+ { phase: "prepare", rejectedBeforeSubmission: true, failureCode: "INTENT_NETWORK_ERROR" }
4198
+ );
3861
4199
  }
3862
4200
  const envelope = await response.json().catch(() => null);
3863
4201
  const intent = envelope?.data;
3864
4202
  if (!response.ok || !envelope?.success || !intent?.intent || !["eoa", "smart"].includes(intent.ownerPath)) {
4203
+ const rawCode = typeof envelope?.failureCode === "string" ? envelope.failureCode : envelope?.code;
4204
+ const failureCode = typeof rawCode === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(rawCode) ? rawCode : void 0;
4205
+ if (input.refreshYieldseekerSignature && (failureCode === "YIELDSEEKER_AUTH_REJECTED" || failureCode === "AUTH_REJECTED" || envelope?.sponsorshipStage === "provider-auth" && (envelope.providerStatus === 401 || envelope.providerStatus === 403))) {
4206
+ const signature = await input.refreshYieldseekerSignature(input.yieldseekerSignature);
4207
+ return requestIntent({ ...input, yieldseekerSignature: signature, refreshYieldseekerSignature: void 0 });
4208
+ }
3865
4209
  throw sponsorError(
3866
4210
  "Withdrawal sponsorship is currently unavailable.",
3867
4211
  true,
3868
- { statusCode: response.status }
4212
+ {
4213
+ statusCode: response.status,
4214
+ phase: "prepare",
4215
+ rejectedBeforeSubmission: true,
4216
+ failureCode: failureCode ?? "INTENT_REJECTED",
4217
+ providerReason: providerReason(envelope?.message)
4218
+ }
3869
4219
  );
3870
4220
  }
3871
4221
  const url = new URL(`${baseUrl}/api/v1/sponsor/yieldseeker-withdraw/rpc`);
@@ -3878,19 +4228,55 @@ async function rpc(url, method, params, phase) {
3878
4228
  response = await fetch(url, {
3879
4229
  method: "POST",
3880
4230
  headers: { "content-type": "application/json" },
3881
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
4231
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
4232
+ signal: AbortSignal.timeout(15e3)
3882
4233
  });
3883
4234
  } catch {
3884
- throw sponsorError("Withdrawal sponsorship request status is unknown.", phase === "prepare");
4235
+ throw sponsorError(
4236
+ "Withdrawal sponsorship request status is unknown.",
4237
+ phase === "prepare",
4238
+ {
4239
+ phase,
4240
+ rpcMethod: method,
4241
+ ...phase === "prepare" ? { rejectedBeforeSubmission: true } : {},
4242
+ failureCode: "RPC_NETWORK_ERROR"
4243
+ }
4244
+ );
3885
4245
  }
3886
4246
  const payload = await response.json().catch(() => null);
3887
4247
  if (!response.ok || payload?.error || payload?.result === void 0) {
3888
4248
  const rejection = payload?.error;
3889
- const definitiveRejection = phase === "send" && response.status < 500 && !!rejection && (rejection.data?.rejectedBeforeSubmission === true || [-32501, -32502, -32503, -32504, -32505, -32507].includes(rejection.code ?? 0) || /maxFeePerGas.*(?:too low|lower than|less than|must be at least)/i.test(rejection.message ?? ""));
4249
+ const diagnostics = rpcDiagnostics(rejection?.data);
4250
+ const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
4251
+ const details = {
4252
+ statusCode: response.status,
4253
+ rpcCode: rejection?.code,
4254
+ rpcMethod: method,
4255
+ phase,
4256
+ providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
4257
+ failureCode: "RPC_REJECTED",
4258
+ ...diagnostics
4259
+ };
4260
+ if (phase === "prepare" && executionReverted)
4261
+ throw new OwneyError(
4262
+ "WITHDRAW_FAILED",
4263
+ "Yieldseeker withdrawal simulation reverted before submission.",
4264
+ {
4265
+ ...details,
4266
+ failureCode: "SPONSOR_EXECUTION_REVERTED",
4267
+ safeToFallback: false,
4268
+ rejectedBeforeSubmission: true
4269
+ },
4270
+ "yieldseeker"
4271
+ );
4272
+ const definitiveRejection = phase === "send" && !executionReverted && response.status < 500 && !!rejection && (rejection.data?.rejectedBeforeSubmission === true || [-32501, -32502, -32503, -32504, -32505, -32507].includes(rejection.code ?? 0) || /maxFeePerGas.*(?:too low|lower than|less than|must be at least)/i.test(rejection.message ?? ""));
3890
4273
  throw sponsorError(
3891
4274
  phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
3892
4275
  phase === "prepare" || definitiveRejection,
3893
- { statusCode: response.status, rpcCode: payload?.error?.code, rpcMethod: method }
4276
+ {
4277
+ ...details,
4278
+ ...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
4279
+ }
3894
4280
  );
3895
4281
  }
3896
4282
  return payload.result;
@@ -3910,13 +4296,15 @@ async function submitEoa(input) {
3910
4296
  let nonce;
3911
4297
  let fees;
3912
4298
  try {
3913
- nonce = await input.client.readContract({
3914
- address: ENTRY_POINT_V06,
3915
- abi: ENTRY_POINT_NONCE_ABI,
3916
- functionName: "getNonce",
3917
- args: [input.agentWallet, 0n]
3918
- });
3919
- fees = await input.client.estimateFeesPerGas();
4299
+ [nonce, fees] = await Promise.all([
4300
+ input.client.readContract({
4301
+ address: ENTRY_POINT_V06,
4302
+ abi: ENTRY_POINT_NONCE_ABI,
4303
+ functionName: "getNonce",
4304
+ args: [input.agentWallet, 0n]
4305
+ }),
4306
+ input.client.estimateFeesPerGas()
4307
+ ]);
3920
4308
  } catch {
3921
4309
  throw sponsorError("Could not prepare the sponsored withdrawal.", true);
3922
4310
  }
@@ -3959,9 +4347,10 @@ async function submitEoa(input) {
3959
4347
  );
3960
4348
  if (!final.paymasterAndData?.startsWith("0x"))
3961
4349
  throw sponsorError("CDP did not return final paymaster data.", true);
3962
- operation = { ...operation, paymasterAndData: final.paymasterAndData };
4350
+ operation = { ...operation, ...finalPaymasterFields(final) };
3963
4351
  }
3964
4352
  const userOpHash = operationHash(operation);
4353
+ await assertOwnerSession(input.state, input.owner);
3965
4354
  const wallet = createWalletClient2({
3966
4355
  account: input.owner,
3967
4356
  chain: base3,
@@ -3969,24 +4358,47 @@ async function submitEoa(input) {
3969
4358
  });
3970
4359
  const signature = await wallet.signMessage({ message: { raw: userOpHash } });
3971
4360
  operation = { ...operation, signature };
3972
- input.onPending?.({ userOpHash });
4361
+ await assertOwnerSession(input.state, input.owner);
4362
+ const userOperation = {
4363
+ sender: operation.sender,
4364
+ nonce: operation.nonce,
4365
+ initCode: operation.initCode,
4366
+ callData: operation.callData,
4367
+ callGasLimit: operation.callGasLimit,
4368
+ verificationGasLimit: operation.verificationGasLimit,
4369
+ preVerificationGas: operation.preVerificationGas,
4370
+ maxFeePerGas: operation.maxFeePerGas,
4371
+ maxPriorityFeePerGas: operation.maxPriorityFeePerGas,
4372
+ paymasterAndData: operation.paymasterAndData
4373
+ };
4374
+ savePending(input.onPending, { userOpHash, userOperation }, true);
4375
+ await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
3973
4376
  const submittedHash = await rpc(
3974
4377
  input.rpcUrl,
3975
4378
  "eth_sendUserOperation",
3976
4379
  [operation, ENTRY_POINT_V06],
3977
4380
  "send"
3978
4381
  );
3979
- if (submittedHash.toLowerCase() !== userOpHash.toLowerCase())
4382
+ if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
3980
4383
  throw sponsorError(
3981
4384
  "Bundler returned a different UserOperation hash.",
3982
4385
  false,
3983
- { userOpHash, submittedHash }
4386
+ { userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
3984
4387
  );
3985
- for (let poll = 0; poll < 40; poll++) {
3986
- const response = await rpc(input.rpcUrl, "eth_getUserOperationReceipt", [userOpHash], "observe");
4388
+ const deadline = Date.now() + OBSERVATION_WINDOW_MS;
4389
+ let lastReadError;
4390
+ for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
4391
+ let response;
4392
+ try {
4393
+ response = await rpc(input.rpcUrl, "eth_getUserOperationReceipt", [userOpHash], "observe");
4394
+ } catch (error) {
4395
+ lastReadError = error;
4396
+ await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
4397
+ continue;
4398
+ }
3987
4399
  const txHash = response?.receipt?.transactionHash;
3988
- if (txHash) {
3989
- input.onPending?.({ userOpHash, txHash });
4400
+ if (isHash(txHash)) {
4401
+ savePending(input.onPending, { userOpHash, txHash });
3990
4402
  if (response?.success === false)
3991
4403
  throw new OwneyError(
3992
4404
  "AGENT_TRANSACTION_REVERTED",
@@ -3996,12 +4408,17 @@ async function submitEoa(input) {
3996
4408
  );
3997
4409
  return { txHash, receipt: await waitForTransaction(input.client, txHash) };
3998
4410
  }
3999
- await new Promise((resolve) => setTimeout(resolve, 1500));
4411
+ await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
4000
4412
  }
4001
4413
  throw sponsorError(
4002
4414
  "Sponsored withdrawal is still pending; check status before retrying.",
4003
4415
  false,
4004
- { userOpHash }
4416
+ {
4417
+ userOpHash,
4418
+ phase: "observe",
4419
+ failureCode: "RECEIPT_UNRESOLVED",
4420
+ ...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
4421
+ }
4005
4422
  );
4006
4423
  }
4007
4424
  async function submitSmart(input) {
@@ -4014,14 +4431,14 @@ async function submitSmart(input) {
4014
4431
  } catch {
4015
4432
  throw sponsorError("This account cannot check paymaster support on Base.", true);
4016
4433
  }
4017
- if (!capabilities?.[toHex(8453)]?.paymasterService?.supported && !capabilities?.["8453"]?.paymasterService?.supported)
4434
+ const supportsPaymaster = capabilities?.[toHex(8453)]?.paymasterService?.supported ?? capabilities?.["8453"]?.paymasterService?.supported ?? capabilities?.["0x0"]?.paymasterService?.supported;
4435
+ if (supportsPaymaster !== true)
4018
4436
  throw sponsorError("This account cannot use a paymaster on Base.", true);
4437
+ await assertOwnerSession(input.state, input.owner);
4019
4438
  let sent;
4020
- try {
4021
- input.onPending?.({});
4022
- } catch {
4023
- throw sponsorError("Upgrade or withdrawal status cannot be saved.", true);
4024
- }
4439
+ const requestedId = toHex(globalThis.crypto.getRandomValues(new Uint8Array(32)));
4440
+ savePending(input.onPending, { callsId: requestedId }, true);
4441
+ await checkpoint(input.onBeforeSubmission, { callsId: requestedId });
4025
4442
  try {
4026
4443
  sent = await input.state.provider.request({
4027
4444
  method: "wallet_sendCalls",
@@ -4029,6 +4446,7 @@ async function submitSmart(input) {
4029
4446
  version: "2.0.0",
4030
4447
  from: input.owner,
4031
4448
  chainId: toHex(8453),
4449
+ id: requestedId,
4032
4450
  atomicRequired: false,
4033
4451
  calls: [{ to: input.agentWallet, value: "0x0", data: input.callData }],
4034
4452
  capabilities: { paymasterService: { url: input.rpcUrl } }
@@ -4037,17 +4455,27 @@ async function submitSmart(input) {
4037
4455
  } catch (error) {
4038
4456
  if (error?.code === 4001) throw error;
4039
4457
  if ([4200, -32601].includes(Number(error?.code)))
4040
- throw sponsorError("This account does not support sponsored calls.", true);
4041
- throw sponsorError("Wallet submission status is unknown. Check activity before retrying.", false);
4458
+ throw sponsorError(
4459
+ "This account does not support sponsored calls.",
4460
+ true,
4461
+ { phase: "send", failureCode: "WALLET_CALLS_UNSUPPORTED" }
4462
+ );
4463
+ throw sponsorError(
4464
+ "Wallet submission status is unknown. Check activity before retrying.",
4465
+ false,
4466
+ { callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
4467
+ );
4042
4468
  }
4043
4469
  const callsId = typeof sent === "string" ? sent : sent?.id;
4044
- if (!callsId) throw sponsorError("Wallet returned no calls ID; submission status is unknown.", false);
4045
- try {
4046
- input.onPending?.({ callsId });
4047
- } catch {
4048
- throw sponsorError("Submitted call status could not be saved.", false, { callsId });
4049
- }
4050
- for (let poll = 0; poll < 40; poll++) {
4470
+ if (typeof callsId !== "string" || callsId.length === 0 || callsId.length > 8194)
4471
+ throw sponsorError(
4472
+ "Wallet returned no calls ID; submission status is unknown.",
4473
+ false,
4474
+ { callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
4475
+ );
4476
+ savePending(input.onPending, { callsId });
4477
+ const deadline = Date.now() + OBSERVATION_WINDOW_MS;
4478
+ for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
4051
4479
  let status;
4052
4480
  try {
4053
4481
  status = await input.state.provider.request({
@@ -4055,33 +4483,40 @@ async function submitSmart(input) {
4055
4483
  params: [callsId]
4056
4484
  });
4057
4485
  } catch {
4058
- throw sponsorError("Sponsored withdrawal status is unknown.", false, { callsId });
4486
+ await new Promise((resolve) => setTimeout(resolve, Math.min(OBSERVATION_POLL_MS, Math.max(0, deadline - Date.now()))));
4487
+ continue;
4059
4488
  }
4060
- if (status?.status === 400 || status?.status === 500)
4489
+ if (status?.id !== void 0 && status.id !== callsId || status?.chainId !== void 0 && Number(status.chainId) !== 8453)
4490
+ throw sponsorError(
4491
+ "Wallet returned a different sponsored operation status.",
4492
+ false,
4493
+ { callsId, phase: "observe", failureCode: "WALLET_STATUS_MISMATCH" }
4494
+ );
4495
+ if (Number(status?.status) === 400 || Number(status?.status) === 500)
4061
4496
  throw sponsorError(
4062
4497
  "Sponsored withdrawal failed in the wallet.",
4063
4498
  false,
4064
- { callsId, executionFailed: true }
4499
+ { callsId, executionFailed: true, phase: "observe", failureCode: "WALLET_EXECUTION_FAILED" }
4065
4500
  );
4066
- const txHash = status?.receipts?.[0]?.transactionHash;
4501
+ const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash).filter(isHash))];
4502
+ const txHash = hashes.length === 1 ? hashes[0] : void 0;
4067
4503
  if (txHash) {
4068
- try {
4069
- input.onPending?.({ callsId, txHash });
4070
- } catch {
4071
- throw sponsorError(
4072
- "Submitted call receipt could not be saved.",
4073
- false,
4074
- { callsId, txHash }
4504
+ savePending(input.onPending, { callsId, txHash });
4505
+ if (status?.receipts?.some((receipt) => receipt.transactionHash === txHash && /^(?:0x0+|0|failed|reverted)$/i.test(String(receipt.status))))
4506
+ throw new OwneyError(
4507
+ "AGENT_TRANSACTION_REVERTED",
4508
+ "Sponsored withdrawal execution reverted.",
4509
+ { callsId, transactionHash: txHash },
4510
+ "yieldseeker"
4075
4511
  );
4076
- }
4077
4512
  return { txHash, receipt: await waitForTransaction(input.client, txHash) };
4078
4513
  }
4079
- await new Promise((resolve) => setTimeout(resolve, 1500));
4514
+ await new Promise((resolve) => setTimeout(resolve, OBSERVATION_POLL_MS));
4080
4515
  }
4081
4516
  throw sponsorError(
4082
4517
  "Sponsored withdrawal is still pending; check status before retrying.",
4083
4518
  false,
4084
- { callsId }
4519
+ { callsId, phase: "observe", failureCode: "RECEIPT_UNRESOLVED" }
4085
4520
  );
4086
4521
  }
4087
4522
  async function submitYieldseekerSponsoredWithdrawal(input) {
@@ -4096,7 +4531,8 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
4096
4531
  agentWallet: input.agentWallet,
4097
4532
  asset: input.asset,
4098
4533
  callData: input.callData,
4099
- action: "withdraw"
4534
+ action: "withdraw",
4535
+ refreshYieldseekerSignature: input.refreshYieldseekerSignature
4100
4536
  });
4101
4537
  const args = { ...input, owner, rpcUrl };
4102
4538
  return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
@@ -4113,7 +4549,8 @@ async function submitYieldseekerSponsoredUpgrade(input) {
4113
4549
  agentWallet: input.agentWallet,
4114
4550
  asset: input.asset,
4115
4551
  callData: input.callData,
4116
- action: "upgrade"
4552
+ action: "upgrade",
4553
+ refreshYieldseekerSignature: input.refreshYieldseekerSignature
4117
4554
  });
4118
4555
  if (intent.ownerPath !== "smart")
4119
4556
  throw sponsorError("V1 upgrade sponsorship requires a Base Account owner.", true);
@@ -4134,6 +4571,8 @@ var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
4134
4571
  var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
4135
4572
  var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
4136
4573
  var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
4574
+ var YIELDSEEKER_MAX_WITHDRAW_SWEEPS = 3;
4575
+ var YIELDSEEKER_MAX_POSITION_VERIFICATION_MS = 2e3;
4137
4576
  function generateYieldseekerUsername() {
4138
4577
  const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
4139
4578
  return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
@@ -4144,6 +4583,13 @@ function isUsernameConflict(error) {
4144
4583
  return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
4145
4584
  }
4146
4585
  var YIELDSEEKER_AGENT_WALLET_ABI = [
4586
+ {
4587
+ type: "function",
4588
+ name: "feeTracker",
4589
+ stateMutability: "view",
4590
+ inputs: [],
4591
+ outputs: [{ name: "", type: "address" }]
4592
+ },
4147
4593
  {
4148
4594
  type: "function",
4149
4595
  name: "withdrawAssetToUser",
@@ -4166,6 +4612,13 @@ var YIELDSEEKER_AGENT_WALLET_ABI = [
4166
4612
  outputs: []
4167
4613
  }
4168
4614
  ];
4615
+ var YIELDSEEKER_FEE_TRACKER_ABI = [{
4616
+ type: "function",
4617
+ name: "getFeesOwed",
4618
+ stateMutability: "view",
4619
+ inputs: [{ name: "wallet", type: "address" }],
4620
+ outputs: [{ name: "", type: "uint256" }]
4621
+ }];
4169
4622
  function query(params) {
4170
4623
  const search = new URLSearchParams();
4171
4624
  for (const [key2, value] of Object.entries(params)) {
@@ -4249,17 +4702,22 @@ var YieldseekerAgent = class {
4249
4702
  getAddress4(state.walletAddress),
4250
4703
  YIELDSEEKER_ASSET_METADATA[asset].address
4251
4704
  );
4705
+ this.rememberWalletStatus(key2, status);
4706
+ return status;
4707
+ }
4708
+ rememberWalletStatus(key2, status) {
4252
4709
  if (status.verified && status.version === "v2") {
4253
4710
  this.withdrawalVersionCache.set(key2, { at: Date.now(), status: { ...status } });
4254
4711
  if (this.withdrawalVersionCache.size > 128)
4255
4712
  this.withdrawalVersionCache.delete(this.withdrawalVersionCache.keys().next().value);
4256
4713
  } else this.withdrawalVersionCache.delete(key2);
4257
- return status;
4258
4714
  }
4259
4715
  /** Explicit owner action. This never runs as a side effect of withdrawal. */
4260
4716
  async upgradeWallet(state, chainId, asset, gasMode = "paid") {
4717
+ const generation = this.readGeneration;
4261
4718
  const before = await this.getWalletStatus(state, chainId, asset);
4262
- if (!before?.verified || !before.upgradeAvailable) {
4719
+ this.assertSession(generation, chainId);
4720
+ if (!before?.verified || before.version === "unknown") {
4263
4721
  throw new OwneyError(
4264
4722
  "WITHDRAW_FAILED",
4265
4723
  "This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
@@ -4267,48 +4725,77 @@ var YieldseekerAgent = class {
4267
4725
  this.id
4268
4726
  );
4269
4727
  }
4270
- const data = encodeFunctionData({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
4271
- let txHash;
4272
- if (gasMode === "sponsored") {
4273
- if (before.ownerPath !== "smart")
4274
- throw new OwneyError(
4275
- "SPONSOR_REQUEST_FAILED",
4276
- "EOA owners pay Base gas for the V1 upgrade.",
4277
- { safeToFallback: true },
4278
- this.id
4279
- );
4280
- let storage5;
4281
- try {
4282
- storage5 = typeof window === "undefined" ? void 0 : window.localStorage;
4283
- } catch {
4284
- storage5 = void 0;
4285
- }
4286
- if (!storage5)
4728
+ const versionKey = `${chainId}:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}:${asset}`;
4729
+ const pendingKey2 = `owney.yieldseeker.upgrade:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
4730
+ const confirmUpgrade = async (hash, receipt2) => {
4731
+ this.withdrawalVersionCache.delete(versionKey);
4732
+ const upgradeTopic = encodeEventTopics({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded" })[0];
4733
+ const lastUpgrade = receipt2?.logs.filter((log) => log.address.toLowerCase() === before.agentWallet.toLowerCase() && log.topics[0] === upgradeTopic).at(-1);
4734
+ const expectedUpgrade = (() => {
4735
+ if (!lastUpgrade || lastUpgrade.removed) return false;
4736
+ try {
4737
+ const event = decodeEventLog2({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded", data: lastUpgrade.data, topics: lastUpgrade.topics });
4738
+ return event.args.implementation.toLowerCase() === YIELDSEEKER_V2_IMPLEMENTATION.toLowerCase();
4739
+ } catch {
4740
+ return false;
4741
+ }
4742
+ })();
4743
+ if (!receipt2 || receipt2.status !== "success" || receipt2.transactionHash.toLowerCase() !== hash.toLowerCase() || typeof receipt2.blockNumber !== "bigint" || !expectedUpgrade)
4287
4744
  throw new OwneyError(
4288
- "SPONSOR_REQUEST_FAILED",
4289
- "Upgrade status cannot be saved in this browser.",
4290
- { safeToFallback: true },
4745
+ "WITHDRAW_FAILED",
4746
+ "Yieldseeker wallet upgrade receipt could not be verified.",
4747
+ { txHash: hash, reason: "UPGRADE_RECEIPT_MISMATCH" },
4291
4748
  this.id
4292
4749
  );
4293
- const pendingKey = `owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
4294
- let prior = null;
4295
- let saved;
4750
+ this.assertSession(generation, chainId);
4751
+ const status = {
4752
+ ...before,
4753
+ implementation: YIELDSEEKER_V2_IMPLEMENTATION,
4754
+ version: "v2",
4755
+ upgradeAvailable: false
4756
+ };
4757
+ this.rememberWalletStatus(versionKey, status);
4758
+ return { txHash: hash, status };
4759
+ };
4760
+ let storage5;
4761
+ try {
4762
+ storage5 = typeof window === "undefined" ? void 0 : window.localStorage;
4763
+ } catch {
4764
+ storage5 = void 0;
4765
+ }
4766
+ const clearConfirmedUpgrade = () => {
4296
4767
  try {
4297
- saved = storage5.getItem(pendingKey);
4768
+ storage5?.removeItem(pendingKey2);
4298
4769
  } catch {
4299
- throw new OwneyError(
4300
- "SPONSOR_REQUEST_FAILED",
4301
- "Upgrade status cannot be read in this browser.",
4302
- { safeToFallback: true },
4303
- this.id
4304
- );
4305
4770
  }
4771
+ };
4772
+ if (!storage5 && (gasMode === "sponsored" || before.ownerPath === "smart"))
4773
+ throw new OwneyError(
4774
+ "SPONSOR_REQUEST_FAILED",
4775
+ "Upgrade status cannot be saved in this browser.",
4776
+ { safeToFallback: false },
4777
+ this.id
4778
+ );
4779
+ let prior = null;
4780
+ try {
4781
+ const saved = storage5?.getItem(pendingKey2);
4306
4782
  try {
4307
4783
  prior = JSON.parse(saved ?? "null");
4308
4784
  } catch {
4309
4785
  prior = { callsId: void 0 };
4310
4786
  }
4311
- if (prior) {
4787
+ } catch {
4788
+ if (gasMode === "paid" && before.ownerPath === "eoa") storage5 = void 0;
4789
+ else throw new OwneyError(
4790
+ "SPONSOR_REQUEST_FAILED",
4791
+ "Upgrade status cannot be read in this browser.",
4792
+ { safeToFallback: false },
4793
+ this.id
4794
+ );
4795
+ }
4796
+ if (prior) {
4797
+ let earlierHash = this.isTransactionHash(prior.txHash) ? prior.txHash : void 0;
4798
+ if (!earlierHash) {
4312
4799
  if (!prior.callsId)
4313
4800
  throw new OwneyError(
4314
4801
  "SPONSOR_REQUEST_FAILED",
@@ -4318,10 +4805,7 @@ var YieldseekerAgent = class {
4318
4805
  );
4319
4806
  let callsStatus;
4320
4807
  try {
4321
- callsStatus = await state.provider.request({
4322
- method: "wallet_getCallsStatus",
4323
- params: [prior.callsId]
4324
- });
4808
+ callsStatus = await state.provider.request({ method: "wallet_getCallsStatus", params: [prior.callsId] });
4325
4809
  } catch {
4326
4810
  throw new OwneyError(
4327
4811
  "SPONSOR_REQUEST_FAILED",
@@ -4330,8 +4814,15 @@ var YieldseekerAgent = class {
4330
4814
  this.id
4331
4815
  );
4332
4816
  }
4333
- if (callsStatus?.status === 400 || callsStatus?.status === 500) {
4334
- storage5.removeItem(pendingKey);
4817
+ if (callsStatus?.id !== void 0 && callsStatus.id !== prior.callsId || callsStatus?.chainId !== void 0 && Number(callsStatus.chainId) !== chainId)
4818
+ throw new OwneyError(
4819
+ "SPONSOR_REQUEST_FAILED",
4820
+ "The earlier upgrade returned a different operation status.",
4821
+ { safeToFallback: false, callsId: prior.callsId },
4822
+ this.id
4823
+ );
4824
+ if (Number(callsStatus?.status) === 400 || Number(callsStatus?.status) === 500) {
4825
+ storage5.removeItem(pendingKey2);
4335
4826
  throw new OwneyError(
4336
4827
  "SPONSOR_REQUEST_FAILED",
4337
4828
  "The earlier sponsored upgrade failed.",
@@ -4339,22 +4830,39 @@ var YieldseekerAgent = class {
4339
4830
  this.id
4340
4831
  );
4341
4832
  }
4342
- const earlierHash = callsStatus?.receipts?.[0]?.transactionHash;
4343
- if (earlierHash) {
4344
- await this.waitForReceipt(state, chainId, earlierHash);
4345
- const earlierStatus = await this.getWalletStatus(state, chainId, asset);
4346
- if (earlierStatus?.version === "v2" && earlierStatus.verified) {
4347
- storage5.removeItem(pendingKey);
4348
- return { txHash: earlierHash, status: earlierStatus };
4349
- }
4350
- }
4833
+ const hashes = [...new Set((callsStatus?.receipts ?? []).map((value) => value.transactionHash).filter(this.isTransactionHash))];
4834
+ earlierHash = hashes.length === 1 ? hashes[0] : void 0;
4835
+ }
4836
+ if (earlierHash) {
4837
+ const recovered = await confirmUpgrade(earlierHash, await this.waitForReceipt(state, chainId, earlierHash));
4838
+ clearConfirmedUpgrade();
4839
+ return recovered;
4840
+ }
4841
+ throw new OwneyError(
4842
+ "SPONSOR_REQUEST_FAILED",
4843
+ "An earlier upgrade is still pending.",
4844
+ { safeToFallback: false, callsId: prior.callsId },
4845
+ this.id
4846
+ );
4847
+ }
4848
+ if (!before.upgradeAvailable)
4849
+ throw new OwneyError(
4850
+ "WITHDRAW_FAILED",
4851
+ "This Yieldseeker wallet cannot be upgraded to the reviewed V2 implementation.",
4852
+ { version: before.version },
4853
+ this.id
4854
+ );
4855
+ const data = encodeFunctionData({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
4856
+ let txHash;
4857
+ let receipt;
4858
+ if (gasMode === "sponsored") {
4859
+ if (before.ownerPath !== "smart")
4351
4860
  throw new OwneyError(
4352
4861
  "SPONSOR_REQUEST_FAILED",
4353
- "An earlier upgrade is still pending.",
4354
- { safeToFallback: false, callsId: prior.callsId },
4862
+ "EOA owners pay Base gas for the V1 upgrade.",
4863
+ { safeToFallback: true },
4355
4864
  this.id
4356
4865
  );
4357
- }
4358
4866
  const context = await this.findAgent(state, chainId, asset);
4359
4867
  if (!context) throw new OwneyError("NOT_CONNECTED", "Yieldseeker agent is unavailable.");
4360
4868
  try {
@@ -4364,14 +4872,15 @@ var YieldseekerAgent = class {
4364
4872
  apiKey: this.owneyApiKey,
4365
4873
  routingApiBaseUrl: this.routingApiBaseUrl,
4366
4874
  yieldseekerSignature: await this.auth.getToken(state, chainId),
4875
+ refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
4367
4876
  userId: context.user.userId,
4368
4877
  agentId: context.agent.agentId,
4369
4878
  agentWallet: before.agentWallet,
4370
4879
  asset: YIELDSEEKER_ASSET_METADATA[asset].address,
4371
4880
  callData: data,
4372
4881
  onPending: (pending) => {
4373
- const current = storage5.getItem(pendingKey);
4374
- storage5.setItem(pendingKey, JSON.stringify({
4882
+ const current = storage5.getItem(pendingKey2);
4883
+ storage5.setItem(pendingKey2, JSON.stringify({
4375
4884
  startedAt: Date.now(),
4376
4885
  ...current ? JSON.parse(current) : {},
4377
4886
  ...pending
@@ -4379,8 +4888,9 @@ var YieldseekerAgent = class {
4379
4888
  }
4380
4889
  });
4381
4890
  txHash = upgraded.txHash;
4891
+ receipt = upgraded.receipt;
4382
4892
  } catch (error) {
4383
- if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey);
4893
+ if (error?.code === 4001 || error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)) storage5.removeItem(pendingKey2);
4384
4894
  throw error;
4385
4895
  }
4386
4896
  } else {
@@ -4390,20 +4900,13 @@ var YieldseekerAgent = class {
4390
4900
  data,
4391
4901
  value: "0",
4392
4902
  chainId
4903
+ }, void 0, (value) => {
4904
+ receipt = value;
4393
4905
  });
4394
4906
  }
4395
- const status = await this.getWalletStatus(state, chainId, asset);
4396
- if (!status?.verified || status.version !== "v2") {
4397
- throw new OwneyError(
4398
- "WITHDRAW_FAILED",
4399
- "Yieldseeker wallet upgrade was not confirmed as V2.",
4400
- { txHash, version: status?.version ?? "missing" },
4401
- this.id
4402
- );
4403
- }
4404
- if (gasMode === "sponsored" && typeof window !== "undefined")
4405
- window.localStorage.removeItem(`owney.yieldseeker.upgrade:${state.walletAddress.toLowerCase()}:${before.agentWallet.toLowerCase()}`);
4406
- return { txHash, status };
4907
+ const confirmed = await confirmUpgrade(txHash, receipt ?? await this.waitForReceipt(state, chainId, txHash));
4908
+ if (gasMode === "sponsored") clearConfirmedUpgrade();
4909
+ return confirmed;
4407
4910
  }
4408
4911
  async disconnect() {
4409
4912
  this.withdrawalVersionCache.clear();
@@ -4476,32 +4979,50 @@ var YieldseekerAgent = class {
4476
4979
  submitted(txHash);
4477
4980
  receipt = await this.waitForReceipt(state, chainId, txHash);
4478
4981
  } else {
4479
- txHash = await this.submitTransaction(state, chainId, {
4480
- from: getAddress4(state.walletAddress),
4481
- to: YIELDSEEKER_ASSET_METADATA[asset].address,
4482
- data: encodeFunctionData({
4483
- abi: erc20Abi2,
4484
- functionName: "transfer",
4485
- args: [getAddress4(context.wallet.walletAddress), BigInt(amount)]
4486
- }),
4487
- value: "0",
4488
- chainId
4489
- }, submitted, (value) => {
4490
- receipt = value;
4491
- });
4492
- }
4493
- if (this.readGeneration === generation) {
4494
- void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
4495
- }
4496
- const base6 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount, confirmedAmount: amount, txHash, transfer: "confirmed", registration: "not-applicable", delivery: "not-applicable" };
4497
- const evidence = receipt ? inspectFundingReceipt(receipt, base6, YIELDSEEKER_ASSET_METADATA[asset].address, "deposit") : base6;
4498
- notifyFunding(onProgress, evidence);
4499
- return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
4500
- }
4501
- async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto") {
4502
- let ownerTransferAttempted = false;
4503
- let evidence = {
4504
- agentId: this.id,
4982
+ try {
4983
+ txHash = await this.submitTransaction(state, chainId, {
4984
+ from: getAddress4(state.walletAddress),
4985
+ to: YIELDSEEKER_ASSET_METADATA[asset].address,
4986
+ data: encodeFunctionData({
4987
+ abi: erc20Abi3,
4988
+ functionName: "transfer",
4989
+ args: [getAddress4(context.wallet.walletAddress), BigInt(amount)]
4990
+ }),
4991
+ value: "0",
4992
+ chainId
4993
+ }, submitted, (value) => {
4994
+ receipt = value;
4995
+ });
4996
+ } catch (error) {
4997
+ if (error instanceof OwneyError && error.details?.ownerTransferNotExecuted === true && error.details.reason === "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL") {
4998
+ notifyFunding(onProgress, {
4999
+ agentId: this.id,
5000
+ chainId,
5001
+ token: asset,
5002
+ walletAddress: state.walletAddress,
5003
+ agentWallet: context.wallet.walletAddress,
5004
+ intendedAmount: amount,
5005
+ txHash: String(error.details.originalTransactionHash),
5006
+ transfer: "failed",
5007
+ registration: "not-applicable",
5008
+ delivery: "not-applicable"
5009
+ });
5010
+ }
5011
+ throw error;
5012
+ }
5013
+ }
5014
+ if (this.readGeneration === generation) {
5015
+ void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
5016
+ }
5017
+ const base6 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount, confirmedAmount: amount, txHash, transfer: "confirmed", registration: "not-applicable", delivery: "not-applicable" };
5018
+ const evidence = receipt ? inspectFundingReceipt(receipt, base6, YIELDSEEKER_ASSET_METADATA[asset].address, "deposit") : base6;
5019
+ notifyFunding(onProgress, evidence);
5020
+ return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
5021
+ }
5022
+ async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
5023
+ let ownerTransferAttempted = false;
5024
+ let evidence = {
5025
+ agentId: this.id,
4505
5026
  chainId,
4506
5027
  token: asset,
4507
5028
  walletAddress: state.walletAddress,
@@ -4516,10 +5037,10 @@ var YieldseekerAgent = class {
4516
5037
  notifyFunding(onProgress, evidence);
4517
5038
  }, () => {
4518
5039
  ownerTransferAttempted = true;
4519
- }, gasMode);
5040
+ }, gasMode, onBeforeSubmission);
4520
5041
  } catch (error) {
4521
5042
  if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
4522
- if (!ownerTransferAttempted || error?.code === 4001 || error instanceof OwneyError && (error.code === "AGENT_TRANSACTION_REVERTED" || error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)))
5043
+ if (!ownerTransferAttempted || error?.code === 4001 || error instanceof OwneyError && (error.code === "AGENT_TRANSACTION_REVERTED" || error.details?.ownerTransferNotExecuted === true || error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)))
4523
5044
  notifyFunding(onProgress, {
4524
5045
  ...evidence,
4525
5046
  ...error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true ? { userOpHash: void 0, callsId: void 0 } : {},
@@ -4529,7 +5050,7 @@ var YieldseekerAgent = class {
4529
5050
  throw error;
4530
5051
  }
4531
5052
  }
4532
- async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto") {
5053
+ async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
4533
5054
  this.assertChain(chainId);
4534
5055
  this.assertAsset(asset);
4535
5056
  if (amount !== void 0 && BigInt(amount) <= 0n) {
@@ -4574,31 +5095,170 @@ var YieldseekerAgent = class {
4574
5095
  let confirmedMovement = false;
4575
5096
  let settlement = null;
4576
5097
  try {
4577
- const portfolio = await this.loadPortfolioContext(
4578
- state,
4579
- chainId,
4580
- context
4581
- );
5098
+ const maxSettlementBasis = amount === void 0 ? this.snapshotMovement(state, chainId, context, 0n) : null;
5099
+ this.advancePortfolioVersion(this.contextKey(state, chainId, asset));
5100
+ const portfolioRead = amount === void 0 ? this.loadActivityContext(state, chainId, context, {}).then((value) => ({ positions: value.positions, feeRateBps: 0 })) : this.loadPortfolioContext(state, chainId, context).then((value) => ({ positions: value.positions, feeRateBps: value.snapshot.feeRateBps }));
4582
5101
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
4583
- const readIdle = async () => {
4584
- const balance = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
4585
- address: metadata.address,
4586
- abi: erc20Abi2,
4587
- functionName: "balanceOf",
4588
- args: [getAddress4(context.wallet.walletAddress)]
4589
- }), chainId, "eth_call", this.id);
5102
+ const agentWallet = getAddress4(context.wallet.walletAddress);
5103
+ const feeTrackerRead = withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5104
+ address: agentWallet,
5105
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
5106
+ functionName: "feeTracker"
5107
+ }), chainId, "eth_call", this.id);
5108
+ const readOwnerFunds = async () => {
5109
+ const feeTracker = await feeTrackerRead;
5110
+ this.assertSession(generation, chainId, asset);
5111
+ if (!isAddress3(feeTracker) || /^0x0{40}$/i.test(feeTracker))
5112
+ throw this.invalidResponse("wallet fee tracker");
5113
+ const [idle2, fees] = await Promise.all([
5114
+ withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5115
+ address: metadata.address,
5116
+ abi: erc20Abi3,
5117
+ functionName: "balanceOf",
5118
+ args: [agentWallet]
5119
+ }), chainId, "eth_call", this.id),
5120
+ withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5121
+ address: getAddress4(feeTracker),
5122
+ abi: YIELDSEEKER_FEE_TRACKER_ABI,
5123
+ functionName: "getFeesOwed",
5124
+ args: [agentWallet]
5125
+ }), chainId, "eth_call", this.id)
5126
+ ]);
4590
5127
  this.assertSession(generation, chainId, asset);
4591
- return balance;
5128
+ if (typeof idle2 !== "bigint" || idle2 < 0n || typeof fees !== "bigint" || fees < 0n)
5129
+ throw this.invalidResponse("wallet withdrawal balance");
5130
+ return { idle: idle2, fees, available: idle2 > fees ? idle2 - fees : 0n };
4592
5131
  };
4593
- const idle = await readIdle();
4594
- const deployed = portfolio.positions.reduce(
5132
+ const [portfolio, initialFunds] = await Promise.all([portfolioRead, readOwnerFunds()]);
5133
+ const idle = initialFunds.idle;
5134
+ let positions = portfolio.positions;
5135
+ let positionsStatus = "empty";
5136
+ const validatePositions = (values) => {
5137
+ const seen = /* @__PURE__ */ new Set();
5138
+ for (const value of values) {
5139
+ const address = value?.yieldOption?.address;
5140
+ if (typeof address !== "string" || !isAddress3(address) || seen.has(address.toLowerCase()) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
5141
+ throw this.invalidResponse("yield positions");
5142
+ seen.add(address.toLowerCase());
5143
+ }
5144
+ return values;
5145
+ };
5146
+ const zeroValuePositions = /* @__PURE__ */ new WeakSet();
5147
+ const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
5148
+ const knownPositions = /* @__PURE__ */ new Map();
5149
+ const rememberPositions = (values) => {
5150
+ for (const position2 of values) knownPositions.set(position2.yieldOption.address.toLowerCase(), position2);
5151
+ };
5152
+ const boundedVerification = async (read2, timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
5153
+ let timer;
5154
+ try {
5155
+ return await Promise.race([read2, new Promise((resolve) => {
5156
+ timer = setTimeout(() => resolve(void 0), Math.max(0, timeoutMs));
5157
+ })]);
5158
+ } finally {
5159
+ if (timer) clearTimeout(timer);
5160
+ }
5161
+ };
5162
+ const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
5163
+ const verifiedPositions = [];
5164
+ if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
5165
+ const reads = values.map(async (position2) => {
5166
+ const address = getAddress4(position2.yieldOption.address);
5167
+ try {
5168
+ const [underlying, shares, withdrawable] = await Promise.all([
5169
+ this.getReceiptClient().readContract({ address, abi: erc4626Abi, functionName: "asset", blockNumber }),
5170
+ this.getReceiptClient().readContract({ address, abi: erc20Abi3, functionName: "balanceOf", args: [agentWallet], blockNumber }),
5171
+ this.getReceiptClient().readContract({ address, abi: erc4626Abi, functionName: "maxWithdraw", args: [agentWallet], blockNumber })
5172
+ ]);
5173
+ if (typeof underlying !== "string" || !isAddress3(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
5174
+ const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: withdrawable.toString() };
5175
+ if (shares > 0n && withdrawable === 0n) {
5176
+ let positiveValue = false;
5177
+ const valuations = await Promise.all(["convertToAssets", "previewRedeem"].map(async (functionName) => {
5178
+ try {
5179
+ const value = await this.getReceiptClient().readContract({
5180
+ address,
5181
+ abi: erc4626Abi,
5182
+ functionName,
5183
+ args: [shares],
5184
+ blockNumber
5185
+ });
5186
+ if (typeof value !== "bigint" || value < 0n) return null;
5187
+ if (value > 0n && !positiveValue) {
5188
+ positiveValue = true;
5189
+ verifiedPositions.push(verified);
5190
+ }
5191
+ return value;
5192
+ } catch {
5193
+ return null;
5194
+ }
5195
+ }));
5196
+ if (positiveValue) return verified;
5197
+ if (blockNumber === void 0 || valuations.some((value) => value !== 0n)) return null;
5198
+ zeroValuePositions.add(verified);
5199
+ }
5200
+ verifiedPositions.push(verified);
5201
+ return verified;
5202
+ } catch {
5203
+ return null;
5204
+ }
5205
+ });
5206
+ const result = await boundedVerification(Promise.all(reads), timeoutMs);
5207
+ this.assertSession(generation, chainId, asset);
5208
+ return {
5209
+ verified: result !== void 0 && result.every((position2) => position2 !== null),
5210
+ positions: [...verifiedPositions]
5211
+ };
5212
+ };
5213
+ const refreshPositions = async () => {
5214
+ this.assertSession(generation, chainId, asset);
5215
+ const response = await this.walletRequest(
5216
+ state,
5217
+ chainId,
5218
+ this.agentPath(context, "yield-positions")
5219
+ );
5220
+ this.assertSession(generation, chainId, asset);
5221
+ if (!Array.isArray(response?.yieldPositions)) throw this.invalidResponse("yield positions");
5222
+ return validatePositions(response.yieldPositions);
5223
+ };
5224
+ if (amount === void 0) {
5225
+ validatePositions(positions);
5226
+ rememberPositions(positions);
5227
+ positionsStatus = hasPositions(positions) ? "remaining" : "empty";
5228
+ }
5229
+ const deployed = positions.reduce(
4595
5230
  (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
4596
5231
  0n
4597
5232
  );
4598
- const totalAvailable = idle + deployed;
5233
+ const grossAvailable = idle + deployed;
5234
+ const totalAvailable = grossAvailable > initialFunds.fees ? grossAvailable - initialFunds.fees : 0n;
4599
5235
  const requested = amount === void 0 ? totalAvailable : BigInt(amount);
4600
5236
  if (requested === 0n) {
4601
5237
  this.invalidateFundingRead(state, chainId, asset);
5238
+ const zeroPayoutCandidates = positions.filter((position2) => BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n);
5239
+ if (amount === void 0 && zeroPayoutCandidates.length) {
5240
+ const deadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
5241
+ const blockNumber = await boundedVerification(
5242
+ Promise.resolve().then(() => this.getReceiptClient().getBlockNumber()).catch(() => void 0)
5243
+ );
5244
+ const proof = typeof blockNumber === "bigint" ? await verifyKnownPositions(blockNumber, zeroPayoutCandidates, deadline - Date.now()) : { verified: false, positions: [] };
5245
+ positions = proof.positions;
5246
+ positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
5247
+ if (positionsStatus !== "empty")
5248
+ throw new OwneyError(
5249
+ "WITHDRAW_FAILED",
5250
+ "Yieldseeker vault funds are not currently withdrawable.",
5251
+ { asset, positionsStatus, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
5252
+ this.id
5253
+ );
5254
+ }
5255
+ if (grossAvailable > 0n && totalAvailable === 0n)
5256
+ throw new OwneyError(
5257
+ "WITHDRAW_INSUFFICIENT_BALANCE",
5258
+ "No owner-withdrawable Yieldseeker funds remain after reserved fees.",
5259
+ { asset, available: "0", reason: "YIELDSEEKER_FEES_ONLY", feesReserved: initialFunds.fees.toString() },
5260
+ this.id
5261
+ );
4602
5262
  throw new OwneyError(
4603
5263
  "WITHDRAW_INSUFFICIENT_BALANCE",
4604
5264
  "No Yieldseeker funds are available to withdraw.",
@@ -4618,46 +5278,104 @@ var YieldseekerAgent = class {
4618
5278
  this.id
4619
5279
  );
4620
5280
  }
4621
- let remaining = requested > idle ? requested - idle : 0n;
5281
+ const requiredIdle = requested + initialFunds.fees;
5282
+ let remaining = requiredIdle > idle ? requiredIdle - idle : 0n;
4622
5283
  let grossUnwound = 0n;
4623
- for (const position2 of portfolio.positions) {
4624
- if (remaining === 0n) break;
4625
- const available = BigInt(position2.withdrawableAssetsRaw);
4626
- if (available <= 0n) continue;
4627
- const assetsRaw = available < remaining ? available : remaining;
4628
- const response = await this.walletRequest(
4629
- state,
4630
- chainId,
4631
- this.agentPath(context, "withdraw-from-position"),
4632
- {
4633
- method: "POST",
4634
- body: {
4635
- chainId,
4636
- vaultAddress: position2.yieldOption.address,
4637
- assetsRaw: assetsRaw.toString()
5284
+ let latestUnwindBlock;
5285
+ const unwind = async (position2, assetsRaw) => {
5286
+ this.assertSession(generation, chainId, asset);
5287
+ let unwindTransactionHash;
5288
+ try {
5289
+ const response = await this.walletRequest(
5290
+ state,
5291
+ chainId,
5292
+ this.agentPath(context, "withdraw-from-position"),
5293
+ {
5294
+ method: "POST",
5295
+ body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
4638
5296
  }
4639
- }
4640
- );
4641
- if (!this.isTransactionHash(response?.transactionHash)) {
4642
- throw this.invalidResponse("position withdrawal");
5297
+ );
5298
+ if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
5299
+ unwindTransactionHash = response.transactionHash;
5300
+ const unwindReceipt = await this.waitForReceipt(state, chainId, unwindTransactionHash);
5301
+ if (unwindReceipt) latestUnwindBlock = unwindReceipt.blockNumber;
5302
+ } catch (error) {
5303
+ this.invalidateFundingRead(state, chainId, asset);
5304
+ throw new OwneyError(
5305
+ error instanceof OwneyError ? error.code : "WITHDRAW_FAILED",
5306
+ "Could not confirm the Yieldseeker vault release. No owner transfer was submitted.",
5307
+ {
5308
+ ...error instanceof OwneyError ? error.details : {},
5309
+ withdrawalPhase: "unwind",
5310
+ reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
5311
+ vaultAddress: position2.yieldOption.address,
5312
+ unwindTransactionHash,
5313
+ ownerTransferSubmitted: false
5314
+ },
5315
+ this.id
5316
+ );
4643
5317
  }
4644
- await this.waitForReceipt(state, chainId, response.transactionHash);
4645
5318
  confirmedMovement = true;
5319
+ this.assertSession(generation, chainId, asset);
4646
5320
  grossUnwound += assetsRaw;
4647
- remaining -= assetsRaw;
5321
+ };
5322
+ if (amount === void 0) {
5323
+ for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
5324
+ const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
5325
+ let unwound = false;
5326
+ for (const position2 of positions) {
5327
+ const available = BigInt(position2.withdrawableAssetsRaw);
5328
+ if (available <= 0n) continue;
5329
+ await unwind(position2, available);
5330
+ unwound = true;
5331
+ }
5332
+ if (!unwound) break;
5333
+ const knownBeforeRefresh = new Set(knownPositions.keys());
5334
+ const [catalog, proof] = await Promise.all([
5335
+ boundedVerification(refreshPositions().catch(() => void 0)),
5336
+ verifyKnownPositions(latestUnwindBlock)
5337
+ ]);
5338
+ if (catalog) rememberPositions(catalog);
5339
+ this.assertSession(generation, chainId, asset);
5340
+ if (proof.verified) {
5341
+ positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.yieldOption.address.toLowerCase()))];
5342
+ } else if (catalog) positions = catalog;
5343
+ else {
5344
+ positionsStatus = "unavailable";
5345
+ break;
5346
+ }
5347
+ positionsStatus = hasPositions(positions) ? "remaining" : "empty";
5348
+ if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
5349
+ }
5350
+ } else {
5351
+ for (const position2 of positions) {
5352
+ if (remaining === 0n) break;
5353
+ const available = BigInt(position2.withdrawableAssetsRaw);
5354
+ if (available <= 0n) continue;
5355
+ const assetsRaw = available < remaining ? available : remaining;
5356
+ await unwind(position2, assetsRaw);
5357
+ remaining -= assetsRaw;
5358
+ }
4648
5359
  }
4649
- if (remaining > 0n) {
5360
+ if (amount !== void 0 && remaining > 0n) {
4650
5361
  throw this.invalidResponse("yield positions", {
4651
5362
  reason: "Withdrawable positions could not cover the request.",
4652
5363
  remaining: remaining.toString()
4653
5364
  });
4654
5365
  }
4655
- const availableForOwner = confirmedMovement ? await readIdle() : idle;
4656
- const feeRate = portfolio.snapshot.feeRateBps;
5366
+ const availableForOwner = confirmedMovement ? (await readOwnerFunds()).available : initialFunds.available;
5367
+ const feeRate = portfolio.feeRateBps;
4657
5368
  const maxFee = confirmedMovement && Number.isInteger(feeRate) && feeRate >= 0 && feeRate <= 1e3 ? (grossUnwound * BigInt(feeRate) + 9999n) / 10000n : 0n;
4658
5369
  const shortfall = availableForOwner < requested ? requested - availableForOwner : 0n;
4659
5370
  if (availableForOwner === 0n || amount !== void 0 && shortfall > maxFee) {
4660
5371
  this.invalidateFundingRead(state, chainId, asset);
5372
+ if (amount === void 0 && positionsStatus !== "empty")
5373
+ throw new OwneyError(
5374
+ "WITHDRAW_FAILED",
5375
+ "Yieldseeker vault funds could not be cleared for the owner transfer.",
5376
+ { asset, positionsStatus },
5377
+ this.id
5378
+ );
4661
5379
  throw new OwneyError(
4662
5380
  "WITHDRAW_INSUFFICIENT_BALANCE",
4663
5381
  "Yieldseeker funds are not available for the owner transfer.",
@@ -4666,7 +5384,11 @@ var YieldseekerAgent = class {
4666
5384
  );
4667
5385
  }
4668
5386
  const payout = amount === void 0 || availableForOwner < requested ? availableForOwner : requested;
4669
- const plannedMovement = this.snapshotMovement(state, chainId, context, -payout);
5387
+ const plannedMovement = amount === void 0 && maxSettlementBasis ? {
5388
+ ...maxSettlementBasis,
5389
+ delta: maxSettlementBasis.delta - payout,
5390
+ expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
5391
+ } : this.snapshotMovement(state, chainId, context, -payout);
4670
5392
  const account = getAddress4(state.walletAddress);
4671
5393
  let receipt;
4672
5394
  const base6 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount === void 0 ? "MAX" : payout.toString(), transfer: "pending", registration: "not-applicable", delivery: "pending" };
@@ -4679,7 +5401,6 @@ var YieldseekerAgent = class {
4679
5401
  functionName: "withdrawAssetToUser",
4680
5402
  args: [account, metadata.address, payout]
4681
5403
  });
4682
- onOwnerTransferAttempted?.();
4683
5404
  let txHash;
4684
5405
  if (sponsor) {
4685
5406
  const submitted = await submitYieldseekerSponsoredWithdrawal({
@@ -4688,16 +5409,22 @@ var YieldseekerAgent = class {
4688
5409
  apiKey: this.owneyApiKey,
4689
5410
  routingApiBaseUrl: this.routingApiBaseUrl,
4690
5411
  yieldseekerSignature: await this.auth.getToken(state, chainId),
5412
+ refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
4691
5413
  userId: context.user.userId,
4692
5414
  agentId: context.agent.agentId,
4693
5415
  agentWallet: getAddress4(context.wallet.walletAddress),
4694
5416
  asset: metadata.address,
4695
5417
  callData: data,
4696
- onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending })
5418
+ onPending: (pending) => notifyFunding(onProgress, { ...base6, ...pending }),
5419
+ onBeforeSubmission: async (pending) => {
5420
+ await onBeforeSubmission?.({ ...base6, ...pending });
5421
+ onOwnerTransferAttempted?.();
5422
+ }
4697
5423
  });
4698
5424
  txHash = submitted.txHash;
4699
5425
  receipt = submitted.receipt;
4700
5426
  } else {
5427
+ onOwnerTransferAttempted?.();
4701
5428
  txHash = await this.submitTransaction(state, chainId, {
4702
5429
  from: account,
4703
5430
  to: getAddress4(context.wallet.walletAddress),
@@ -4712,6 +5439,46 @@ var YieldseekerAgent = class {
4712
5439
  settlement = plannedMovement;
4713
5440
  const evidence = receipt ? inspectFundingReceipt(receipt, { ...base6, txHash }, metadata.address, "withdraw") : { ...base6, txHash, confirmedAmount: payout.toString(), transfer: "confirmed", delivery: "confirmed" };
4714
5441
  notifyFunding(onProgress, evidence);
5442
+ let completion;
5443
+ if (amount === void 0) {
5444
+ const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
5445
+ const knownAtPayout = new Set(knownPositions.keys());
5446
+ const [catalogVerified, knownProof] = await Promise.all([
5447
+ boundedVerification(refreshPositions().then((values) => {
5448
+ rememberPositions(values);
5449
+ return true;
5450
+ }).catch(() => false)),
5451
+ verifyKnownPositions(receipt?.blockNumber)
5452
+ ]);
5453
+ const newPositions = [...knownPositions].filter(([address]) => !knownAtPayout.has(address)).map(([, position2]) => position2);
5454
+ const newProof = newPositions.length ? await verifyKnownPositions(receipt?.blockNumber, newPositions, verificationDeadline - Date.now()) : { verified: true, positions: [] };
5455
+ const proof = {
5456
+ verified: catalogVerified === true && knownProof.verified && newProof.verified,
5457
+ positions: [...knownProof.positions, ...newProof.positions]
5458
+ };
5459
+ positions = proof.positions;
5460
+ positionsStatus = hasPositions(positions) ? "remaining" : proof.verified ? "empty" : "unavailable";
5461
+ if (positionsStatus === "remaining") {
5462
+ const result = { txHash, type: "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
5463
+ throw new OwneyError(
5464
+ "WITHDRAW_PARTIAL_FAILURE",
5465
+ "Yieldseeker MAX could not confirm that all vault positions were cleared.",
5466
+ {
5467
+ reason: "YIELDSEEKER_MAX_INCOMPLETE",
5468
+ asset,
5469
+ positionsStatus,
5470
+ remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
5471
+ vaultAddress: position2.yieldOption.address,
5472
+ sharesRaw: position2.assetsRaw,
5473
+ withdrawableAmountRaw: position2.withdrawableAssetsRaw
5474
+ })) : void 0,
5475
+ partialResults: { [this.id]: result }
5476
+ },
5477
+ this.id
5478
+ );
5479
+ }
5480
+ if (!proof.verified) completion = "unverified";
5481
+ }
4715
5482
  if (evidence.transfer === "failed" && evidence.confirmedAmount === "0")
4716
5483
  throw new OwneyError(
4717
5484
  "WITHDRAW_INSUFFICIENT_BALANCE",
@@ -4719,7 +5486,13 @@ var YieldseekerAgent = class {
4719
5486
  { asset, available: "0", txHash },
4720
5487
  this.id
4721
5488
  );
4722
- return { txHash, type: amount === void 0 ? "full" : "partial", amount: evidence.confirmedAmount ?? payout.toString(), evidence };
5489
+ return {
5490
+ txHash,
5491
+ type: amount === void 0 && !completion ? "full" : "partial",
5492
+ ...completion ? { completion } : {},
5493
+ amount: evidence.confirmedAmount ?? payout.toString(),
5494
+ evidence
5495
+ };
4723
5496
  } finally {
4724
5497
  if (confirmedMovement && this.readGeneration === generation) {
4725
5498
  void this.refreshSnapshotAfterMovement(
@@ -4766,6 +5539,23 @@ var YieldseekerAgent = class {
4766
5539
  if (!context) throw new OwneyError("NOT_CONNECTED", "Reconnect the agent before recovering funding.");
4767
5540
  return context.wallet.walletAddress;
4768
5541
  }
5542
+ async hasExistingDepositFunds(state, chainId, asset) {
5543
+ this.assertChain(chainId);
5544
+ this.assertAsset(asset);
5545
+ const generation = this.readGeneration;
5546
+ const context = await this.findAgent(state, chainId, asset);
5547
+ this.assertSession(generation, chainId, asset);
5548
+ if (!context) return void 0;
5549
+ const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5550
+ address: YIELDSEEKER_ASSET_METADATA[asset].address,
5551
+ abi: erc20Abi3,
5552
+ functionName: "balanceOf",
5553
+ args: [getAddress4(context.wallet.walletAddress)]
5554
+ }), chainId, "eth_call", this.id);
5555
+ this.assertSession(generation, chainId, asset);
5556
+ if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
5557
+ return idle > 0n ? true : void 0;
5558
+ }
4769
5559
  async getEarnings(state, chainId) {
4770
5560
  this.assertChain(chainId);
4771
5561
  return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
@@ -4796,7 +5586,7 @@ var YieldseekerAgent = class {
4796
5586
  );
4797
5587
  const vaultAddresses = new Set(
4798
5588
  catalog.flat().filter(
4799
- (yieldOption) => yieldOption.chainId === chainId && isAddress2(yieldOption.address)
5589
+ (yieldOption) => yieldOption.chainId === chainId && isAddress3(yieldOption.address)
4800
5590
  ).map((yieldOption) => yieldOption.address.toLowerCase())
4801
5591
  );
4802
5592
  return mapYieldseekerHistory(contexts, {
@@ -4923,7 +5713,7 @@ var YieldseekerAgent = class {
4923
5713
  `/users/${user.userId}/agents/${agent.agentId}/wallet`
4924
5714
  ).then((walletResponse) => {
4925
5715
  this.assertSession(generation, chainId, asset);
4926
- if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
5716
+ if (!walletResponse?.agentWallet || !isAddress3(walletResponse.agentWallet.walletAddress)) {
4927
5717
  throw this.invalidResponse("agent wallet");
4928
5718
  }
4929
5719
  const context = {
@@ -5336,7 +6126,7 @@ var YieldseekerAgent = class {
5336
6126
  this.agentPath(context, "deploy"),
5337
6127
  { method: "POST", body: {} }
5338
6128
  );
5339
- if (!deployed?.agentWallet || !isAddress2(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
6129
+ if (!deployed?.agentWallet || !isAddress3(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
5340
6130
  throw this.invalidResponse("agent deployment", {
5341
6131
  reason: "Deploy did not return the expected Agent Wallet."
5342
6132
  });
@@ -5467,25 +6257,79 @@ var YieldseekerAgent = class {
5467
6257
  value: BigInt(transaction.value)
5468
6258
  });
5469
6259
  onSubmitted?.(hash);
6260
+ let replacement;
5470
6261
  const receipt = await withPaidRpcDiagnostics(
5471
6262
  () => this.getReceiptClient().waitForTransactionReceipt({
5472
6263
  hash,
5473
- confirmations: 1
6264
+ confirmations: 1,
6265
+ onReplaced: (value) => {
6266
+ replacement = value;
6267
+ }
5474
6268
  }),
5475
6269
  8453,
5476
6270
  "eth_getTransactionReceipt",
5477
6271
  this.id
5478
6272
  );
6273
+ const actualHash = receipt.transactionHash;
6274
+ if (!this.isTransactionHash(actualHash))
6275
+ throw new OwneyError(
6276
+ "WITHDRAW_FAILED",
6277
+ "The confirmed transaction hash could not be verified.",
6278
+ { originalTransactionHash: hash, ownerTransferNotExecuted: false },
6279
+ this.id
6280
+ );
6281
+ if (actualHash.toLowerCase() !== hash.toLowerCase()) {
6282
+ if (!replacement || replacement.transaction.hash.toLowerCase() !== actualHash.toLowerCase()) {
6283
+ try {
6284
+ const [original2, actual2] = await Promise.all([
6285
+ this.getReceiptClient().getTransaction({ hash }),
6286
+ this.getReceiptClient().getTransaction({ hash: actualHash })
6287
+ ]);
6288
+ replacement = { replacedTransaction: original2, transaction: actual2 };
6289
+ } catch {
6290
+ replacement = void 0;
6291
+ }
6292
+ }
6293
+ const original = replacement?.replacedTransaction;
6294
+ const actual = replacement?.transaction;
6295
+ const sameNonce = !!original && !!actual && original.hash.toLowerCase() === hash.toLowerCase() && actual.hash.toLowerCase() === actualHash.toLowerCase() && original.from.toLowerCase() === account.toLowerCase() && actual.from.toLowerCase() === account.toLowerCase() && original.nonce === actual.nonce && (actual.chainId === void 0 || actual.chainId === chainId) && receipt.blockNumber !== null && receipt.blockNumber !== void 0;
6296
+ if (!sameNonce)
6297
+ throw new OwneyError(
6298
+ "WITHDRAW_FAILED",
6299
+ "The replacement transaction could not be verified.",
6300
+ {
6301
+ originalTransactionHash: hash,
6302
+ replacementTransactionHash: actualHash,
6303
+ reason: "TRANSACTION_REPLACEMENT_UNRESOLVED",
6304
+ ownerTransferNotExecuted: false
6305
+ },
6306
+ this.id
6307
+ );
6308
+ const sameCall = actual.to?.toLowerCase() === transaction.to.toLowerCase() && actual.input?.toLowerCase() === transaction.data.toLowerCase() && actual.value === BigInt(transaction.value);
6309
+ if (!sameCall)
6310
+ throw new OwneyError(
6311
+ "WITHDRAW_FAILED",
6312
+ "The transaction was replaced without executing this withdrawal.",
6313
+ {
6314
+ originalTransactionHash: hash,
6315
+ replacementTransactionHash: actualHash,
6316
+ reason: "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL",
6317
+ ownerTransferNotExecuted: true
6318
+ },
6319
+ this.id
6320
+ );
6321
+ onSubmitted?.(actualHash);
6322
+ }
5479
6323
  if (receipt.status !== "success") {
5480
6324
  throw new OwneyError(
5481
6325
  "AGENT_TRANSACTION_REVERTED",
5482
- `Yieldseeker transaction reverted (${hash}).`,
5483
- { transactionHash: hash },
6326
+ `Yieldseeker transaction reverted (${actualHash}).`,
6327
+ { transactionHash: actualHash },
5484
6328
  this.id
5485
6329
  );
5486
6330
  }
5487
6331
  onReceipt?.(receipt);
5488
- return hash;
6332
+ return actualHash;
5489
6333
  }
5490
6334
  async waitForReceipt(state, chainId, transactionHash) {
5491
6335
  if (this.unwindReceiptWaiter) {
@@ -5512,12 +6356,12 @@ var YieldseekerAgent = class {
5512
6356
  return receipt;
5513
6357
  }
5514
6358
  assertTransaction(transaction, state, chainId) {
5515
- if (!transaction || typeof transaction.from !== "string" || !isAddress2(transaction.from) || typeof transaction.to !== "string" || !isAddress2(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || getAddress4(transaction.from) !== getAddress4(state.walletAddress)) {
6359
+ if (!transaction || typeof transaction.from !== "string" || !isAddress3(transaction.from) || typeof transaction.to !== "string" || !isAddress3(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || getAddress4(transaction.from) !== getAddress4(state.walletAddress)) {
5516
6360
  throw this.invalidResponse("transaction");
5517
6361
  }
5518
6362
  }
5519
6363
  assertAgent(agent) {
5520
- if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress2(agent.assetAddress) || agent.chainId !== 8453) {
6364
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress3(agent.assetAddress) || agent.chainId !== 8453) {
5521
6365
  throw this.invalidResponse("agent");
5522
6366
  }
5523
6367
  }
@@ -5965,6 +6809,18 @@ var planOf = (transfers) => JSON.stringify(
5965
6809
  amount: BigInt(t.amount).toString()
5966
6810
  }))
5967
6811
  );
6812
+ var pendingKey = (i) => `owney.token-batch.v1:${keccak256(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
6813
+ function pendingTokenBatch(i) {
6814
+ try {
6815
+ const saved = read(pendingKey(i));
6816
+ if (!saved) return;
6817
+ const body = JSON.parse(saved);
6818
+ if (!body.serializedTransaction || body.chainId !== i.chainId || !isAddressEqual2(body.from, i.owner) || !isAddressEqual2(body.token, i.token) || !body.transfers?.length) return;
6819
+ return { txHash: keccak256(body.serializedTransaction), transfers: body.transfers.map(({ to, amount }) => ({ to, amount })) };
6820
+ } catch {
6821
+ return;
6822
+ }
6823
+ }
5968
6824
  function read(key2) {
5969
6825
  return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
5970
6826
  }
@@ -5981,11 +6837,11 @@ function clear(key2) {
5981
6837
  else window.localStorage.removeItem(key2);
5982
6838
  }
5983
6839
  function sponsorTokenBatch(i) {
5984
- const key2 = `owney.token-batch.v1:${keccak256(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
6840
+ const key2 = pendingKey(i);
5985
6841
  const plan = planOf(i.transfers);
5986
6842
  const active = inflight.get(key2);
5987
6843
  if (active) {
5988
- if (active.plan !== plan)
6844
+ if (active.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
5989
6845
  return Promise.reject(
5990
6846
  new Error(
5991
6847
  "A token deposit is already in progress. Wait for its result before depositing again."
@@ -6060,6 +6916,8 @@ async function execute(i, key2, plan) {
6060
6916
  }
6061
6917
  };
6062
6918
  const saved = read(key2);
6919
+ if (i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
6920
+ throw new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit changed. Check its status before depositing again.");
6063
6921
  if (saved) {
6064
6922
  const previous = JSON.parse(saved);
6065
6923
  if (previous.chainId !== i.chainId || !isAddressEqual2(previous.from, i.owner) || !isAddressEqual2(previous.token, i.token) || planOf(previous.transfers) !== plan)
@@ -6171,7 +7029,8 @@ function makeSponsoredTokenCallback(deps) {
6171
7029
  pub,
6172
7030
  wallet,
6173
7031
  onApproved: deps.onApproved,
6174
- onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers)
7032
+ onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers),
7033
+ resumeTxHash: deps.resumeTxHash
6175
7034
  });
6176
7035
  };
6177
7036
  const callback = makeVerificationAwareDepositCallback(
@@ -6257,7 +7116,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
6257
7116
  }
6258
7117
 
6259
7118
  // src/lib/sponsored-calls-deposit.ts
6260
- import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi3, toHex as toHex2 } from "viem";
7119
+ import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi4, toHex as toHex2 } from "viem";
6261
7120
  var DEFAULT_POLL_INTERVAL_MS = 1500;
6262
7121
  var DEFAULT_MAX_POLLS = 30;
6263
7122
  async function paymasterSupported(provider, owner, chainId) {
@@ -6303,7 +7162,7 @@ function makeSponsoredCallsCallback(deps) {
6303
7162
  to: token,
6304
7163
  value: "0x0",
6305
7164
  data: encodeFunctionData2({
6306
- abi: erc20Abi3,
7165
+ abi: erc20Abi4,
6307
7166
  functionName: "transfer",
6308
7167
  args: [transfer.to, BigInt(transfer.amount)]
6309
7168
  })
@@ -6335,50 +7194,73 @@ function makeSponsoredCallsCallback(deps) {
6335
7194
  url.searchParams.append("owneyIntent", intent);
6336
7195
  paymasterUrl = url.toString();
6337
7196
  }
6338
- const sendResult = await deps.provider.request({
6339
- method: "wallet_sendCalls",
6340
- params: [
6341
- {
6342
- version: "2.0.0",
6343
- from: deps.ownerAddress,
6344
- chainId: toHex2(chainId),
6345
- atomicRequired: transfers.length > 1,
6346
- calls,
6347
- capabilities: {
6348
- paymasterService: { url: paymasterUrl }
7197
+ const requestedId = toHex2(globalThis.crypto.getRandomValues(new Uint8Array(32)));
7198
+ const progress = (id, transfer) => {
7199
+ try {
7200
+ deps.onCallsProgress?.(id, transfers, transfer);
7201
+ } catch {
7202
+ }
7203
+ };
7204
+ progress(requestedId, "pending");
7205
+ let sendResult;
7206
+ try {
7207
+ sendResult = await deps.provider.request({
7208
+ method: "wallet_sendCalls",
7209
+ params: [
7210
+ {
7211
+ version: "2.0.0",
7212
+ id: requestedId,
7213
+ from: deps.ownerAddress,
7214
+ chainId: toHex2(chainId),
7215
+ atomicRequired: transfers.length > 1,
7216
+ calls,
7217
+ capabilities: {
7218
+ paymasterService: { url: paymasterUrl }
7219
+ }
6349
7220
  }
6350
- }
6351
- ]
6352
- });
6353
- deps.onApproved?.();
7221
+ ]
7222
+ });
7223
+ } catch (error) {
7224
+ if (error?.code === 4001 || error?.code === "ACTION_REJECTED")
7225
+ progress(requestedId, "failed");
7226
+ throw error;
7227
+ }
7228
+ try {
7229
+ deps.onApproved?.();
7230
+ } catch {
7231
+ }
6354
7232
  const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
6355
7233
  if (!callsId) {
6356
7234
  throw new OwneyError(
6357
7235
  "SPONSORED_CALLS_NO_ID",
6358
7236
  "Wallet accepted wallet_sendCalls but returned no calls id.",
6359
- { chainId }
7237
+ { chainId, callsId: requestedId }
6360
7238
  );
6361
7239
  }
7240
+ progress(callsId, "pending");
6362
7241
  for (let attempt = 0; attempt < maxPolls; attempt += 1) {
6363
7242
  const status = await deps.provider.request({
6364
7243
  method: "wallet_getCallsStatus",
6365
7244
  params: [callsId]
6366
7245
  });
7246
+ if (status?.id && status.id !== callsId || status?.chainId && Number(status.chainId) !== chainId)
7247
+ throw new OwneyError("SPONSORED_CALLS_NO_RECEIPT", "Wallet returned status for a different deposit batch.", { chainId, callsId });
6367
7248
  const txHash = status?.receipts?.[0]?.transactionHash;
6368
- if (txHash && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
7249
+ if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && !status?.receipts?.some((receipt) => receipt.transactionHash !== txHash)) {
6369
7250
  try {
6370
7251
  deps.onSubmitted?.(txHash, transfers);
6371
7252
  } catch {
6372
7253
  }
6373
7254
  }
6374
- if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
7255
+ if (status?.status === 400 || status?.status === 500) {
7256
+ progress(callsId, "failed");
6375
7257
  throw new OwneyError(
6376
7258
  "SPONSOR_REQUEST_FAILED",
6377
7259
  "The sponsored deposit did not complete successfully.",
6378
7260
  { chainId, callsId, safeToFallback: false }
6379
7261
  );
6380
7262
  }
6381
- if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
7263
+ if (txHash && /^0x[\da-f]{64}$/i.test(txHash) && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
6382
7264
  if (status?.receipts?.some(
6383
7265
  (receipt) => receipt.transactionHash !== txHash
6384
7266
  ))
@@ -6594,8 +7476,8 @@ var OwneySDK = class {
6594
7476
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
6595
7477
  }
6596
7478
  /** Builds the default USDC batch callback for the connected wallet. */
6597
- getDefaultSponsoredCallback(onApproved, onSubmitted) {
6598
- if (!onApproved && !onSubmitted && this.cachedSponsoredCallback)
7479
+ getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
7480
+ if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedSponsoredCallback)
6599
7481
  return this.cachedSponsoredCallback;
6600
7482
  const provider = this.requireConnectedProvider();
6601
7483
  const owner = this.state.walletAddress;
@@ -6605,6 +7487,7 @@ var OwneySDK = class {
6605
7487
  ownerAddress: owner,
6606
7488
  onApproved,
6607
7489
  onSubmitted,
7490
+ resumeTxHash,
6608
7491
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
6609
7492
  // Casts work around viem's chain-narrowed Client vs the generic
6610
7493
  // PublicClient/WalletClient param types — structurally identical at
@@ -6620,13 +7503,13 @@ var OwneySDK = class {
6620
7503
  transport: custom4(provider)
6621
7504
  })
6622
7505
  });
6623
- if (!onApproved && !onSubmitted) this.cachedSponsoredCallback = callback;
7506
+ if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedSponsoredCallback = callback;
6624
7507
  return callback;
6625
7508
  }
6626
7509
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
6627
- getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) {
7510
+ getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) {
6628
7511
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
6629
- if (!onApproved && !onSubmitted && cached) return cached;
7512
+ if (!onApproved && !onSubmitted && !onCallsProgress && cached) return cached;
6630
7513
  const provider = this.requireConnectedProvider();
6631
7514
  const callback = makeSponsoredCallsCallback({
6632
7515
  apiKey: this.apiKey,
@@ -6636,9 +7519,10 @@ var OwneySDK = class {
6636
7519
  paymasterServiceUrl: this.paymasterServiceUrl,
6637
7520
  onApproved,
6638
7521
  onSubmitted,
7522
+ onCallsProgress,
6639
7523
  tokenAddressByChain: sponsoredTokensFor(asset)
6640
7524
  });
6641
- if (!onApproved && !onSubmitted) this.cachedSponsoredCallsCallbacks.set(asset, callback);
7525
+ if (!onApproved && !onSubmitted && !onCallsProgress) this.cachedSponsoredCallsCallbacks.set(asset, callback);
6642
7526
  return callback;
6643
7527
  }
6644
7528
  /**
@@ -6647,8 +7531,8 @@ var OwneySDK = class {
6647
7531
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
6648
7532
  * single-use batch authorization instead of an EIP-3009 authorization.
6649
7533
  */
6650
- getDefaultWethSponsoredCallback(onApproved, onSubmitted) {
6651
- if (!onApproved && !onSubmitted && this.cachedWethSponsoredCallback)
7534
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
7535
+ if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedWethSponsoredCallback)
6652
7536
  return this.cachedWethSponsoredCallback;
6653
7537
  const provider = this.requireConnectedProvider();
6654
7538
  const owner = this.state.walletAddress;
@@ -6658,6 +7542,7 @@ var OwneySDK = class {
6658
7542
  ownerAddress: owner,
6659
7543
  onApproved,
6660
7544
  onSubmitted,
7545
+ resumeTxHash,
6661
7546
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
6662
7547
  // Casts work around viem's chain-narrowed Client vs the generic
6663
7548
  // PublicClient/WalletClient param types — structurally identical at
@@ -6673,7 +7558,7 @@ var OwneySDK = class {
6673
7558
  transport: custom4(provider)
6674
7559
  })
6675
7560
  });
6676
- if (!onApproved && !onSubmitted) this.cachedWethSponsoredCallback = callback;
7561
+ if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedWethSponsoredCallback = callback;
6677
7562
  return callback;
6678
7563
  }
6679
7564
  getAgent(agentId) {
@@ -6982,6 +7867,7 @@ var OwneySDK = class {
6982
7867
  async deposit(options) {
6983
7868
  const { amount, asset, depositCallback, agentId, onApproved } = options;
6984
7869
  const prepared = /* @__PURE__ */ new Map();
7870
+ const paidFallbackAgents = /* @__PURE__ */ new Set();
6985
7871
  const onProgress = options.onProgress ? (evidence) => {
6986
7872
  if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
6987
7873
  notifyFunding(options.onProgress, evidence);
@@ -6992,152 +7878,184 @@ var OwneySDK = class {
6992
7878
  if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash, transfer: "pending" });
6993
7879
  }
6994
7880
  } : void 0;
7881
+ const onCallsProgress = onProgress ? (callsId, destinations, transfer) => {
7882
+ for (const destination of destinations) {
7883
+ const evidence = prepared.get(destination.to.toLowerCase());
7884
+ if (evidence) notifyFunding(onProgress, { ...evidence, callsId, transfer });
7885
+ }
7886
+ } : void 0;
6995
7887
  if (BigInt(amount) === 0n) {
6996
7888
  return { agentResults: {} };
6997
7889
  }
6998
7890
  const state = this.requireState();
6999
7891
  const chainId = this.requireChainId();
7000
- const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted) : this.getDefaultSponsoredCallback(onApproved, onSubmitted));
7001
- if (agentId) {
7002
- if (this.isAgentDisabled(agentId)) {
7003
- throw new OwneyError(
7004
- "AGENT_DISABLED",
7005
- `Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
7006
- { agentId }
7007
- );
7008
- }
7009
- const agent = this.getAgent(agentId);
7010
- this.validateAssetSupport(agent, chainId, asset);
7011
- await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
7012
- return this.depositWithFallback(
7013
- agent,
7014
- state,
7015
- chainId,
7016
- amount,
7017
- asset,
7018
- effectiveCallback,
7019
- depositCallback,
7020
- onProgress
7021
- );
7022
- }
7023
- const totalAmount = BigInt(amount);
7024
- const eligibleAgents = this.getEligibleAgents(chainId, asset, {
7025
- excludeDisabled: true
7026
- });
7027
- const exemptFlags = await Promise.all(
7028
- eligibleAgents.map(
7029
- (a) => this.hasExistingBalance(a, state, chainId, asset, true)
7030
- )
7031
- );
7032
- const exempt = new Set(
7033
- eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
7034
- );
7035
- const agentAmounts = this.splitDepositAmount(
7036
- totalAmount,
7037
- eligibleAgents,
7038
- chainId,
7039
- asset,
7040
- exempt
7041
- );
7042
- if (agentAmounts.length === 0) {
7043
- const agentsRequiringActivation = eligibleAgents.filter(
7044
- (agent) => !exempt.has(agent.id)
7045
- );
7046
- const perAgentMinimum = agentsRequiringActivation.reduce(
7047
- (highest, agent) => {
7048
- const minimum = this.getMinDepositAmount(agent, chainId, asset);
7049
- return minimum > highest ? minimum : highest;
7050
- },
7051
- 0n
7052
- );
7053
- const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
7054
- throw new OwneyError(
7055
- "DEPOSIT_AMOUNT_BELOW_MINIMUM",
7056
- `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
7057
- {
7058
- amount,
7059
- asset,
7060
- chainId,
7061
- minDepositAmount: minimumRequired.toString(),
7062
- perAgentDepositAmount: perAgentMinimum.toString(),
7063
- agentMinimums: agentsRequiringActivation.map((agent) => ({
7064
- agentId: agent.id,
7065
- minDepositAmount: this.getMinDepositAmount(
7066
- agent,
7067
- chainId,
7068
- asset
7069
- ).toString()
7070
- }))
7071
- }
7072
- );
7073
- }
7074
- const batchTransfer = getDepositBatchTransfer(effectiveCallback);
7075
- if (!depositCallback && batchTransfer) {
7076
- return runAgentDepositBatch(
7077
- chainId,
7078
- agentAmounts.map(({ agent, amount: amount2 }) => ({
7079
- id: agent.id,
7080
- amount: amount2,
7081
- run: (callback) => withFailureReporting(
7082
- this.apiKey,
7083
- agent.id,
7084
- () => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
7085
- this.routingApiBaseUrl
7086
- )
7087
- })),
7088
- async (cid, transfers) => {
7089
- try {
7090
- return await batchTransfer(cid, transfers);
7091
- } catch (error) {
7092
- if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
7093
- throw error;
7094
- const requiredAmount = transfers.reduce(
7095
- (sum, transfer) => sum + BigInt(transfer.amount),
7096
- 0n
7097
- );
7098
- await this.approvePermit2(
7099
- asset,
7100
- requiredAmount,
7101
- cid
7102
- );
7103
- return batchTransfer(cid, transfers);
7104
- }
7892
+ if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
7893
+ throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
7894
+ const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash));
7895
+ try {
7896
+ if (agentId) {
7897
+ if (this.isAgentDisabled(agentId)) {
7898
+ throw new OwneyError(
7899
+ "AGENT_DISABLED",
7900
+ `Cannot deposit into disabled agent "${agentId}". Existing balances remain visible and withdrawable.`,
7901
+ { agentId }
7902
+ );
7105
7903
  }
7106
- );
7107
- }
7108
- const agentResults = {};
7109
- for (const [
7110
- index,
7111
- { agent, amount: agentAmount }
7112
- ] of agentAmounts.entries()) {
7113
- try {
7114
- agentResults[agent.id] = await this.depositWithFallback(
7904
+ const agent = this.getAgent(agentId);
7905
+ this.validateAssetSupport(agent, chainId, asset);
7906
+ await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
7907
+ return await this.depositWithFallback(
7115
7908
  agent,
7116
7909
  state,
7117
7910
  chainId,
7118
- agentAmount,
7911
+ amount,
7119
7912
  asset,
7120
7913
  effectiveCallback,
7121
7914
  depositCallback,
7122
- onProgress
7915
+ onProgress,
7916
+ () => paidFallbackAgents.add(agent.id),
7917
+ !!options.resumeTxHash
7123
7918
  );
7124
- } catch (error) {
7125
- if (Object.keys(agentResults).length === 0) throw error;
7919
+ }
7920
+ const totalAmount = BigInt(amount);
7921
+ const eligibleAgents = this.getEligibleAgents(chainId, asset, {
7922
+ excludeDisabled: true
7923
+ });
7924
+ const prospectiveShare = totalAmount / BigInt(eligibleAgents.length);
7925
+ const meetsEveryMinimum = eligibleAgents.every((agent) => prospectiveShare >= this.getMinDepositAmount(agent, chainId, asset));
7926
+ const exemptFlags = meetsEveryMinimum ? eligibleAgents.map(() => false) : await Promise.all(eligibleAgents.map((agent) => this.hasExistingBalance(agent, state, chainId, asset, true)));
7927
+ const exempt = new Set(
7928
+ eligibleAgents.filter((_, i) => exemptFlags[i]).map((a) => a.id)
7929
+ );
7930
+ const agentAmounts = this.splitDepositAmount(
7931
+ totalAmount,
7932
+ eligibleAgents,
7933
+ chainId,
7934
+ asset,
7935
+ exempt
7936
+ );
7937
+ if (agentAmounts.length === 0) {
7938
+ const agentsRequiringActivation = eligibleAgents.filter(
7939
+ (agent) => !exempt.has(agent.id)
7940
+ );
7941
+ const perAgentMinimum = agentsRequiringActivation.reduce(
7942
+ (highest, agent) => {
7943
+ const minimum = this.getMinDepositAmount(agent, chainId, asset);
7944
+ return minimum > highest ? minimum : highest;
7945
+ },
7946
+ 0n
7947
+ );
7948
+ const minimumRequired = agentsRequiringActivation.length > 0 ? (perAgentMinimum > 0n ? perAgentMinimum : 1n) * BigInt(agentsRequiringActivation.length) : BigInt(eligibleAgents.length);
7126
7949
  throw new OwneyError(
7127
- "DEPOSIT_PARTIAL_FAILURE",
7128
- "Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
7950
+ "DEPOSIT_AMOUNT_BELOW_MINIMUM",
7951
+ `Amount "${amount}" cannot activate every eligible agent. The combined minimum is "${minimumRequired.toString()}" for ${asset}.`,
7129
7952
  {
7130
- agentResults,
7131
- failedAgentId: agent.id,
7132
- failedAmount: agentAmount,
7133
- unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
7134
- cause: error
7135
- },
7136
- agent.id
7953
+ amount,
7954
+ asset,
7955
+ chainId,
7956
+ minDepositAmount: minimumRequired.toString(),
7957
+ perAgentDepositAmount: perAgentMinimum.toString(),
7958
+ agentMinimums: agentsRequiringActivation.map((agent) => ({
7959
+ agentId: agent.id,
7960
+ minDepositAmount: this.getMinDepositAmount(
7961
+ agent,
7962
+ chainId,
7963
+ asset
7964
+ ).toString()
7965
+ }))
7966
+ }
7137
7967
  );
7138
7968
  }
7969
+ const batchTransfer = getDepositBatchTransfer(effectiveCallback);
7970
+ if (!depositCallback && batchTransfer) {
7971
+ return await runAgentDepositBatch(
7972
+ chainId,
7973
+ agentAmounts.map(({ agent, amount: amount2 }) => ({
7974
+ id: agent.id,
7975
+ amount: amount2,
7976
+ run: (callback) => withFailureReporting(
7977
+ this.apiKey,
7978
+ agent.id,
7979
+ () => onProgress ? agent.deposit(state, chainId, amount2, asset, callback, onProgress) : agent.deposit(state, chainId, amount2, asset, callback),
7980
+ this.routingApiBaseUrl
7981
+ )
7982
+ })),
7983
+ async (cid, transfers) => {
7984
+ try {
7985
+ return await batchTransfer(cid, transfers);
7986
+ } catch (error) {
7987
+ if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
7988
+ throw error;
7989
+ const requiredAmount = transfers.reduce(
7990
+ (sum, transfer) => sum + BigInt(transfer.amount),
7991
+ 0n
7992
+ );
7993
+ await this.approvePermit2(
7994
+ asset,
7995
+ requiredAmount,
7996
+ cid
7997
+ );
7998
+ return batchTransfer(cid, transfers);
7999
+ }
8000
+ }
8001
+ );
8002
+ }
8003
+ const agentResults = {};
8004
+ for (const [
8005
+ index,
8006
+ { agent, amount: agentAmount }
8007
+ ] of agentAmounts.entries()) {
8008
+ try {
8009
+ agentResults[agent.id] = await this.depositWithFallback(
8010
+ agent,
8011
+ state,
8012
+ chainId,
8013
+ agentAmount,
8014
+ asset,
8015
+ effectiveCallback,
8016
+ depositCallback,
8017
+ onProgress,
8018
+ () => paidFallbackAgents.add(agent.id),
8019
+ !!options.resumeTxHash
8020
+ );
8021
+ } catch (error) {
8022
+ if (Object.keys(agentResults).length === 0) throw error;
8023
+ throw new OwneyError(
8024
+ "DEPOSIT_PARTIAL_FAILURE",
8025
+ "Some deposits completed before another agent failed. Check activity and balances before depositing again; the failed transfer may still settle.",
8026
+ {
8027
+ agentResults,
8028
+ failedAgentId: agent.id,
8029
+ failedAmount: agentAmount,
8030
+ unattemptedAgentIds: agentAmounts.slice(index + 1).map(({ agent: agent2 }) => agent2.id),
8031
+ cause: error
8032
+ },
8033
+ agent.id
8034
+ );
8035
+ }
8036
+ }
8037
+ return { agentResults };
8038
+ } catch (error) {
8039
+ if (!depositCallback && onProgress && !options.resumeTxHash) {
8040
+ const ids = agentId ? [agentId] : this.getEligibleAgents(chainId, asset).map((agent) => agent.id);
8041
+ for (const id of ids) {
8042
+ const evidence = [...prepared.values()].find((value) => value.agentId === id);
8043
+ if (paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
8044
+ const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
8045
+ if (!rejected && (evidence?.txHash || evidence?.userOpHash || evidence?.callsId)) continue;
8046
+ notifyFunding(onProgress, { ...evidence ?? {
8047
+ agentId: id,
8048
+ chainId,
8049
+ token: asset,
8050
+ walletAddress: state.walletAddress,
8051
+ intendedAmount: amount,
8052
+ registration: "unknown",
8053
+ delivery: "not-applicable"
8054
+ }, transfer: "failed" });
8055
+ }
8056
+ }
8057
+ throw error;
7139
8058
  }
7140
- return { agentResults };
7141
8059
  }
7142
8060
  /**
7143
8061
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -7166,7 +8084,7 @@ var OwneySDK = class {
7166
8084
  * propagates unchanged. Scoped per agent invocation so one agent's failure
7167
8085
  * doesn't force a retry of sibling agents in a multi-agent split.
7168
8086
  */
7169
- async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress) {
8087
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false) {
7170
8088
  const attempt = (callback) => withFailureReporting(
7171
8089
  this.apiKey,
7172
8090
  agent.id,
@@ -7178,6 +8096,7 @@ var OwneySDK = class {
7178
8096
  try {
7179
8097
  return await attempt(effectiveCallback);
7180
8098
  } catch (error) {
8099
+ if (resumeOnly) throw error;
7181
8100
  if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
7182
8101
  approvalAttempted = true;
7183
8102
  console.warn(
@@ -7195,6 +8114,7 @@ var OwneySDK = class {
7195
8114
  `[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
7196
8115
  error
7197
8116
  );
8117
+ onPaidFallback?.();
7198
8118
  return attempt(void 0);
7199
8119
  }
7200
8120
  }
@@ -7277,7 +8197,12 @@ var OwneySDK = class {
7277
8197
  */
7278
8198
  async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
7279
8199
  try {
7280
- const balance = await agent.getBalances(state, chainId);
8200
+ if (agent.hasExistingDepositFunds && await agent.hasExistingDepositFunds(
8201
+ state,
8202
+ chainId,
8203
+ asset
8204
+ ).catch(() => void 0) === true) return true;
8205
+ const balance = await agent.getBalances(state, chainId, asset);
7281
8206
  const target = asset.toLowerCase();
7282
8207
  const token = balance.tokens.find(
7283
8208
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
@@ -7390,7 +8315,7 @@ var OwneySDK = class {
7390
8315
  if (direction === "withdraw" && evidence.agentId === "yieldseeker" && verified.transfer === "unknown" && verified.confirmedAmount && BigInt(verified.confirmedAmount) > 0n) {
7391
8316
  const transaction = await rpc2.getTransaction({ hash: evidence.txHash });
7392
8317
  assertCurrent();
7393
- if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address))
8318
+ if (isFullYieldseekerWithdrawal(transaction, { ...evidence, agentWallet }, token.address, receipt))
7394
8319
  verified = inspectFundingReceipt(receipt, { ...evidence, agentWallet, intendedAmount: "MAX" }, token.address, direction);
7395
8320
  }
7396
8321
  const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
@@ -7403,7 +8328,8 @@ var OwneySDK = class {
7403
8328
  return verified;
7404
8329
  }
7405
8330
  /** Resolve a saved sponsored-withdrawal ID without opening a wallet prompt. */
7406
- async resolveSponsoredWithdrawalEvidence(evidence) {
8331
+ async resolveSponsoredWithdrawalEvidence(evidence, options = {}) {
8332
+ if (evidence.callsId && !evidence.userOpHash) return this.resolveWalletCallsFundingEvidence(evidence, "withdraw");
7407
8333
  const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
7408
8334
  if (evidence.agentId !== "yieldseeker" || chainId !== 8453 || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
7409
8335
  const agent = this.getAgent("yieldseeker");
@@ -7419,18 +8345,26 @@ var OwneySDK = class {
7419
8345
  owner: state.walletAddress,
7420
8346
  agentWallet: wallet,
7421
8347
  asset: SupportedAssets.find((t) => t.chainId === chainId && t.symbol === evidence.token)?.address,
7422
- userOpHash: evidence.userOpHash
8348
+ userOpHash: evidence.userOpHash,
8349
+ ...evidence.userOperation ? { userOperation: Object.fromEntries([
8350
+ "sender",
8351
+ "nonce",
8352
+ "initCode",
8353
+ "callData",
8354
+ "callGasLimit",
8355
+ "verificationGasLimit",
8356
+ "preVerificationGas",
8357
+ "maxFeePerGas",
8358
+ "maxPriorityFeePerGas",
8359
+ "paymasterAndData"
8360
+ ].map((key2) => [key2, evidence.userOperation[key2]])) } : {},
8361
+ ...Number.isFinite(options.submittedAt) && options.submittedAt > 0 ? { submittedAt: options.submittedAt } : {}
7423
8362
  })
7424
8363
  });
7425
8364
  if (!response.ok) throw new OwneyError("AGENT_API_ERROR", "Could not resolve sponsored withdrawal status.");
7426
8365
  const payload = await response.json();
7427
8366
  txHash = payload.data?.txHash ?? void 0;
7428
8367
  executionFailed = payload.data?.executionFailed === true;
7429
- } else if (evidence.callsId) {
7430
- const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
7431
- if (status?.id && status.id !== evidence.callsId || status?.chainId && Number(status.chainId) !== chainId) return evidence;
7432
- executionFailed = status?.status === 400 || status?.status === 500;
7433
- txHash = status?.receipts?.[0]?.transactionHash;
7434
8368
  }
7435
8369
  assertCurrent();
7436
8370
  if (executionFailed) return {
@@ -7441,6 +8375,60 @@ var OwneySDK = class {
7441
8375
  };
7442
8376
  return txHash && /^0x[0-9a-fA-F]{64}$/.test(txHash) ? { ...evidence, txHash } : evidence;
7443
8377
  }
8378
+ /** Recover an EIP-5792 deposit or payout using this account's exact batch ID. */
8379
+ async resolveWalletCallsFundingEvidence(evidence, direction) {
8380
+ const { state, chainId, assertCurrent } = this.captureReadScope(evidence.chainId);
8381
+ if (!evidence.callsId || evidence.txHash || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return evidence;
8382
+ const agent = this.getAgent(evidence.agentId);
8383
+ const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
8384
+ if (!wallet || wallet.toLowerCase() !== evidence.agentWallet?.toLowerCase()) return evidence;
8385
+ const status = await state.provider.request({ method: "wallet_getCallsStatus", params: [evidence.callsId] });
8386
+ assertCurrent();
8387
+ if (status?.id !== void 0 && status.id !== evidence.callsId || status?.chainId !== void 0 && Number(status.chainId) !== chainId) return evidence;
8388
+ const statusCode2 = Number(status?.status);
8389
+ if (statusCode2 === 400 || statusCode2 === 500) return {
8390
+ ...evidence,
8391
+ transfer: "failed",
8392
+ delivery: direction === "withdraw" ? "failed" : "not-applicable"
8393
+ };
8394
+ const hashes = [...new Set((status?.receipts ?? []).map((receipt) => receipt.transactionHash?.toLowerCase()).filter((hash) => !!hash && /^0x[\da-f]{64}$/i.test(hash)))];
8395
+ if (hashes.length === 1) return { ...evidence, txHash: hashes[0] };
8396
+ if (hashes.length > 16) return evidence;
8397
+ const matches = [];
8398
+ for (const txHash of hashes) {
8399
+ const inspected = await this.inspectFunding({ ...evidence, txHash }, direction);
8400
+ assertCurrent();
8401
+ if (inspected.transfer === "confirmed") matches.push(inspected);
8402
+ }
8403
+ return matches.length === 1 ? matches[0] : evidence;
8404
+ }
8405
+ /** Whether a foreground deposit can resume the same signed batch, never a
8406
+ * fresh authorization. The batch rail independently rechecks the exact plan. */
8407
+ async canResumePendingDeposit(evidences, amount) {
8408
+ if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
8409
+ const first = evidences[0];
8410
+ const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
8411
+ const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
8412
+ if (!token) return false;
8413
+ const pending = pendingTokenBatch({
8414
+ apiKey: this.apiKey,
8415
+ baseUrl: this.routingApiBaseUrl,
8416
+ owner: state.walletAddress,
8417
+ chainId,
8418
+ token: token.address
8419
+ });
8420
+ if (!pending || pending.transfers.length !== evidences.length || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n).toString() !== amount) return false;
8421
+ const destinations = /* @__PURE__ */ new Set();
8422
+ for (const evidence of evidences) {
8423
+ if (evidence.chainId !== chainId || evidence.token !== first.token || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.callsId || evidence.userOpHash || evidence.txHash?.toLowerCase() !== pending.txHash.toLowerCase()) return false;
8424
+ const agent = this.getAgent(evidence.agentId);
8425
+ const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
8426
+ if (!wallet || destinations.has(wallet.toLowerCase()) || !pending.transfers.some((transfer) => transfer.to.toLowerCase() === wallet.toLowerCase() && transfer.amount === evidence.intendedAmount)) return false;
8427
+ destinations.add(wallet.toLowerCase());
8428
+ }
8429
+ assertCurrent();
8430
+ return true;
8431
+ }
7444
8432
  /** A display acknowledgement controls polling only; it never confirms a financial outcome. */
7445
8433
  endFundingBalanceRefresh(evidence, read2) {
7446
8434
  const { state, chainId } = this.captureReadScope(evidence.chainId);
@@ -7541,6 +8529,30 @@ var OwneySDK = class {
7541
8529
  }
7542
8530
  };
7543
8531
  const token = asset;
8532
+ const agentFailures = {};
8533
+ const recordAgentFailure = (id, error) => {
8534
+ if (!(error instanceof OwneyError)) return;
8535
+ const details = error.details ?? {};
8536
+ const codeValue = (value) => typeof value === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(value) ? value : void 0;
8537
+ const revertNames = ["InsufficientBalance", "InvalidAsset", "Unauthorized", "Error", "Panic", "FailedOp", "FailedOpWithRevert"];
8538
+ const panicCodes = [0, 1, 17, 18, 33, 34, 49, 50, 65, 81];
8539
+ agentFailures[id] = {
8540
+ code: error.code,
8541
+ phase: ["prepare", "send", "observe"].includes(String(details.phase)) ? details.phase : void 0,
8542
+ failureCode: codeValue(details.failureCode),
8543
+ reason: codeValue(details.reason),
8544
+ withdrawalPhase: details.withdrawalPhase === "unwind" ? "unwind" : void 0,
8545
+ statusCode: typeof details.statusCode === "number" ? details.statusCode : void 0,
8546
+ rpcCode: typeof details.rpcCode === "number" ? details.rpcCode : void 0,
8547
+ rpcMethod: typeof details.rpcMethod === "string" && /^[A-Za-z_]{1,80}$/.test(details.rpcMethod) ? details.rpcMethod : void 0,
8548
+ revertSelector: typeof details.revertSelector === "string" && /^0x[0-9a-f]{8}$/i.test(details.revertSelector) ? details.revertSelector : void 0,
8549
+ revertName: typeof details.revertName === "string" && revertNames.includes(details.revertName) ? details.revertName : void 0,
8550
+ aaCode: typeof details.aaCode === "string" && /^AA[0-9]{2}$/.test(details.aaCode) ? details.aaCode : void 0,
8551
+ panicCode: typeof details.panicCode === "string" && /^0x[0-9a-f]{1,2}$/i.test(details.panicCode) && panicCodes.includes(Number.parseInt(details.panicCode.slice(2), 16)) ? details.panicCode : void 0,
8552
+ safeToFallback: typeof details.safeToFallback === "boolean" ? details.safeToFallback : void 0,
8553
+ executionFailed: typeof details.executionFailed === "boolean" ? details.executionFailed : void 0
8554
+ };
8555
+ };
7544
8556
  const submissionEvidence = /* @__PURE__ */ new Map();
7545
8557
  const submit = async (agent, requested2) => {
7546
8558
  attempted.add(agent.id);
@@ -7561,9 +8573,19 @@ var OwneySDK = class {
7561
8573
  };
7562
8574
  progress(evidence);
7563
8575
  try {
8576
+ if (options.onBeforeSubmission)
8577
+ return await agent.withdraw(
8578
+ state,
8579
+ chainId,
8580
+ token,
8581
+ requested2,
8582
+ progress,
8583
+ options.gasMode ?? "auto",
8584
+ (next) => options.onBeforeSubmission({ ...evidence, ...next })
8585
+ );
7564
8586
  return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
7565
8587
  } catch (error) {
7566
- if (!evidence.txHash && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
8588
+ if (!evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
7567
8589
  progress({ ...evidence, transfer: "failed", delivery: "failed" });
7568
8590
  throw error;
7569
8591
  }
@@ -7594,6 +8616,7 @@ var OwneySDK = class {
7594
8616
  if (!amount) {
7595
8617
  const results2 = {};
7596
8618
  const agentErrors2 = {};
8619
+ const incompleteWithdrawals = {};
7597
8620
  for (const agent of withdrawalAgents) {
7598
8621
  try {
7599
8622
  results2[agent.id] = await submit(agent);
@@ -7602,8 +8625,17 @@ var OwneySDK = class {
7602
8625
  if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
7603
8626
  if (this.isUserRejectedWithdrawal(err)) throw err;
7604
8627
  if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
8628
+ if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
8629
+ incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
8630
+ const evidence = submissionEvidence.get(agent.id);
8631
+ 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) {
8632
+ results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
8633
+ notifyAgentResult(agent.id, results2[agent.id]);
8634
+ }
8635
+ }
7605
8636
  console.error(`withdraw failed for agent "${agent.id}":`, err);
7606
8637
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
8638
+ recordAgentFailure(agent.id, err);
7607
8639
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
7608
8640
  void reportAgentFailure(
7609
8641
  this.apiKey,
@@ -7614,17 +8646,23 @@ var OwneySDK = class {
7614
8646
  }
7615
8647
  }
7616
8648
  if (Object.keys(results2).length === 0) {
8649
+ if (Object.keys(agentErrors2).length === 0)
8650
+ throw new OwneyError(
8651
+ "WITHDRAW_INSUFFICIENT_BALANCE",
8652
+ "No funds are currently available to withdraw for this asset.",
8653
+ { asset, available: "0" }
8654
+ );
7617
8655
  throw new OwneyError(
7618
8656
  "WITHDRAW_ALL_FAILED",
7619
8657
  `Failed to withdraw "${asset}" from all eligible agents.`,
7620
- { asset, agentErrors: agentErrors2 }
8658
+ { asset, agentErrors: agentErrors2, agentFailures }
7621
8659
  );
7622
8660
  }
7623
8661
  if (Object.keys(agentErrors2).length > 0) {
7624
8662
  throw new OwneyError(
7625
8663
  "WITHDRAW_PARTIAL_FAILURE",
7626
8664
  "Some agents could not complete the withdrawal.",
7627
- { asset, partialResults: results2, agentErrors: agentErrors2 }
8665
+ { asset, partialResults: results2, agentErrors: agentErrors2, agentFailures, ...Object.keys(incompleteWithdrawals).length ? { incompleteWithdrawals } : {} }
7628
8666
  );
7629
8667
  }
7630
8668
  const amountsKnown = Object.values(results2).every(
@@ -7705,6 +8743,7 @@ var OwneySDK = class {
7705
8743
  if (this.isUserRejectedWithdrawal(err)) throw err;
7706
8744
  console.error(`withdraw failed for agent "${p.agent.id}":`, err);
7707
8745
  agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
8746
+ recordAgentFailure(p.agent.id, err);
7708
8747
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
7709
8748
  void reportAgentFailure(
7710
8749
  this.apiKey,
@@ -7725,7 +8764,8 @@ var OwneySDK = class {
7725
8764
  {
7726
8765
  asset,
7727
8766
  amount,
7728
- agentErrors
8767
+ agentErrors,
8768
+ agentFailures
7729
8769
  }
7730
8770
  );
7731
8771
  }
@@ -7740,6 +8780,7 @@ var OwneySDK = class {
7740
8780
  withdrawn: withdrawn.toString(),
7741
8781
  partialResults: results,
7742
8782
  agentErrors,
8783
+ agentFailures,
7743
8784
  ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
7744
8785
  }
7745
8786
  );