@owney/sdk 0.7.32-beta.2 → 0.7.32-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -209,9 +209,12 @@ var init_permit2_batch = __esm({
209
209
  // src/lib/token-batch-family.ts
210
210
  var token_batch_family_exports = {};
211
211
  __export(token_batch_family_exports, {
212
+ discardHashlessTokenBatch: () => discardHashlessTokenBatch,
213
+ dropExpiredHashlessTokenBatch: () => dropExpiredHashlessTokenBatch,
212
214
  finishTokenBatchFamily: () => finishTokenBatchFamily,
213
215
  loadTokenBatchFamily: () => loadTokenBatchFamily,
214
216
  saveTokenBatchAttempt: () => saveTokenBatchAttempt,
217
+ savedTokenBatchBody: () => savedTokenBatchBody,
215
218
  tokenBatchAuthorizationId: () => tokenBatchAuthorizationId,
216
219
  tokenBatchCall: () => tokenBatchCall,
217
220
  tokenBatchClear: () => tokenBatchClear,
@@ -423,6 +426,9 @@ function merge(base7, incoming) {
423
426
  async function loadTokenBatchFamily(scope, expectedHash, allowMissing = false) {
424
427
  try {
425
428
  const key2 = tokenBatchKey(scope), records = /* @__PURE__ */ new Map();
429
+ for (const sidecar of [...unfinished.keys()])
430
+ if (sidecar.startsWith(`${key2}:family:`) && !tokenBatchRead(sidecar))
431
+ unfinished.delete(sidecar);
426
432
  for (const k of keys().filter((k2) => k2.startsWith(`${key2}:family:`))) {
427
433
  const raw2 = tokenBatchRead(k);
428
434
  if (!raw2) continue;
@@ -450,6 +456,19 @@ async function loadTokenBatchFamily(scope, expectedHash, allowMissing = false) {
450
456
  );
451
457
  }
452
458
  }
459
+ const marked = /* @__PURE__ */ new Set();
460
+ for (const [id, family] of [...records]) {
461
+ const sidecar = `${key2}:family:${id}`;
462
+ const terminal = unfinished.get(sidecar);
463
+ if (!terminal) continue;
464
+ if (family.terminal) {
465
+ unfinished.delete(sidecar);
466
+ continue;
467
+ }
468
+ finishTokenBatchFamily(scope, family, terminal);
469
+ marked.add(id);
470
+ records.set(id, { ...family, terminal });
471
+ }
453
472
  if (expectedHash) {
454
473
  let found = [...records.values()].filter(
455
474
  (f) => f.attempts.some((a) => same(a.hash, expectedHash))
@@ -467,16 +486,69 @@ async function loadTokenBatchFamily(scope, expectedHash, allowMissing = false) {
467
486
  if (found.length !== 1) throw pending();
468
487
  return found[0];
469
488
  }
470
- const active2 = [...records.values()].filter(
471
- (f) => !f.terminal || f.authorizationId === projectedId
489
+ const active = [...records.values()].filter(
490
+ (f) => !f.terminal || f.authorizationId === projectedId && !marked.has(f.authorizationId)
472
491
  );
473
- if (active2.length > 1) throw pending();
474
- return active2[0];
492
+ if (active.length > 1) throw pending();
493
+ return active[0];
475
494
  } catch (error) {
476
495
  if (error instanceof OwneyError) throw error;
477
496
  throw pending();
478
497
  }
479
498
  }
499
+ async function dropExpiredHashlessTokenBatch(scope, pub) {
500
+ try {
501
+ const key2 = tokenBatchKey(scope);
502
+ const family = await loadTokenBatchFamily(scope);
503
+ const raw2 = family ? void 0 : tokenBatchRead(key2);
504
+ const body = family ? family.terminal ? void 0 : family.body : raw2 ? JSON.parse(raw2) : void 0;
505
+ if (!body || body.serializedTransaction || family?.attempts.some((a) => a.phase === "deposit") || !uint(body.deadline))
506
+ return false;
507
+ const block = await pub.getBlock();
508
+ if (typeof block?.timestamp !== "bigint" || block.timestamp <= BigInt(body.deadline))
509
+ return false;
510
+ if (family)
511
+ finishTokenBatchFamily(scope, family, {
512
+ kind: "expired",
513
+ blockNumber: typeof block.number === "bigint" ? block.number.toString() : "0"
514
+ });
515
+ else tokenBatchClear(key2, raw2);
516
+ return true;
517
+ } catch {
518
+ return false;
519
+ }
520
+ }
521
+ async function discardHashlessTokenBatch(scope, deadline, matches) {
522
+ try {
523
+ const key2 = tokenBatchKey(scope);
524
+ const family = await loadTokenBatchFamily(scope);
525
+ const raw2 = family ? void 0 : tokenBatchRead(key2);
526
+ const body = family ? family.terminal ? void 0 : family.body : raw2 ? JSON.parse(raw2) : void 0;
527
+ if (!body || body.serializedTransaction || family?.attempts.some((a) => a.phase === "deposit") || body.deadline !== deadline || body.chainId !== scope.chainId || !same(body.from, scope.owner) || !same(body.token, scope.token) || !matches(body))
528
+ return false;
529
+ if (family)
530
+ finishTokenBatchFamily(scope, family, { kind: "failed", blockNumber: "0" });
531
+ else tokenBatchClear(key2, raw2);
532
+ return true;
533
+ } catch {
534
+ return false;
535
+ }
536
+ }
537
+ async function savedTokenBatchBody(scope, pub) {
538
+ try {
539
+ if (pub) await dropExpiredHashlessTokenBatch(scope, pub);
540
+ const family = await loadTokenBatchFamily(scope);
541
+ if (family) return family.terminal ? void 0 : family.body;
542
+ const raw2 = tokenBatchRead(tokenBatchKey(scope));
543
+ if (!raw2) return void 0;
544
+ const body = JSON.parse(raw2);
545
+ if (body?.chainId !== scope.chainId || typeof body.from !== "string" || typeof body.token !== "string" || !same(body.from, scope.owner) || !same(body.token, scope.token) || !uint(body.deadline) || !Array.isArray(body.transfers) || !body.transfers.length)
546
+ return void 0;
547
+ return body;
548
+ } catch {
549
+ return void 0;
550
+ }
551
+ }
480
552
  async function saveTokenBatchAttempt(scope, body, attempt, assertCurrent = () => {
481
553
  }) {
482
554
  try {
@@ -535,17 +607,22 @@ function finishTokenBatchFamily(scope, family, terminal) {
535
607
  return;
536
608
  const evidence = terminalOf(terminal, current.attempts);
537
609
  if (!evidence) return;
538
- tokenBatchWrite(
539
- sidecar,
540
- JSON.stringify({ ...current, terminal: evidence })
541
- );
610
+ try {
611
+ tokenBatchWrite(
612
+ sidecar,
613
+ JSON.stringify({ ...current, terminal: evidence })
614
+ );
615
+ unfinished.delete(sidecar);
616
+ } catch {
617
+ unfinished.set(sidecar, evidence);
618
+ }
542
619
  const projected = tokenBatchRead(key2);
543
620
  if (projected && tokenBatchAuthorizationId(JSON.parse(projected), family.relayer) === family.authorizationId)
544
621
  tokenBatchClear(key2, projected);
545
622
  } catch {
546
623
  }
547
624
  }
548
- var import_viem2, memory, settled, hashPattern, uint, same, tokenBatchKey, tokenBatchAuthorizationId, tokenBatchStoredBody, approvalAbi;
625
+ var import_viem2, memory, unfinished, settled, hashPattern, uint, same, tokenBatchKey, tokenBatchAuthorizationId, tokenBatchStoredBody, approvalAbi;
549
626
  var init_token_batch_family = __esm({
550
627
  "src/lib/token-batch-family.ts"() {
551
628
  "use strict";
@@ -553,6 +630,7 @@ var init_token_batch_family = __esm({
553
630
  init_errors();
554
631
  init_permit2_batch();
555
632
  memory = /* @__PURE__ */ new Map();
633
+ unfinished = /* @__PURE__ */ new Map();
556
634
  settled = /* @__PURE__ */ new Map();
557
635
  hashPattern = /^0x[\da-f]{64}$/i;
558
636
  uint = (value) => typeof value === "string" && /^\d{1,78}$/.test(value) && BigInt(value) < 1n << 256n;
@@ -579,12 +657,20 @@ var index_exports = {};
579
657
  __export(index_exports, {
580
658
  AgentChainIncompatibleError: () => AgentChainIncompatibleError,
581
659
  AgentNotFoundError: () => AgentNotFoundError,
660
+ FUNDING_DEPOSIT_RECOVERY_REPORT: () => FUNDING_DEPOSIT_RECOVERY_REPORT,
661
+ FUSION_SECRET_SERVER_PUBLISH: () => FUSION_SECRET_SERVER_PUBLISH,
582
662
  InvalidHistoryCursorError: () => InvalidHistoryCursorError,
583
663
  NotConnectedError: () => NotConnectedError,
584
664
  OwneyError: () => OwneyError,
585
665
  OwneySDK: () => OwneySDK,
586
666
  SDK_VERSION: () => SDK_VERSION,
667
+ SPONSORED_DEPOSIT_SERVER_BROADCAST: () => SPONSORED_DEPOSIT_SERVER_BROADCAST,
668
+ YIELDSEEKER_WITHDRAW_SERVER_SUBMIT: () => YIELDSEEKER_WITHDRAW_SERVER_SUBMIT,
587
669
  YieldseekerAgent: () => YieldseekerAgent,
670
+ ZYFAI_REGISTRATION_NOT_SENT_CODES: () => ZYFAI_REGISTRATION_NOT_SENT_CODES,
671
+ ZYFAI_SERVER_TASKS: () => ZYFAI_SERVER_TASKS,
672
+ ZYFAI_WITHDRAWAL_NOT_SENT_CODES: () => ZYFAI_WITHDRAWAL_NOT_SENT_CODES,
673
+ ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT: () => ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT,
588
674
  clearWithdrawSwap: () => clearWithdrawSwap,
589
675
  createOwneySIWX: () => createOwneySIWX,
590
676
  listPendingDeposits: () => listPendingDeposits,
@@ -940,25 +1026,41 @@ async function observeTokenBatchFamily(input) {
940
1026
 
941
1027
  // src/lib/swap/swap.observation.ts
942
1028
  init_errors();
943
- var active = /* @__PURE__ */ new Set();
944
- var isOrderObserved = (key2) => active.has(key2);
945
- async function observeOrder(key2, run) {
1029
+ var claims = /* @__PURE__ */ new Map();
1030
+ var watches = /* @__PURE__ */ new Map();
1031
+ var isOrderObserved = (key2) => claims.has(key2) || watches.has(key2);
1032
+ function claimOrder(key2) {
1033
+ claims.set(key2, (claims.get(key2) ?? 0) + 1);
1034
+ let released = false;
1035
+ return () => {
1036
+ if (released) return;
1037
+ released = true;
1038
+ const left = (claims.get(key2) ?? 1) - 1;
1039
+ if (left > 0) claims.set(key2, left);
1040
+ else claims.delete(key2);
1041
+ };
1042
+ }
1043
+ async function observeOrder(key2, run, options = {}) {
946
1044
  const busy = () => new OwneyError(
947
1045
  "SWAP_REQUEST_FAILED",
948
1046
  "This order is already being observed.",
949
1047
  { orderHash: key2, watchActive: true, settlementUnconfirmed: true }
950
1048
  );
951
- if (active.has(key2)) throw busy();
952
- active.add(key2);
1049
+ if (options.wait) {
1050
+ for (let other = watches.get(key2); other; other = watches.get(key2))
1051
+ await other.catch(() => void 0);
1052
+ } else if (watches.has(key2)) throw busy();
1053
+ const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
1054
+ const name = `owney.swap.observe:${key2}`;
1055
+ const watch = !locks ? run() : options.wait ? locks.request(name, () => run()) : locks.request(name, { ifAvailable: true }, (lock) => {
1056
+ if (!lock) throw busy();
1057
+ return run();
1058
+ });
1059
+ watches.set(key2, watch);
953
1060
  try {
954
- if (typeof navigator !== "undefined" && navigator.locks)
955
- return await navigator.locks.request(`owney.swap.observe:${key2}`, { ifAvailable: true }, (lock) => {
956
- if (!lock) throw busy();
957
- return run();
958
- });
959
- return await run();
1061
+ return await watch;
960
1062
  } finally {
961
- active.delete(key2);
1063
+ if (watches.get(key2) === watch) watches.delete(key2);
962
1064
  }
963
1065
  }
964
1066
 
@@ -1117,7 +1219,7 @@ function parseLeg(item) {
1117
1219
  const r = item;
1118
1220
  if (typeof r.legKey !== "string" || !r.legKey) return null;
1119
1221
  if (typeof r.status !== "string" || !r.status) return null;
1120
- return {
1222
+ const leg = {
1121
1223
  legKey: r.legKey,
1122
1224
  agentType: strOrNull(r.agentType),
1123
1225
  status: r.status,
@@ -1127,8 +1229,11 @@ function parseLeg(item) {
1127
1229
  confirmedAt: strOrNull(r.confirmedAt),
1128
1230
  agentWallet: strOrNull(r.agentWallet),
1129
1231
  providerIndexedAt: strOrNull(r.providerIndexedAt),
1130
- providerBookedAt: strOrNull(r.providerBookedAt)
1232
+ providerBookedAt: strOrNull(r.providerBookedAt),
1233
+ providerRef: strOrNull(r.providerRef)
1131
1234
  };
1235
+ if (r.closed === true) leg.closed = true;
1236
+ return leg;
1132
1237
  }
1133
1238
  function parseRow(item) {
1134
1239
  if (!item || typeof item !== "object") return null;
@@ -1548,7 +1653,7 @@ var SnapshotHistory = class {
1548
1653
  };
1549
1654
 
1550
1655
  // src/lib/funding-ledger.ts
1551
- var SDK_VERSION = "0.7.32-beta.2";
1656
+ var SDK_VERSION = "0.7.32-beta.4";
1552
1657
  var SCOPE2 = "funding-ledger";
1553
1658
  var REQUEST_TIMEOUT_MS2 = 5e3;
1554
1659
  var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
@@ -1798,13 +1903,213 @@ function buildReporter(config) {
1798
1903
  } catch (err) {
1799
1904
  debugLog(SCOPE2, `event failed: ${describeError2(err)}`);
1800
1905
  }
1906
+ },
1907
+ settle(timeoutMs) {
1908
+ return new Promise((resolve) => {
1909
+ let deadline;
1910
+ const done = () => {
1911
+ if (deadline !== void 0) clearTimeout(deadline);
1912
+ resolve();
1913
+ };
1914
+ try {
1915
+ flush();
1916
+ deadline = setTimeout(done, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 0);
1917
+ unrefTimer(deadline);
1918
+ chain2.then(done, done);
1919
+ } catch (err) {
1920
+ debugLog(SCOPE2, `settle failed: ${describeError2(err)}`);
1921
+ done();
1922
+ }
1923
+ });
1801
1924
  }
1802
1925
  };
1803
1926
  }
1804
1927
  }
1805
1928
 
1929
+ // src/lib/zyfai-server-tasks.ts
1930
+ var SCOPE3 = "zyfai-server-tasks";
1931
+ var ZYFAI_SERVER_TASKS = 1;
1932
+ var ZYFAI_SERVER_TASKS_OFF_CODE = "ZYFAI_SERVER_TASKS_OFF";
1933
+ function zyfaiServerTasksEnabled(option) {
1934
+ if (typeof option !== "function") return option === true;
1935
+ try {
1936
+ return option() === true;
1937
+ } catch {
1938
+ return false;
1939
+ }
1940
+ }
1941
+ var ZYFAI_HANDOFF_FEATURE_OFF_CODES = /* @__PURE__ */ new Set([
1942
+ "FUNDING_TASK_KIND_DISABLED",
1943
+ "ZYFAI_NOT_CONFIGURED",
1944
+ "PROBE_ONLY",
1945
+ "HTTP_404",
1946
+ "HTTP_401",
1947
+ "HTTP_403",
1948
+ ZYFAI_SERVER_TASKS_OFF_CODE
1949
+ ]);
1950
+ var ZYFAI_HANDOFF_NO_TASK_CODES = /* @__PURE__ */ new Set([
1951
+ ...ZYFAI_HANDOFF_FEATURE_OFF_CODES,
1952
+ "ZYFAI_SESSION_MISMATCH",
1953
+ "NEEDS_USER_SESSION"
1954
+ ]);
1955
+ var UNCHECKED_NO_TASK_CODES = /* @__PURE__ */ new Set([
1956
+ "HTTP_401",
1957
+ "HTTP_403",
1958
+ "HTTP_404",
1959
+ ZYFAI_SERVER_TASKS_OFF_CODE
1960
+ ]);
1961
+ function provesNoServerTask(code, afterLostAnswer) {
1962
+ return ZYFAI_HANDOFF_NO_TASK_CODES.has(code) && !(afterLostAnswer && UNCHECKED_NO_TASK_CODES.has(code));
1963
+ }
1964
+ var ZYFAI_WITHDRAWAL_NOT_SENT_CODES = /* @__PURE__ */ new Set([
1965
+ "PREREQUISITE_FAILED",
1966
+ "PREREQUISITE_TIMEOUT",
1967
+ "SUPERSEDED",
1968
+ "ZYFAI_SESSION_MISMATCH",
1969
+ "ZYFAI_REDEMPTION_IN_FLIGHT",
1970
+ "NEEDS_USER_SESSION",
1971
+ "NEEDS_USER_SESSION_KEY",
1972
+ "ZYFAI_FORBIDDEN",
1973
+ "WITHDRAW_INSUFFICIENT_BALANCE",
1974
+ "WITHDRAWAL_IN_PROGRESS",
1975
+ // The task's own agent:zyfai leg was already failed or confirmed (P4-8).
1976
+ "LEG_CLOSED",
1977
+ "MAX_ATTEMPTS",
1978
+ "SECRET_UNREADABLE"
1979
+ ]);
1980
+ var ZYFAI_REGISTRATION_NOT_SENT_CODES = /* @__PURE__ */ new Set([
1981
+ "TRANSFER_NOT_SEEN",
1982
+ "DEPOSIT_NOT_SENT",
1983
+ "DEPOSIT_REVERTED",
1984
+ "TRANSFER_MISMATCH",
1985
+ "NEEDS_USER_SESSION",
1986
+ "ZYFAI_REGISTRATION_REJECTED",
1987
+ "MAX_ATTEMPTS",
1988
+ "SECRET_UNREADABLE"
1989
+ ]);
1990
+ var ZYFAI_TASK_LIFETIME_MS = {
1991
+ zyfai_withdrawal: 72e5,
1992
+ zyfai_deposit_registration: 36e5
1993
+ };
1994
+ var SESSION_MARGIN_MS = 3e5;
1995
+ var SESSION_SLACK_MS = 6e4;
1996
+ function sessionExp(session) {
1997
+ try {
1998
+ const parts = session.split(".");
1999
+ if (parts.length !== 3) return void 0;
2000
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
2001
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
2002
+ const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
2003
+ const claims2 = JSON.parse(decoded);
2004
+ const exp = isRecord(claims2) ? claims2.exp : void 0;
2005
+ return typeof exp === "number" && Number.isFinite(exp) ? exp : void 0;
2006
+ } catch {
2007
+ return void 0;
2008
+ }
2009
+ }
2010
+ function zyfaiSessionOutlivesTask(session, kind, nowMs = Date.now()) {
2011
+ const lifetimeMs = Object.prototype.hasOwnProperty.call(ZYFAI_TASK_LIFETIME_MS, kind) ? ZYFAI_TASK_LIFETIME_MS[kind] : void 0;
2012
+ if (typeof session !== "string" || lifetimeMs === void 0) return false;
2013
+ const exp = sessionExp(session);
2014
+ return exp !== void 0 && exp * 1e3 > nowMs + lifetimeMs + SESSION_MARGIN_MS + SESSION_SLACK_MS;
2015
+ }
2016
+ var DEFAULT_TIMEOUT_MS = 25e3;
2017
+ var DECLINED_STATUSES = /* @__PURE__ */ new Set([400, 401, 403, 404, 409, 429]);
2018
+ var ID = /^.{1,128}$/s;
2019
+ var TX_HASH = /^0x[0-9a-fA-F]{64}$/;
2020
+ var unknown = () => ({ type: "unknown" });
2021
+ var isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
2022
+ function requestBody(body, session) {
2023
+ const out = {
2024
+ kind: body.kind,
2025
+ operationId: body.operationId,
2026
+ owner: body.owner,
2027
+ chainId: body.chainId,
2028
+ asset: body.asset,
2029
+ amount: body.amount,
2030
+ agentWallet: body.agentWallet,
2031
+ zyfaiSession: session
2032
+ };
2033
+ if (body.after !== void 0) out.after = body.after;
2034
+ if (body.runNow !== void 0) out.runNow = body.runNow;
2035
+ return out;
2036
+ }
2037
+ function taskResult(value) {
2038
+ const result = {};
2039
+ if (!isRecord(value)) return result;
2040
+ if (typeof value.providerWithdrawalId === "string" && ID.test(value.providerWithdrawalId))
2041
+ result.providerWithdrawalId = value.providerWithdrawalId;
2042
+ if (typeof value.providerDepositId === "string" && ID.test(value.providerDepositId))
2043
+ result.providerDepositId = value.providerDepositId;
2044
+ if (typeof value.txHash === "string" && TX_HASH.test(value.txHash)) result.txHash = value.txHash;
2045
+ if (value.asyncPending === true) result.asyncPending = true;
2046
+ if (value.handedOff === "reconciler") result.handedOff = "reconciler";
2047
+ if (value.lifecycle === "failed") result.lifecycle = "failed";
2048
+ return result;
2049
+ }
2050
+ function answerOf(payload) {
2051
+ if (!isRecord(payload) || payload.success !== true || !isRecord(payload.data)) return unknown();
2052
+ const data = payload.data;
2053
+ if (data.accepted === false)
2054
+ return typeof data.code === "string" && data.code ? { type: "declined", code: data.code } : unknown();
2055
+ if (data.accepted !== true || typeof data.taskId !== "string" || !data.taskId || typeof data.state !== "string" || !data.state) return unknown();
2056
+ return {
2057
+ type: "accepted",
2058
+ taskId: data.taskId,
2059
+ state: data.state,
2060
+ ...typeof data.code === "string" && data.code ? { code: data.code } : {},
2061
+ result: taskResult(data.result)
2062
+ };
2063
+ }
2064
+ async function handOffZyfaiTask(cfg, body, session) {
2065
+ if (cfg?.zyfaiServerTasks !== true) return { type: "declined", code: ZYFAI_SERVER_TASKS_OFF_CODE };
2066
+ let timer;
2067
+ try {
2068
+ const controller = new AbortController();
2069
+ const timeoutMs = typeof cfg.timeoutMs === "number" && Number.isFinite(cfg.timeoutMs) && cfg.timeoutMs > 0 ? cfg.timeoutMs : DEFAULT_TIMEOUT_MS;
2070
+ const url = `${getRoutingApiBaseUrl(cfg.baseUrl).replace(/\/$/, "")}/api/v1/funding/zyfai-tasks`;
2071
+ const timedOut = new Promise((resolve) => {
2072
+ timer = setTimeout(() => {
2073
+ controller.abort();
2074
+ resolve("timeout");
2075
+ }, timeoutMs);
2076
+ timer.unref?.();
2077
+ });
2078
+ const exchange = (async () => {
2079
+ const res = await fetch(url, {
2080
+ method: "POST",
2081
+ headers: { "content-type": "application/json", "x-owney-api-key": cfg.apiKey },
2082
+ body: JSON.stringify(requestBody(body, session)),
2083
+ signal: controller.signal,
2084
+ // The body carries the session: never follow a redirect to another host.
2085
+ redirect: "error"
2086
+ });
2087
+ if (DECLINED_STATUSES.has(res.status)) return { type: "declined", code: `HTTP_${res.status}` };
2088
+ if (res.status !== 200) {
2089
+ debugLog(SCOPE3, `handoff answered ${res.status}`);
2090
+ return unknown();
2091
+ }
2092
+ return answerOf(await res.json());
2093
+ })();
2094
+ exchange.catch(() => {
2095
+ });
2096
+ const answer = await Promise.race([exchange, timedOut]);
2097
+ if (answer === "timeout") {
2098
+ debugLog(SCOPE3, "handoff timed out");
2099
+ return unknown();
2100
+ }
2101
+ debugLog(SCOPE3, `handoff ${answer.type}`);
2102
+ return answer;
2103
+ } catch {
2104
+ debugLog(SCOPE3, "handoff failed");
2105
+ return unknown();
2106
+ } finally {
2107
+ if (timer !== void 0) clearTimeout(timer);
2108
+ }
2109
+ }
2110
+
1806
2111
  // src/lib/withdrawal-reservation.ts
1807
- var SCOPE3 = "withdrawal-reservation";
2112
+ var SCOPE4 = "withdrawal-reservation";
1808
2113
  var CLAIM_TIMEOUT_MS = 4e3;
1809
2114
  var HEARTBEAT_INTERVAL_MS = 6e4;
1810
2115
  var HOLDER_TIMEOUT_MS = 5e3;
@@ -1902,7 +2207,7 @@ function compact2(obj) {
1902
2207
  async function claimWithdrawalReservation(cfg, input) {
1903
2208
  const result = await claimOnce(cfg, input);
1904
2209
  if (result.status === "unavailable" && result.httpStatus === 403 && input.supersedes) {
1905
- debugLog(SCOPE3, "claim 403 with supersedes: retrying without it");
2210
+ debugLog(SCOPE4, "claim 403 with supersedes: retrying without it");
1906
2211
  const { supersedes: _dropped, ...rest } = input;
1907
2212
  const retried = await claimOnce(cfg, rest);
1908
2213
  return retried.status === "claimed" ? { ...retried, supersedesDropped: true } : retried;
@@ -1929,7 +2234,7 @@ async function claimOnce(cfg, input) {
1929
2234
  res = await post(cfg, endpoint(cfg), body, CLAIM_TIMEOUT_MS);
1930
2235
  } catch (err) {
1931
2236
  const reason = err instanceof RequestTimeout ? "timeout" : "network";
1932
- debugLog(SCOPE3, `claim ${reason}: ${describeError3(err)}`);
2237
+ debugLog(SCOPE4, `claim ${reason}: ${describeError3(err)}`);
1933
2238
  return { status: "unavailable", reason };
1934
2239
  }
1935
2240
  if (!res) return { status: "unavailable", reason: "network" };
@@ -1945,7 +2250,7 @@ async function claimOnce(cfg, input) {
1945
2250
  }
1946
2251
  }
1947
2252
  if (!res.ok) {
1948
- debugLog(SCOPE3, `claim ${res.status}`);
2253
+ debugLog(SCOPE4, `claim ${res.status}`);
1949
2254
  return { status: "unavailable", reason: "http", httpStatus: res.status };
1950
2255
  }
1951
2256
  const data = unwrap2(res.body);
@@ -1980,12 +2285,12 @@ async function holderRequest(cfg, operationId, action, body, retryDelays = []) {
1980
2285
  if (res.ok) return "ok";
1981
2286
  if (res.status === 404) return "gone";
1982
2287
  if (res.status < 500 && res.status !== 429) {
1983
- debugLog(SCOPE3, `${action} rejected with ${res.status}`);
2288
+ debugLog(SCOPE4, `${action} rejected with ${res.status}`);
1984
2289
  return "rejected";
1985
2290
  }
1986
- debugLog(SCOPE3, `${action} ${res.status}`);
2291
+ debugLog(SCOPE4, `${action} ${res.status}`);
1987
2292
  } catch (err) {
1988
- debugLog(SCOPE3, `${action} failed: ${describeError3(err)}`);
2293
+ debugLog(SCOPE4, `${action} failed: ${describeError3(err)}`);
1989
2294
  }
1990
2295
  if (attempt < retryDelays.length) {
1991
2296
  await new Promise((resolve) => unrefTimer2(setTimeout(resolve, retryDelays[attempt])));
@@ -2158,7 +2463,7 @@ function ledgerLegFromEvidence(e, kind) {
2158
2463
  if (e.callsId && e.callsId.length <= 200) leg.callsId = e.callsId;
2159
2464
  if (isUint(e.confirmedAmount)) leg.confirmedAmountRaw = e.confirmedAmount;
2160
2465
  if (e.blockNumber && /^\d{1,19}$/.test(e.blockNumber)) leg.blockNumber = e.blockNumber;
2161
- const providerRef = e.withdrawalRequest?.providerWithdrawalId;
2466
+ const providerRef = e.withdrawalRequest?.providerWithdrawalId || e.providerDepositId;
2162
2467
  if (typeof providerRef === "string" && providerRef.length >= 1 && providerRef.length <= 128) leg.providerRef = providerRef;
2163
2468
  const failure = e.withdrawalRequest?.failure;
2164
2469
  if (failure) leg.errorCode = failure.toUpperCase().replace(/-/g, "_");
@@ -2187,6 +2492,12 @@ function withdrawalLedgerRail(e) {
2187
2492
  return e.userOpHash ? "yieldseeker_userop" : e.callsId ? "yieldseeker_wallet_calls" : e.txHash ? "yieldseeker_paid" : void 0;
2188
2493
  return void 0;
2189
2494
  }
2495
+ function depositLedgerRail(e) {
2496
+ if (e.callsId) return "wallet_calls";
2497
+ if (e.depositAuthorization || e.serverBroadcast) return "permit2_batch";
2498
+ return void 0;
2499
+ }
2500
+ var FUNDING_DEPOSIT_RECOVERY_REPORT = 1;
2190
2501
  var SWAP_LEDGER_RAILS = {
2191
2502
  classic: "swap_classic",
2192
2503
  fusion: "swap_fusion",
@@ -2254,6 +2565,12 @@ var TRANSFER_EVENT = (0, import_viem4.getAbiItem)({
2254
2565
  abi: (0, import_viem4.parseAbi)(["event Transfer(address indexed from, address indexed to, uint256 value)"]),
2255
2566
  name: "Transfer"
2256
2567
  });
2568
+ var acceptedChunk = /* @__PURE__ */ new Map();
2569
+ var acceptedChunkByClient = /* @__PURE__ */ new WeakMap();
2570
+ function providerKey(client) {
2571
+ const { chain: chain2, transport } = client;
2572
+ return typeof chain2?.id === "number" ? `${chain2.id}:${typeof transport?.url === "string" ? transport.url : ""}` : void 0;
2573
+ }
2257
2574
  function parseBlock(value) {
2258
2575
  if (value === void 0 || value === null) return void 0;
2259
2576
  return typeof value === "string" && /^\d{1,19}$/.test(value) ? BigInt(value) : null;
@@ -2278,11 +2595,12 @@ async function scanWithdrawalPayouts(client, input) {
2278
2595
  const candidates = [];
2279
2596
  if (start > end) {
2280
2597
  const kept = input.cursor ?? (start > 0n ? start - 1n : 0n);
2281
- return { candidates, scannedTo: kept.toString() };
2598
+ return { candidates, scannedTo: kept.toString(), caughtUp: true };
2282
2599
  }
2283
2600
  const tokenAddress = input.token.toLowerCase();
2284
2601
  const owner = input.owner.toLowerCase();
2285
- let size = PAYOUT_SCAN_CHUNK_BLOCKS;
2602
+ const key2 = providerKey(client);
2603
+ let size = (key2 !== void 0 ? acceptedChunk.get(key2) : acceptedChunkByClient.get(client)) ?? PAYOUT_SCAN_CHUNK_BLOCKS;
2286
2604
  let from = start;
2287
2605
  let scannedTo = start - 1n;
2288
2606
  for (let requests = 0; from <= end && requests < MAX_CHUNKS_PER_SCAN; ) {
@@ -2297,12 +2615,21 @@ async function scanWithdrawalPayouts(client, input) {
2297
2615
  toBlock: to
2298
2616
  });
2299
2617
  } catch (error) {
2300
- if (size > 1n && !isRateLimited(error) && isRangeError(error)) {
2301
- size /= 2n;
2302
- continue;
2618
+ if (!isRateLimited(error) && isRangeError(error)) {
2619
+ const requested = to - from + 1n;
2620
+ const allowed = allowedRange(error);
2621
+ const next = allowed !== void 0 && allowed < requested ? allowed : requested / 2n;
2622
+ if (next >= 1n && next < requested) {
2623
+ size = next;
2624
+ continue;
2625
+ }
2303
2626
  }
2304
2627
  throw error;
2305
2628
  }
2629
+ if (size < PAYOUT_SCAN_CHUNK_BLOCKS) {
2630
+ if (key2 !== void 0) acceptedChunk.set(key2, size);
2631
+ else acceptedChunkByClient.set(client, size);
2632
+ }
2306
2633
  check();
2307
2634
  requests++;
2308
2635
  for (const log of logs) {
@@ -2320,7 +2647,8 @@ async function scanWithdrawalPayouts(client, input) {
2320
2647
  scannedTo = to;
2321
2648
  from = to + 1n;
2322
2649
  }
2323
- return { candidates, scannedTo: (scannedTo < 0n ? 0n : scannedTo).toString() };
2650
+ const stalled = input.cursor !== void 0 && scannedTo <= input.cursor;
2651
+ return { candidates, scannedTo: (scannedTo < 0n ? 0n : scannedTo).toString(), caughtUp: from > end || stalled };
2324
2652
  }
2325
2653
  async function estimateBlockAt(client, latest, targetMs) {
2326
2654
  const head = await client.getBlock({ blockNumber: latest });
@@ -2345,6 +2673,7 @@ function causeChain(error) {
2345
2673
  chain2.push({
2346
2674
  code: value.code,
2347
2675
  status: value.status,
2676
+ data: value.data,
2348
2677
  text: [value.message, value.details, value.shortMessage].filter((part) => typeof part === "string").join(" ")
2349
2678
  });
2350
2679
  current = value.cause;
@@ -2354,6 +2683,14 @@ function causeChain(error) {
2354
2683
  function isRateLimited(error) {
2355
2684
  return causeChain(error).some((entry) => entry.status === 429 || entry.code === 429 || /rate|too many requests/i.test(entry.text));
2356
2685
  }
2686
+ function allowedRange(error) {
2687
+ for (const entry of causeChain(error)) {
2688
+ const hinted = entry.data && typeof entry.data === "object" ? entry.data.maxBlockRange : void 0;
2689
+ const value = typeof hinted === "number" ? hinted : Number(/"maxBlockRange"\s*:\s*(\d+)/.exec(entry.text)?.[1]);
2690
+ if (Number.isSafeInteger(value) && value >= 1) return BigInt(value);
2691
+ }
2692
+ return void 0;
2693
+ }
2357
2694
  function isRangeError(error) {
2358
2695
  return causeChain(error).some((entry) => entry.code === -32005 || /block range|range (is )?too (large|wide|big)|range limit|exceed(s|ed)? .*(range|limit|results)|too many (results|logs|blocks)|response size|query returned more than|max(imum)? .*blocks/i.test(entry.text));
2359
2696
  }
@@ -2463,6 +2800,7 @@ async function getSponsorRelayerAddress(input) {
2463
2800
  return parsed.data.relayer;
2464
2801
  }
2465
2802
  async function postSponsorBatchTransfer(input) {
2803
+ const flaggedPrepare = input.body.serverBroadcast === true && !input.body.serializedTransaction;
2466
2804
  let res;
2467
2805
  try {
2468
2806
  res = await fetch(
@@ -2481,7 +2819,7 @@ async function postSponsorBatchTransfer(input) {
2481
2819
  throw new OwneyError(
2482
2820
  "SPONSOR_REQUEST_FAILED",
2483
2821
  "Deposit status is unknown. Retry the same amount to check it.",
2484
- { safeToFallback: false }
2822
+ { safeToFallback: false, ...flaggedPrepare ? { outcomeUnknown: true } : {} }
2485
2823
  );
2486
2824
  }
2487
2825
  const parsed = await res.json().catch(() => null);
@@ -2492,7 +2830,13 @@ async function postSponsorBatchTransfer(input) {
2492
2830
  {
2493
2831
  statusCode: res.status,
2494
2832
  safeToFallback: false,
2495
- notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
2833
+ notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true,
2834
+ // Another authorization of this operation is being sent by the
2835
+ // server (409, nothing signed).
2836
+ ...parsed?.code === "DEPOSIT_ALREADY_SENDING" ? { code: "DEPOSIT_ALREADY_SENDING" } : {},
2837
+ // No definitive answer (5xx, an unreadable or malformed body): the
2838
+ // server may have accepted it. A 4xx with a body is a refusal.
2839
+ ...flaggedPrepare && (res.status >= 500 || parsed === null || res.ok) ? { outcomeUnknown: true } : {}
2496
2840
  }
2497
2841
  );
2498
2842
  }
@@ -2509,19 +2853,19 @@ async function postSponsorBatchRecovery(input) {
2509
2853
  input.signal?.throwIfAborted();
2510
2854
  if (!response.ok || payload?.success !== true) throw failure();
2511
2855
  const value = payload.data;
2512
- const { isAddress: isAddress6, keccak256: keccak2565, parseTransaction: parseTransaction4, recoverTransactionAddress: recoverTransactionAddress2 } = await import("viem");
2856
+ const { isAddress: isAddress7, keccak256: keccak2565, parseTransaction: parseTransaction4, recoverTransactionAddress: recoverTransactionAddress2 } = await import("viem");
2513
2857
  const { validateTokenBatchAttempt: validateTokenBatchAttempt2 } = await Promise.resolve().then(() => (init_token_batch_family(), token_batch_family_exports));
2514
2858
  const phase = input.body.phase;
2515
2859
  const original = phase === "approval" ? input.body.serializedPermitTransaction : input.body.serializedTransaction;
2516
2860
  if (!original) throw failure();
2517
2861
  const hash2 = keccak2565(original), relayer = await recoverTransactionAddress2({ serializedTransaction: original });
2518
- const validHash = (v) => typeof v === "string" && /^0x[\da-f]{64}$/i.test(v);
2519
- if (!value || value.protocol !== 1 || value.phase !== phase || !validHash(value.txHash) || !isAddress6(value.relayer) || value.relayer.toLowerCase() !== relayer.toLowerCase()) throw failure();
2862
+ const validHash2 = (v) => typeof v === "string" && /^0x[\da-f]{64}$/i.test(v);
2863
+ if (!value || value.protocol !== 1 || value.phase !== phase || !validHash2(value.txHash) || !isAddress7(value.relayer) || value.relayer.toLowerCase() !== relayer.toLowerCase()) throw failure();
2520
2864
  if (value.status !== "prepared" && value.txHash.toLowerCase() !== hash2.toLowerCase()) throw failure();
2521
2865
  if (value.status === "unresolved") {
2522
2866
  if (!["nonce-not-consumed", "authorization-used", "authorization-expired", "state-unavailable", "preparation-rejected", "relayer-changed"].includes(value.reason)) throw failure();
2523
2867
  } else {
2524
- if (!("blockNumber" in value) || typeof value.blockNumber !== "string" || !/^\d{1,78}$/.test(value.blockNumber) || BigInt(value.blockNumber) >= 1n << 256n || !validHash(value.blockHash)) throw failure();
2868
+ if (!("blockNumber" in value) || typeof value.blockNumber !== "string" || !/^\d{1,78}$/.test(value.blockNumber) || BigInt(value.blockNumber) >= 1n << 256n || !validHash2(value.blockHash)) throw failure();
2525
2869
  if (value.status === "prepared") {
2526
2870
  if (value.predecessorHash?.toLowerCase() !== hash2.toLowerCase()) throw failure();
2527
2871
  const tx = parseTransaction4(value.serializedTransaction), old = parseTransaction4(original);
@@ -2823,11 +3167,19 @@ var AgentReads = class {
2823
3167
  }
2824
3168
  };
2825
3169
 
3170
+ // src/agents/zyfai/zyfai.session-errors.ts
3171
+ var SESSION_REJECTED = /(?:^|: )Unauthorized: (?:(?:Invalid or expired user session|Missing user session)\b.*|Invalid API key)$/;
3172
+ function isZyfaiSessionRejected(message) {
3173
+ return SESSION_REJECTED.test(message);
3174
+ }
3175
+
2826
3176
  // src/agents/zyfai/zyfai.withdraw-submission.ts
2827
3177
  var BELOW_MINIMUM = /^Idle balance (\d+(?:\.\d+)?) (USDC|WETH|ETH|EURC) is below the minimum withdrawal of (\d+(?:\.\d+)?) \2\.?$/;
2828
3178
  var listenersByClient = /* @__PURE__ */ new WeakMap();
3179
+ var ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT = 1;
2829
3180
  var MAX_WITHDRAWAL_ID_LENGTH = 128;
2830
- function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
3181
+ var WITHDRAWAL_PATHS = { get: "/users/withdraw/v2", post: "/users/partial-withdraw/v2" };
3182
+ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent, beforeDispatch) {
2831
3183
  const client = sdk.httpClient;
2832
3184
  if (!client || typeof client.get !== "function" || typeof client.post !== "function")
2833
3185
  return {
@@ -2847,7 +3199,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2847
3199
  const original = client[method];
2848
3200
  client[method] = async function(...args) {
2849
3201
  const [url, options] = args;
2850
- const withdrawal = method === "get" && url === "/users/withdraw" || method === "post" && url === "/users/partial-withdraw";
3202
+ const withdrawal = url === WITHDRAWAL_PATHS[method];
2851
3203
  const payload = method === "get" ? options?.params : options;
2852
3204
  const matching = withdrawal ? [...observers].filter((observer) => observer.chainId === payload?.chainId && observer.token === payload?.tokenSymbol) : [];
2853
3205
  for (const observer of matching) observer.assertCurrent?.();
@@ -2855,9 +3207,12 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2855
3207
  const request3 = {
2856
3208
  attempted: true,
2857
3209
  method: method === "get" ? "GET" : "POST",
2858
- path: method === "get" ? "/users/withdraw" : "/users/partial-withdraw"
3210
+ path: WITHDRAWAL_PATHS[method]
2859
3211
  };
2860
- if (withdrawal) request3.path = `${request3.path}/v2`;
3212
+ if (withdrawal) {
3213
+ for (const observer of matching) await observer.beforeDispatch?.({ ...request3 });
3214
+ for (const observer of matching) observer.assertCurrent?.();
3215
+ }
2861
3216
  let belowMinimum2;
2862
3217
  const notify2 = (outcome) => {
2863
3218
  if (outcome === "rejected" && matching.length !== 1) return;
@@ -2890,23 +3245,11 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2890
3245
  captureStatus
2891
3246
  ] };
2892
3247
  }
2893
- const send = (route) => original.apply(this, [route, ...callArgs.slice(1)]);
2894
3248
  try {
2895
- let result;
2896
- if (!withdrawal) result = await send(url);
2897
- else {
2898
- try {
2899
- result = await send(`${url}/v2`);
2900
- } catch (error) {
2901
- const status = request3.httpStatus ?? error?.response?.status;
2902
- if (status !== 404) throw error;
2903
- for (const observer of matching) observer.assertCurrent?.();
2904
- request3.path = url;
2905
- delete request3.httpStatus;
2906
- result = await send(url);
2907
- }
3249
+ const result = await original.apply(this, callArgs);
3250
+ if (withdrawal) {
2908
3251
  const id = result?.id;
2909
- if (request3.path?.endsWith("/v2") && typeof id === "string" && id.length >= 1 && id.length <= MAX_WITHDRAWAL_ID_LENGTH)
3252
+ if (typeof id === "string" && id.length >= 1 && id.length <= MAX_WITHDRAWAL_ID_LENGTH)
2910
3253
  request3.providerWithdrawalId = id;
2911
3254
  }
2912
3255
  return result;
@@ -2917,7 +3260,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2917
3260
  const status = error?.response?.status;
2918
3261
  if (request3.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
2919
3262
  request3.httpStatus = status;
2920
- request3.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : request3.httpStatus === 400 && message === "No balance to withdraw" ? "insufficient-balance" : minimum ? "insufficient-balance" : /^User doesn't have any active session key\.?$/.test(message) ? "session-key-missing" : method === "post" && request3.httpStatus === 400 && message === "Partial withdrawal already in progress" ? "withdrawal-in-progress" : message === "Network error: Unable to reach the server" ? "network" : "other";
3263
+ request3.failure = isZyfaiSessionRejected(message) && (request3.httpStatus === void 0 || request3.httpStatus === 401) ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : request3.httpStatus === 400 && message === "No balance to withdraw" ? "insufficient-balance" : minimum ? "insufficient-balance" : /^User doesn't have any active session key\.?$/.test(message) ? "session-key-missing" : method === "post" && request3.httpStatus === 400 && message === "Partial withdrawal already in progress" ? "withdrawal-in-progress" : message === "Network error: Unable to reach the server" ? "network" : "other";
2921
3264
  if (["unauthorized", "forbidden", "rate-limited", "insufficient-balance", "session-key-missing", "withdrawal-in-progress"].includes(request3.failure) && (request3.httpStatus === void 0 || request3.httpStatus >= 400 && request3.httpStatus < 500)) notify2("rejected");
2922
3265
  throw error;
2923
3266
  } finally {
@@ -2931,7 +3274,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2931
3274
  let rejected = false;
2932
3275
  let request2 = { attempted: false };
2933
3276
  let belowMinimum;
2934
- const listener = { chainId, token, assertCurrent, notify: (outcome, evidence, detail) => {
3277
+ const listener = { chainId, token, assertCurrent, beforeDispatch, notify: (outcome, evidence, detail) => {
2935
3278
  request2 = evidence;
2936
3279
  if (outcome === "attempted") submitted = true;
2937
3280
  else if (outcome === "rejected") {
@@ -3073,7 +3416,7 @@ async function submitZyfaiDeposit({ sdk, state, chain: chain2, amount, asset, sm
3073
3416
  if (!token || !(0, import_viem7.isAddress)(owner) || !(0, import_viem7.isAddress)(smartWallet) || !/^\d+$/.test(amount) || BigInt(amount) <= 0n)
3074
3417
  throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId: chain2.id, asset }, "zyfai");
3075
3418
  assertCurrent();
3076
- const prepared = await sdk.prepareEnterPosition({ userAddress: owner, chainId: chain2.id, amount, asset, strategy: "aggressive" });
3419
+ const prepared = await sdk.prepareDeposit({ userAddress: owner, chainId: chain2.id, amount, asset });
3077
3420
  assertCurrent();
3078
3421
  const expectedData = (0, import_viem7.encodeFunctionData)({ abi: import_viem7.erc20Abi, functionName: "transfer", args: [smartWallet, BigInt(amount)] });
3079
3422
  const transfer = prepared?.transfer;
@@ -3156,6 +3499,13 @@ function tokenDecimals(symbol2, explicit) {
3156
3499
  return explicit;
3157
3500
  return DECIMALS_BY_SYMBOL[(symbol2 ?? "").toLowerCase()] ?? 6;
3158
3501
  }
3502
+ function decodeDeltaAmount(raw2, decimals) {
3503
+ if (typeof raw2 !== "string") return void 0;
3504
+ if (/^0x[0-9a-f]+$/i.test(raw2)) return String(hexToDecimal(raw2, decimals));
3505
+ if (/^\d+$/.test(raw2))
3506
+ return String(hexToDecimal(BigInt(raw2).toString(16), decimals));
3507
+ return void 0;
3508
+ }
3159
3509
  function mapWithdraw(raw2) {
3160
3510
  return {
3161
3511
  txHash: raw2.txHash,
@@ -3205,6 +3555,40 @@ function mapPendingAllocations(raw2) {
3205
3555
  }
3206
3556
  return pending2.length > 0 ? pending2 : void 0;
3207
3557
  }
3558
+ function mapPendingWithdrawals(raw2) {
3559
+ if (!Array.isArray(raw2)) return void 0;
3560
+ const pending2 = [];
3561
+ for (const entry of raw2) {
3562
+ if (typeof entry !== "object" || entry === null) continue;
3563
+ if (typeof entry.id !== "string" || entry.id === "") continue;
3564
+ if (entry.status !== "REQUESTED" && entry.status !== "CLAIMABLE") continue;
3565
+ if (typeof entry.chainId !== "number" || !Number.isFinite(entry.chainId)) continue;
3566
+ const asset = entry.token?.symbol;
3567
+ if (typeof asset !== "string" || asset === "" || typeof entry.amount !== "string") continue;
3568
+ let amountRaw;
3569
+ try {
3570
+ const amount = BigInt(/^0x/i.test(entry.amount) ? entry.amount : `0x${entry.amount}`);
3571
+ if (amount <= 0n) continue;
3572
+ amountRaw = amount.toString();
3573
+ } catch {
3574
+ continue;
3575
+ }
3576
+ pending2.push({ id: entry.id, status: entry.status, chainId: entry.chainId, asset, amountRaw });
3577
+ }
3578
+ return pending2.length > 0 ? pending2 : void 0;
3579
+ }
3580
+ function mapRecentAsyncWithdrawals(raw2) {
3581
+ if (!Array.isArray(raw2)) return void 0;
3582
+ const recent = [];
3583
+ for (const entry of raw2) {
3584
+ if (typeof entry !== "object" || entry === null) return void 0;
3585
+ if (entry.status === "FAILED") continue;
3586
+ const asset = entry.token?.symbol;
3587
+ if (typeof entry.id !== "string" || entry.id === "" || typeof entry.status !== "string" || entry.status === "" || typeof entry.chainId !== "number" || !Number.isFinite(entry.chainId) || typeof asset !== "string" || asset === "") return void 0;
3588
+ recent.push({ id: entry.id, status: entry.status, chainId: entry.chainId, asset });
3589
+ }
3590
+ return recent;
3591
+ }
3208
3592
  function mapBalances(raw2, _chainId, smartWallet) {
3209
3593
  const portfolio = raw2.portfolio;
3210
3594
  if (!portfolio?.portfolioByChain || typeof portfolio.portfolioByChain !== "object")
@@ -3263,6 +3647,9 @@ function mapBalances(raw2, _chainId, smartWallet) {
3263
3647
  const pendingAllocations = mapPendingAllocations(
3264
3648
  portfolio.staleBalances
3265
3649
  );
3650
+ const asyncWithdrawals = portfolio.pendingAsyncWithdrawals;
3651
+ const pendingWithdrawals = mapPendingWithdrawals(asyncWithdrawals);
3652
+ const recentAsyncWithdrawals = mapRecentAsyncWithdrawals(asyncWithdrawals);
3266
3653
  return {
3267
3654
  smartWallet,
3268
3655
  totalBalance: String(totalBalance),
@@ -3278,7 +3665,49 @@ function mapBalances(raw2, _chainId, smartWallet) {
3278
3665
  blockNumber: provenance.blockNumber === void 0 ? void 0 : String(provenance.blockNumber),
3279
3666
  positionsStatus: Array.isArray(portfolio.positions) ? "available" : "unavailable"
3280
3667
  })),
3281
- ...pendingAllocations ? { pendingAllocations } : {}
3668
+ ...pendingAllocations ? { pendingAllocations } : {},
3669
+ ...pendingWithdrawals ? { pendingWithdrawals } : {},
3670
+ ...recentAsyncWithdrawals ? { recentAsyncWithdrawals } : {}
3671
+ };
3672
+ }
3673
+ var TX_HASH2 = /^0x[0-9a-fA-F]{64}$/;
3674
+ var validHash = (value) => typeof value === "string" && TX_HASH2.test(value) ? value : void 0;
3675
+ var validDate = (value) => typeof value === "string" && Number.isFinite(Date.parse(value)) ? value : void 0;
3676
+ function mapWithdrawalLifecycle(raw2) {
3677
+ const body = raw2;
3678
+ if (!body || typeof body !== "object" || typeof body.status !== "string" || typeof body.syncSettled !== "boolean")
3679
+ return null;
3680
+ const syncTxHash = validHash(body.syncTxHash);
3681
+ const estimatedClaimAt = validDate(body.estimatedClaimAt);
3682
+ const asyncLegs = [];
3683
+ for (const leg of Array.isArray(body.asyncLegs) ? body.asyncLegs : []) {
3684
+ if (!leg || typeof leg !== "object" || typeof leg.id !== "string" || leg.id === "" || typeof leg.status !== "string") continue;
3685
+ const legClaimAt = validDate(leg.estimatedClaimAt);
3686
+ const claimTxHash = validHash(leg.claimTxHash);
3687
+ asyncLegs.push({
3688
+ id: leg.id,
3689
+ status: leg.status,
3690
+ ...legClaimAt ? { estimatedClaimAt: legClaimAt } : {},
3691
+ ...claimTxHash ? { claimTxHash } : {}
3692
+ });
3693
+ }
3694
+ const failedAsyncIds = Array.isArray(body.failedAsyncIds) ? body.failedAsyncIds.filter((id) => typeof id === "string" && id !== "") : [];
3695
+ return {
3696
+ status: body.status,
3697
+ syncSettled: body.syncSettled,
3698
+ ...syncTxHash ? { syncTxHash } : {},
3699
+ asyncLegs,
3700
+ failedAsyncIds,
3701
+ ...estimatedClaimAt ? { estimatedClaimAt } : {},
3702
+ hasError: typeof body.lastError === "string" && body.lastError !== ""
3703
+ };
3704
+ }
3705
+ function mapDepositPlacement(raw2) {
3706
+ const body = raw2;
3707
+ if (!body || typeof body !== "object" || typeof body.status !== "string") return null;
3708
+ return {
3709
+ status: body.status,
3710
+ ...typeof body.positionOutcome === "string" ? { positionOutcome: body.positionOutcome } : {}
3282
3711
  };
3283
3712
  }
3284
3713
  function sumTokenValues(tokens) {
@@ -3442,12 +3871,24 @@ function mapEntries(rawEntries, chainId) {
3442
3871
  const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
3443
3872
  const rawAmount2 = entry?.positions?.[0]?.amount;
3444
3873
  const hasAmount = mappedAction !== "Withdraw" && typeof rawAmount2 === "string" && rawAmount2 !== "" && rawAmount2 !== "0x" && rawAmount2 !== "0x0" && rawAmount2 !== "0";
3874
+ const rawDelta = mappedAction === "Deposit" ? decodeDeltaAmount(
3875
+ entry?.positions?.[0]?.deltaAmount,
3876
+ tokenDecimals(entry?.positions?.[0]?.token_symbol)
3877
+ ) : void 0;
3445
3878
  const mappedPositions = positions.filter((p) => p.protocol_name).map((p) => ({
3446
3879
  protocol: p.protocol_name,
3447
3880
  pool: p.pool ?? void 0,
3448
3881
  tokenSymbol: p.token_symbol ?? void 0,
3449
- amount: mappedAction !== "Withdraw" && typeof p.amount === "string" && p.amount !== "" && p.amount !== "0x" ? String(hexToDecimal(p.amount, tokenDecimals(p.token_symbol))) : void 0
3882
+ amount: mappedAction !== "Withdraw" && typeof p.amount === "string" && p.amount !== "" && p.amount !== "0x" ? String(hexToDecimal(p.amount, tokenDecimals(p.token_symbol))) : void 0,
3883
+ deltaAmount: mappedAction !== "Withdraw" ? decodeDeltaAmount(p.deltaAmount, tokenDecimals(p.token_symbol)) : void 0
3450
3884
  }));
3885
+ const reportedAmount = hasAmount ? String(
3886
+ hexToDecimal(
3887
+ rawAmount2,
3888
+ tokenDecimals(entry?.positions?.[0]?.token_symbol)
3889
+ )
3890
+ ) : void 0;
3891
+ const transactionAmount = rawDelta === void 0 ? reportedAmount : Number(rawDelta) > 0 ? rawDelta : void 0;
3451
3892
  return {
3452
3893
  agent: "zyfai",
3453
3894
  action: mappedAction,
@@ -3460,12 +3901,7 @@ function mapEntries(rawEntries, chainId) {
3460
3901
  txHashes: entry.transactionHash ? [entry.transactionHash] : [],
3461
3902
  chainId: entry.chainId,
3462
3903
  tokenSymbol: entry?.positions?.[0]?.token_symbol,
3463
- amount: hasAmount ? String(
3464
- hexToDecimal(
3465
- rawAmount2,
3466
- tokenDecimals(entry?.positions?.[0]?.token_symbol)
3467
- )
3468
- ) : void 0
3904
+ amount: transactionAmount
3469
3905
  }
3470
3906
  ],
3471
3907
  rebalanceLog
@@ -3667,9 +4103,9 @@ var isJwtExpired = (token) => {
3667
4103
  const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
3668
4104
  const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
3669
4105
  const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
3670
- const claims = JSON.parse(decoded);
3671
- if (typeof claims.exp !== "number") return false;
3672
- return Date.now() / 1e3 >= claims.exp - 30;
4106
+ const claims2 = JSON.parse(decoded);
4107
+ if (typeof claims2.exp !== "number") return false;
4108
+ return Date.now() / 1e3 >= claims2.exp - 30;
3673
4109
  } catch {
3674
4110
  return false;
3675
4111
  }
@@ -3910,7 +4346,7 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
3910
4346
  }
3911
4347
 
3912
4348
  // src/lib/agent-accounts.ts
3913
- var SCOPE4 = "agent-accounts";
4349
+ var SCOPE5 = "agent-accounts";
3914
4350
  var REQUEST_TIMEOUT_MS3 = 5e3;
3915
4351
  function compact3(obj) {
3916
4352
  const out = {};
@@ -3980,7 +4416,7 @@ async function fetchAgentAccounts(input) {
3980
4416
  signal
3981
4417
  });
3982
4418
  if (!res.ok) {
3983
- debugLog(SCOPE4, `fetch rejected with ${res.status}`);
4419
+ debugLog(SCOPE5, `fetch rejected with ${res.status}`);
3984
4420
  return null;
3985
4421
  }
3986
4422
  const data = unwrap3(await res.json());
@@ -3995,7 +4431,7 @@ async function fetchAgentAccounts(input) {
3995
4431
  cancel();
3996
4432
  }
3997
4433
  } catch (err) {
3998
- debugLog(SCOPE4, `fetch failed: ${describeError4(err)}`);
4434
+ debugLog(SCOPE5, `fetch failed: ${describeError4(err)}`);
3999
4435
  return null;
4000
4436
  }
4001
4437
  }
@@ -4026,7 +4462,7 @@ async function reportAgentAccount(input) {
4026
4462
  signal
4027
4463
  });
4028
4464
  if (!res.ok) {
4029
- debugLog(SCOPE4, `report rejected with ${res.status}`);
4465
+ debugLog(SCOPE5, `report rejected with ${res.status}`);
4030
4466
  return null;
4031
4467
  }
4032
4468
  const data = unwrap3(await res.json());
@@ -4038,7 +4474,7 @@ async function reportAgentAccount(input) {
4038
4474
  cancel();
4039
4475
  }
4040
4476
  } catch (err) {
4041
- debugLog(SCOPE4, `report failed: ${describeError4(err)}`);
4477
+ debugLog(SCOPE5, `report failed: ${describeError4(err)}`);
4042
4478
  return null;
4043
4479
  }
4044
4480
  }
@@ -4197,6 +4633,17 @@ var ZYFAI_LEDGER_IDLE_CACHE_MS = 6e4;
4197
4633
  var ZYFAI_LEDGER_REUSE_MS = 6e4;
4198
4634
  var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
4199
4635
  var PAYOUT_SEARCH_HEAD_LIMIT_MS = 2e3;
4636
+ var WITHDRAW_HANDOFF_TIMEOUT_MS = 25e3;
4637
+ var PLANNED_HANDOFF_TIMEOUT_MS = 8e3;
4638
+ var PLANNED_HANDOFFS_LIMIT = 32;
4639
+ var REGISTRATION_HANDOFF_TIMEOUT_MS = 8e3;
4640
+ var REGISTRATION_NUDGE_TIMEOUT_MS = 25e3;
4641
+ var REGISTRATION_AMOUNT = /^[1-9][0-9]{0,77}$/;
4642
+ var TASK_CODE = /^[A-Z0-9_]{1,64}$/;
4643
+ var SERVER_LEG_CLEARED_CODE = "ZYFAI_LEG_CLEARED";
4644
+ var SESSION_ENDING_CODE = "ZYFAI_SESSION_ENDING";
4645
+ var SERVER_LEG_STATES_LIMIT = 2e3;
4646
+ var ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
4200
4647
  function settleWithin(value, ms) {
4201
4648
  let timer;
4202
4649
  const limit = new Promise((resolve) => {
@@ -4206,6 +4653,28 @@ function settleWithin(value, ms) {
4206
4653
  if (timer) clearTimeout(timer);
4207
4654
  });
4208
4655
  }
4656
+ function depositIdFromLog(logged) {
4657
+ const id = logged?.deposit?.id;
4658
+ return typeof id === "string" && id.length >= 1 && id.length <= 128 ? id : void 0;
4659
+ }
4660
+ function registrationOutcome(answer, knownTask) {
4661
+ if (answer.type === "unknown") return { kind: "unknown" };
4662
+ if (answer.type === "declined") {
4663
+ return !knownTask && provesNoServerTask(answer.code, true) ? { kind: "local" } : { kind: "unknown", code: answer.code };
4664
+ }
4665
+ if (answer.result.providerDepositId) return { kind: "confirmed", providerDepositId: answer.result.providerDepositId };
4666
+ if (answer.state === "failed" && answer.code && ZYFAI_REGISTRATION_NOT_SENT_CODES.has(answer.code)) return { kind: "local" };
4667
+ if (answer.state === "queued" || answer.state === "running") return { kind: "pending" };
4668
+ return { kind: "unknown", code: answer.code ?? answer.state.toUpperCase(), accepted: true };
4669
+ }
4670
+ function registrationOutcomeUnknown(code) {
4671
+ const error = new OwneyError(
4672
+ "AGENT_API_ERROR",
4673
+ "Zyfai registration by the server has no known outcome, so this browser does not register the deposit again.",
4674
+ { status: 409, ...code && TASK_CODE.test(code) ? { reason: code } : {} }
4675
+ );
4676
+ return Object.assign(error, { status: 409 });
4677
+ }
4209
4678
  function belowMinimumCeiling(rejection, token, decimals) {
4210
4679
  if ((rejection.symbol === "ETH" ? "WETH" : rejection.symbol) !== token) return void 0;
4211
4680
  const ceiling = (display) => {
@@ -4229,6 +4698,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
4229
4698
  referralSource;
4230
4699
  accountReporting;
4231
4700
  id = "zyfai";
4701
+ /** This adapter can hand its withdrawals to our server (part 4). */
4702
+ supportsServerTasks = true;
4232
4703
  supportedChainIds = [8453, 42161, 1];
4233
4704
  supportedAssets = [
4234
4705
  {
@@ -4305,6 +4776,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
4305
4776
  /** A ledger read slower than this counts as "no ledger information" for that read. */
4306
4777
  ledgerBudgetMs = LEDGER_READ_BUDGET_MS;
4307
4778
  balanceSnapshots = /* @__PURE__ */ new WeakMap();
4779
+ /** operationId -> this session's plan-time handoff of that withdrawal (part 4),
4780
+ * read once by its nudge: the `after` legs and the planned share the task
4781
+ * was handed off with ("MAX" or digits). Who owns the leg is
4782
+ * `serverLegStates`, never this record. */
4783
+ plannedHandoffs = /* @__PURE__ */ new Map();
4784
+ /** operationId -> the server ownership of its Zyfai withdrawal leg (T9-I5).
4785
+ * The one source for "may this browser send it" (`mayWithdrawLocally`). */
4786
+ serverLegStates = /* @__PURE__ */ new Map();
4787
+ /** Deposit operations whose registration create was sent in this session:
4788
+ * a retry of one is always handed off again, whatever its session's
4789
+ * lifetime (F-I1(a)), so the server answers the task it may hold. */
4790
+ requestedRegistrations = /* @__PURE__ */ new Set();
4308
4791
  /** A replaced wallet session gets its own vendor client. In-flight calls
4309
4792
  * keep their original client; authentication must never change owners under
4310
4793
  * an operation that is waiting for provider reads or an HTTP response. */
@@ -4515,10 +4998,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
4515
4998
  * Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
4516
4999
  * cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
4517
5000
  * we drop the cache and try again — the second attempt has no cache entry,
4518
- * so it falls through to a real SIWE prompt. Zyfai labels every HTTP 401 with
4519
- * an `"Unauthorized: Invalid API key"` error so we can't distinguish revoked
4520
- * JWT from a misconfigured API key; we retry once either way, and if the
4521
- * second attempt still fails the original error propagates.
5001
+ * so it falls through to a real SIWE prompt. @zyfai/sdk 0.10.0 labels every
5002
+ * HTTP 401 as an invalid/expired or missing user session ("Unauthorized: …"),
5003
+ * which does not separate a revoked JWT from a misconfigured API key; we
5004
+ * retry once either way, and if the second attempt still fails the original
5005
+ * error propagates.
4522
5006
  */
4523
5007
  async connectAccountWithRetry(provider, chainId, executionSdk = this.sdk) {
4524
5008
  try {
@@ -4555,9 +5039,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
4555
5039
  }
4556
5040
  }
4557
5041
  /** A rejected execution session must not be reused by the next foreground attempt. */
4558
- invalidateRejectedWithdrawalSession(error, state, chainId, authToken) {
5042
+ invalidateRejectedWithdrawalSession(error, state, chainId, authToken, httpStatus) {
4559
5043
  const message = error instanceof Error ? error.message : "";
4560
- if (!/(?:^|: )Unauthorized: Invalid API key$/.test(message)) return;
5044
+ if (!isZyfaiSessionRejected(message) || httpStatus !== void 0 && httpStatus !== 401) return;
5045
+ this.dropWithdrawalSession(state, chainId, authToken);
5046
+ }
5047
+ /** Drops the execution session a withdrawal used (Zyfai rejected it), unless
5048
+ * it has already changed. The next foreground connection signs in again. */
5049
+ dropWithdrawalSession(state, chainId, authToken) {
4561
5050
  const internals = this.sdk;
4562
5051
  if (typeof authToken !== "string" || internals.httpClient.authToken !== authToken || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== chainId) return;
4563
5052
  clearSession(state.walletAddress, chainId);
@@ -5165,7 +5654,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
5165
5654
  await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
5166
5655
  return redeployed.address;
5167
5656
  }
5168
- async deposit(state, chainId, amount, asset, depositCallback, onProgress, onBeforeSubmission) {
5657
+ async deposit(state, chainId, amount, asset, depositCallback, onProgress, onBeforeSubmission, context) {
5169
5658
  const validChainId = isValidChainId(chainId);
5170
5659
  const owner = state.walletAddress;
5171
5660
  const provider = state.provider;
@@ -5180,6 +5669,12 @@ var ZyfaiAgent = class _ZyfaiAgent {
5180
5669
  assertCurrent();
5181
5670
  const smartWallet = await this.ensureWalletDeployed(owner, validChainId);
5182
5671
  assertCurrent();
5672
+ let server;
5673
+ if (this.accountReporting.owneyApiKey && typeof context?.operationId === "string" && context.operationId && this.serverTasksOn(this.requestedRegistrations.has(context.operationId))) {
5674
+ server = await this.handOffDepositRegistration(owner, validChainId, asset, amount, smartWallet, context.operationId);
5675
+ assertCurrent();
5676
+ }
5677
+ const mark = server ?? {};
5183
5678
  notifyFunding(onProgress, {
5184
5679
  agentId: this.id,
5185
5680
  chainId,
@@ -5189,7 +5684,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
5189
5684
  intendedAmount: amount,
5190
5685
  transfer: "pending",
5191
5686
  registration: "pending",
5192
- delivery: "not-applicable"
5687
+ delivery: "not-applicable",
5688
+ ...mark
5193
5689
  });
5194
5690
  const txHash = depositCallback ? await depositCallback(smartWallet, validChainId, amount) : await submitZyfaiDeposit({
5195
5691
  sdk: executionSdk,
@@ -5208,7 +5704,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
5208
5704
  intendedAmount: amount,
5209
5705
  transfer: "pending",
5210
5706
  registration: "pending",
5211
- delivery: "not-applicable"
5707
+ delivery: "not-applicable",
5708
+ ...mark
5212
5709
  }) : void 0
5213
5710
  });
5214
5711
  let evidence = {
@@ -5221,7 +5718,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
5221
5718
  txHash,
5222
5719
  transfer: "pending",
5223
5720
  registration: "pending",
5224
- delivery: "not-applicable"
5721
+ delivery: "not-applicable",
5722
+ ...mark
5225
5723
  };
5226
5724
  notifyFunding(onProgress, evidence);
5227
5725
  try {
@@ -5241,11 +5739,32 @@ var ZyfaiAgent = class _ZyfaiAgent {
5241
5739
  throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
5242
5740
  if (evidence.transfer !== "confirmed") return { txHash, smartWallet, amount, evidence };
5243
5741
  this.ledgerReads.clear();
5742
+ if (server) {
5743
+ let outcome;
5744
+ try {
5745
+ assertCurrent();
5746
+ outcome = await this.nudgeDepositRegistration(evidence, this.executionSession());
5747
+ } catch {
5748
+ outcome = { kind: "unknown" };
5749
+ }
5750
+ if (outcome.kind !== "local") {
5751
+ evidence = outcome.kind === "confirmed" ? { ...evidence, registration: "confirmed", providerDepositId: outcome.providerDepositId, serverTask: true } : {
5752
+ ...evidence,
5753
+ registration: outcome.kind,
5754
+ ...outcome.kind === "pending" || outcome.accepted ? { serverTask: true } : {}
5755
+ };
5756
+ if (outcome.kind !== "confirmed") this.notePendingRegistration(owner, validChainId, txHash, Date.now(), asset);
5757
+ notifyFunding(onProgress, evidence);
5758
+ return { txHash, smartWallet, amount, evidence };
5759
+ }
5760
+ evidence = { ...evidence, serverTask: false };
5761
+ }
5244
5762
  try {
5245
5763
  assertCurrent();
5246
5764
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
5247
- await (tokenAddress ? executionSdk.logDeposit(validChainId, txHash, amount, tokenAddress) : executionSdk.logDeposit(validChainId, txHash, amount));
5248
- evidence = { ...evidence, registration: "confirmed" };
5765
+ const logged = await (tokenAddress ? executionSdk.logDeposit(validChainId, txHash, amount, tokenAddress) : executionSdk.logDeposit(validChainId, txHash, amount));
5766
+ const providerDepositId = depositIdFromLog(logged);
5767
+ evidence = { ...evidence, registration: "confirmed", ...providerDepositId ? { providerDepositId } : {} };
5249
5768
  } catch {
5250
5769
  evidence = { ...evidence, registration: "unknown" };
5251
5770
  this.notePendingRegistration(owner, validChainId, txHash, Date.now(), asset);
@@ -5365,10 +5884,69 @@ var ZyfaiAgent = class _ZyfaiAgent {
5365
5884
  throw unavailable();
5366
5885
  }
5367
5886
  }
5368
- async withdraw(state, chainId, token, amount, onProgress, _gasMode, _onBeforeSubmission, assertOperation) {
5887
+ /**
5888
+ * Settles Zyfai's own wallet prompts before another agent's owner signature:
5889
+ * the SIWE login and, for a legacy wallet the login reported without a
5890
+ * session key, that key (attempted once per session, like activation). Reads
5891
+ * only the login's cached flags; never withdraws. A rejected prompt throws;
5892
+ * any other session-key failure is left to `withdraw`'s own recovery.
5893
+ */
5894
+ async prepareWithdrawal(state, chainId, _token) {
5895
+ const validChainId = isValidChainId(chainId);
5896
+ await this.ensureConnected(state, validChainId);
5897
+ await this.ensureWithdrawalSessionKey(state.walletAddress, validChainId);
5898
+ }
5899
+ /** The session key a legacy wallet's login reported missing, created once per
5900
+ * session (one prompt). A rejected prompt throws; any other failure is left
5901
+ * to the withdrawal itself. */
5902
+ async ensureWithdrawalSessionKey(owner, validChainId) {
5903
+ const internals = this.sdk;
5904
+ const key2 = `${owner.toLowerCase()}:${validChainId}`;
5905
+ if (internals.isPredeployed !== false || internals.hasActiveSessionKey !== false || this.sessionKeyAttempts.has(key2)) return;
5906
+ this.sessionKeyAttempts.add(key2);
5907
+ try {
5908
+ await this.sdk.createSessionKey(owner, validChainId);
5909
+ } catch (error) {
5910
+ const code = error?.code;
5911
+ if (code === 4001 || code === "ACTION_REJECTED" || error instanceof Error && /user (rejected|denied)|request rejected|action rejected/i.test(error.message)) {
5912
+ this.sessionKeyAttempts.delete(key2);
5913
+ throw error;
5914
+ }
5915
+ console.warn(
5916
+ "[zyfai] session key before withdrawal skipped (non-fatal):",
5917
+ error instanceof Error ? error.message : String(error)
5918
+ );
5919
+ }
5920
+ }
5921
+ async withdraw(state, chainId, token, amount, onProgress, _gasMode, onBeforeSubmission, assertOperation, context) {
5369
5922
  if (amount !== void 0 && (typeof amount !== "string" || !/^[0-9]+$/.test(amount) || BigInt(amount) <= 0n))
5370
5923
  throw new OwneyError("WITHDRAW_INVALID_AMOUNT", "Withdrawal amount must be a positive integer in token base units.", void 0, this.id);
5371
5924
  const validChainId = isValidChainId(chainId);
5925
+ if (this.accountReporting.owneyApiKey && typeof context?.operationId === "string" && context.operationId && // T10-I1: a leg whose mark was cleared is never handed off again.
5926
+ this.serverLegStates.get(context.operationId) !== "cleared" && // F-I2(iii): the opt-in, read now, unless the leg was already handed
5927
+ // off (planned, requested or accepted).
5928
+ this.serverTasksOn(context.planned === true || this.serverLegStates.has(context.operationId))) {
5929
+ const served = await this.withdrawThroughServer(
5930
+ state,
5931
+ validChainId,
5932
+ token,
5933
+ amount,
5934
+ onProgress,
5935
+ onBeforeSubmission,
5936
+ assertOperation,
5937
+ { operationId: context.operationId, planned: context.planned === true }
5938
+ );
5939
+ if (served) return served;
5940
+ }
5941
+ if (typeof context?.operationId === "string" && context.operationId && !this.mayWithdrawLocally(context.operationId))
5942
+ this.serverLegUnresolved(onProgress, {
5943
+ agentId: this.id,
5944
+ chainId,
5945
+ token,
5946
+ walletAddress: state.walletAddress,
5947
+ intendedAmount: amount ?? "MAX",
5948
+ registration: "not-applicable"
5949
+ });
5372
5950
  try {
5373
5951
  await this.ensureConnected(state, validChainId);
5374
5952
  } catch (error) {
@@ -5405,15 +5983,35 @@ var ZyfaiAgent = class _ZyfaiAgent {
5405
5983
  assertCurrent();
5406
5984
  assertOperation?.();
5407
5985
  };
5408
- let observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
5986
+ let headRead = Promise.resolve(void 0);
5987
+ let settledHead;
5988
+ const searchHead = () => settledHead ??= settleWithin(headRead, PAYOUT_SEARCH_HEAD_LIMIT_MS);
5989
+ const checkpoint2 = onBeforeSubmission ? async (request2) => {
5990
+ const head = await searchHead();
5991
+ const agentWallet = this.smartWalletCache.get(`${owner.toLowerCase()}_${validChainId}`);
5992
+ await onBeforeSubmission({
5993
+ agentId: this.id,
5994
+ chainId,
5995
+ token,
5996
+ walletAddress: state.walletAddress,
5997
+ intendedAmount: amount ?? "MAX",
5998
+ transfer: "pending",
5999
+ registration: "not-applicable",
6000
+ delivery: "pending",
6001
+ withdrawalRequest: request2,
6002
+ ...agentWallet ? { agentWallet } : {},
6003
+ ...head !== void 0 ? { payoutSearch: { fromBlock: head.toString() } } : {}
6004
+ });
6005
+ } : void 0;
6006
+ let observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission, checkpoint2);
5409
6007
  const authToken = internals.httpClient?.authToken;
5410
6008
  let raw2;
5411
- let headRead = Promise.resolve(void 0);
5412
6009
  try {
5413
6010
  for (let attempt = 0; attempt < 2; attempt++) {
5414
6011
  try {
5415
6012
  assertCurrent();
5416
6013
  headRead = this.readPayoutSearchHead(validChainId);
6014
+ settledHead = void 0;
5417
6015
  raw2 = await executionSdk.withdrawFunds(owner, validChainId, amount, token);
5418
6016
  break;
5419
6017
  } catch (error) {
@@ -5442,7 +6040,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
5442
6040
  assertSubmission();
5443
6041
  await this.recoverWithdrawalSession(state, validChainId, token, owner);
5444
6042
  observation.stop();
5445
- observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
6043
+ observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission, checkpoint2);
5446
6044
  }
5447
6045
  }
5448
6046
  if (!raw2) throw new OwneyError(
@@ -5453,7 +6051,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
5453
6051
  );
5454
6052
  } catch (error) {
5455
6053
  if (staleSessionError && observation.attempted() === false) error = staleSessionError;
5456
- this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
6054
+ this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken, observation.request()?.httpStatus);
5457
6055
  if (observation.attempted() !== void 0) {
5458
6056
  const failed = observation.attempted() === false || observation.rejected();
5459
6057
  notifyFunding(onProgress, {
@@ -5507,7 +6105,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
5507
6105
  if (!raw2.txHash) {
5508
6106
  const acceptedAt = Date.now();
5509
6107
  const agentWallet = this.smartWalletCache.get(`${owner.toLowerCase()}_${validChainId}`);
5510
- const head = await settleWithin(headRead, PAYOUT_SEARCH_HEAD_LIMIT_MS);
6108
+ const head = await searchHead();
5511
6109
  evidence = {
5512
6110
  ...evidence,
5513
6111
  ...agentWallet ? { agentWallet } : {},
@@ -5519,7 +6117,456 @@ var ZyfaiAgent = class _ZyfaiAgent {
5519
6117
  this.ledgerReads.clear();
5520
6118
  return { ...mapWithdraw(raw2), evidence };
5521
6119
  }
5522
- /** Registration recovery never signs, transfers, activates, or refreshes authentication. */
6120
+ /**
6121
+ * Hands a planned withdrawal leg to our server at plan time (part 4), before
6122
+ * the earlier wallet-approval legs (`after`) are signed. The server sends it
6123
+ * once they are accepted; the browser nudges it at that point
6124
+ * (`withdraw(…, { operationId, planned: true })`). Uses the current execution
6125
+ * client's session, after `ensureConnected`. Never throws, never prompts
6126
+ * beyond that connection, and never withdraws locally.
6127
+ */
6128
+ async handOffPlannedWithdrawal(state, chainId, token, amount, ctx) {
6129
+ const { owneyApiKey, routingApiBaseUrl } = this.accountReporting;
6130
+ if (!this.serverTasksOn(this.serverLegStates.has(ctx.operationId)))
6131
+ return { type: "declined", code: ZYFAI_SERVER_TASKS_OFF_CODE };
6132
+ if (this.serverLegStates.get(ctx.operationId) === "cleared") return { type: "declined", code: SERVER_LEG_CLEARED_CODE };
6133
+ const noSession = { type: "declined", code: "NO_SESSION" };
6134
+ if (!owneyApiKey) return noSession;
6135
+ let validChainId;
6136
+ let session;
6137
+ let agentWallet;
6138
+ try {
6139
+ validChainId = isValidChainId(chainId);
6140
+ await this.ensureConnected(state, validChainId);
6141
+ session = this.executionSession();
6142
+ if (!session) return noSession;
6143
+ if (this.serverLegStates.get(ctx.operationId) === void 0 && !zyfaiSessionOutlivesTask(session, "zyfai_withdrawal")) return { type: "declined", code: SESSION_ENDING_CODE };
6144
+ agentWallet = await this.handoffAgentWallet(state.walletAddress, validChainId);
6145
+ } catch {
6146
+ return noSession;
6147
+ }
6148
+ if (!agentWallet) return { type: "declined", code: "NO_AGENT_WALLET" };
6149
+ try {
6150
+ ctx.beforeRequest?.();
6151
+ } catch {
6152
+ }
6153
+ const afterLostAnswer = this.serverLegStates.get(ctx.operationId) === "requested";
6154
+ this.setServerLegState(ctx.operationId, "requested");
6155
+ let answer = await handOffZyfaiTask(
6156
+ { apiKey: owneyApiKey, baseUrl: routingApiBaseUrl, timeoutMs: PLANNED_HANDOFF_TIMEOUT_MS, zyfaiServerTasks: true },
6157
+ {
6158
+ kind: "zyfai_withdrawal",
6159
+ operationId: ctx.operationId,
6160
+ owner: state.walletAddress,
6161
+ chainId: validChainId,
6162
+ asset: token,
6163
+ amount,
6164
+ agentWallet,
6165
+ after: ctx.after
6166
+ },
6167
+ session
6168
+ );
6169
+ if (answer.type === "declined" && ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code) && !provesNoServerTask(answer.code, afterLostAnswer)) answer = { type: "unknown" };
6170
+ if (answer.type === "accepted" || answer.type === "declined" && answer.code === "LEG_ALREADY_SENT")
6171
+ this.setServerLegState(ctx.operationId, "accepted");
6172
+ if (answer.type === "declined") {
6173
+ if (answer.code !== "LEG_ALREADY_SENT" && this.serverLegStates.get(ctx.operationId) === "accepted")
6174
+ return { type: "unknown" };
6175
+ if (provesNoServerTask(answer.code, afterLostAnswer)) this.clearServerLeg(ctx.operationId, "no-task-decline");
6176
+ return answer;
6177
+ }
6178
+ this.plannedHandoffs.delete(ctx.operationId);
6179
+ this.plannedHandoffs.set(ctx.operationId, { after: [...ctx.after], amount });
6180
+ while (this.plannedHandoffs.size > PLANNED_HANDOFFS_LIMIT)
6181
+ this.plannedHandoffs.delete(this.plannedHandoffs.keys().next().value);
6182
+ return answer;
6183
+ }
6184
+ /**
6185
+ * F-I2(iii): whether the server path may run. The caller's opt-in is read
6186
+ * now (a function is called) as an operation's first handoff would start.
6187
+ * An operation this session already handed off (`handedOff`: a planned leg,
6188
+ * a leg requested or accepted, a registration already sent) keeps going
6189
+ * through the server even after the opt-in turns off: its nudges, re-runs
6190
+ * and retries, so the server answers the task it may hold and the browser
6191
+ * never sends it too. Without any opt-in (P4-0) nothing was handed off.
6192
+ */
6193
+ serverTasksOn(handedOff = false) {
6194
+ const option = this.accountReporting.zyfaiServerTasks;
6195
+ if (handedOff) return option === true || typeof option === "function";
6196
+ return zyfaiServerTasksEnabled(option);
6197
+ }
6198
+ /** True when this browser may send the operation's Zyfai withdrawal leg
6199
+ * itself: it never handed it off, or the server proved it has no task. */
6200
+ mayWithdrawLocally(operationId) {
6201
+ const current = this.serverLegStates.get(operationId);
6202
+ return current === void 0 || current === "cleared";
6203
+ }
6204
+ /** Moves the leg's server ownership forward: absent -> requested ->
6205
+ * accepted. "accepted" is sticky, and "cleared" is final (such a leg is
6206
+ * never handed off again). Clearing goes through `clearServerLeg`. */
6207
+ setServerLegState(operationId, next) {
6208
+ const current = this.serverLegStates.get(operationId);
6209
+ if (current === "accepted" || current === "cleared" || current === next) return;
6210
+ this.serverLegStates.set(operationId, next);
6211
+ while (this.serverLegStates.size > SERVER_LEG_STATES_LIMIT)
6212
+ this.serverLegStates.delete(this.serverLegStates.keys().next().value);
6213
+ }
6214
+ /** Clears the leg on a proof that the server has no task: a no-task decline
6215
+ * after a request, or nothing ever requested (T8-R4b). Never from accepted.
6216
+ * Returns whether the leg is cleared; the caller then emits
6217
+ * `serverTask: false` and may send locally (T10-I1, T9-I5). */
6218
+ clearServerLeg(operationId, proof) {
6219
+ const current = this.serverLegStates.get(operationId);
6220
+ if (current === "cleared") return true;
6221
+ if (proof === "no-task-decline" ? current !== "requested" : current !== void 0) return false;
6222
+ this.serverLegStates.set(operationId, "cleared");
6223
+ while (this.serverLegStates.size > SERVER_LEG_STATES_LIMIT)
6224
+ this.serverLegStates.delete(this.serverLegStates.keys().next().value);
6225
+ return true;
6226
+ }
6227
+ /** T9-I5: the server may hold this leg's task, so this browser never sends
6228
+ * it. The leg is reported failed with `serverTask: "unknown"` (never
6229
+ * cleared; a re-run hands it off again), and `error`, or an unresolved-leg
6230
+ * error, is thrown. */
6231
+ serverLegUnresolved(onProgress, base7, error) {
6232
+ notifyFunding(onProgress, {
6233
+ ...base7,
6234
+ transfer: "failed",
6235
+ delivery: "failed",
6236
+ withdrawalRequest: { attempted: false },
6237
+ serverTask: "unknown"
6238
+ });
6239
+ throw error ?? new OwneyError(
6240
+ "WITHDRAW_FAILED",
6241
+ "The server may still send this Zyfai withdrawal, so this browser does not send it.",
6242
+ { reason: "ZYFAI_SERVER_LEG_UNRESOLVED", serverTask: true, rejectedBeforeSubmission: true },
6243
+ this.id
6244
+ );
6245
+ }
6246
+ /** The current execution client's session. Never logged or returned. */
6247
+ executionSession() {
6248
+ const token = this.sdk.httpClient?.authToken;
6249
+ return typeof token === "string" && token ? token : void 0;
6250
+ }
6251
+ /** The owner's Zyfai wallet for a handoff (read-only), or undefined. */
6252
+ async handoffAgentWallet(owner, chainId) {
6253
+ if (!ADDRESS_PATTERN.test(owner)) return void 0;
6254
+ try {
6255
+ const wallet = await this.readSmartWallet(owner, chainId);
6256
+ return typeof wallet === "string" && ADDRESS_PATTERN.test(wallet) ? wallet : void 0;
6257
+ } catch {
6258
+ return void 0;
6259
+ }
6260
+ }
6261
+ /**
6262
+ * Part 4: hands this withdrawal to our server, or nudges the task a plan-time
6263
+ * handoff created (`planned`). The server is the one sender from then on:
6264
+ * this never calls Zyfai's withdraw route, and never sends again after an
6265
+ * ambiguous answer. Returns undefined only when nothing was handed off, or
6266
+ * when the server proved no task exists (a decline in
6267
+ * `ZYFAI_HANDOFF_NO_TASK_CODES`, rulings P4-11, T7-R1, T8-C1 and F-I1(b);
6268
+ * after a lost answer only the server's own declines, F-I2(i)); today's
6269
+ * local path then runs, after the handoff's mark was cleared (T8-C2).
6270
+ */
6271
+ async withdrawThroughServer(state, chainId, token, requestedAmount, onProgress, onBeforeSubmission, assertOperation, context) {
6272
+ const owner = state.walletAddress;
6273
+ const operationId = context.operationId;
6274
+ const plan = context.planned ? this.plannedHandoffs.get(operationId) : void 0;
6275
+ if (context.planned) this.plannedHandoffs.delete(operationId);
6276
+ if (context.planned && this.serverLegStates.get(operationId) === void 0)
6277
+ this.setServerLegState(operationId, "accepted");
6278
+ const planLost = context.planned && this.serverLegStates.get(operationId) === "requested";
6279
+ const serverOwned = () => context.planned || this.serverLegStates.get(operationId) === "accepted";
6280
+ const amount = plan?.amount === "MAX" ? void 0 : plan && /^[1-9][0-9]*$/.test(plan.amount) ? plan.amount : requestedAmount;
6281
+ const route = amount ? { attempted: true, method: "POST", path: "/users/partial-withdraw/v2" } : { attempted: true, method: "GET", path: "/users/withdraw/v2" };
6282
+ const base7 = {
6283
+ agentId: this.id,
6284
+ chainId,
6285
+ token,
6286
+ walletAddress: owner,
6287
+ intendedAmount: amount ?? "MAX",
6288
+ registration: "not-applicable"
6289
+ };
6290
+ let head;
6291
+ const searchHead = () => head ??= settleWithin(this.readPayoutSearchHead(chainId), PAYOUT_SEARCH_HEAD_LIMIT_MS);
6292
+ const accept = async (serverTask, agentWallet2, txHash, providerWithdrawalId) => {
6293
+ const withdrawalRequest = { ...route, ...providerWithdrawalId ? { providerWithdrawalId } : {} };
6294
+ let evidence;
6295
+ if (txHash) {
6296
+ evidence = { ...base7, txHash, transfer: "pending", delivery: "pending", withdrawalRequest, serverTask };
6297
+ } else {
6298
+ const acceptedAt = Date.now();
6299
+ const fromBlock = await searchHead();
6300
+ evidence = {
6301
+ ...base7,
6302
+ transfer: "pending",
6303
+ delivery: "pending",
6304
+ ...agentWallet2 ? { agentWallet: agentWallet2 } : {},
6305
+ withdrawalRequest: { ...withdrawalRequest, acceptedAt },
6306
+ ...fromBlock !== void 0 ? { payoutSearch: { fromBlock: fromBlock.toString() } } : {},
6307
+ serverTask
6308
+ };
6309
+ }
6310
+ notifyFunding(onProgress, evidence);
6311
+ this.ledgerReads.clear();
6312
+ return { ...mapWithdraw({
6313
+ success: true,
6314
+ message: "Withdrawal handed to the server",
6315
+ txHash,
6316
+ type: amount ? "partial" : "full",
6317
+ amount: amount || "all"
6318
+ }), evidence };
6319
+ };
6320
+ try {
6321
+ await this.ensureConnected(state, chainId);
6322
+ } catch (error) {
6323
+ if (serverOwned()) return accept("unknown", void 0);
6324
+ if (!this.mayWithdrawLocally(operationId)) this.serverLegUnresolved(onProgress, base7, error);
6325
+ notifyFunding(onProgress, { ...base7, transfer: "failed", delivery: "failed" });
6326
+ throw error;
6327
+ }
6328
+ const generation = this.connectionGeneration;
6329
+ const internals = this.sdk;
6330
+ const session = this.executionSession();
6331
+ if (session && !context.planned && this.serverLegStates.get(operationId) === void 0 && !zyfaiSessionOutlivesTask(session, "zyfai_withdrawal")) return void 0;
6332
+ const agentWallet = session ? await this.handoffAgentWallet(owner, chainId) : void 0;
6333
+ if (!session || !agentWallet) {
6334
+ if (serverOwned()) return accept("unknown", agentWallet);
6335
+ if (!this.mayWithdrawLocally(operationId)) this.serverLegUnresolved(onProgress, base7);
6336
+ return void 0;
6337
+ }
6338
+ const assertSubmission = () => {
6339
+ const account = internals.walletClient?.account?.address;
6340
+ if (generation !== this.connectionGeneration || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || state.chainId !== null && state.chainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
6341
+ throw new OwneyError(
6342
+ "NOT_CONNECTED",
6343
+ "Wallet changed before Zyfai withdrawal submission.",
6344
+ { phase: "prepare", rejectedBeforeSubmission: true },
6345
+ this.id
6346
+ );
6347
+ assertOperation?.();
6348
+ };
6349
+ try {
6350
+ if (!context.planned) await this.ensureWithdrawalSessionKey(owner, chainId);
6351
+ assertSubmission();
6352
+ const fromBlock = await searchHead();
6353
+ await onBeforeSubmission?.({
6354
+ ...base7,
6355
+ transfer: "pending",
6356
+ delivery: "pending",
6357
+ withdrawalRequest: { ...route },
6358
+ agentWallet,
6359
+ ...fromBlock !== void 0 ? { payoutSearch: { fromBlock: fromBlock.toString() } } : {},
6360
+ serverTask: true
6361
+ });
6362
+ assertSubmission();
6363
+ } catch (error) {
6364
+ if (serverOwned()) return accept("unknown", agentWallet);
6365
+ if (!this.clearServerLeg(operationId, "never-requested")) this.serverLegUnresolved(onProgress, base7, error);
6366
+ notifyFunding(onProgress, {
6367
+ ...base7,
6368
+ transfer: "failed",
6369
+ delivery: "failed",
6370
+ withdrawalRequest: { attempted: false },
6371
+ serverTask: false
6372
+ });
6373
+ throw error;
6374
+ }
6375
+ const cfg = {
6376
+ apiKey: this.accountReporting.owneyApiKey,
6377
+ baseUrl: this.accountReporting.routingApiBaseUrl,
6378
+ timeoutMs: WITHDRAW_HANDOFF_TIMEOUT_MS,
6379
+ zyfaiServerTasks: true
6380
+ };
6381
+ const body = {
6382
+ kind: "zyfai_withdrawal",
6383
+ operationId: context.operationId,
6384
+ owner,
6385
+ chainId,
6386
+ asset: token,
6387
+ amount: amount ?? "MAX",
6388
+ agentWallet,
6389
+ ...plan?.after.length ? { after: plan.after } : {},
6390
+ ...context.planned ? { runNow: true } : {}
6391
+ };
6392
+ const requestedBefore = this.serverLegStates.get(operationId) === "requested";
6393
+ let afterUnknown = planLost;
6394
+ this.setServerLegState(operationId, "requested");
6395
+ let answer = await handOffZyfaiTask(cfg, body, session);
6396
+ if (answer.type === "unknown" && !context.planned) {
6397
+ afterUnknown = true;
6398
+ answer = await handOffZyfaiTask(cfg, body, session);
6399
+ }
6400
+ if (answer.type === "declined" && ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code) && !provesNoServerTask(answer.code, requestedBefore || afterUnknown)) answer = { type: "unknown" };
6401
+ if (answer.type === "accepted" || answer.type === "declined" && answer.code === "LEG_ALREADY_SENT")
6402
+ this.setServerLegState(operationId, "accepted");
6403
+ if (answer.type === "declined") {
6404
+ if (ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code) && this.clearServerLeg(operationId, "no-task-decline")) {
6405
+ const cleared = { ...base7, transfer: "pending", delivery: "pending", serverTask: false };
6406
+ notifyFunding(onProgress, cleared);
6407
+ try {
6408
+ await onBeforeSubmission?.({ ...cleared, withdrawalRequest: { attempted: false } });
6409
+ } catch (error) {
6410
+ notifyFunding(onProgress, { ...cleared, transfer: "failed", delivery: "failed", withdrawalRequest: { attempted: false } });
6411
+ throw error;
6412
+ }
6413
+ return void 0;
6414
+ }
6415
+ if (this.serverLegStates.get(operationId) === "accepted" || afterUnknown) return accept("unknown", agentWallet);
6416
+ return this.serverWithdrawalNotSent(answer.code, state, chainId, token, session, onProgress, base7, route, "unknown");
6417
+ }
6418
+ if (answer.type === "unknown") return accept("unknown", agentWallet);
6419
+ if (answer.state === "failed" && answer.code && ZYFAI_WITHDRAWAL_NOT_SENT_CODES.has(answer.code))
6420
+ return this.serverWithdrawalNotSent(answer.code, state, chainId, token, session, onProgress, base7, route, true);
6421
+ if (answer.state === "failed" || answer.state === "expired") return accept("unknown", agentWallet);
6422
+ return accept(true, agentWallet, answer.result.txHash, answer.result.providerWithdrawalId);
6423
+ }
6424
+ /** A server answer proving the withdrawal was never sent: today's matching
6425
+ * error, with evidence transfer and delivery `failed`. "Already in progress"
6426
+ * keeps the hold: failure `withdrawal-in-progress`, never rejectedBeforeSubmission.
6427
+ * `serverTask`: true for a task that ended unsent (the server owns the leg's
6428
+ * close), "unknown" for a declined handoff whose code does not prove no task
6429
+ * (never false: such a leg is never cleared, ruling T9-I3). */
6430
+ serverWithdrawalNotSent(code, state, chainId, token, session, onProgress, base7, route, serverTask) {
6431
+ let failure;
6432
+ let error;
6433
+ const sessionUnavailable = () => new OwneyError(
6434
+ "AGENT_AUTH_FAILED",
6435
+ "Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
6436
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token, serverTask: true, rejectedBeforeSubmission: true },
6437
+ this.id
6438
+ );
6439
+ switch (code) {
6440
+ case "NEEDS_USER_SESSION":
6441
+ failure = "unauthorized";
6442
+ error = sessionUnavailable();
6443
+ this.dropWithdrawalSession(state, chainId, session);
6444
+ break;
6445
+ case "NEEDS_USER_SESSION_KEY":
6446
+ failure = "session-key-missing";
6447
+ error = sessionUnavailable();
6448
+ this.rearmWithdrawalSessionKey(state.walletAddress, chainId);
6449
+ break;
6450
+ case "WITHDRAW_INSUFFICIENT_BALANCE":
6451
+ failure = "insufficient-balance";
6452
+ error = new OwneyError(
6453
+ "WITHDRAW_INSUFFICIENT_BALANCE",
6454
+ "No funds remain in Zyfai for this asset.",
6455
+ { available: "0", chainId, token, rejectedBeforeSubmission: true },
6456
+ this.id
6457
+ );
6458
+ break;
6459
+ case "WITHDRAWAL_IN_PROGRESS":
6460
+ case "ZYFAI_REDEMPTION_IN_FLIGHT":
6461
+ failure = "withdrawal-in-progress";
6462
+ error = new OwneyError(
6463
+ "WITHDRAW_IN_PROGRESS",
6464
+ "A Zyfai withdrawal is already in progress.",
6465
+ { reason: code, chainId, token, serverTask: true },
6466
+ this.id
6467
+ );
6468
+ break;
6469
+ default:
6470
+ failure = code === "ZYFAI_FORBIDDEN" ? "forbidden" : "other";
6471
+ error = new OwneyError(
6472
+ "WITHDRAW_FAILED",
6473
+ "The server did not send the Zyfai withdrawal.",
6474
+ { reason: code, serverTask: true, rejectedBeforeSubmission: true },
6475
+ this.id
6476
+ );
6477
+ }
6478
+ notifyFunding(onProgress, {
6479
+ ...base7,
6480
+ transfer: "failed",
6481
+ delivery: "failed",
6482
+ serverTask,
6483
+ withdrawalRequest: { ...route, failure }
6484
+ });
6485
+ throw error;
6486
+ }
6487
+ /** Zyfai has no session key for this wallet: for a legacy (not pre-deployed)
6488
+ * wallet, forget the login's "active" flag and this session's attempt, so the
6489
+ * next `ensureWithdrawalSessionKey` prompts once. Sends and prompts nothing. */
6490
+ rearmWithdrawalSessionKey(owner, chainId) {
6491
+ const internals = this.sdk;
6492
+ if (internals.isPredeployed !== false || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId) return;
6493
+ internals.hasActiveSessionKey = false;
6494
+ this.sessionKeyAttempts.delete(`${owner.toLowerCase()}:${chainId}`);
6495
+ }
6496
+ /** The routing API's handoff config. Only called with the opt-in and the key. */
6497
+ registrationHandoffConfig(timeoutMs) {
6498
+ return {
6499
+ apiKey: this.accountReporting.owneyApiKey,
6500
+ baseUrl: this.accountReporting.routingApiBaseUrl,
6501
+ timeoutMs,
6502
+ zyfaiServerTasks: true
6503
+ };
6504
+ }
6505
+ /**
6506
+ * Part 4: hands a deposit's registration to our server before its transfer.
6507
+ * Undefined when nothing was handed off, or when the server proved it has no
6508
+ * task (a decline in `ZYFAI_HANDOFF_NO_TASK_CODES`, rulings P4-11, T7-R1 and
6509
+ * T8-F2; for a retried create only the server's own declines, F-I2(i)):
6510
+ * today's local registration then runs. Any other answer means the
6511
+ * server holds the registration, or may hold it (an unknown answer, or
6512
+ * another decline), so the browser never registers it except by the
6513
+ * not-sent fallback. Never throws; never logs or returns the session.
6514
+ */
6515
+ async handOffDepositRegistration(owner, chainId, asset, amount, agentWallet, operationId) {
6516
+ const session = this.executionSession();
6517
+ if (!session || !ADDRESS_PATTERN.test(owner) || !ADDRESS_PATTERN.test(agentWallet) || !REGISTRATION_AMOUNT.test(amount) || asset !== "USDC" && asset !== "WETH") return void 0;
6518
+ const retried = this.requestedRegistrations.has(operationId);
6519
+ if (!retried) {
6520
+ if (!zyfaiSessionOutlivesTask(session, "zyfai_deposit_registration")) return void 0;
6521
+ this.requestedRegistrations.add(operationId);
6522
+ while (this.requestedRegistrations.size > SERVER_LEG_STATES_LIMIT)
6523
+ this.requestedRegistrations.delete(this.requestedRegistrations.values().next().value);
6524
+ }
6525
+ const answer = await handOffZyfaiTask(
6526
+ this.registrationHandoffConfig(REGISTRATION_HANDOFF_TIMEOUT_MS),
6527
+ { kind: "zyfai_deposit_registration", operationId, owner, chainId, asset, amount, agentWallet },
6528
+ session
6529
+ );
6530
+ if (answer.type === "declined" && provesNoServerTask(answer.code, retried)) return void 0;
6531
+ if (answer.type === "accepted") return { serverTask: true, serverTaskOperationId: operationId };
6532
+ const lost = answer.type === "unknown" || ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code);
6533
+ return { serverTask: "unknown", serverTaskOperationId: operationId, serverTaskCreate: lost ? "lost" : "declined" };
6534
+ }
6535
+ /**
6536
+ * Nudges the server's registration task of this deposit evidence (with
6537
+ * `serverTask`) and reads what its answer proves. Without the opt-in (read
6538
+ * now, unless this session sent the registration's create, F-I2(iii)), the
6539
+ * key, a session or the task's identity no nudge is sent and the outcome is
6540
+ * unknown. Never throws; never registers locally.
6541
+ */
6542
+ async nudgeDepositRegistration(evidence, session) {
6543
+ const { owneyApiKey } = this.accountReporting;
6544
+ const operationId = evidence.serverTaskOperationId;
6545
+ const agentWallet = evidence.agentWallet;
6546
+ const chainId = evidence.chainId;
6547
+ if (typeof operationId !== "string" || !operationId || !this.serverTasksOn(this.requestedRegistrations.has(operationId)) || !owneyApiKey || !session || typeof agentWallet !== "string" || !ADDRESS_PATTERN.test(agentWallet) || !ADDRESS_PATTERN.test(evidence.walletAddress) || !REGISTRATION_AMOUNT.test(evidence.intendedAmount) || evidence.token !== "USDC" && evidence.token !== "WETH" || chainId !== 1 && chainId !== 8453 && chainId !== 42161)
6548
+ return { kind: "unknown" };
6549
+ const answer = await handOffZyfaiTask(
6550
+ this.registrationHandoffConfig(REGISTRATION_NUDGE_TIMEOUT_MS),
6551
+ {
6552
+ kind: "zyfai_deposit_registration",
6553
+ operationId,
6554
+ owner: evidence.walletAddress,
6555
+ chainId,
6556
+ asset: evidence.token,
6557
+ amount: evidence.intendedAmount,
6558
+ agentWallet,
6559
+ runNow: true
6560
+ },
6561
+ session
6562
+ );
6563
+ return registrationOutcome(answer, evidence.serverTask === true || evidence.serverTaskCreate !== "lost");
6564
+ }
6565
+ /** Registration recovery never signs, transfers, activates, or refreshes authentication.
6566
+ * For a deposit the server registers (`serverTask`), it nudges the server's
6567
+ * task instead: still pending gives a transient error, and an unknown or
6568
+ * expired outcome the status-409 error. It registers locally only when the
6569
+ * server proved it never sent the registration (ruling A4). */
5523
6570
  async retryDepositRegistration(state, evidence) {
5524
6571
  const executionSdk = this.sdk;
5525
6572
  const internals = executionSdk;
@@ -5530,11 +6577,37 @@ var ZyfaiAgent = class _ZyfaiAgent {
5530
6577
  throw new OwneyError("AGENT_API_ERROR", "Deposit transfer must be verified before registration recovery.");
5531
6578
  }
5532
6579
  const cid = isValidChainId(evidence.chainId);
6580
+ let cleared = {};
6581
+ if (evidence.serverTask === true || evidence.serverTask === "unknown") {
6582
+ const outcome = await this.nudgeDepositRegistration(evidence, internals.httpClient.authToken ?? void 0);
6583
+ if (outcome.kind === "confirmed") {
6584
+ this.pendingRegistrations.get(`${evidence.walletAddress.toLowerCase()}:${cid}`)?.delete(evidence.txHash.toLowerCase());
6585
+ this.ledgerReads.clear();
6586
+ return { ...evidence, registration: "confirmed", providerDepositId: outcome.providerDepositId, serverTask: true };
6587
+ }
6588
+ if (outcome.kind === "pending")
6589
+ throw new OwneyError("AGENT_API_ERROR", "Zyfai registration by the server is temporarily unavailable.");
6590
+ if (outcome.kind === "unknown") throw registrationOutcomeUnknown(outcome.code);
6591
+ cleared = { serverTask: false };
6592
+ }
5533
6593
  const tokenAddress = evidence.token === "WETH" ? WETH_ADDRESS_BY_CHAIN[cid] : void 0;
5534
- await executionSdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
6594
+ const logged = await executionSdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
6595
+ const providerDepositId = depositIdFromLog(logged);
5535
6596
  this.pendingRegistrations.get(`${evidence.walletAddress.toLowerCase()}:${cid}`)?.delete(evidence.txHash.toLowerCase());
5536
6597
  this.ledgerReads.clear();
5537
- return { ...evidence, registration: "confirmed" };
6598
+ return { ...evidence, ...cleared, registration: "confirmed", ...providerDepositId ? { providerDepositId } : {} };
6599
+ }
6600
+ /** Reads a deposit's placement status by its id. Never signs, prompts or authenticates:
6601
+ * it uses only a session the vendor client already holds, and any failure is `null`. */
6602
+ async getDepositPlacement(state, depositId) {
6603
+ const internals = this.sdk;
6604
+ const client = internals.httpClient;
6605
+ if (!client?.authToken || typeof client.get !== "function" || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase()) return null;
6606
+ try {
6607
+ return mapDepositPlacement(await client.get(`/users/deposits/${encodeURIComponent(depositId)}/v2`));
6608
+ } catch {
6609
+ return null;
6610
+ }
5538
6611
  }
5539
6612
  /** Reads a withdrawal's lifecycle by its id. Never signs, prompts or authenticates:
5540
6613
  * it uses only a session the vendor client already holds, and any failure is `null`. */
@@ -5543,11 +6616,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
5543
6616
  const client = internals.httpClient;
5544
6617
  if (!client?.authToken || typeof client.get !== "function" || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase()) return null;
5545
6618
  try {
5546
- const body = await client.get(`/users/withdrawals/${encodeURIComponent(withdrawalId)}`);
5547
- if (!body || typeof body !== "object" || typeof body.status !== "string" || typeof body.syncSettled !== "boolean")
5548
- return null;
5549
- const hash2 = typeof body.syncTxHash === "string" && /^0x[0-9a-fA-F]{64}$/.test(body.syncTxHash) ? body.syncTxHash : void 0;
5550
- return { status: body.status, syncSettled: body.syncSettled, ...hash2 ? { syncTxHash: hash2 } : {} };
6619
+ return mapWithdrawalLifecycle(await client.get(`/users/withdrawals/${encodeURIComponent(withdrawalId)}`));
5551
6620
  } catch {
5552
6621
  return null;
5553
6622
  }
@@ -5780,16 +6849,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
5780
6849
  }
5781
6850
  const now = Date.now();
5782
6851
  const pending2 = [...this.pendingRegistrations.get(scope)?.values() ?? []].map((entry) => entry.at);
5783
- const active2 = zyfaiHoldActive({
6852
+ const active = zyfaiHoldActive({
5784
6853
  movements,
5785
6854
  pendingRegistrations: pending2,
5786
6855
  lastCheckTimestamp: raw2?.data?.lastCheckTimestamp,
5787
6856
  lastLogDate: raw2?.data?.lastLogDate?.[String(chainId)],
5788
6857
  now
5789
6858
  });
5790
- if (active2) this.holdActiveScopes.add(scope);
6859
+ if (active) this.holdActiveScopes.add(scope);
5791
6860
  else this.holdActiveScopes.delete(scope);
5792
- if (!active2) {
6861
+ if (!active) {
5793
6862
  this.lastGoodEarnings.set(key2, raw2);
5794
6863
  return { value: raw2, active: false };
5795
6864
  }
@@ -6757,7 +7826,7 @@ function actionEntry(action) {
6757
7826
  rebalanceLog: []
6758
7827
  };
6759
7828
  }
6760
- var TX_HASH = /^0x[a-fA-F0-9]{64}$/;
7829
+ var TX_HASH3 = /^0x[a-fA-F0-9]{64}$/;
6761
7830
  function allocationLegs(value, context, requireAmount) {
6762
7831
  if (!Array.isArray(value)) return void 0;
6763
7832
  const legs = [];
@@ -6786,7 +7855,7 @@ function allocationEntry(action, context) {
6786
7855
  if (action.actionType.toLowerCase() !== "autoseek") return void 0;
6787
7856
  const hash2 = action.details?.depositTransactionHash;
6788
7857
  const withdrawHash = action.details?.withdrawTransactionHash;
6789
- if (typeof hash2 !== "string" || !TX_HASH.test(hash2)) return null;
7858
+ if (typeof hash2 !== "string" || !TX_HASH3.test(hash2)) return null;
6790
7859
  const deposits = allocationLegs(action.details?.deposits, context, true);
6791
7860
  const withdrawals = action.details?.withdrawals == null ? [] : allocationLegs(action.details.withdrawals, context, false);
6792
7861
  if (!deposits?.length || !withdrawals) return null;
@@ -6799,7 +7868,7 @@ function allocationEntry(action, context) {
6799
7868
  }));
6800
7869
  const amountRaw = deposits.reduce((total, leg) => total + leg.rawAmount, 0n);
6801
7870
  const rebalance = withdrawals.length > 0;
6802
- const hashes = rebalance && typeof withdrawHash === "string" && TX_HASH.test(withdrawHash) && withdrawHash.toLowerCase() !== hash2.toLowerCase() ? [hash2, withdrawHash] : [hash2];
7871
+ const hashes = rebalance && typeof withdrawHash === "string" && TX_HASH3.test(withdrawHash) && withdrawHash.toLowerCase() !== hash2.toLowerCase() ? [hash2, withdrawHash] : [hash2];
6803
7872
  const source = withdrawals.length === 1 ? withdrawals[0] : {
6804
7873
  protocol: [...new Set(withdrawals.map((leg) => leg.protocol))].join(" + "),
6805
7874
  pool: withdrawals.map((leg) => leg.pool).filter(Boolean).join(" + ") || void 0
@@ -7183,6 +8252,7 @@ function operationHash(operation) {
7183
8252
  }
7184
8253
  });
7185
8254
  }
8255
+ var YIELDSEEKER_WITHDRAW_SERVER_SUBMIT = 1;
7186
8256
  function sponsorError(message, safeToFallback, details = {}) {
7187
8257
  return new OwneyError(
7188
8258
  "SPONSOR_REQUEST_FAILED",
@@ -7205,6 +8275,22 @@ function assertIntentFresh(expiresAt) {
7205
8275
  { failureCode: "SPONSOR_INTENT_EXPIRED" }
7206
8276
  );
7207
8277
  }
8278
+ var PAYMASTER_EXPIRY_MARGIN_SECONDS = 30;
8279
+ function paymasterValidUntil(paymasterAndData) {
8280
+ if (paymasterAndData.length < 2 + 64) return void 0;
8281
+ const validUntil = Number.parseInt(paymasterAndData.slice(42, 54), 16);
8282
+ const now = Date.now() / 1e3;
8283
+ return validUntil > now - 86400 && validUntil < now + 86400 ? validUntil : void 0;
8284
+ }
8285
+ function assertPaymasterFresh(paymasterAndData) {
8286
+ const validUntil = paymasterValidUntil(paymasterAndData);
8287
+ if (validUntil !== void 0 && Date.now() / 1e3 + PAYMASTER_EXPIRY_MARGIN_SECONDS >= validUntil)
8288
+ throw sponsorError(
8289
+ "The withdrawal sponsorship expired while waiting for the signature. Please try again.",
8290
+ true,
8291
+ { failureCode: "SPONSOR_INTENT_EXPIRED" }
8292
+ );
8293
+ }
7208
8294
  function providerReason(value) {
7209
8295
  if (typeof value !== "string") return void 0;
7210
8296
  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);
@@ -7286,6 +8372,12 @@ async function checkpoint(callback, pending2) {
7286
8372
  );
7287
8373
  }
7288
8374
  }
8375
+ function reportAccepted(onAccepted, accepted) {
8376
+ try {
8377
+ onAccepted?.(accepted);
8378
+ } catch {
8379
+ }
8380
+ }
7289
8381
  function finalPaymasterFields(value) {
7290
8382
  const result = {};
7291
8383
  for (const key2 of [
@@ -7364,15 +8456,57 @@ async function requestIntent(input) {
7364
8456
  url.searchParams.set("intent", intent.intent);
7365
8457
  return { intent, rpcUrl: url.toString() };
7366
8458
  }
8459
+ var RPC_TIMEOUT_MS = 15e3;
8460
+ var SERVER_SUBMIT_SEND_TIMEOUT_MS = 25e3;
8461
+ function postRpc(url, method, params, timeoutMs) {
8462
+ return fetch(url, {
8463
+ method: "POST",
8464
+ headers: { "content-type": "application/json" },
8465
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
8466
+ signal: AbortSignal.timeout(timeoutMs)
8467
+ });
8468
+ }
8469
+ function rpcRejection(status, payload, method, phase) {
8470
+ const rejection = payload?.error;
8471
+ const diagnostics = rpcDiagnostics(rejection?.data);
8472
+ const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
8473
+ const details = {
8474
+ statusCode: status,
8475
+ rpcCode: rejection?.code,
8476
+ rpcMethod: method,
8477
+ phase,
8478
+ providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
8479
+ failureCode: "RPC_REJECTED",
8480
+ ...diagnostics
8481
+ };
8482
+ if (phase === "prepare" && executionReverted)
8483
+ return new OwneyError(
8484
+ "WITHDRAW_FAILED",
8485
+ "Yieldseeker withdrawal simulation reverted before submission.",
8486
+ {
8487
+ ...details,
8488
+ failureCode: "SPONSOR_EXECUTION_REVERTED",
8489
+ safeToFallback: false,
8490
+ rejectedBeforeSubmission: true
8491
+ },
8492
+ "yieldseeker"
8493
+ );
8494
+ const definitiveRejection = phase === "send" && !executionReverted && status < 500 && !!rejection && (rejection.data?.rejectedBeforeSubmission === true || [-32501, -32502, -32503, -32504, -32505, -32507].includes(rejection.code ?? 0) || // AA3x: the paymaster failed validation (e.g. AA32 expired), so the
8495
+ // bundler never accepted it. AA2x stays ambiguous (AA25 can mean it ran).
8496
+ /\bAA3\d\b/.test(rejection.message ?? "") || /maxFeePerGas.*(?:too low|lower than|less than|must be at least)/i.test(rejection.message ?? ""));
8497
+ return sponsorError(
8498
+ phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
8499
+ phase === "prepare" || definitiveRejection,
8500
+ {
8501
+ ...details,
8502
+ ...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
8503
+ }
8504
+ );
8505
+ }
7367
8506
  async function rpc(url, method, params, phase) {
7368
8507
  let response;
7369
8508
  try {
7370
- response = await fetch(url, {
7371
- method: "POST",
7372
- headers: { "content-type": "application/json" },
7373
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
7374
- signal: AbortSignal.timeout(15e3)
7375
- });
8509
+ response = await postRpc(url, method, params, RPC_TIMEOUT_MS);
7376
8510
  } catch {
7377
8511
  throw sponsorError(
7378
8512
  "Withdrawal sponsorship request status is unknown.",
@@ -7386,43 +8520,37 @@ async function rpc(url, method, params, phase) {
7386
8520
  );
7387
8521
  }
7388
8522
  const payload = await response.json().catch(() => null);
7389
- if (!response.ok || payload?.error || payload?.result === void 0) {
7390
- const rejection = payload?.error;
7391
- const diagnostics = rpcDiagnostics(rejection?.data);
7392
- const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
7393
- const details = {
7394
- statusCode: response.status,
7395
- rpcCode: rejection?.code,
7396
- rpcMethod: method,
7397
- phase,
7398
- providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
7399
- failureCode: "RPC_REJECTED",
7400
- ...diagnostics
7401
- };
7402
- if (phase === "prepare" && executionReverted)
7403
- throw new OwneyError(
7404
- "WITHDRAW_FAILED",
7405
- "Yieldseeker withdrawal simulation reverted before submission.",
7406
- {
7407
- ...details,
7408
- failureCode: "SPONSOR_EXECUTION_REVERTED",
7409
- safeToFallback: false,
7410
- rejectedBeforeSubmission: true
7411
- },
7412
- "yieldseeker"
7413
- );
7414
- 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 ?? ""));
7415
- throw sponsorError(
7416
- phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
7417
- phase === "prepare" || definitiveRejection,
7418
- {
7419
- ...details,
7420
- ...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
7421
- }
7422
- );
7423
- }
8523
+ if (!response.ok || payload?.error || payload?.result === void 0)
8524
+ throw rpcRejection(response.status, payload, method, phase);
7424
8525
  return payload.result;
7425
8526
  }
8527
+ async function sendSignedUserOperation(rpcUrl, operation, operationId) {
8528
+ const method = "eth_sendUserOperation";
8529
+ const params = [operation, ENTRY_POINT_V06];
8530
+ if (!operationId) return { type: "accepted", hash: await rpc(rpcUrl, method, params, "send"), serverSubmit: false };
8531
+ const url = new URL(rpcUrl);
8532
+ url.searchParams.set("operationId", operationId);
8533
+ url.searchParams.set("serverSubmit", "1");
8534
+ let response;
8535
+ try {
8536
+ response = await postRpc(url.toString(), method, params, SERVER_SUBMIT_SEND_TIMEOUT_MS);
8537
+ } catch {
8538
+ return { type: "pending", serverSubmit: "unknown" };
8539
+ }
8540
+ const payload = await response.json().catch(() => null);
8541
+ if (!payload) return { type: "pending", serverSubmit: "unknown" };
8542
+ if (response.ok && !payload.error && payload.result !== void 0)
8543
+ return {
8544
+ type: "accepted",
8545
+ hash: payload.result,
8546
+ serverSubmit: payload.owney?.serverSubmit === true && payload.owney.state === "accepted"
8547
+ };
8548
+ const rejection = rpcRejection(response.status, payload, method, "send");
8549
+ if (rejection.details?.rejectedBeforeSubmission === true) throw rejection;
8550
+ if (payload.error?.data?.serverSubmit === true) return { type: "pending", serverSubmit: true };
8551
+ if (response.status >= 500) return { type: "pending", serverSubmit: "unknown" };
8552
+ throw rejection;
8553
+ }
7426
8554
  async function waitForTransaction(client, txHash) {
7427
8555
  const receipt = await client.waitForTransactionReceipt({ hash: txHash, confirmations: 1 });
7428
8556
  if (receipt.status !== "success")
@@ -7504,6 +8632,7 @@ async function submitEoa(input) {
7504
8632
  operation = { ...operation, signature };
7505
8633
  await assertOwnerSession(input.state, input.owner);
7506
8634
  assertIntentFresh(input.expiresAt);
8635
+ assertPaymasterFresh(operation.paymasterAndData);
7507
8636
  const userOperation = {
7508
8637
  sender: operation.sender,
7509
8638
  nonce: operation.nonce,
@@ -7520,18 +8649,19 @@ async function submitEoa(input) {
7520
8649
  input.assertCurrent?.();
7521
8650
  await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
7522
8651
  input.assertCurrent?.();
7523
- const submittedHash = await rpc(
7524
- input.rpcUrl,
7525
- "eth_sendUserOperation",
7526
- [operation, ENTRY_POINT_V06],
7527
- "send"
7528
- );
7529
- if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
7530
- throw sponsorError(
7531
- "Bundler returned a different UserOperation hash.",
7532
- false,
7533
- { userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
7534
- );
8652
+ const sent = await sendSignedUserOperation(input.rpcUrl, operation, input.operationId);
8653
+ const serverSubmit = sent.type === "pending" ? sent.serverSubmit : sent.serverSubmit ? true : void 0;
8654
+ if (serverSubmit) savePending(input.onPending, { userOpHash, serverSubmit });
8655
+ if (sent.type === "accepted") {
8656
+ const submittedHash = sent.hash;
8657
+ if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
8658
+ throw sponsorError(
8659
+ "Bundler returned a different UserOperation hash.",
8660
+ false,
8661
+ { userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
8662
+ );
8663
+ reportAccepted(input.onAccepted, { userOpHash });
8664
+ }
7535
8665
  const deadline = Date.now() + OBSERVATION_WINDOW_MS;
7536
8666
  let lastReadError;
7537
8667
  for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
@@ -7564,6 +8694,7 @@ async function submitEoa(input) {
7564
8694
  userOpHash,
7565
8695
  phase: "observe",
7566
8696
  failureCode: "RECEIPT_UNRESOLVED",
8697
+ ...serverSubmit ? { serverSubmit } : {},
7567
8698
  ...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
7568
8699
  }
7569
8700
  );
@@ -7624,6 +8755,7 @@ async function submitSmart(input) {
7624
8755
  { callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
7625
8756
  );
7626
8757
  savePending(input.onPending, { callsId });
8758
+ reportAccepted(input.onAccepted, { callsId });
7627
8759
  const deadline = Date.now() + OBSERVATION_WINDOW_MS;
7628
8760
  for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
7629
8761
  let status;
@@ -7917,6 +9049,9 @@ var YieldseekerAgent = class {
7917
9049
  endpointReadFailures = /* @__PURE__ */ new Map();
7918
9050
  reconcileUntil = /* @__PURE__ */ new Map();
7919
9051
  allocationRefresh = /* @__PURE__ */ new Set();
9052
+ /** Contexts whose next permitted snapshot read is forced once: a funding
9053
+ * invalidation with a tx hash (receipt recovery, a re-armed refresh). */
9054
+ forceNextSnapshot = /* @__PURE__ */ new Set();
7920
9055
  activityRefreshUntil = /* @__PURE__ */ new Map();
7921
9056
  // null means a confirmed movement has no reliable baseline. Do not guess
7922
9057
  // settlement; keep slow reconciliation after the fast window expires.
@@ -8323,6 +9458,7 @@ var YieldseekerAgent = class {
8323
9458
  this.endpointReadFailures.clear();
8324
9459
  this.reconcileUntil.clear();
8325
9460
  this.allocationRefresh.clear();
9461
+ this.forceNextSnapshot.clear();
8326
9462
  this.activityRefreshUntil.clear();
8327
9463
  this.snapshotMovements.clear();
8328
9464
  this.movementEpochs.clear();
@@ -8436,7 +9572,7 @@ var YieldseekerAgent = class {
8436
9572
  notifyFunding(onProgress, evidence);
8437
9573
  return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
8438
9574
  }
8439
- async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation) {
9575
+ async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation, submission) {
8440
9576
  let ownerTransferAttempted = false;
8441
9577
  let evidence = {
8442
9578
  agentId: this.id,
@@ -8449,12 +9585,23 @@ var YieldseekerAgent = class {
8449
9585
  delivery: "pending"
8450
9586
  };
8451
9587
  try {
8452
- return await this.withdrawInternal(state, chainId, asset, amount, (next) => {
8453
- evidence = { ...evidence, ...next };
8454
- notifyFunding(onProgress, evidence);
8455
- }, () => {
8456
- ownerTransferAttempted = true;
8457
- }, gasMode, onBeforeSubmission, assertOperation);
9588
+ return await this.withdrawInternal(
9589
+ state,
9590
+ chainId,
9591
+ asset,
9592
+ amount,
9593
+ (next) => {
9594
+ evidence = { ...evidence, ...next };
9595
+ notifyFunding(onProgress, evidence);
9596
+ },
9597
+ () => {
9598
+ ownerTransferAttempted = true;
9599
+ },
9600
+ gasMode,
9601
+ onBeforeSubmission,
9602
+ assertOperation,
9603
+ submission?.operationId
9604
+ );
8458
9605
  } catch (error) {
8459
9606
  if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") {
8460
9607
  this.withdrawalVersionCache.clear();
@@ -8470,7 +9617,7 @@ var YieldseekerAgent = class {
8470
9617
  throw error;
8471
9618
  }
8472
9619
  }
8473
- async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission, assertOperation) {
9620
+ async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission, assertOperation, operationId) {
8474
9621
  this.assertChain(chainId);
8475
9622
  this.assertAsset(asset);
8476
9623
  if (amount !== void 0 && BigInt(amount) <= 0n) {
@@ -8602,13 +9749,13 @@ var YieldseekerAgent = class {
8602
9749
  const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
8603
9750
  const verifiedPositions = [];
8604
9751
  if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
8605
- let active2 = true;
9752
+ let active = true;
8606
9753
  const checks = this.readVaultShares(values.map((position2) => position2.vaultOption), agentWallet, blockNumber).catch(() => []);
8607
9754
  const reads = values.map(async (position2, index) => {
8608
9755
  const address = (0, import_viem17.getAddress)(position2.vaultOption.address);
8609
9756
  try {
8610
9757
  const group = (await checks).slice(index * 2, index * 2 + 2);
8611
- if (!active2 || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
9758
+ if (!active || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
8612
9759
  this.assertSession(generation, chainId, asset);
8613
9760
  const [underlying, shares] = group.map((value) => value.result);
8614
9761
  if (typeof underlying !== "string" || !(0, import_viem17.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
@@ -8621,7 +9768,7 @@ var YieldseekerAgent = class {
8621
9768
  shares,
8622
9769
  blockNumber
8623
9770
  );
8624
- if (!active2 || value === void 0) return null;
9771
+ if (!active || value === void 0) return null;
8625
9772
  this.assertSession(generation, chainId, asset);
8626
9773
  verified.withdrawableAssetsRaw = value.toString();
8627
9774
  verifiedPositions.push(verified);
@@ -8638,7 +9785,7 @@ var YieldseekerAgent = class {
8638
9785
  args: [shares],
8639
9786
  blockNumber
8640
9787
  });
8641
- if (!active2 || typeof value !== "bigint" || value < 0n) return null;
9788
+ if (!active || typeof value !== "bigint" || value < 0n) return null;
8642
9789
  this.assertSession(generation, chainId, asset);
8643
9790
  if (value > 0n && !positiveValue) {
8644
9791
  positiveValue = true;
@@ -8666,19 +9813,20 @@ var YieldseekerAgent = class {
8666
9813
  }
8667
9814
  });
8668
9815
  const result = await boundedVerification(Promise.all(reads), timeoutMs);
8669
- active2 = false;
9816
+ active = false;
8670
9817
  this.assertSession(generation, chainId, asset);
8671
9818
  return {
8672
9819
  verified: result !== void 0 && result.every((position2) => position2 !== null),
8673
9820
  positions: [...verifiedPositions]
8674
9821
  };
8675
9822
  };
8676
- const refreshPositions = async () => {
9823
+ const refreshPositions = async (promptless = false) => {
8677
9824
  this.assertSession(generation, chainId, asset);
8678
9825
  const response = await this.walletRequest(
8679
9826
  state,
8680
9827
  chainId,
8681
- this.agentPath(context, "vault-positions")
9828
+ this.agentPath(context, "vault-positions"),
9829
+ { promptless }
8682
9830
  );
8683
9831
  this.assertSession(generation, chainId, asset);
8684
9832
  if (!Array.isArray(response?.vaultPositions)) throw this.invalidResponse("vault positions");
@@ -8909,6 +10057,8 @@ var YieldseekerAgent = class {
8909
10057
  agentWallet: (0, import_viem17.getAddress)(context.wallet.walletAddress),
8910
10058
  asset: metadata.address,
8911
10059
  callData: data,
10060
+ // The EOA send opts in to the server-owned send with it.
10061
+ operationId,
8912
10062
  onPending: (pending2) => {
8913
10063
  submissionEvidence = { ...submissionEvidence, ...pending2 };
8914
10064
  notifyFunding(onProgress, submissionEvidence);
@@ -8917,6 +10067,11 @@ var YieldseekerAgent = class {
8917
10067
  assertOperation?.();
8918
10068
  await onBeforeSubmission?.({ ...base7, ...pending2 });
8919
10069
  onOwnerTransferAttempted?.();
10070
+ },
10071
+ // The owner's signed operation was accepted: another agent may start.
10072
+ onAccepted: (accepted) => {
10073
+ submissionEvidence = { ...submissionEvidence, ...accepted, ownerAuthorization: "accepted" };
10074
+ notifyFunding(onProgress, submissionEvidence);
8920
10075
  }
8921
10076
  });
8922
10077
  txHash = submitted.txHash;
@@ -8933,7 +10088,10 @@ var YieldseekerAgent = class {
8933
10088
  value: "0",
8934
10089
  chainId
8935
10090
  },
8936
- (hash2) => notifyFunding(onProgress, { ...base7, txHash: hash2 }),
10091
+ (hash2) => {
10092
+ submissionEvidence = { ...submissionEvidence, txHash: hash2, ownerAuthorization: "accepted" };
10093
+ notifyFunding(onProgress, submissionEvidence);
10094
+ },
8937
10095
  (value) => {
8938
10096
  receipt = value;
8939
10097
  },
@@ -8964,7 +10122,9 @@ var YieldseekerAgent = class {
8964
10122
  const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
8965
10123
  const knownAtPayout = new Set(knownPositions.keys());
8966
10124
  const [catalogVerified, knownProof] = await Promise.all([
8967
- boundedVerification(refreshPositions().then((values) => {
10125
+ // Never a sign-in prompt here: a refused or missing cached sign-in
10126
+ // leaves the catalog unverified, the existing unverified-MAX outcome.
10127
+ boundedVerification(refreshPositions(true).then((values) => {
8968
10128
  rememberPositions(values);
8969
10129
  return true;
8970
10130
  }).catch(() => false)),
@@ -9072,14 +10232,14 @@ var YieldseekerAgent = class {
9072
10232
  }
9073
10233
  /** Recheck MAX without re-unwinding, paying out, or requesting a signature. */
9074
10234
  async verifyWithdrawalCompletion(state, evidence) {
9075
- const unknown = (reason2) => ({ status: "unavailable", reason: reason2 });
10235
+ const unknown2 = (reason2) => ({ status: "unavailable", reason: reason2 });
9076
10236
  const proof = evidence.maxCompletion;
9077
- if (evidence.agentId !== this.id || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.chainId !== 8453 || !proof || !evidence.agentWallet || !(0, import_viem17.isAddress)(evidence.agentWallet) || !evidence.blockNumber || !/^\d+$/.test(evidence.blockNumber) || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || !this.isTransactionHash(evidence.txHash)) return unknown("PAYOUT_PROOF_UNAVAILABLE");
10237
+ if (evidence.agentId !== this.id || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.chainId !== 8453 || !proof || !evidence.agentWallet || !(0, import_viem17.isAddress)(evidence.agentWallet) || !evidence.blockNumber || !/^\d+$/.test(evidence.blockNumber) || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || !this.isTransactionHash(evidence.txHash)) return unknown2("PAYOUT_PROOF_UNAVAILABLE");
9078
10238
  this.assertAsset(evidence.token);
9079
10239
  const asset = evidence.token;
9080
10240
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
9081
10241
  const signature = this.auth.peekToken(state, evidence.chainId);
9082
- if (!signature) return unknown("AUTHENTICATION_REQUIRED");
10242
+ if (!signature) return unknown2("AUTHENTICATION_REQUIRED");
9083
10243
  let reason = "CATALOG_UNAVAILABLE";
9084
10244
  try {
9085
10245
  return await bounded(async () => {
@@ -9087,7 +10247,7 @@ var YieldseekerAgent = class {
9087
10247
  `/users/${encodeURIComponent(proof.userId)}/agents/${encodeURIComponent(proof.agentId)}/vault-positions`,
9088
10248
  { signature }
9089
10249
  );
9090
- if (!Array.isArray(response?.vaultPositions)) return unknown("CATALOG_INVALID");
10250
+ if (!Array.isArray(response?.vaultPositions)) return unknown2("CATALOG_INVALID");
9091
10251
  const positions = this.validateWithdrawalPositions(response.vaultPositions, evidence.chainId, asset);
9092
10252
  const vaults = new Map(proof.vaults.map((address) => [address.toLowerCase(), { address }]));
9093
10253
  positions.forEach((p) => vaults.set(p.vaultOption.address.toLowerCase(), p.vaultOption));
@@ -9095,14 +10255,14 @@ var YieldseekerAgent = class {
9095
10255
  if (needsInventory || [...vaults.values()].some((option) => !option.provider)) {
9096
10256
  const catalog = await this.loadVaultOptions(asset).catch(() => []);
9097
10257
  const eligible = catalog.filter((o) => o.chainId === evidence.chainId && o.assetAddress?.toLowerCase() === metadata.address.toLowerCase() && (0, import_viem17.isAddress)(o.address));
9098
- if (needsInventory && !eligible.length) return unknown("INVENTORY_INCOMPLETE");
10258
+ if (needsInventory && !eligible.length) return unknown2("INVENTORY_INCOMPLETE");
9099
10259
  for (const option of eligible) {
9100
10260
  const key2 = option.address.toLowerCase();
9101
10261
  if (needsInventory || vaults.has(key2)) vaults.set(key2, option);
9102
10262
  }
9103
10263
  }
9104
10264
  if (vaults.size > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT || [...vaults.keys()].some((a) => !(0, import_viem17.isAddress)(a)))
9105
- return unknown("INVENTORY_INVALID");
10265
+ return unknown2("INVENTORY_INVALID");
9106
10266
  reason = "VAULT_PROOF_UNAVAILABLE";
9107
10267
  const blockNumber = BigInt(evidence.blockNumber);
9108
10268
  const options = [...vaults.values()];
@@ -9133,7 +10293,7 @@ var YieldseekerAgent = class {
9133
10293
  };
9134
10294
  }, 8e3, () => new Error("MAX proof read timed out"));
9135
10295
  } catch {
9136
- return unknown(reason);
10296
+ return unknown2(reason);
9137
10297
  }
9138
10298
  }
9139
10299
  async readVaultShares(options, wallet, blockNumber) {
@@ -9214,12 +10374,12 @@ var YieldseekerAgent = class {
9214
10374
  { asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
9215
10375
  this.id
9216
10376
  );
9217
- let active2 = true;
10377
+ let active = true;
9218
10378
  let knownProof;
9219
10379
  const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
9220
10380
  const assertReadCurrent = () => {
9221
10381
  this.assertSession(generation, chainId, context.asset);
9222
- if (!active2 || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
10382
+ if (!active || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
9223
10383
  };
9224
10384
  const read2 = async () => {
9225
10385
  const client = this.getReceiptClient();
@@ -9408,7 +10568,7 @@ var YieldseekerAgent = class {
9408
10568
  if (error instanceof OwneyError) throw error;
9409
10569
  throw unavailable();
9410
10570
  } finally {
9411
- active2 = false;
10571
+ active = false;
9412
10572
  if (timer) clearTimeout(timer);
9413
10573
  }
9414
10574
  }
@@ -9493,6 +10653,7 @@ var YieldseekerAgent = class {
9493
10653
  this.assertAsset(asset);
9494
10654
  const until = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_SNAPSHOT_WINDOW_MS);
9495
10655
  const activityUntil = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
10656
+ const hash2 = typeof txHash === "string" && /^0x[0-9a-fA-F]{64}$/.test(txHash);
9496
10657
  this.invalidateSlot(state, chainId, asset, (key2) => {
9497
10658
  const failure = this.snapshotFailures.get(key2);
9498
10659
  this.advancePortfolioVersion(key2);
@@ -9504,6 +10665,7 @@ var YieldseekerAgent = class {
9504
10665
  }
9505
10666
  this.reconcileUntil.set(key2, Math.max(this.reconcileUntil.get(key2) ?? 0, until));
9506
10667
  this.activityRefreshUntil.set(key2, Math.max(this.activityRefreshUntil.get(key2) ?? 0, activityUntil));
10668
+ if (hash2) this.forceNextSnapshot.add(key2);
9507
10669
  });
9508
10670
  }
9509
10671
  endFundingBalanceRefresh(state, chainId, asset, read2) {
@@ -10339,7 +11501,7 @@ var YieldseekerAgent = class {
10339
11501
  return this.walletRequest(
10340
11502
  state,
10341
11503
  chainId,
10342
- `${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`
11504
+ this.snapshotPath(context, forceRefresh)
10343
11505
  ).then((response) => {
10344
11506
  if (!response?.agentSnapshot) {
10345
11507
  throw this.invalidResponse("agent snapshot");
@@ -10369,6 +11531,16 @@ var YieldseekerAgent = class {
10369
11531
  throw error;
10370
11532
  });
10371
11533
  }
11534
+ snapshotPath(context, forceRefresh) {
11535
+ return `${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`;
11536
+ }
11537
+ /** True while `walletRequest` would refuse a read of `path`: this user's
11538
+ * rate-limit cooldown or that endpoint's outage cooldown. */
11539
+ readCoolingDown(state, chainId, path) {
11540
+ const userKey = this.userKey(state, chainId);
11541
+ const now = Date.now();
11542
+ return (this.standardReadFailures.get(userKey)?.retryAt ?? 0) > now || (this.endpointReadFailures.get(`${userKey}:${path}`)?.retryAt ?? 0) > now;
11543
+ }
10372
11544
  portfolioSnapshot(state, chainId, context, contextKey) {
10373
11545
  const key2 = `snapshot:${contextKey}`;
10374
11546
  const cached = this.readCache.get(key2);
@@ -10387,6 +11559,7 @@ var YieldseekerAgent = class {
10387
11559
  const settling = inWindow && !this.deferredToLedger(contextKey);
10388
11560
  if (inWindow) this.settlementChecks.set(contextKey, (this.settlementChecks.get(contextKey) ?? 0) + 1);
10389
11561
  const ready = this.readyForces.delete(contextKey) && this.noteReadyForce(contextKey);
11562
+ const movementForce = this.forceNextSnapshot.has(contextKey) && !this.readCoolingDown(state, chainId, this.snapshotPath(context, true)) && this.forceNextSnapshot.delete(contextKey);
10390
11563
  return this.requestPortfolioSnapshot(
10391
11564
  state,
10392
11565
  chainId,
@@ -10394,7 +11567,7 @@ var YieldseekerAgent = class {
10394
11567
  contextKey,
10395
11568
  version,
10396
11569
  generation,
10397
- allocationChanged || settling || ready
11570
+ allocationChanged || settling || ready || movementForce
10398
11571
  );
10399
11572
  })
10400
11573
  };
@@ -10844,7 +12017,7 @@ var YieldseekerAgent = class {
10844
12017
  }
10845
12018
  const ttl = this.portfolioCacheMs(contextKey);
10846
12019
  this.settlementChecks.set(contextKey, 1);
10847
- const force = movement === "withdrawal" || !this.deferredToLedger(contextKey);
12020
+ const force = true;
10848
12021
  await this.cachedRead(
10849
12022
  `snapshot:${contextKey}`,
10850
12023
  ttl,
@@ -10899,13 +12072,22 @@ var YieldseekerAgent = class {
10899
12072
  options.assertCurrent?.();
10900
12073
  return this.api.request(path, { method: options.method, body: options.body, signature: signature2 });
10901
12074
  };
10902
- let signature = await this.auth.getToken(state, chainId);
12075
+ const cached = options.promptless ? this.auth.peekToken(state, chainId) : void 0;
12076
+ if (options.promptless && !cached)
12077
+ throw new OwneyError(
12078
+ "AGENT_AUTH_FAILED",
12079
+ "No Yieldseeker sign-in is available without a prompt.",
12080
+ { reason: "YIELDSEEKER_SIGN_IN_REQUIRED" },
12081
+ this.id
12082
+ );
12083
+ let signature = cached ?? await this.auth.getToken(state, chainId);
10903
12084
  try {
10904
12085
  return await request2(signature);
10905
12086
  } catch (error) {
10906
12087
  if (!(error instanceof YieldseekerApiError)) throw error;
10907
12088
  if (error.providerCode === "NO_USER") throw error;
10908
12089
  if (!error.isAuthenticationError) throw error;
12090
+ if (options.promptless) throw error;
10909
12091
  signature = await this.auth.refreshToken(state, chainId, signature);
10910
12092
  try {
10911
12093
  return await request2(signature);
@@ -11137,6 +12319,16 @@ init_errors();
11137
12319
 
11138
12320
  // src/lib/swap/swap-api.ts
11139
12321
  init_errors();
12322
+ var FUSION_SECRET_SERVER_PUBLISH = 1;
12323
+ var FUSION_SECRET_HANDOFF_TIMEOUT_MS = 5e3;
12324
+ function handoffFailure(error) {
12325
+ const statusCode2 = error instanceof OwneyError && typeof error.details?.statusCode === "number" ? error.details.statusCode : void 0;
12326
+ return new OwneyError(
12327
+ error instanceof OwneyError ? error.code : "SWAP_REQUEST_FAILED",
12328
+ "The swap secret handoff did not complete.",
12329
+ statusCode2 === void 0 ? {} : { statusCode: statusCode2 }
12330
+ );
12331
+ }
11140
12332
  async function request(baseUrl, apiKey, path, init) {
11141
12333
  const url = `${baseUrl}/api/v1/swap${path}`;
11142
12334
  const controller = new AbortController();
@@ -11149,7 +12341,8 @@ async function request(baseUrl, apiKey, path, init) {
11149
12341
  "Content-Type": "application/json",
11150
12342
  "x-owney-api-key": apiKey
11151
12343
  },
11152
- ...init ? { body: JSON.stringify(init.body) } : {}
12344
+ ...init ? { body: JSON.stringify(init.body) } : {},
12345
+ ...init?.redirect ? { redirect: init.redirect } : {}
11153
12346
  });
11154
12347
  if (!res.ok) {
11155
12348
  const text = await res.text().catch(() => "");
@@ -11182,7 +12375,7 @@ async function request(baseUrl, apiKey, path, init) {
11182
12375
  );
11183
12376
  }
11184
12377
  return json.data;
11185
- }, 2e4, () => new OwneyError(
12378
+ }, init?.timeoutMs ?? 2e4, () => new OwneyError(
11186
12379
  "SWAP_REQUEST_FAILED",
11187
12380
  "Swap request timed out. Its outcome may still need confirmation.",
11188
12381
  { requestTimedOut: true, phase: path === "/order" ? "submit" : "read" }
@@ -11266,8 +12459,9 @@ function createSwapApi(baseUrl, apiKey) {
11266
12459
  /**
11267
12460
  * Builds a Fusion+ order server-side and returns EIP-712 typed data.
11268
12461
  *
11269
- * Only HASHES go over the wire. The preimages never leave the browser —
11270
- * see swap.secrets.
12462
+ * Only HASHES go over the wire. The preimages leave the browser only
12463
+ * through `handoffSecrets`, and only with the caller's opt-in — see
12464
+ * swap.secrets.
11271
12465
  */
11272
12466
  buildOrder: (params) => request(baseUrl, apiKey, "/order/build", {
11273
12467
  method: "POST",
@@ -11294,6 +12488,23 @@ function createSwapApi(baseUrl, apiKey) {
11294
12488
  method: "POST",
11295
12489
  body: { orderHash, secret }
11296
12490
  }),
12491
+ /**
12492
+ * Hands a signed Fusion+ order's secrets to our routing API, which checks
12493
+ * them against the order and publishes each one only once 1inch reports
12494
+ * its destination escrow ready (part 5). The browser keeps publishing too;
12495
+ * a double publish is a no-op upstream.
12496
+ *
12497
+ * Only our own API: a redirect is refused, since the body carries the
12498
+ * preimages. Bounded to 5 s.
12499
+ */
12500
+ handoffSecrets: (body) => request(baseUrl, apiKey, "/order/secrets", {
12501
+ method: "POST",
12502
+ body,
12503
+ redirect: "error",
12504
+ timeoutMs: FUSION_SECRET_HANDOFF_TIMEOUT_MS
12505
+ }).catch((error) => {
12506
+ throw handoffFailure(error);
12507
+ }),
11297
12508
  /**
11298
12509
  * @param chainId present only for a SAME-CHAIN order. That product keys its
11299
12510
  * orders per chain and the Fusion+ endpoint does not know them, so asking
@@ -11472,14 +12683,14 @@ init_errors();
11472
12683
 
11473
12684
  // src/lib/swap/swap.arrival.ts
11474
12685
  init_errors();
11475
- var DEFAULT_TIMEOUT_MS = 18e4;
12686
+ var DEFAULT_TIMEOUT_MS2 = 18e4;
11476
12687
  var DEFAULT_POLL_MS = 4e3;
11477
12688
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
11478
12689
  async function awaitBalanceIncrease(options) {
11479
12690
  const {
11480
12691
  readBalance,
11481
12692
  baseline,
11482
- timeoutMs = DEFAULT_TIMEOUT_MS,
12693
+ timeoutMs = DEFAULT_TIMEOUT_MS2,
11483
12694
  pollMs = DEFAULT_POLL_MS
11484
12695
  } = options;
11485
12696
  const deadline = Date.now() + timeoutMs;
@@ -11576,14 +12787,14 @@ var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
11576
12787
  var DEFAULT_POLL_MS2 = 5e3;
11577
12788
  var MAX_BACKOFF_MS = 3e4;
11578
12789
  var backoffFor = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
11579
- var DEFAULT_TIMEOUT_MS2 = 15 * 60 * 1e3;
12790
+ var DEFAULT_TIMEOUT_MS3 = 15 * 60 * 1e3;
11580
12791
  async function runFusionOrderInternal(deps, options) {
11581
12792
  const {
11582
12793
  orderHash,
11583
12794
  secrets,
11584
12795
  onStage,
11585
12796
  pollIntervalMs = DEFAULT_POLL_MS2,
11586
- timeoutMs = DEFAULT_TIMEOUT_MS2
12797
+ timeoutMs = DEFAULT_TIMEOUT_MS3
11587
12798
  } = options;
11588
12799
  const deadline = deps.now() + timeoutMs;
11589
12800
  let partial = options.hasPartialFill === true;
@@ -11664,7 +12875,7 @@ async function runFusionOrderInternal(deps, options) {
11664
12875
  }
11665
12876
  }
11666
12877
  function runFusionOrder(deps, options) {
11667
- return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options));
12878
+ return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options), { wait: options.waitForWatcher === true });
11668
12879
  }
11669
12880
 
11670
12881
  // src/lib/swap/swap.same-chain-runner.ts
@@ -11672,7 +12883,7 @@ init_errors();
11672
12883
  var DEFAULT_POLL_MS3 = 5e3;
11673
12884
  var MAX_BACKOFF_MS2 = 3e4;
11674
12885
  var backoffFor2 = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS2);
11675
- var DEFAULT_TIMEOUT_MS3 = 3 * 60 * 1e3;
12886
+ var DEFAULT_TIMEOUT_MS4 = 3 * 60 * 1e3;
11676
12887
  function explain(status) {
11677
12888
  switch (status) {
11678
12889
  case "expired":
@@ -11696,7 +12907,7 @@ async function runSameChainOrderInternal(deps, options) {
11696
12907
  orderHash,
11697
12908
  onStage,
11698
12909
  pollIntervalMs = DEFAULT_POLL_MS3,
11699
- timeoutMs = DEFAULT_TIMEOUT_MS3
12910
+ timeoutMs = DEFAULT_TIMEOUT_MS4
11700
12911
  } = options;
11701
12912
  const deadline = deps.now() + timeoutMs;
11702
12913
  let partial = options.hasPartialFill === true;
@@ -11749,7 +12960,7 @@ async function runSameChainOrderInternal(deps, options) {
11749
12960
  }
11750
12961
  }
11751
12962
  function runSameChainOrder(deps, options) {
11752
- return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options));
12963
+ return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options), { wait: options.waitForWatcher === true });
11753
12964
  }
11754
12965
 
11755
12966
  // src/lib/swap/swap.secret-store.ts
@@ -11774,6 +12985,21 @@ function saveOrder(order) {
11774
12985
  return false;
11775
12986
  }
11776
12987
  }
12988
+ function loadOrder(orderHash) {
12989
+ const store = storage5();
12990
+ if (!store) return null;
12991
+ try {
12992
+ const raw2 = store.getItem(keyFor2(orderHash));
12993
+ if (!raw2) return null;
12994
+ const parsed = JSON.parse(raw2);
12995
+ if (!Array.isArray(parsed.secrets) || parsed.secrets.length === 0) {
12996
+ return null;
12997
+ }
12998
+ return parsed;
12999
+ } catch {
13000
+ return null;
13001
+ }
13002
+ }
11777
13003
  function clearOrder(orderHash) {
11778
13004
  const store = storage5();
11779
13005
  if (!store) return;
@@ -11852,10 +13078,10 @@ async function affordableAmount(deps, quoted, reserve = 0n) {
11852
13078
  }
11853
13079
  async function executeSwap(deps, options) {
11854
13080
  const { quote, walletAddress, onStage } = options;
11855
- let active2 = true;
13081
+ let active = true;
11856
13082
  const original = deps;
11857
13083
  const assertActive = () => {
11858
- if (!active2) throw new OwneyError("SWAP_REQUEST_FAILED", "This swap attempt is no longer active.");
13084
+ if (!active) throw new OwneyError("SWAP_REQUEST_FAILED", "This swap attempt is no longer active.");
11859
13085
  };
11860
13086
  const call = async (work, write = false) => {
11861
13087
  assertActive();
@@ -11916,7 +13142,7 @@ async function executeSwap(deps, options) {
11916
13142
  }
11917
13143
  return { received: arrived.toString(), ...result };
11918
13144
  } finally {
11919
- active2 = false;
13145
+ active = false;
11920
13146
  }
11921
13147
  }
11922
13148
  async function runClassic(deps, options) {
@@ -12035,6 +13261,7 @@ async function runSameChainFusion(deps, options, walletAddress) {
12035
13261
  }
12036
13262
  await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
12037
13263
  orderHash: built.orderHash,
13264
+ waitForWatcher: true,
12038
13265
  ...onStage ? { onStage } : {}
12039
13266
  });
12040
13267
  return { orderHash: built.orderHash, spent: amount.toString() };
@@ -12103,67 +13330,118 @@ async function runFusion(deps, options, walletAddress) {
12103
13330
  ...direction ? { direction } : {},
12104
13331
  ...permit ? { permit } : {}
12105
13332
  });
12106
- const durable = saveOrder({
12107
- orderHash: built.orderHash,
12108
- walletAddress,
12109
- secrets,
12110
- srcChainId: quote.src.chainId,
12111
- srcSymbol: quote.src.symbol,
12112
- dstChainId: quote.dst.chainId,
12113
- dstSymbol: quote.dst.symbol,
12114
- amount: amount.toString(),
12115
- createdAt: Date.now()
12116
- });
12117
- debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
12118
- let signature;
13333
+ const release = claimOrder(built.orderHash);
12119
13334
  try {
12120
- onStage?.("signing");
12121
- await deps.ensureChain(quote.src.chainId);
12122
- signature = await awaitWalletPrompt(() => deps.signTypedData(built.typedData), "sign the swap");
12123
- options.assertCurrent?.();
12124
- options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
12125
- } catch (error) {
13335
+ const stored = {
13336
+ orderHash: built.orderHash,
13337
+ walletAddress,
13338
+ secrets,
13339
+ srcChainId: quote.src.chainId,
13340
+ srcSymbol: quote.src.symbol,
13341
+ dstChainId: quote.dst.chainId,
13342
+ dstSymbol: quote.dst.symbol,
13343
+ amount: amount.toString(),
13344
+ createdAt: Date.now(),
13345
+ ...options.pendingDeposit ? { pendingDeposit: withinOrderMinimum(options.pendingDeposit, built.dstAmountMin) } : {}
13346
+ };
13347
+ const durable = saveOrder(stored);
13348
+ debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
13349
+ let signature;
13350
+ try {
13351
+ onStage?.("signing");
13352
+ await deps.ensureChain(quote.src.chainId);
13353
+ signature = await awaitWalletPrompt(() => deps.signTypedData(built.typedData), "sign the swap");
13354
+ options.assertCurrent?.();
13355
+ options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
13356
+ } catch (error) {
13357
+ clearOrder(built.orderHash);
13358
+ throw error;
13359
+ }
13360
+ if (options.secretHandoff && deps.api.handoffSecrets) {
13361
+ if (await tryHandoff(deps, options.secretHandoff, built, quote, secretHashes, secrets))
13362
+ saveOrder({ ...stored, serverSecrets: true });
13363
+ }
13364
+ debugLog("owney-sdk", "swap: signed, submitting to relayer");
13365
+ try {
13366
+ await deps.api.submitOrder({
13367
+ srcChainId: quote.src.chainId,
13368
+ // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
13369
+ // the envelope here gets a bare 500 from the relayer.
13370
+ order: built.order,
13371
+ signature,
13372
+ quoteId: built.quoteId,
13373
+ // Single-fill orders must NOT carry secretHashes — the relayer rejects
13374
+ // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
13375
+ // order's hashlock, so repeating it here is redundant, and only a
13376
+ // multi-fill order (a Merkle tree of hashes) needs them listed.
13377
+ ...secretHashes.length > 1 ? { secretHashes } : {},
13378
+ ...built.extension ? { extension: built.extension } : {}
13379
+ });
13380
+ } catch (error) {
13381
+ throw submissionUnknown(error, built.orderHash);
13382
+ }
13383
+ debugLog("owney-sdk", "swap: order submitted, polling escrows");
13384
+ try {
13385
+ options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
13386
+ } catch {
13387
+ }
13388
+ try {
13389
+ await runFusionOrder(deps.runner, {
13390
+ orderHash: built.orderHash,
13391
+ // This run owns the order: wait out a watcher another tab started.
13392
+ waitForWatcher: true,
13393
+ secrets,
13394
+ ...onStage ? { onStage } : {}
13395
+ });
13396
+ } catch (error) {
13397
+ if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
13398
+ clearOrder(built.orderHash);
13399
+ }
13400
+ throw error;
13401
+ }
12126
13402
  clearOrder(built.orderHash);
12127
- throw error;
12128
- }
12129
- debugLog("owney-sdk", "swap: signed, submitting to relayer");
13403
+ return { orderHash: built.orderHash, spent: amount.toString() };
13404
+ } finally {
13405
+ release();
13406
+ }
13407
+ }
13408
+ var HANDOFF_DECLINE_CODES = /* @__PURE__ */ new Set([
13409
+ "FUNDING_TASK_KIND_DISABLED",
13410
+ "SECRETS_INVALID",
13411
+ "SECRET_HASH_MISMATCH",
13412
+ "ORDER_UNREADABLE",
13413
+ "ORDER_HASH_MISMATCH",
13414
+ "HASHLOCK_MISMATCH",
13415
+ "MAKER_MISMATCH",
13416
+ "CHAIN_MISMATCH",
13417
+ "OPERATION_INVALID",
13418
+ "ORDER_CONFLICT"
13419
+ ]);
13420
+ async function tryHandoff(deps, handoff, built, quote, secretHashes, secrets) {
12130
13421
  try {
12131
- await deps.api.submitOrder({
13422
+ const answer = await bounded(() => deps.api.handoffSecrets({
13423
+ operation: handoff.operation,
13424
+ orderHash: built.orderHash,
12132
13425
  srcChainId: quote.src.chainId,
12133
- // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
12134
- // the envelope here gets a bare 500 from the relayer.
13426
+ dstChainId: quote.dst.chainId,
12135
13427
  order: built.order,
12136
- signature,
12137
- quoteId: built.quoteId,
12138
- // Single-fill orders must NOT carry secretHashes — the relayer rejects
12139
- // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
12140
- // order's hashlock, so repeating it here is redundant, and only a
12141
- // multi-fill order (a Merkle tree of hashes) needs them listed.
12142
- ...secretHashes.length > 1 ? { secretHashes } : {},
12143
- ...built.extension ? { extension: built.extension } : {}
12144
- });
12145
- } catch (error) {
12146
- throw submissionUnknown(error, built.orderHash);
12147
- }
12148
- debugLog("owney-sdk", "swap: order submitted, polling escrows");
12149
- try {
12150
- options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
12151
- } catch {
12152
- }
12153
- try {
12154
- await runFusionOrder(deps.runner, {
12155
- orderHash: built.orderHash,
12156
- secrets,
12157
- ...onStage ? { onStage } : {}
12158
- });
12159
- } catch (error) {
12160
- if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
12161
- clearOrder(built.orderHash);
13428
+ extension: built.extension,
13429
+ secretHashes,
13430
+ fusionSecrets: secrets
13431
+ }), FUSION_SECRET_HANDOFF_TIMEOUT_MS, () => new Error("secret handoff timed out"));
13432
+ if (answer?.accepted === true) {
13433
+ debugLog("owney-sdk", "swap: secrets handed to the server", { orderHash: built.orderHash });
13434
+ return true;
12162
13435
  }
12163
- throw error;
13436
+ const code = typeof answer?.code === "string" && HANDOFF_DECLINE_CODES.has(answer.code) ? answer.code : "OTHER";
13437
+ debugLog("owney-sdk", "swap: secret handoff declined, the browser publishes", { code });
13438
+ } catch {
13439
+ debugLog("owney-sdk", "swap: secret handoff unavailable, the browser publishes");
12164
13440
  }
12165
- clearOrder(built.orderHash);
12166
- return { orderHash: built.orderHash, spent: amount.toString() };
13441
+ return false;
13442
+ }
13443
+ function withinOrderMinimum(deposit, orderMinimum) {
13444
+ return typeof orderMinimum === "string" && /^\d+$/.test(orderMinimum) && /^\d+$/.test(deposit.received) && BigInt(orderMinimum) < BigInt(deposit.received) ? { ...deposit, received: orderMinimum } : deposit;
12167
13445
  }
12168
13446
  function submissionUnknown(error, orderHash) {
12169
13447
  return new OwneyError(
@@ -12187,7 +13465,7 @@ async function awaitWithdrawalPayouts(options) {
12187
13465
  const payouts = new Map(Object.entries(options.results).map(([id, result]) => [id, { result, evidence: result.evidence }]));
12188
13466
  const timeoutMs = Math.max(0, options.timeoutMs ?? 18e4);
12189
13467
  const deadline = options.deadline ?? Date.now() + timeoutMs;
12190
- let active2 = true;
13468
+ let active = true;
12191
13469
  let timer;
12192
13470
  const incomplete = (reason, agentId) => new OwneyError(
12193
13471
  "WITHDRAW_SWAP_INCOMPLETE",
@@ -12205,7 +13483,7 @@ async function awaitWithdrawalPayouts(options) {
12205
13483
  );
12206
13484
  const assertActive = () => {
12207
13485
  options.assertCurrent();
12208
- if (!active2 || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
13486
+ if (!active || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
12209
13487
  };
12210
13488
  const pause = async () => {
12211
13489
  assertActive();
@@ -12382,7 +13660,7 @@ async function awaitWithdrawalPayouts(options) {
12382
13660
  timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), Math.max(0, deadline - Date.now()));
12383
13661
  })]);
12384
13662
  } finally {
12385
- active2 = false;
13663
+ active = false;
12386
13664
  if (timer) clearTimeout(timer);
12387
13665
  }
12388
13666
  }
@@ -12951,21 +14229,42 @@ init_errors();
12951
14229
  var import_viem22 = require("viem");
12952
14230
  init_errors();
12953
14231
  init_token_batch_family();
14232
+ var SPONSORED_DEPOSIT_SERVER_BROADCAST = 1;
12954
14233
  async function executeTokenBatch(input, initial) {
12955
14234
  let body = initial, family, ended = false;
12956
14235
  const deadlineAt = Date.now() + 9e4, controller = new AbortController();
12957
14236
  let timer;
12958
- const pending2 = () => new OwneyError(
12959
- "DEPOSIT_IN_PROGRESS",
12960
- "Deposit is awaiting confirmation. Its transaction is still being checked.",
12961
- {
12962
- sponsorPending: true,
12963
- phase: body.serializedTransaction ? "deposit" : "approval",
12964
- txHash: body.serializedTransaction ? (0, import_viem22.keccak256)(body.serializedTransaction) : body.serializedPermitTransaction ? (0, import_viem22.keccak256)(body.serializedPermitTransaction) : void 0,
12965
- notSubmitted: false,
12966
- safeToFallback: false
12967
- }
12968
- );
14237
+ const serverSent = /* @__PURE__ */ new Set();
14238
+ let accepted;
14239
+ let maybeAccepted = !!input.resumeSaved, alreadySending = false;
14240
+ const verifiedFailures = /* @__PURE__ */ new WeakSet();
14241
+ const pending2 = () => {
14242
+ const deposit = body.serializedTransaction ? (0, import_viem22.keccak256)(body.serializedTransaction) : accepted;
14243
+ const unknown2 = !deposit && maybeAccepted;
14244
+ const deadline = /^\d{1,15}$/.test(body.deadline) ? Number(body.deadline) : void 0;
14245
+ return new OwneyError(
14246
+ "DEPOSIT_IN_PROGRESS",
14247
+ "Deposit is awaiting confirmation. Its transaction is still being checked.",
14248
+ {
14249
+ sponsorPending: true,
14250
+ phase: deposit || unknown2 ? "deposit" : "approval",
14251
+ txHash: deposit ?? (body.serializedPermitTransaction && !unknown2 ? (0, import_viem22.keccak256)(body.serializedPermitTransaction) : void 0),
14252
+ notSubmitted: false,
14253
+ safeToFallback: false,
14254
+ // Only while the current deposit is the one the server will send.
14255
+ ...deposit && serverSent.has(deposit) ? { serverBroadcast: true } : unknown2 ? { serverBroadcast: "unknown" } : {},
14256
+ ...unknown2 && alreadySending ? { code: "DEPOSIT_ALREADY_SENDING" } : {},
14257
+ // The Permit2 deadline (seconds): after it no deposit of this
14258
+ // authorization can execute, which bounds an unknown outcome. Never
14259
+ // after a 409 DEPOSIT_ALREADY_SENDING: this refused authorization is
14260
+ // not the deposit being sent.
14261
+ ...unknown2 && !alreadySending && deadline !== void 0 ? { authorizationDeadline: deadline } : {},
14262
+ // Its Permit2 nonce: with the deadline, the chain shows whether any
14263
+ // deposit of this authorization executed.
14264
+ ...unknown2 && !alreadySending || deposit && serverSent.has(deposit) ? { permit2Nonce: body.nonce } : {}
14265
+ }
14266
+ );
14267
+ };
12969
14268
  const assertCurrent = () => {
12970
14269
  if (ended || Date.now() >= deadlineAt) throw pending2();
12971
14270
  input.assertCurrent?.();
@@ -12990,20 +14289,52 @@ async function executeTokenBatch(input, initial) {
12990
14289
  }
12991
14290
  return operationId ? { ...value, operationId } : value;
12992
14291
  };
12993
- const post2 = () => bounded2(
12994
- postSponsorBatchTransfer({
12995
- apiKey: input.apiKey,
12996
- baseUrl: input.baseUrl,
12997
- body: withOperationId(body),
12998
- signal: controller.signal
12999
- })
13000
- );
14292
+ const withServerBroadcast = (value) => {
14293
+ const request2 = withOperationId(value);
14294
+ return request2.operationId ? { ...request2, serverBroadcast: true } : request2;
14295
+ };
14296
+ const post2 = async () => {
14297
+ const request2 = withServerBroadcast(body);
14298
+ const flaggedPrepare = request2.serverBroadcast === true && !request2.serializedTransaction && !(request2.erc2612Permit && !request2.serializedPermitTransaction);
14299
+ let result;
14300
+ try {
14301
+ result = await Promise.race([
14302
+ postSponsorBatchTransfer({
14303
+ apiKey: input.apiKey,
14304
+ baseUrl: input.baseUrl,
14305
+ body: request2,
14306
+ signal: controller.signal
14307
+ }),
14308
+ timeout
14309
+ ]);
14310
+ } catch (error) {
14311
+ const details = error instanceof OwneyError ? error.details : void 0;
14312
+ const sending = details?.code === "DEPOSIT_ALREADY_SENDING";
14313
+ if (flaggedPrepare && (ended || !(error instanceof OwneyError) || details?.outcomeUnknown === true || sending)) {
14314
+ maybeAccepted = true;
14315
+ alreadySending = sending;
14316
+ }
14317
+ throw error;
14318
+ }
14319
+ maybeAccepted = false;
14320
+ alreadySending = false;
14321
+ if (result.serverBroadcast === true && result.serializedTransaction && !body.serializedTransaction) {
14322
+ serverSent.add(result.txHash);
14323
+ accepted = result.txHash;
14324
+ try {
14325
+ input.onSubmitted?.(result.txHash, reference(result.txHash), true);
14326
+ } catch {
14327
+ }
14328
+ }
14329
+ assertCurrent();
14330
+ return result;
14331
+ };
13001
14332
  const replacements = { approval: 0, deposit: 0 };
13002
14333
  const unavailable = /* @__PURE__ */ new Set();
13003
14334
  const observedApprovals = /* @__PURE__ */ new Set();
13004
14335
  const checkpointed = /* @__PURE__ */ new Set();
13005
14336
  const accounted = /* @__PURE__ */ new Set();
13006
- const remember = async (phase, bytes, hash2, predecessorHash) => {
14337
+ const remember = async (phase, bytes, hash2, predecessorHash, submissionAttempted = false) => {
13007
14338
  assertCurrent();
13008
14339
  if ((0, import_viem22.keccak256)(bytes) !== hash2) throw pending2();
13009
14340
  const tx = (0, import_viem22.parseTransaction)(bytes);
@@ -13022,7 +14353,7 @@ async function executeTokenBatch(input, initial) {
13022
14353
  nonce: String(tx.nonce),
13023
14354
  predecessorHash,
13024
14355
  preparedAt: Date.now(),
13025
- submissionAttempted: false
14356
+ submissionAttempted
13026
14357
  },
13027
14358
  assertCurrent
13028
14359
  )
@@ -13036,13 +14367,18 @@ async function executeTokenBatch(input, initial) {
13036
14367
  const markSubmission = async (phase) => {
13037
14368
  const hash2 = phase === "deposit" ? family?.current.deposit : family?.current.approval;
13038
14369
  if (!family || !hash2) throw pending2();
14370
+ const flag = serverSent.has(hash2) ? [true] : [];
13039
14371
  if (phase === "deposit" && !checkpointed.has(hash2)) {
13040
14372
  try {
13041
14373
  await bounded2(
13042
- Promise.resolve(input.onBeforeSubmission?.(hash2, reference(hash2)))
14374
+ Promise.resolve(
14375
+ input.onBeforeSubmission?.(hash2, reference(hash2), ...flag)
14376
+ )
13043
14377
  );
14378
+ checkpointed.add(hash2);
13044
14379
  } catch (error) {
13045
- if (error instanceof OwneyError && error.code === "DEPOSIT_CHECKPOINT_FAILED")
14380
+ if (serverSent.has(hash2)) {
14381
+ } else if (error instanceof OwneyError && error.code === "DEPOSIT_CHECKPOINT_FAILED")
13046
14382
  throw new OwneyError(error.code, error.message, {
13047
14383
  ...error.details,
13048
14384
  notSubmitted: !family.attempts.some(
@@ -13050,9 +14386,8 @@ async function executeTokenBatch(input, initial) {
13050
14386
  ),
13051
14387
  safeToFallback: false
13052
14388
  });
13053
- throw error;
14389
+ else throw error;
13054
14390
  }
13055
- checkpointed.add(hash2);
13056
14391
  }
13057
14392
  assertCurrent();
13058
14393
  const attempt = family.attempts.find((a) => a.hash === hash2);
@@ -13067,7 +14402,7 @@ async function executeTokenBatch(input, initial) {
13067
14402
  assertCurrent();
13068
14403
  if (phase === "deposit")
13069
14404
  try {
13070
- input.onSubmitted?.(hash2, reference(hash2));
14405
+ input.onSubmitted?.(hash2, reference(hash2), ...flag);
13071
14406
  } catch {
13072
14407
  }
13073
14408
  assertCurrent();
@@ -13103,12 +14438,15 @@ async function executeTokenBatch(input, initial) {
13103
14438
  }
13104
14439
  return observation.txHash;
13105
14440
  }
13106
- if (observation.status === "failed" || observation.status === "expired")
13107
- throw new OwneyError(
14441
+ if (observation.status === "failed" || observation.status === "expired") {
14442
+ const failure = new OwneyError(
13108
14443
  "SPONSOR_REQUEST_FAILED",
13109
14444
  observation.status === "expired" ? "The deposit authorization expired without transferring funds. Start a new deposit." : "Sponsored deposit transactions reverted. Please retry.",
13110
14445
  { notSubmitted: true, safeToFallback: false }
13111
14446
  );
14447
+ verifiedFailures.add(failure);
14448
+ throw failure;
14449
+ }
13112
14450
  };
13113
14451
  try {
13114
14452
  assertCurrent();
@@ -13124,6 +14462,14 @@ async function executeTokenBatch(input, initial) {
13124
14462
  tokenBatchKey(input),
13125
14463
  JSON.stringify(tokenBatchStoredBody(body))
13126
14464
  );
14465
+ if (/^\d{1,78}$/.test(body.nonce) && /^\d{1,15}$/.test(body.deadline))
14466
+ try {
14467
+ input.onAuthorization?.({
14468
+ permit2Nonce: body.nonce,
14469
+ authorizationDeadline: Number(body.deadline)
14470
+ });
14471
+ } catch {
14472
+ }
13127
14473
  if (family) {
13128
14474
  const winner = await observe();
13129
14475
  if (winner) return winner;
@@ -13147,6 +14493,20 @@ async function executeTokenBatch(input, initial) {
13147
14493
  result = await post2();
13148
14494
  } catch (error) {
13149
14495
  assertCurrent();
14496
+ if (maybeAccepted && alreadySending) {
14497
+ if (family)
14498
+ finishTokenBatchFamily(input, family, {
14499
+ kind: "failed",
14500
+ blockNumber: "0"
14501
+ });
14502
+ else
14503
+ tokenBatchClear(
14504
+ tokenBatchKey(input),
14505
+ JSON.stringify(tokenBatchStoredBody(body))
14506
+ );
14507
+ throw pending2();
14508
+ }
14509
+ if (maybeAccepted && !family) throw pending2();
13150
14510
  if (!family) {
13151
14511
  tokenBatchClear(
13152
14512
  tokenBatchKey(input),
@@ -13169,7 +14529,9 @@ async function executeTokenBatch(input, initial) {
13169
14529
  await remember(
13170
14530
  "deposit",
13171
14531
  result.serializedTransaction,
13172
- result.txHash
14532
+ result.txHash,
14533
+ void 0,
14534
+ serverSent.has(result.txHash)
13173
14535
  );
13174
14536
  needsPost = true;
13175
14537
  continue;
@@ -13245,6 +14607,10 @@ async function executeTokenBatch(input, initial) {
13245
14607
  )
13246
14608
  );
13247
14609
  }
14610
+ } catch (error) {
14611
+ if ((serverSent.size || maybeAccepted) && !(error && typeof error === "object" && verifiedFailures.has(error)))
14612
+ throw pending2();
14613
+ throw error;
13248
14614
  } finally {
13249
14615
  ended = true;
13250
14616
  if (timer) clearTimeout(timer);
@@ -13271,14 +14637,14 @@ async function releaseExpiredTokenBatch(i) {
13271
14637
  let timer;
13272
14638
  let expired = false;
13273
14639
  const check = async () => {
13274
- let active2 = read(key2);
13275
- if (active2 && i.expectedTxHash) {
13276
- const current = JSON.parse(active2);
14640
+ let active = read(key2);
14641
+ if (active && i.expectedTxHash) {
14642
+ const current = JSON.parse(active);
13277
14643
  if (!current.serializedTransaction || (0, import_viem23.keccak256)(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
13278
- active2 = void 0;
14644
+ active = void 0;
13279
14645
  }
13280
14646
  const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
13281
- const saved = active2 ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
14647
+ const saved = active ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
13282
14648
  if (!saved) return;
13283
14649
  const body = JSON.parse(saved);
13284
14650
  if (!body.serializedTransaction || body.chainId !== i.chainId || !(0, import_viem23.isAddressEqual)(body.from, i.owner) || !(0, import_viem23.isAddressEqual)(body.token, i.token) || !body.transfers?.length) return;
@@ -13302,8 +14668,8 @@ async function releaseExpiredTokenBatch(i) {
13302
14668
  args: [i.owner, nonce >> 8n],
13303
14669
  blockNumber: block.number
13304
14670
  });
13305
- if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active2 && read(key2) !== active2) return;
13306
- if (active2) {
14671
+ if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active && read(key2) !== active) return;
14672
+ if (active) {
13307
14673
  const terminalKey = `${key2}:expired:${txHash.toLowerCase()}`;
13308
14674
  expiredRecords.set(terminalKey, saved);
13309
14675
  let persisted = typeof window === "undefined";
@@ -13340,15 +14706,15 @@ function clear(key2, body) {
13340
14706
  function sponsorTokenBatch(i) {
13341
14707
  const key2 = pendingKey(i);
13342
14708
  const plan = planOf(i.transfers);
13343
- const active2 = inflight.get(key2);
13344
- if (active2) {
13345
- if (active2.plan !== plan)
14709
+ const active = inflight.get(key2);
14710
+ if (active) {
14711
+ if (active.plan !== plan)
13346
14712
  return Promise.reject(
13347
14713
  new Error(
13348
14714
  "A token deposit is already in progress. Wait for its result before depositing again."
13349
14715
  )
13350
14716
  );
13351
- return active2.promise;
14717
+ return active.promise;
13352
14718
  }
13353
14719
  const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
13354
14720
  inflight.set(key2, { plan, promise });
@@ -13360,8 +14726,15 @@ async function execute(i, key2, plan) {
13360
14726
  ) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
13361
14727
  throw new Error("Invalid token deposit shares.");
13362
14728
  const send = (body) => executeTokenBatch(i, body);
14729
+ if (!i.resumeTxHash) await dropExpiredHashlessTokenBatch(i, i.pub);
13363
14730
  const family = await loadTokenBatchFamily(i, i.resumeTxHash);
13364
14731
  const saved = family ? JSON.stringify(family.body) : read(key2);
14732
+ if (!saved && i.resumeSaved)
14733
+ throw new OwneyError(
14734
+ "DEPOSIT_IN_PROGRESS",
14735
+ "The saved deposit is no longer available. Check its status before depositing again.",
14736
+ { notSubmitted: false, safeToFallback: false }
14737
+ );
13365
14738
  if (saved) {
13366
14739
  const previous = JSON.parse(saved);
13367
14740
  if (previous.chainId !== i.chainId || !(0, import_viem23.isAddressEqual)(previous.from, i.owner) || !(0, import_viem23.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
@@ -13471,10 +14844,13 @@ function makeSponsoredTokenCallback(deps) {
13471
14844
  pub,
13472
14845
  wallet,
13473
14846
  onApproved: deps.onApproved,
13474
- onSubmitted: (hash2, reference) => deps.onSubmitted?.(hash2, transfers, reference),
13475
- onBeforeSubmission: (hash2, reference) => deps.onBeforeSubmission?.(hash2, transfers, reference),
14847
+ // Rest arguments keep the callbacks' arity: the flag appears only when set.
14848
+ onSubmitted: (hash2, ...rest) => deps.onSubmitted?.(hash2, transfers, ...rest),
14849
+ onBeforeSubmission: (hash2, ...rest) => deps.onBeforeSubmission?.(hash2, transfers, ...rest),
13476
14850
  assertCurrent: deps.assertCurrent,
13477
14851
  resumeTxHash: deps.resumeTxHash,
14852
+ ...deps.resumeSaved ? { resumeSaved: true } : {},
14853
+ ...deps.onAuthorization ? { onAuthorization: deps.onAuthorization } : {},
13478
14854
  operationId: deps.operationId
13479
14855
  });
13480
14856
  };
@@ -13783,6 +15159,33 @@ function sponsoredTokensFor(asset) {
13783
15159
  function shouldFallbackToUserPaid(error, asset, appCallback) {
13784
15160
  return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
13785
15161
  }
15162
+ function serverBroadcastOf(error) {
15163
+ const flag = (value) => {
15164
+ const details = value?.details;
15165
+ return details?.serverBroadcast === true || details?.serverBroadcast === "unknown" ? details.serverBroadcast : void 0;
15166
+ };
15167
+ return flag(error) ?? flag(error?.details?.cause);
15168
+ }
15169
+ function authorizationDeadlineOf(error) {
15170
+ const read2 = (value) => {
15171
+ const deadline = value?.details?.authorizationDeadline;
15172
+ return typeof deadline === "number" && Number.isSafeInteger(deadline) && deadline > 0 ? deadline : void 0;
15173
+ };
15174
+ return read2(error) ?? read2(error?.details?.cause);
15175
+ }
15176
+ function alreadySendingOf(error) {
15177
+ const code = (value) => value?.details?.code;
15178
+ return code(error) === "DEPOSIT_ALREADY_SENDING" || code(error?.details?.cause) === "DEPOSIT_ALREADY_SENDING";
15179
+ }
15180
+ function permit2NonceOf(error) {
15181
+ const read2 = (value) => {
15182
+ const nonce = value?.details?.permit2Nonce;
15183
+ return typeof nonce === "string" && /^\d{1,78}$/.test(nonce) ? nonce : void 0;
15184
+ };
15185
+ return read2(error) ?? read2(error?.details?.cause);
15186
+ }
15187
+ var serverMayOwn = (leg) => leg !== void 0 && leg.settled !== null;
15188
+ var SERVER_HANDOFF_WAIT_MS = 1e4;
13786
15189
  var OwneySDK = class {
13787
15190
  agents = /* @__PURE__ */ new Map();
13788
15191
  activeAgents = /* @__PURE__ */ new Set();
@@ -13811,13 +15214,21 @@ var OwneySDK = class {
13811
15214
  ledger;
13812
15215
  reservationMode;
13813
15216
  reservationTransport;
15217
+ /** The caller's Zyfai server tasks opt-in: `true`, a function read each
15218
+ * time a handoff would start (F-I2(iii)), or undefined (off). */
15219
+ zyfaiServerTasks;
15220
+ /** The caller opted in to the Fusion+ secret handoff (`fusionSecretHandoff: true`). */
15221
+ fusionSecretHandoff;
13814
15222
  cachedSponsoredCallback = null;
13815
15223
  cachedWethSponsoredCallback = null;
13816
15224
  paymasterServiceUrl;
13817
15225
  cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
13818
15226
  agentReads = new AgentReads();
13819
15227
  depositFamilyReads = /* @__PURE__ */ new Map();
13820
- recoveredFundingReads = /* @__PURE__ */ new Set();
15228
+ /** Movements (owner, chain, agent, token, tx hash) whose balance refresh this
15229
+ * session started: true when it armed the refresh, false when its deadline
15230
+ * had passed (it only dropped the cache and forced one snapshot read). */
15231
+ recoveredFundingReads = /* @__PURE__ */ new Map();
13821
15232
  fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
13822
15233
  activationChecks = /* @__PURE__ */ new Map();
13823
15234
  // Protect the SDK entry point as well as the mini-app hook. This owns only
@@ -13855,6 +15266,8 @@ var OwneySDK = class {
13855
15266
  });
13856
15267
  this.reservationMode = config.withdrawalReservation === "fail-closed" || config.withdrawalReservation === "off" ? config.withdrawalReservation : "fail-open";
13857
15268
  this.reservationTransport = { apiKey: config.apiKey, baseUrl: config.routingApiBaseUrl, appName: config.appName };
15269
+ this.zyfaiServerTasks = config.zyfaiServerTasks === true || typeof config.zyfaiServerTasks === "function" ? config.zyfaiServerTasks : void 0;
15270
+ this.fusionSecretHandoff = config.fusionSecretHandoff === true;
13858
15271
  }
13859
15272
  /** Finishes a funding-ledger operation on success and adds its reference to
13860
15273
  * the result. The result object is copied, never mutated or replaced.
@@ -13920,16 +15333,34 @@ var OwneySDK = class {
13920
15333
  return void 0;
13921
15334
  }
13922
15335
  }
15336
+ /**
15337
+ * Part 5: the swap's ledger identity, for the executor's Fusion+ secret
15338
+ * handoff. Only with the `fusionSecretHandoff` opt-in and an open operation;
15339
+ * otherwise undefined, and the executor sends nothing. Owner and token are
15340
+ * lowercased, as the ledger reports them.
15341
+ */
15342
+ secretHandoffFor(op, input) {
15343
+ if (!this.fusionSecretHandoff || !op || !input || input.kind !== "swap_deposit" && input.kind !== "withdraw_swap")
15344
+ return void 0;
15345
+ return { operation: {
15346
+ clientOperationId: op.operationId,
15347
+ kind: input.kind,
15348
+ owner: input.owner.toLowerCase(),
15349
+ chainId: input.chainId,
15350
+ token: input.token.toLowerCase()
15351
+ } };
15352
+ }
13923
15353
  closeSwapLedger(op) {
13924
15354
  if (op && this.openSwapLedgers.get(op.operationId) === op) this.openSwapLedgers.delete(op.operationId);
13925
15355
  }
13926
- /** Reports one agent's withdrawal evidence as its leg. Never throws. */
13927
- ledgerWithdrawalLeg(op, evidence) {
15356
+ /** Reports one agent's withdrawal evidence as its leg (with `errorCode`, when
15357
+ * given). Never throws. */
15358
+ ledgerWithdrawalLeg(op, evidence, errorCode) {
13928
15359
  if (!op) return;
13929
15360
  try {
13930
15361
  const leg = ledgerLegFromEvidence(evidence, "withdraw");
13931
15362
  const rail = withdrawalLedgerRail(evidence);
13932
- if (leg) op.leg(rail ? { ...leg, rail } : leg);
15363
+ if (leg) op.leg({ ...rail ? { ...leg, rail } : leg, ...errorCode ? { errorCode } : {} });
13933
15364
  if (leg?.status === "confirmed") this.ledgerProviderSamples(op, evidence, "withdraw");
13934
15365
  } catch {
13935
15366
  }
@@ -14164,8 +15595,8 @@ var OwneySDK = class {
14164
15595
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
14165
15596
  }
14166
15597
  /** Builds the default USDC batch callback for the connected wallet. */
14167
- getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId) {
14168
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && this.cachedSponsoredCallback)
15598
+ getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId, resumeSaved, onAuthorization) {
15599
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization && this.cachedSponsoredCallback)
14169
15600
  return this.cachedSponsoredCallback;
14170
15601
  const provider = this.requireConnectedProvider();
14171
15602
  const connection = this.requireState();
@@ -14179,6 +15610,8 @@ var OwneySDK = class {
14179
15610
  onSubmitted,
14180
15611
  onBeforeSubmission,
14181
15612
  resumeTxHash,
15613
+ ...resumeSaved ? { resumeSaved: true } : {},
15614
+ ...onAuthorization ? { onAuthorization } : {},
14182
15615
  operationId,
14183
15616
  assertCurrent: () => {
14184
15617
  assertSubmissionCurrent?.();
@@ -14199,7 +15632,7 @@ var OwneySDK = class {
14199
15632
  transport: (0, import_viem25.custom)(provider)
14200
15633
  })
14201
15634
  });
14202
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId) this.cachedSponsoredCallback = callback;
15635
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization) this.cachedSponsoredCallback = callback;
14203
15636
  return callback;
14204
15637
  }
14205
15638
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
@@ -14228,8 +15661,8 @@ var OwneySDK = class {
14228
15661
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
14229
15662
  * single-use batch authorization instead of an EIP-3009 authorization.
14230
15663
  */
14231
- getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId) {
14232
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && this.cachedWethSponsoredCallback)
15664
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId, resumeSaved, onAuthorization) {
15665
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization && this.cachedWethSponsoredCallback)
14233
15666
  return this.cachedWethSponsoredCallback;
14234
15667
  const provider = this.requireConnectedProvider();
14235
15668
  const connection = this.requireState();
@@ -14243,6 +15676,8 @@ var OwneySDK = class {
14243
15676
  onSubmitted,
14244
15677
  onBeforeSubmission,
14245
15678
  resumeTxHash,
15679
+ ...resumeSaved ? { resumeSaved: true } : {},
15680
+ ...onAuthorization ? { onAuthorization } : {},
14246
15681
  operationId,
14247
15682
  assertCurrent: () => {
14248
15683
  assertSubmissionCurrent?.();
@@ -14263,7 +15698,7 @@ var OwneySDK = class {
14263
15698
  transport: (0, import_viem25.custom)(provider)
14264
15699
  })
14265
15700
  });
14266
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId) this.cachedWethSponsoredCallback = callback;
15701
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization) this.cachedWethSponsoredCallback = callback;
14267
15702
  return callback;
14268
15703
  }
14269
15704
  getAgent(agentId) {
@@ -14277,12 +15712,12 @@ var OwneySDK = class {
14277
15712
  return [...this.getActiveAgents().keys()].sort();
14278
15713
  }
14279
15714
  getActiveAgents() {
14280
- const active2 = /* @__PURE__ */ new Map();
15715
+ const active = /* @__PURE__ */ new Map();
14281
15716
  for (const id of this.activeAgents) {
14282
15717
  const agent = this.agents.get(id);
14283
- if (agent) active2.set(id, agent);
15718
+ if (agent) active.set(id, agent);
14284
15719
  }
14285
- return active2;
15720
+ return active;
14286
15721
  }
14287
15722
  /**
14288
15723
  * Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
@@ -14381,7 +15816,13 @@ var OwneySDK = class {
14381
15816
  this.routingApiBaseUrl
14382
15817
  ) : this.rpcUrls,
14383
15818
  this.referralSource,
14384
- { owneyApiKey: this.apiKey, routingApiBaseUrl: this.routingApiBaseUrl }
15819
+ {
15820
+ owneyApiKey: this.apiKey,
15821
+ routingApiBaseUrl: this.routingApiBaseUrl,
15822
+ // Only an explicit opt-in lets the agent hand the Zyfai session to
15823
+ // Owney; a function is passed as it is and read per handoff.
15824
+ ...this.zyfaiServerTasks !== void 0 ? { zyfaiServerTasks: this.zyfaiServerTasks } : {}
15825
+ }
14385
15826
  );
14386
15827
  }
14387
15828
  if (agentId === "yieldseeker") {
@@ -14603,10 +16044,21 @@ var OwneySDK = class {
14603
16044
  } catch {
14604
16045
  }
14605
16046
  };
14606
- const onSubmitted = (hash2, destinations, depositAuthorization) => {
16047
+ let permit;
16048
+ const onAuthorization = (value) => {
16049
+ permit = value;
16050
+ };
16051
+ const permitFields = () => permit ? { permit2Nonce: permit.permit2Nonce, authorizationDeadline: permit.authorizationDeadline } : {};
16052
+ const onSubmitted = (hash2, destinations, depositAuthorization, serverBroadcast) => {
14607
16053
  for (const destination of destinations) {
14608
16054
  const evidence = prepared.get(destination.to.toLowerCase());
14609
- if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash2, ...depositAuthorization ? { depositAuthorization } : {}, transfer: "pending" });
16055
+ if (evidence) notifyFunding(onProgress, {
16056
+ ...evidence,
16057
+ txHash: hash2,
16058
+ ...depositAuthorization ? { depositAuthorization } : {},
16059
+ ...serverBroadcast ? { serverBroadcast, ...permitFields() } : {},
16060
+ transfer: "pending"
16061
+ });
14610
16062
  }
14611
16063
  };
14612
16064
  const onCallsProgress = (callsId, destinations, transfer) => {
@@ -14615,6 +16067,7 @@ var OwneySDK = class {
14615
16067
  if (evidence) notifyFunding(onProgress, { ...evidence, callsId, transfer });
14616
16068
  }
14617
16069
  };
16070
+ const serverTaskContext = (agent) => this.zyfaiServerTasks !== void 0 && agent.supportsServerTasks === true && ledgerOp ? { operationId: ledgerOp.operationId } : void 0;
14618
16071
  const ledgerDone = (result) => this.ledgerSucceeded(ledgerOp, result);
14619
16072
  const ledgerFailed = (error) => this.ledgerFailed(ledgerOp, error);
14620
16073
  if (BigInt(amount) === 0n) {
@@ -14650,7 +16103,7 @@ var OwneySDK = class {
14650
16103
  });
14651
16104
  await checkpoint2(evidence);
14652
16105
  } : void 0;
14653
- const checkpointHash = checkpointDestinations ? (hash2, destinations, depositAuthorization) => checkpointDestinations(destinations, { txHash: hash2, depositAuthorization }) : void 0;
16106
+ const checkpointHash = checkpointDestinations ? (hash2, destinations, depositAuthorization, serverBroadcast) => checkpointDestinations(destinations, { txHash: hash2, depositAuthorization, ...serverBroadcast ? { serverBroadcast, ...permitFields() } : {} }) : void 0;
14654
16107
  const checkpointCalls = checkpointDestinations ? (id, destinations) => checkpointDestinations(destinations, { callsId: id }) : void 0;
14655
16108
  const checkpointPaid = checkpoint2 ? (evidence) => checkpoint2([evidence]) : void 0;
14656
16109
  const customCallback = depositCallback && checkpointDestinations ? makeVerificationAwareDepositCallback(async (to, cid, value, verification) => {
@@ -14658,9 +16111,10 @@ var OwneySDK = class {
14658
16111
  await checkpointDestinations([{ to, amount: value }], {});
14659
16112
  return depositCallback(to, cid, value);
14660
16113
  }) : depositCallback;
14661
- if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
16114
+ if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)) || options.resumeSavedBatch && (options.resumeTxHash || depositCallback || this.paymasterServiceUrl))
14662
16115
  throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
14663
- const effectiveCallback = customCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress, checkpointCalls) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash, options.assertSubmissionCurrent, ledgerOperationId) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash, options.assertSubmissionCurrent, ledgerOperationId));
16116
+ const effectiveCallback = customCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress, checkpointCalls) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash, options.assertSubmissionCurrent, ledgerOperationId, options.resumeSavedBatch, onAuthorization) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash, options.assertSubmissionCurrent, ledgerOperationId, options.resumeSavedBatch, onAuthorization));
16117
+ let resumedDeadline;
14664
16118
  try {
14665
16119
  const ledgerToken = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
14666
16120
  ledgerOp = this.startLedger({
@@ -14675,16 +16129,19 @@ var OwneySDK = class {
14675
16129
  } catch {
14676
16130
  }
14677
16131
  try {
14678
- if (options.resumeTxHash) {
16132
+ if (options.resumeTxHash || options.resumeSavedBatch) {
14679
16133
  const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
14680
- const family = token && await loadTokenBatchFamily({
16134
+ const scope = token && {
14681
16135
  apiKey: this.apiKey,
14682
16136
  baseUrl: this.routingApiBaseUrl,
14683
16137
  owner: state.walletAddress,
14684
16138
  chainId,
14685
16139
  token: token.address
14686
- }, options.resumeTxHash);
14687
- const pending2 = family && { transfers: family.body.transfers };
16140
+ };
16141
+ const family = scope && (options.resumeTxHash ? await loadTokenBatchFamily(scope, options.resumeTxHash) : { body: await savedTokenBatchBody(scope, this.getPaidRpcClient(chainId)) });
16142
+ if (options.resumeSavedBatch && family?.body && /^\d{1,15}$/.test(family.body.deadline))
16143
+ resumedDeadline = Number(family.body.deadline);
16144
+ const pending2 = family?.body && { transfers: family.body.transfers };
14688
16145
  const invalid2 = () => new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit does not match this request. Check its status before depositing again.");
14689
16146
  if (!pending2 || pending2.transfers.some((transfer) => !/^\d+$/.test(transfer.amount) || BigInt(transfer.amount) <= 0n) || pending2.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n) !== BigInt(amount)) throw invalid2();
14690
16147
  const candidates = agentId ? [this.getAgent(agentId)] : this.getEligibleAgents(chainId, asset);
@@ -14704,12 +16161,10 @@ var OwneySDK = class {
14704
16161
  return {
14705
16162
  id: agent.id,
14706
16163
  amount: transfer.amount,
14707
- run: (callback) => withFailureReporting(
14708
- this.apiKey,
14709
- agent.id,
14710
- () => agent.deposit(state, chainId, transfer.amount, asset, callback, onProgress),
14711
- this.routingApiBaseUrl
14712
- )
16164
+ run: (callback) => withFailureReporting(this.apiKey, agent.id, () => {
16165
+ const context = serverTaskContext(agent);
16166
+ return context ? agent.deposit(state, chainId, transfer.amount, asset, callback, onProgress, void 0, context) : agent.deposit(state, chainId, transfer.amount, asset, callback, onProgress);
16167
+ }, this.routingApiBaseUrl)
14713
16168
  };
14714
16169
  });
14715
16170
  const batchTransfer2 = getDepositBatchTransfer(effectiveCallback);
@@ -14748,7 +16203,8 @@ var OwneySDK = class {
14748
16203
  onProgress,
14749
16204
  () => paidFallbackAgents.add(agent.id),
14750
16205
  !!options.resumeTxHash,
14751
- checkpointPaid
16206
+ checkpointPaid,
16207
+ serverTaskContext(agent)
14752
16208
  ));
14753
16209
  }
14754
16210
  const totalAmount = BigInt(amount);
@@ -14810,7 +16266,10 @@ var OwneySDK = class {
14810
16266
  run: (callback) => withFailureReporting(
14811
16267
  this.apiKey,
14812
16268
  agent.id,
14813
- () => agent.deposit(state, chainId, amount2, asset, callback, onProgress),
16269
+ () => {
16270
+ const context = serverTaskContext(agent);
16271
+ return context ? agent.deposit(state, chainId, amount2, asset, callback, onProgress, void 0, context) : agent.deposit(state, chainId, amount2, asset, callback, onProgress);
16272
+ },
14814
16273
  this.routingApiBaseUrl
14815
16274
  )
14816
16275
  })),
@@ -14851,7 +16310,8 @@ var OwneySDK = class {
14851
16310
  onProgress,
14852
16311
  () => paidFallbackAgents.add(agent.id),
14853
16312
  !!options.resumeTxHash,
14854
- checkpointPaid
16313
+ checkpointPaid,
16314
+ serverTaskContext(agent)
14855
16315
  );
14856
16316
  } catch (error) {
14857
16317
  if (Object.keys(agentResults).length === 0) throw error;
@@ -14871,7 +16331,22 @@ var OwneySDK = class {
14871
16331
  }
14872
16332
  return ledgerDone({ agentResults });
14873
16333
  } catch (error) {
14874
- if (checkpointFailure && !(error instanceof OwneyError && error.details?.notSubmitted === false)) {
16334
+ if (options.resumeSavedBatch && !serverBroadcastOf(error))
16335
+ error = new OwneyError(
16336
+ "DEPOSIT_IN_PROGRESS",
16337
+ "Deposit is awaiting confirmation. Its transaction is still being checked.",
16338
+ {
16339
+ sponsorPending: true,
16340
+ phase: "deposit",
16341
+ notSubmitted: false,
16342
+ safeToFallback: false,
16343
+ serverBroadcast: "unknown",
16344
+ ...resumedDeadline !== void 0 ? { authorizationDeadline: resumedDeadline } : {},
16345
+ cause: error
16346
+ }
16347
+ );
16348
+ const serverMaySend = serverBroadcastOf(error);
16349
+ if (checkpointFailure && !serverMaySend && !(error instanceof OwneyError && error.details?.notSubmitted === false)) {
14875
16350
  error = error instanceof OwneyError && error.code === "DEPOSIT_PARTIAL_FAILURE" ? new OwneyError(
14876
16351
  error.code,
14877
16352
  "Some deposits completed before another agent could be submitted.",
@@ -14889,6 +16364,19 @@ var OwneySDK = class {
14889
16364
  const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
14890
16365
  if (!rejected && paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
14891
16366
  if (!rejected && (evidence?.txHash || evidence?.userOpHash || evidence?.callsId)) continue;
16367
+ if (serverMaySend) {
16368
+ const refused = alreadySendingOf(error);
16369
+ const deadline = refused ? void 0 : authorizationDeadlineOf(error) ?? permit?.authorizationDeadline;
16370
+ const nonce = refused ? void 0 : permit2NonceOf(error) ?? permit?.permit2Nonce;
16371
+ if (evidence) notifyFunding(onProgress, {
16372
+ ...evidence,
16373
+ serverBroadcast: evidence.serverBroadcast ?? "unknown",
16374
+ ...deadline !== void 0 && !evidence.authorizationDeadline ? { authorizationDeadline: deadline } : {},
16375
+ ...nonce !== void 0 && !evidence.permit2Nonce ? { permit2Nonce: nonce } : {},
16376
+ transfer: "pending"
16377
+ });
16378
+ continue;
16379
+ }
14892
16380
  notifyFunding(onProgress, { ...evidence ?? {
14893
16381
  agentId: id,
14894
16382
  chainId,
@@ -14935,11 +16423,11 @@ var OwneySDK = class {
14935
16423
  * propagates unchanged. Scoped per agent invocation so one agent's failure
14936
16424
  * doesn't force a retry of sibling agents in a multi-agent split.
14937
16425
  */
14938
- async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false, onBeforeSubmission) {
16426
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false, onBeforeSubmission, serverTaskContext) {
14939
16427
  const attempt = (callback) => withFailureReporting(
14940
16428
  this.apiKey,
14941
16429
  agent.id,
14942
- () => onBeforeSubmission ? agent.deposit(state, chainId, amount, asset, callback, onProgress, onBeforeSubmission) : onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
16430
+ () => serverTaskContext ? agent.deposit(state, chainId, amount, asset, callback, onProgress, onBeforeSubmission, serverTaskContext) : onBeforeSubmission ? agent.deposit(state, chainId, amount, asset, callback, onProgress, onBeforeSubmission) : onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
14943
16431
  this.routingApiBaseUrl
14944
16432
  );
14945
16433
  let approvalAttempted = false;
@@ -15236,11 +16724,36 @@ var OwneySDK = class {
15236
16724
  const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
15237
16725
  if (options.refreshBalance !== false && (verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
15238
16726
  agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil, evidence.txHash);
15239
- this.recoveredFundingReads.add(key2);
15240
- if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.values().next().value);
16727
+ const until = options.balanceRefreshUntil;
16728
+ this.noteFundingRefresh(key2, until === void 0 || until > Date.now());
15241
16729
  }
15242
16730
  return verified;
15243
16731
  }
16732
+ noteFundingRefresh(key2, armed) {
16733
+ this.recoveredFundingReads.delete(key2);
16734
+ this.recoveredFundingReads.set(key2, armed);
16735
+ if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.keys().next().value);
16736
+ }
16737
+ /**
16738
+ * Re-arms an agent's balance refresh after a confirmed deposit or withdrawal
16739
+ * the caller already verified (e.g. after a page reload, which loses the
16740
+ * refresh the SDK started in memory). Same refresh as receipt recovery
16741
+ * (`inspectFunding`), without a receipt read, and shared with it: once per
16742
+ * movement (`txHash`) in this SDK session, unless that recovery came after
16743
+ * its own deadline and so armed nothing. `until` bounds it (the agent caps
16744
+ * it further). Returns false when the agent has no such refresh or `until`
16745
+ * has passed; throws when not connected or on another chain. Never prompts.
16746
+ */
16747
+ refreshAgentBalanceAfterMovement(input) {
16748
+ const { state, chainId } = this.captureReadScope(input.chainId);
16749
+ const agent = this.getAgent(input.agentId);
16750
+ if (typeof agent.invalidateFundingRead !== "function" || !Number.isFinite(input.until) || input.until <= Date.now()) return false;
16751
+ const key2 = input.txHash ? `${state.walletAddress.toLowerCase()}:${chainId}:${input.agentId}:${input.token}:${input.txHash}` : void 0;
16752
+ if (key2 && this.recoveredFundingReads.get(key2) === true) return true;
16753
+ agent.invalidateFundingRead(state, chainId, input.token, input.until, input.txHash);
16754
+ if (key2) this.noteFundingRefresh(key2, true);
16755
+ return true;
16756
+ }
15244
16757
  /**
15245
16758
  * Lists on-chain payout candidates for a withdrawal the provider accepted
15246
16759
  * without a hash: ERC-20 `Transfer` logs on the evidence's token that reach
@@ -15362,22 +16875,25 @@ var OwneySDK = class {
15362
16875
  try {
15363
16876
  if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
15364
16877
  const first = evidences[0];
15365
- if (!first.txHash) return false;
16878
+ const saved = !first.txHash && evidences.every((e) => !e.txHash && (e.serverBroadcast === "unknown" || e.serverBroadcast === true) && typeof e.authorizationDeadline === "number");
16879
+ if (!first.txHash && !saved) return false;
15366
16880
  const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
15367
16881
  const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
15368
16882
  if (!token) return false;
15369
- const family = await loadTokenBatchFamily({
16883
+ const scope = {
15370
16884
  apiKey: this.apiKey,
15371
16885
  baseUrl: this.routingApiBaseUrl,
15372
16886
  owner: state.walletAddress,
15373
16887
  chainId,
15374
16888
  token: token.address
15375
- }, first.txHash);
15376
- const transfers = family?.body.transfers;
15377
- if (!family || !transfers || transfers.length !== evidences.length || transfers.reduce((n, t) => n + BigInt(t.amount), 0n).toString() !== amount) return false;
16889
+ };
16890
+ const family = saved ? void 0 : await loadTokenBatchFamily(scope, first.txHash);
16891
+ const savedBody = saved ? await savedTokenBatchBody(scope, this.getPaidRpcClient(chainId)) : void 0;
16892
+ const transfers = saved ? savedBody?.transfers : family?.body.transfers;
16893
+ if (!family && !savedBody || !transfers || transfers.length !== evidences.length || transfers.reduce((n, t) => n + BigInt(t.amount), 0n).toString() !== amount) return false;
15378
16894
  const destinations = /* @__PURE__ */ new Set();
15379
16895
  for (const e of evidences) {
15380
- if (e.chainId !== chainId || e.token !== first.token || e.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || e.callsId || e.userOpHash || !family.attempts.some((a) => a.phase === "deposit" && a.hash.toLowerCase() === e.txHash?.toLowerCase()) || e.depositAuthorization && e.depositAuthorization.authorizationId !== family.authorizationId) return false;
16896
+ if (e.chainId !== chainId || e.token !== first.token || e.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || e.callsId || e.userOpHash || (family ? !family.attempts.some((a) => a.phase === "deposit" && a.hash.toLowerCase() === e.txHash?.toLowerCase()) || e.depositAuthorization && e.depositAuthorization.authorizationId !== family.authorizationId : String(e.authorizationDeadline) !== savedBody.deadline)) return false;
15381
16897
  const agent = this.getAgent(e.agentId);
15382
16898
  const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, e.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
15383
16899
  assertCurrent();
@@ -15390,6 +16906,62 @@ var OwneySDK = class {
15390
16906
  return false;
15391
16907
  }
15392
16908
  }
16909
+ /** Whether a Permit2 deposit authorization can still execute and whether it
16910
+ * did: `expired` when the finalized block is more than 300 s past
16911
+ * `deadline` (chain time), `used` when the owner's Permit2 nonce bit is set
16912
+ * at that block. Null when anything cannot be read. Never throws. */
16913
+ async checkDepositAuthorization(input) {
16914
+ try {
16915
+ const { chainId, owner, permit2Nonce, deadline } = input;
16916
+ if (!SupportedAssets.some((asset) => asset.chainId === chainId) || !(0, import_viem25.isAddress)(owner) || typeof permit2Nonce !== "string" || !/^\d{1,78}$/.test(permit2Nonce) || !Number.isSafeInteger(deadline) || deadline <= 0) return null;
16917
+ const nonce = BigInt(permit2Nonce);
16918
+ if (nonce >= 1n << 256n) return null;
16919
+ const pub = this.getPaidRpcClient(chainId);
16920
+ const block = await pub.getBlock({ blockTag: "finalized" });
16921
+ if (typeof block?.timestamp !== "bigint" || typeof block.number !== "bigint") return null;
16922
+ const bitmap = await pub.readContract({
16923
+ address: BATCH_PERMIT2_ADDRESS,
16924
+ abi: PERMIT2_BATCH_ABI,
16925
+ functionName: "nonceBitmap",
16926
+ args: [owner, nonce >> 8n],
16927
+ blockNumber: block.number
16928
+ });
16929
+ if (typeof bitmap !== "bigint" || bitmap < 0n) return null;
16930
+ return { expired: block.timestamp > BigInt(deadline) + 300n, used: (bitmap & 1n << (nonce & 255n)) !== 0n };
16931
+ } catch {
16932
+ return null;
16933
+ }
16934
+ }
16935
+ /** Drops the saved signed body of a hashless deposit the server may have
16936
+ * been sending (`serverBroadcast` 'unknown'/true, no hash) once the app has
16937
+ * settled it another way: by the ledger's hash or its expired deadline.
16938
+ * Only the body with this Permit2 deadline and these exact shares is
16939
+ * dropped, so it never resumes or blocks a different deposit. Never throws. */
16940
+ async discardSavedDepositBatch(evidences) {
16941
+ try {
16942
+ if (this.paymasterServiceUrl || !evidences.length) return false;
16943
+ const first = evidences[0];
16944
+ const deadline = first.authorizationDeadline;
16945
+ if (typeof deadline !== "number" || !Number.isSafeInteger(deadline) || deadline <= 0) return false;
16946
+ const { state, chainId } = this.captureReadScope(first.chainId);
16947
+ const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
16948
+ if (!token) return false;
16949
+ if (evidences.some((e) => e.chainId !== chainId || e.token !== first.token || e.authorizationDeadline !== deadline || e.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || !e.agentWallet || e.serverBroadcast !== "unknown" && e.serverBroadcast !== true)) return false;
16950
+ return await discardHashlessTokenBatch(
16951
+ {
16952
+ apiKey: this.apiKey,
16953
+ baseUrl: this.routingApiBaseUrl,
16954
+ owner: state.walletAddress,
16955
+ chainId,
16956
+ token: token.address
16957
+ },
16958
+ String(deadline),
16959
+ (body) => evidences.every((e) => body.transfers.some((t) => t.to.toLowerCase() === e.agentWallet.toLowerCase() && t.amount === e.intendedAmount))
16960
+ );
16961
+ } catch {
16962
+ return false;
16963
+ }
16964
+ }
15393
16965
  /** Validate signed journal membership, never just a caller-supplied family ID. */
15394
16966
  async validateDepositReplacement(previous, next) {
15395
16967
  try {
@@ -15490,6 +17062,14 @@ var OwneySDK = class {
15490
17062
  if (evidence.agentId !== "zyfai" || !id || !state || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return null;
15491
17063
  return this.getAgent("zyfai").getWithdrawalLifecycle(state, id);
15492
17064
  }
17065
+ /** A Zyfai deposit's placement status by the id recorded on its evidence. Never signs,
17066
+ * prompts or authenticates; `null` when unavailable or not this wallet's Zyfai deposit. */
17067
+ async getZyfaiDeposit(evidence) {
17068
+ const state = this.state;
17069
+ const id = evidence.providerDepositId;
17070
+ if (evidence.agentId !== "zyfai" || !id || !state || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return null;
17071
+ return this.getAgent("zyfai").getDepositPlacement(state, id);
17072
+ }
15493
17073
  // --- Fund operations ---
15494
17074
  /** On-chain version and ownership status for this owner's Yieldseeker agent. */
15495
17075
  async getYieldseekerWalletStatus(asset) {
@@ -16072,6 +17652,17 @@ var OwneySDK = class {
16072
17652
  onStage: (stage) => options.onStage(order.orderHash, stage)
16073
17653
  } : {}
16074
17654
  });
17655
+ const deposit = order.pendingDeposit;
17656
+ if (deposit && typeof deposit.received === "string" && typeof deposit.symbol === "string" && typeof deposit.chainId === "number" && loadOrder(order.orderHash))
17657
+ savePendingDeposit({
17658
+ id: `${deposit.chainId}-${deposit.symbol}-${order.orderHash}`,
17659
+ received: deposit.received,
17660
+ symbol: deposit.symbol,
17661
+ chainId: deposit.chainId,
17662
+ createdAt: Date.now(),
17663
+ ...typeof deposit.agentId === "string" ? { agentId: deposit.agentId } : {},
17664
+ ...typeof deposit.operationId === "string" ? { operationId: deposit.operationId } : {}
17665
+ });
16075
17666
  clearOrder(order.orderHash);
16076
17667
  updateWithdrawSwap(order.orderHash, { status: "landed" });
16077
17668
  return { orderHash: order.orderHash, status: "completed" };
@@ -16242,14 +17833,16 @@ var OwneySDK = class {
16242
17833
  options.to.symbol
16243
17834
  );
16244
17835
  }
16245
- const ledgerOp = this.openSwapLedger(() => ({
17836
+ let ledgerInput;
17837
+ const ledgerOp = this.openSwapLedger(() => ledgerInput = {
16246
17838
  operationId: options.operationId,
16247
17839
  kind: "swap_deposit",
16248
17840
  owner: state.walletAddress,
16249
17841
  chainId: options.to.chainId,
16250
17842
  token: SupportedAssets.find((value) => value.chainId === options.to.chainId && value.symbol === options.to.symbol)?.address ?? "",
16251
17843
  asset: options.to.symbol.toLowerCase()
16252
- }));
17844
+ });
17845
+ const secretHandoff = this.secretHandoffFor(ledgerOp, ledgerInput);
16253
17846
  try {
16254
17847
  const api = this.swapApi();
16255
17848
  const quoted = await api.quote({
@@ -16277,6 +17870,17 @@ var OwneySDK = class {
16277
17870
  ...options.assertCurrent ? { assertCurrent: options.assertCurrent } : {},
16278
17871
  ...options.slippage === void 0 ? {} : { slippage: options.slippage },
16279
17872
  ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
17873
+ // Stored with a cross-chain order, so a resume that finishes the swap
17874
+ // can still lead to the deposit (ROUT-625). The guaranteed minimum,
17875
+ // never the quoted amount.
17876
+ pendingDeposit: {
17877
+ received: quote.dstAmountMin,
17878
+ symbol: options.to.symbol,
17879
+ chainId: quote.dst.chainId,
17880
+ ...options.agentId ? { agentId: options.agentId } : {},
17881
+ ...ledgerOp ? { operationId: ledgerOp.operationId } : {}
17882
+ },
17883
+ ...secretHandoff ? { secretHandoff } : {},
16280
17884
  ...ledgerOp ? { onOrderSubmitted: (orderHash) => {
16281
17885
  swapIds.orderHash = orderHash;
16282
17886
  this.ledgerSwap(ledgerOp, quote, { status: "submitted", orderHash });
@@ -16296,6 +17900,7 @@ var OwneySDK = class {
16296
17900
  receivedDstAmountRaw: swap.received
16297
17901
  });
16298
17902
  options.onSwapProgress?.("depositing");
17903
+ const backgroundDepositId = swap.orderHash ? `${quote.dst.chainId}-${options.to.symbol}-${swap.orderHash}` : void 0;
16299
17904
  try {
16300
17905
  options.assertCurrent?.();
16301
17906
  await this.ensureSwapChain(quote.dst.chainId);
@@ -16325,10 +17930,13 @@ var OwneySDK = class {
16325
17930
  ...options.assertCurrent ? { assertSubmissionCurrent: options.assertCurrent } : {},
16326
17931
  ...ledgerOp ? { operationId: ledgerOp.operationId } : {}
16327
17932
  });
17933
+ if (backgroundDepositId) clearPendingDeposit(backgroundDepositId);
16328
17934
  return this.ledgerSucceeded(ledgerOp, { swap, deposit, deposited: amount });
16329
17935
  } catch (error) {
16330
17936
  const pending2 = {
16331
- id: `${quote.dst.chainId}-${options.to.symbol}-${Date.now()}`,
17937
+ // Keyed by the order when there is one, so it replaces any record a
17938
+ // background resume wrote for the same order rather than doubling it.
17939
+ id: backgroundDepositId ?? `${quote.dst.chainId}-${options.to.symbol}-${Date.now()}`,
16332
17940
  received: swap.received,
16333
17941
  symbol: options.to.symbol,
16334
17942
  chainId: quote.dst.chainId,
@@ -16484,7 +18092,7 @@ var OwneySDK = class {
16484
18092
  const state = this.requireState();
16485
18093
  const activeChainId = this.requireChainId();
16486
18094
  const revision = this.connectionRevision;
16487
- let active2 = true;
18095
+ let active = true;
16488
18096
  let record2;
16489
18097
  let ledgerOp;
16490
18098
  let hadDurableRecord = false;
@@ -16492,7 +18100,7 @@ var OwneySDK = class {
16492
18100
  let confirmationDeadline = Infinity;
16493
18101
  const assertCurrent = () => {
16494
18102
  options.assertCurrent?.();
16495
- if (!active2) throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal attempt is no longer active.", { swapSubmitted: false });
18103
+ if (!active) throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal attempt is no longer active.", { swapSubmitted: false });
16496
18104
  if (this.state !== state || this.connectionRevision !== revision || state.chainId !== activeChainId)
16497
18105
  throw new OwneyError("NOT_CONNECTED", "Wallet changed during withdrawal and swap.");
16498
18106
  };
@@ -16537,9 +18145,10 @@ var OwneySDK = class {
16537
18145
  const saved = options.recoveryId ? listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === options.recoveryId) : void 0;
16538
18146
  if (options.recoveryId && (!saved?.recovery?.results || saved.recovery.phase !== "ready" || saved.walletAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || saved.srcChainId !== activeChainId || saved.srcSymbol !== asset.symbol || saved.dstChainId !== options.to.chainId || saved.dstSymbol !== options.to.symbol))
16539
18147
  throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal is not ready to continue.");
18148
+ let ledgerInput;
16540
18149
  ledgerOp = this.openSwapLedger(() => {
16541
18150
  const requestedRaw = saved ? saved.recovery.requested : options.amount === void 0 ? void 0 : (0, import_viem25.parseUnits)(options.amount, asset.decimals).toString();
16542
- return {
18151
+ return ledgerInput = {
16543
18152
  operationId: saved?.operationId ?? options.operationId,
16544
18153
  kind: "withdraw_swap",
16545
18154
  owner: state.walletAddress,
@@ -16550,6 +18159,7 @@ var OwneySDK = class {
16550
18159
  ...requestedRaw === void 0 ? {} : { requestedAmountRaw: requestedRaw }
16551
18160
  };
16552
18161
  });
18162
+ const secretHandoff = this.secretHandoffFor(ledgerOp, ledgerInput);
16553
18163
  const prepareTimeout = () => new OwneyError(
16554
18164
  "WITHDRAW_SWAP_INCOMPLETE",
16555
18165
  "Could not read the source wallet. No new withdrawal was submitted.",
@@ -16609,7 +18219,7 @@ var OwneySDK = class {
16609
18219
  evidence.set(next.agentId, next);
16610
18220
  if (record2?.recovery) {
16611
18221
  record2.recovery.evidence[next.agentId] = next;
16612
- if (active2) saveWithdrawSwap(record2);
18222
+ if (active) saveWithdrawSwap(record2);
16613
18223
  else {
16614
18224
  const latest = listWithdrawSwaps(Date.now(), true).find((row) => row.recovery?.attemptId === record2.recovery.attemptId);
16615
18225
  if (latest?.recovery) updateWithdrawSwap(latest.orderHash, { recovery: {
@@ -16725,6 +18335,7 @@ var OwneySDK = class {
16725
18335
  quote,
16726
18336
  walletAddress: state.walletAddress,
16727
18337
  direction: "withdraw",
18338
+ ...secretHandoff ? { secretHandoff } : {},
16728
18339
  ...options.slippage === void 0 ? {} : { slippage: options.slippage },
16729
18340
  ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
16730
18341
  onOrderPrepared: (orderHash, details) => {
@@ -16813,7 +18424,7 @@ var OwneySDK = class {
16813
18424
  this.ledgerFailed(ledgerOp, error);
16814
18425
  throw error;
16815
18426
  } finally {
16816
- active2 = false;
18427
+ active = false;
16817
18428
  releaseSubmission();
16818
18429
  this.closeSwapLedger(ledgerOp);
16819
18430
  }
@@ -16861,11 +18472,13 @@ var OwneySDK = class {
16861
18472
  });
16862
18473
  } catch {
16863
18474
  }
16864
- const ledgerLeg = (evidence) => this.ledgerWithdrawalLeg(ledgerOp, evidence);
18475
+ const ledgerLeg = (evidence, errorCode) => this.ledgerWithdrawalLeg(ledgerOp, evidence, errorCode);
16865
18476
  let hold;
16866
18477
  let reservationRejected = false;
16867
18478
  let innerStarted = false;
16868
18479
  const reservationLegs = /* @__PURE__ */ new Map();
18480
+ const plannedLegs = /* @__PURE__ */ new Map();
18481
+ const serverLegs = /* @__PURE__ */ new Map();
16869
18482
  const reservation = {
16870
18483
  dispatching: (id) => hold?.dispatch(id),
16871
18484
  settled: (id, leg) => {
@@ -16875,11 +18488,22 @@ var OwneySDK = class {
16875
18488
  const settleHold = () => {
16876
18489
  if (!hold) return;
16877
18490
  hold.stopHeartbeat();
16878
- this.settleWithdrawalReservation(hold, ids, attempted, reservationLegs);
18491
+ const serverOwned = new Set(ids.filter((id) => !attempted.has(id) && serverMayOwn(serverLegs.get(id))));
18492
+ this.settleWithdrawalReservation(hold, ids, attempted, reservationLegs, serverOwned);
16879
18493
  };
16880
18494
  const runAgents = () => {
16881
18495
  innerStarted = true;
16882
- const inner = this.withdrawFromAgents(options, state, chainId, attempted, ledgerLeg, reservation);
18496
+ const inner = this.withdrawFromAgents(
18497
+ options,
18498
+ state,
18499
+ chainId,
18500
+ attempted,
18501
+ ledgerLeg,
18502
+ reservation,
18503
+ plannedLegs,
18504
+ (timeoutMs) => ledgerOp?.settle?.(timeoutMs),
18505
+ serverLegs
18506
+ );
16883
18507
  void inner.then(settleHold, settleHold);
16884
18508
  return inner;
16885
18509
  };
@@ -16903,7 +18527,9 @@ var OwneySDK = class {
16903
18527
  } catch (error) {
16904
18528
  if (!reservationRejected) {
16905
18529
  for (const id of ids) if (!attempted.has(id)) {
16906
- notifyFunding(options.onProgress, {
18530
+ if (serverMayOwn(serverLegs.get(id))) continue;
18531
+ const planned = plannedLegs.get(id);
18532
+ notifyFunding(options.onProgress, planned ? { ...planned, transfer: "failed", delivery: "failed" } : {
16907
18533
  agentId: id,
16908
18534
  chainId,
16909
18535
  token: options.asset,
@@ -17025,12 +18651,13 @@ var OwneySDK = class {
17025
18651
  * - a throw without submission evidence: `not_submitted`;
17026
18652
  * - Zyfai "withdrawal already in progress": kept, dispatch `provider_in_progress`.
17027
18653
  */
17028
- settleWithdrawalReservation(hold, ids, attempted, legs) {
18654
+ settleWithdrawalReservation(hold, ids, attempted, legs, keep) {
17029
18655
  try {
17030
18656
  if ([...attempted].some((id) => !legs.has(id))) return;
17031
18657
  const releases = /* @__PURE__ */ new Map();
17032
18658
  for (const id of new Set(ids)) {
17033
18659
  if (!RESERVATION_AGENTS.includes(id)) continue;
18660
+ if (keep?.has(id)) continue;
17034
18661
  const leg = legs.get(id);
17035
18662
  const outcome = attempted.has(id) && leg ? reservationLegOutcome(leg) : "not_submitted";
17036
18663
  if (outcome === "keep") continue;
@@ -17061,6 +18688,31 @@ var OwneySDK = class {
17061
18688
  } catch {
17062
18689
  }
17063
18690
  }
18691
+ /**
18692
+ * Reports a withdrawal step the app closed as never sent (a reload before its
18693
+ * request was saved) as a failed ledger leg with `NOT_SUBMITTED`, so other
18694
+ * devices and later recovery stop treating it as in flight. Refuses any step
18695
+ * with a hash or a saved request. Never touches the hold; never throws.
18696
+ */
18697
+ async reportWithdrawalNotSubmitted(operationId, evidence) {
18698
+ try {
18699
+ if (typeof operationId !== "string" || !operationId || !evidence || evidence.transfer !== "failed" || evidence.txHash || evidence.userOpHash || evidence.callsId || evidence.withdrawalRequest?.attempted === true) return;
18700
+ const leg = ledgerLegFromEvidence(evidence, "withdraw");
18701
+ if (leg?.status !== "failed") return;
18702
+ const token = SupportedAssets.find((value) => value.chainId === evidence.chainId && value.symbol === evidence.token);
18703
+ const op = this.ledger.resume({
18704
+ operationId,
18705
+ kind: "withdraw",
18706
+ owner: evidence.walletAddress,
18707
+ chainId: evidence.chainId,
18708
+ token: token?.address ?? "",
18709
+ asset: String(evidence.token).toLowerCase()
18710
+ });
18711
+ op.leg({ ...leg, errorCode: "NOT_SUBMITTED" });
18712
+ op.finish({});
18713
+ } catch {
18714
+ }
18715
+ }
17064
18716
  /**
17065
18717
  * Reports a withdrawal leg that was proven after its call ended (a hashless
17066
18718
  * Zyfai payout found on chain or in history) to the funding ledger, then
@@ -17110,7 +18762,36 @@ var OwneySDK = class {
17110
18762
  } catch {
17111
18763
  }
17112
18764
  }
17113
- async withdrawFromAgents(options, state, chainId, attempted, ledgerLeg, reservation) {
18765
+ /**
18766
+ * Reports a deposit leg that the app's recovery verified on chain after its
18767
+ * call ended to the funding ledger. Only evidence whose transfer is confirmed
18768
+ * with a transaction hash is reported; anything else is ignored. The ledger
18769
+ * PATCHes the existing operation (creating it only on a 404) and the server
18770
+ * re-verifies the hash. Deposits hold no reservation, so nothing is released.
18771
+ * Never throws.
18772
+ */
18773
+ async reportDepositRecovery(operationId, evidence) {
18774
+ try {
18775
+ if (typeof operationId !== "string" || !operationId || !evidence || evidence.transfer !== "confirmed") return;
18776
+ const leg = ledgerLegFromEvidence(evidence, "deposit");
18777
+ if (leg?.status !== "confirmed" || !leg.txHash) return;
18778
+ const rail = depositLedgerRail(evidence);
18779
+ const token = SupportedAssets.find((value) => value.chainId === evidence.chainId && value.symbol === evidence.token);
18780
+ const op = this.ledger.resume({
18781
+ operationId,
18782
+ kind: "deposit",
18783
+ owner: evidence.walletAddress,
18784
+ chainId: evidence.chainId,
18785
+ token: token?.address ?? "",
18786
+ asset: String(evidence.token).toLowerCase()
18787
+ });
18788
+ op.leg(rail ? { ...leg, rail } : leg);
18789
+ op.finish({});
18790
+ this.ledgerProviderSamples(op, evidence, "deposit");
18791
+ } catch {
18792
+ }
18793
+ }
18794
+ async withdrawFromAgents(options, state, chainId, attempted, ledgerLeg, reservation, plannedLegs, settleLedger, serverLegs = /* @__PURE__ */ new Map()) {
17114
18795
  const { asset, amount, agentId, onAgentResult } = options;
17115
18796
  const notifyAgentResult = (id, result) => {
17116
18797
  try {
@@ -17156,7 +18837,22 @@ var OwneySDK = class {
17156
18837
  { asset, amount, agentErrors: agentErrors2, agentFailures, pendingSubmissions: pending2 }
17157
18838
  );
17158
18839
  };
17159
- const submit = async (agent, requested2) => {
18840
+ const awaitServerHandoffs = async () => {
18841
+ const pending2 = [...serverLegs.values()].filter((leg) => leg.settled === void 0).map((leg) => leg.answer);
18842
+ if (!pending2.length) return;
18843
+ let timer;
18844
+ await Promise.race([
18845
+ Promise.all(pending2),
18846
+ new Promise((resolve) => {
18847
+ timer = setTimeout(resolve, SERVER_HANDOFF_WAIT_MS);
18848
+ })
18849
+ ]);
18850
+ clearTimeout(timer);
18851
+ };
18852
+ const submit = async (agent, requested2, onAccepted) => {
18853
+ const plannedTask = serverLegs.get(agent.id);
18854
+ const serverTask = plannedTask ? await plannedTask.answer : void 0;
18855
+ if (serverTask) await settleLedger?.(3e3);
17160
18856
  if (this.state !== state || this.state.chainId !== chainId)
17161
18857
  throw new OwneyError("NOT_CONNECTED", "Wallet connection changed before withdrawal submission.");
17162
18858
  attempted.add(agent.id);
@@ -17178,6 +18874,7 @@ var OwneySDK = class {
17178
18874
  submissionEvidence.set(agent.id, evidence);
17179
18875
  notifyFunding(options.onProgress, evidence);
17180
18876
  ledgerLeg?.(evidence);
18877
+ if (onAccepted && evidence.ownerAuthorization === "accepted" && evidence.transfer !== "failed" && (evidence.userOpHash || evidence.callsId || evidence.txHash)) onAccepted();
17181
18878
  };
17182
18879
  progress(evidence);
17183
18880
  adapterReported = false;
@@ -17192,14 +18889,26 @@ var OwneySDK = class {
17192
18889
  reservation?.dispatching(agent.id);
17193
18890
  } catch {
17194
18891
  }
17195
- const result = options.onBeforeSubmission || options.assertCurrent ? await agent.withdraw(
18892
+ const context = typeof options.operationId === "string" ? { operationId: options.operationId, ...serverTask ? { planned: true } : {} } : void 0;
18893
+ const onBeforeSubmission = options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0;
18894
+ const result = context ? await agent.withdraw(
18895
+ state,
18896
+ chainId,
18897
+ token,
18898
+ requested2,
18899
+ progress,
18900
+ options.gasMode ?? "auto",
18901
+ onBeforeSubmission,
18902
+ options.assertCurrent,
18903
+ context
18904
+ ) : options.onBeforeSubmission || options.assertCurrent ? await agent.withdraw(
17196
18905
  state,
17197
18906
  chainId,
17198
18907
  token,
17199
18908
  requested2,
17200
18909
  progress,
17201
18910
  options.gasMode ?? "auto",
17202
- options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0,
18911
+ onBeforeSubmission,
17203
18912
  options.assertCurrent
17204
18913
  ) : await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
17205
18914
  settled2({
@@ -17210,6 +18919,10 @@ var OwneySDK = class {
17210
18919
  } catch (error) {
17211
18920
  if (!adapterReported && !evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
17212
18921
  progress({ ...evidence, transfer: "failed", delivery: "failed" });
18922
+ else if (error instanceof OwneyError && error.details?.rejectedBeforeSubmission === true && evidence.transfer !== "confirmed")
18923
+ progress({ ...evidence, transfer: "failed", delivery: "failed" });
18924
+ if (serverLegs.size && requested2 === void 0 && agent.withdrawalRequiresWalletApproval && error instanceof OwneyError && error.code === "WITHDRAW_INSUFFICIENT_BALANCE" && error.details?.available === "0" && evidence.transfer === "failed" && !evidence.txHash && !evidence.userOpHash && !evidence.callsId)
18925
+ ledgerLeg?.(evidence, "WITHDRAW_INSUFFICIENT_BALANCE");
17213
18926
  settled2({ settled: "threw", evidence });
17214
18927
  throw error;
17215
18928
  }
@@ -17235,40 +18948,151 @@ var OwneySDK = class {
17235
18948
  notifyAgentResult(agent.id, result);
17236
18949
  return result;
17237
18950
  }
18951
+ const dispatch = async (agent, requested2) => {
18952
+ let accept;
18953
+ const accepted = new Promise((resolve) => {
18954
+ accept = () => resolve(true);
18955
+ });
18956
+ const outcome = submit(agent, requested2, agent.withdrawalRequiresWalletApproval ? accept : void 0);
18957
+ const handedOver = await Promise.race([accepted, outcome.then(() => false, () => false)]);
18958
+ return { outcome, handedOver };
18959
+ };
18960
+ const prepareLaterAgents = async (agents) => {
18961
+ if (!agents.some((agent) => agent.withdrawalRequiresWalletApproval)) return;
18962
+ for (const agent of agents) {
18963
+ if (agent.withdrawalRequiresWalletApproval || typeof agent.prepareWithdrawal !== "function") continue;
18964
+ try {
18965
+ await agent.prepareWithdrawal(state, chainId, token);
18966
+ } catch (error) {
18967
+ if (this.isUserRejectedWithdrawal(error)) throw error;
18968
+ console.warn(`withdraw preparation failed for agent "${agent.id}":`, error);
18969
+ }
18970
+ }
18971
+ };
18972
+ const recordPlan = (legs) => {
18973
+ if (legs.length < 2) return;
18974
+ const handOffs = [];
18975
+ for (const [index, [agent, intendedAmount]] of legs.entries()) {
18976
+ const planned = {
18977
+ agentId: agent.id,
18978
+ chainId,
18979
+ token,
18980
+ walletAddress: state.walletAddress,
18981
+ intendedAmount,
18982
+ transfer: "pending",
18983
+ registration: "not-applicable",
18984
+ delivery: "pending",
18985
+ planned: true
18986
+ };
18987
+ plannedLegs?.set(planned.agentId, planned);
18988
+ notifyFunding(options.onProgress, planned);
18989
+ const after = legs.slice(0, index).filter(([earlier]) => earlier.withdrawalRequiresWalletApproval).map(([earlier]) => `agent:${earlier.id}`);
18990
+ if (agent.supportsServerTasks === true && typeof agent.handOffPlannedWithdrawal === "function" && after.length && zyfaiServerTasksEnabled(this.zyfaiServerTasks)) handOffs.push([agent, planned, after]);
18991
+ }
18992
+ const operationId = options.operationId;
18993
+ if (typeof operationId !== "string" || !operationId) return;
18994
+ for (const [agent, planned, after] of handOffs) {
18995
+ const leg = { answer: Promise.resolve(void 0), share: planned.intendedAmount };
18996
+ leg.answer = (async () => {
18997
+ let serverTask;
18998
+ let noTask = false;
18999
+ try {
19000
+ const answer = await agent.handOffPlannedWithdrawal(
19001
+ state,
19002
+ chainId,
19003
+ token,
19004
+ planned.intendedAmount,
19005
+ {
19006
+ operationId,
19007
+ after,
19008
+ // T10-I1: saved before the request leaves, as the dispatch
19009
+ // handoff's checkpoint is, so a reload while the server
19010
+ // answers finds a leg the server may own.
19011
+ beforeRequest: () => notifyFunding(options.onProgress, { ...planned, serverTask: "unknown" })
19012
+ }
19013
+ );
19014
+ serverTask = answer.type === "accepted" ? true : answer.type === "unknown" || answer.code === "LEG_ALREADY_SENT" ? "unknown" : void 0;
19015
+ noTask = answer.type === "declined" && ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code);
19016
+ } catch {
19017
+ serverTask = "unknown";
19018
+ }
19019
+ leg.settled = serverTask ?? null;
19020
+ if (serverTask) {
19021
+ const marked = { ...planned, serverTask };
19022
+ plannedLegs?.set(planned.agentId, marked);
19023
+ notifyFunding(options.onProgress, marked);
19024
+ } else if (noTask) {
19025
+ notifyFunding(options.onProgress, { ...planned, serverTask: false });
19026
+ }
19027
+ return serverTask;
19028
+ })();
19029
+ serverLegs.set(planned.agentId, leg);
19030
+ }
19031
+ };
17238
19032
  const eligibleAgents = this.getEligibleAgents(chainId, asset).filter((agent) => !options.excludeAgentIds?.includes(agent.id));
17239
19033
  const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
17240
19034
  if (!amount) {
17241
19035
  const results2 = {};
17242
19036
  const agentErrors2 = {};
17243
19037
  const incompleteWithdrawals = {};
17244
- for (const agent of withdrawalAgents) {
17245
- try {
17246
- results2[agent.id] = await submit(agent);
17247
- notifyAgentResult(agent.id, results2[agent.id]);
17248
- } catch (err) {
19038
+ const failed2 = (agent, err, handedOver) => {
19039
+ if (!handedOver) {
17249
19040
  if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
17250
19041
  if (this.isUserRejectedWithdrawal(err)) throw err;
17251
- if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
17252
- if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
17253
- incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
17254
- const evidence = submissionEvidence.get(agent.id);
17255
- 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) {
17256
- results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
17257
- notifyAgentResult(agent.id, results2[agent.id]);
17258
- }
19042
+ }
19043
+ if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") return;
19044
+ if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
19045
+ incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
19046
+ const evidence = submissionEvidence.get(agent.id);
19047
+ 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) {
19048
+ results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
19049
+ notifyAgentResult(agent.id, results2[agent.id]);
19050
+ }
19051
+ }
19052
+ console.error(`withdraw failed for agent "${agent.id}":`, err);
19053
+ agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
19054
+ recordAgentFailure(agent.id, err);
19055
+ const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
19056
+ void reportAgentFailure(
19057
+ this.apiKey,
19058
+ agent.id,
19059
+ code,
19060
+ this.routingApiBaseUrl
19061
+ );
19062
+ };
19063
+ const handedOverLegs2 = [];
19064
+ const settleHandedOver2 = async () => {
19065
+ for (const { agent, outcome } of handedOverLegs2.splice(0)) {
19066
+ try {
19067
+ results2[agent.id] = await outcome;
19068
+ notifyAgentResult(agent.id, results2[agent.id]);
19069
+ } catch (err) {
19070
+ failed2(agent, err, true);
19071
+ }
19072
+ }
19073
+ };
19074
+ await prepareLaterAgents(withdrawalAgents);
19075
+ recordPlan(withdrawalAgents.map((agent) => [agent, "MAX"]));
19076
+ try {
19077
+ for (const agent of withdrawalAgents) {
19078
+ const leg = await dispatch(agent);
19079
+ if (leg.handedOver) {
19080
+ handedOverLegs2.push({ agent, outcome: leg.outcome });
19081
+ continue;
19082
+ }
19083
+ try {
19084
+ results2[agent.id] = await leg.outcome;
19085
+ notifyAgentResult(agent.id, results2[agent.id]);
19086
+ } catch (err) {
19087
+ failed2(agent, err, false);
17259
19088
  }
17260
- console.error(`withdraw failed for agent "${agent.id}":`, err);
17261
- agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
17262
- recordAgentFailure(agent.id, err);
17263
- const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
17264
- void reportAgentFailure(
17265
- this.apiKey,
17266
- agent.id,
17267
- code,
17268
- this.routingApiBaseUrl
17269
- );
17270
19089
  }
19090
+ } catch (error) {
19091
+ if (serverLegs.size) await awaitServerHandoffs();
19092
+ await settleHandedOver2();
19093
+ throw error;
17271
19094
  }
19095
+ await settleHandedOver2();
17272
19096
  if (Object.keys(results2).length === 0) {
17273
19097
  throwUnconfirmed(agentErrors2);
17274
19098
  if (Object.keys(agentErrors2).length === 0)
@@ -17413,32 +19237,65 @@ var OwneySDK = class {
17413
19237
  const agentErrors = {
17414
19238
  ...aggregated.agentErrors ?? {}
17415
19239
  };
17416
- for (let i = 0; i < orderedPlans.length; i++) {
17417
- const p = orderedPlans[i];
17418
- assertPlan();
17419
- if (p.planned <= 0n) continue;
17420
- try {
17421
- results[p.agent.id] = await submit(p.agent, p.planned.toString());
17422
- notifyAgentResult(p.agent.id, results[p.agent.id]);
17423
- } catch (err) {
19240
+ const failed = (p, index, err, handedOver) => {
19241
+ if (!handedOver) {
17424
19242
  if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
17425
19243
  if (this.isUserRejectedWithdrawal(err)) throw err;
17426
- console.error(`withdraw failed for agent "${p.agent.id}":`, err);
17427
- agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
17428
- recordAgentFailure(p.agent.id, err);
17429
- const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
17430
- void reportAgentFailure(
17431
- this.apiKey,
17432
- p.agent.id,
17433
- code,
17434
- this.routingApiBaseUrl
17435
- );
17436
- const failedAmount = p.planned;
17437
- p.planned = 0n;
17438
- if (submissionEvidence.get(p.agent.id)?.transfer === "failed")
17439
- redistributeShare(orderedPlans, i, failedAmount);
17440
19244
  }
19245
+ console.error(`withdraw failed for agent "${p.agent.id}":`, err);
19246
+ agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
19247
+ recordAgentFailure(p.agent.id, err);
19248
+ const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
19249
+ void reportAgentFailure(
19250
+ this.apiKey,
19251
+ p.agent.id,
19252
+ code,
19253
+ this.routingApiBaseUrl
19254
+ );
19255
+ const failedAmount = p.planned;
19256
+ p.planned = 0n;
19257
+ if (!handedOver && submissionEvidence.get(p.agent.id)?.transfer === "failed")
19258
+ redistributeShare(orderedPlans, index, failedAmount);
19259
+ };
19260
+ const handedOverLegs = [];
19261
+ const settleHandedOver = async () => {
19262
+ for (const { plan, index, outcome } of handedOverLegs.splice(0)) {
19263
+ try {
19264
+ results[plan.agent.id] = await outcome;
19265
+ notifyAgentResult(plan.agent.id, results[plan.agent.id]);
19266
+ } catch (err) {
19267
+ failed(plan, index, err, true);
19268
+ }
19269
+ }
19270
+ };
19271
+ const dispatchedPlans = orderedPlans.filter((plan) => plan.planned > 0n);
19272
+ await prepareLaterAgents(dispatchedPlans.map((plan) => plan.agent));
19273
+ recordPlan(dispatchedPlans.map((plan) => [plan.agent, plan.planned.toString()]));
19274
+ try {
19275
+ for (let i = 0; i < orderedPlans.length; i++) {
19276
+ const p = orderedPlans[i];
19277
+ assertPlan();
19278
+ if (p.planned <= 0n) continue;
19279
+ const serverLeg = serverLegs.get(p.agent.id);
19280
+ if (serverLeg && await serverLeg.answer && /^[1-9][0-9]*$/.test(serverLeg.share)) p.planned = BigInt(serverLeg.share);
19281
+ const leg = await dispatch(p.agent, p.planned.toString());
19282
+ if (leg.handedOver) {
19283
+ handedOverLegs.push({ plan: p, index: i, outcome: leg.outcome });
19284
+ continue;
19285
+ }
19286
+ try {
19287
+ results[p.agent.id] = await leg.outcome;
19288
+ notifyAgentResult(p.agent.id, results[p.agent.id]);
19289
+ } catch (err) {
19290
+ failed(p, i, err, false);
19291
+ }
19292
+ }
19293
+ } catch (error) {
19294
+ if (serverLegs.size) await awaitServerHandoffs();
19295
+ await settleHandedOver();
19296
+ throw error;
17441
19297
  }
19298
+ await settleHandedOver();
17442
19299
  if (Object.keys(results).length === 0) {
17443
19300
  throwUnconfirmed(agentErrors);
17444
19301
  throw new OwneyError(
@@ -18378,12 +20235,20 @@ function createOwneySIWX(config) {
18378
20235
  0 && (module.exports = {
18379
20236
  AgentChainIncompatibleError,
18380
20237
  AgentNotFoundError,
20238
+ FUNDING_DEPOSIT_RECOVERY_REPORT,
20239
+ FUSION_SECRET_SERVER_PUBLISH,
18381
20240
  InvalidHistoryCursorError,
18382
20241
  NotConnectedError,
18383
20242
  OwneyError,
18384
20243
  OwneySDK,
18385
20244
  SDK_VERSION,
20245
+ SPONSORED_DEPOSIT_SERVER_BROADCAST,
20246
+ YIELDSEEKER_WITHDRAW_SERVER_SUBMIT,
18386
20247
  YieldseekerAgent,
20248
+ ZYFAI_REGISTRATION_NOT_SENT_CODES,
20249
+ ZYFAI_SERVER_TASKS,
20250
+ ZYFAI_WITHDRAWAL_NOT_SENT_CODES,
20251
+ ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT,
18387
20252
  clearWithdrawSwap,
18388
20253
  createOwneySIWX,
18389
20254
  listPendingDeposits,