@owney/sdk 0.7.32-beta.1 → 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.1";
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,6 +2463,8 @@ 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;
2466
+ const providerRef = e.withdrawalRequest?.providerWithdrawalId || e.providerDepositId;
2467
+ if (typeof providerRef === "string" && providerRef.length >= 1 && providerRef.length <= 128) leg.providerRef = providerRef;
2161
2468
  const failure = e.withdrawalRequest?.failure;
2162
2469
  if (failure) leg.errorCode = failure.toUpperCase().replace(/-/g, "_");
2163
2470
  return leg;
@@ -2185,6 +2492,12 @@ function withdrawalLedgerRail(e) {
2185
2492
  return e.userOpHash ? "yieldseeker_userop" : e.callsId ? "yieldseeker_wallet_calls" : e.txHash ? "yieldseeker_paid" : void 0;
2186
2493
  return void 0;
2187
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;
2188
2501
  var SWAP_LEDGER_RAILS = {
2189
2502
  classic: "swap_classic",
2190
2503
  fusion: "swap_fusion",
@@ -2252,6 +2565,12 @@ var TRANSFER_EVENT = (0, import_viem4.getAbiItem)({
2252
2565
  abi: (0, import_viem4.parseAbi)(["event Transfer(address indexed from, address indexed to, uint256 value)"]),
2253
2566
  name: "Transfer"
2254
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
+ }
2255
2574
  function parseBlock(value) {
2256
2575
  if (value === void 0 || value === null) return void 0;
2257
2576
  return typeof value === "string" && /^\d{1,19}$/.test(value) ? BigInt(value) : null;
@@ -2276,11 +2595,12 @@ async function scanWithdrawalPayouts(client, input) {
2276
2595
  const candidates = [];
2277
2596
  if (start > end) {
2278
2597
  const kept = input.cursor ?? (start > 0n ? start - 1n : 0n);
2279
- return { candidates, scannedTo: kept.toString() };
2598
+ return { candidates, scannedTo: kept.toString(), caughtUp: true };
2280
2599
  }
2281
2600
  const tokenAddress = input.token.toLowerCase();
2282
2601
  const owner = input.owner.toLowerCase();
2283
- 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;
2284
2604
  let from = start;
2285
2605
  let scannedTo = start - 1n;
2286
2606
  for (let requests = 0; from <= end && requests < MAX_CHUNKS_PER_SCAN; ) {
@@ -2295,12 +2615,21 @@ async function scanWithdrawalPayouts(client, input) {
2295
2615
  toBlock: to
2296
2616
  });
2297
2617
  } catch (error) {
2298
- if (size > 1n && !isRateLimited(error) && isRangeError(error)) {
2299
- size /= 2n;
2300
- 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
+ }
2301
2626
  }
2302
2627
  throw error;
2303
2628
  }
2629
+ if (size < PAYOUT_SCAN_CHUNK_BLOCKS) {
2630
+ if (key2 !== void 0) acceptedChunk.set(key2, size);
2631
+ else acceptedChunkByClient.set(client, size);
2632
+ }
2304
2633
  check();
2305
2634
  requests++;
2306
2635
  for (const log of logs) {
@@ -2318,7 +2647,8 @@ async function scanWithdrawalPayouts(client, input) {
2318
2647
  scannedTo = to;
2319
2648
  from = to + 1n;
2320
2649
  }
2321
- 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 };
2322
2652
  }
2323
2653
  async function estimateBlockAt(client, latest, targetMs) {
2324
2654
  const head = await client.getBlock({ blockNumber: latest });
@@ -2343,6 +2673,7 @@ function causeChain(error) {
2343
2673
  chain2.push({
2344
2674
  code: value.code,
2345
2675
  status: value.status,
2676
+ data: value.data,
2346
2677
  text: [value.message, value.details, value.shortMessage].filter((part) => typeof part === "string").join(" ")
2347
2678
  });
2348
2679
  current = value.cause;
@@ -2352,6 +2683,14 @@ function causeChain(error) {
2352
2683
  function isRateLimited(error) {
2353
2684
  return causeChain(error).some((entry) => entry.status === 429 || entry.code === 429 || /rate|too many requests/i.test(entry.text));
2354
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
+ }
2355
2694
  function isRangeError(error) {
2356
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));
2357
2696
  }
@@ -2461,6 +2800,7 @@ async function getSponsorRelayerAddress(input) {
2461
2800
  return parsed.data.relayer;
2462
2801
  }
2463
2802
  async function postSponsorBatchTransfer(input) {
2803
+ const flaggedPrepare = input.body.serverBroadcast === true && !input.body.serializedTransaction;
2464
2804
  let res;
2465
2805
  try {
2466
2806
  res = await fetch(
@@ -2479,7 +2819,7 @@ async function postSponsorBatchTransfer(input) {
2479
2819
  throw new OwneyError(
2480
2820
  "SPONSOR_REQUEST_FAILED",
2481
2821
  "Deposit status is unknown. Retry the same amount to check it.",
2482
- { safeToFallback: false }
2822
+ { safeToFallback: false, ...flaggedPrepare ? { outcomeUnknown: true } : {} }
2483
2823
  );
2484
2824
  }
2485
2825
  const parsed = await res.json().catch(() => null);
@@ -2490,7 +2830,13 @@ async function postSponsorBatchTransfer(input) {
2490
2830
  {
2491
2831
  statusCode: res.status,
2492
2832
  safeToFallback: false,
2493
- 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 } : {}
2494
2840
  }
2495
2841
  );
2496
2842
  }
@@ -2507,19 +2853,19 @@ async function postSponsorBatchRecovery(input) {
2507
2853
  input.signal?.throwIfAborted();
2508
2854
  if (!response.ok || payload?.success !== true) throw failure();
2509
2855
  const value = payload.data;
2510
- 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");
2511
2857
  const { validateTokenBatchAttempt: validateTokenBatchAttempt2 } = await Promise.resolve().then(() => (init_token_batch_family(), token_batch_family_exports));
2512
2858
  const phase = input.body.phase;
2513
2859
  const original = phase === "approval" ? input.body.serializedPermitTransaction : input.body.serializedTransaction;
2514
2860
  if (!original) throw failure();
2515
2861
  const hash2 = keccak2565(original), relayer = await recoverTransactionAddress2({ serializedTransaction: original });
2516
- const validHash = (v) => typeof v === "string" && /^0x[\da-f]{64}$/i.test(v);
2517
- 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();
2518
2864
  if (value.status !== "prepared" && value.txHash.toLowerCase() !== hash2.toLowerCase()) throw failure();
2519
2865
  if (value.status === "unresolved") {
2520
2866
  if (!["nonce-not-consumed", "authorization-used", "authorization-expired", "state-unavailable", "preparation-rejected", "relayer-changed"].includes(value.reason)) throw failure();
2521
2867
  } else {
2522
- 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();
2523
2869
  if (value.status === "prepared") {
2524
2870
  if (value.predecessorHash?.toLowerCase() !== hash2.toLowerCase()) throw failure();
2525
2871
  const tx = parseTransaction4(value.serializedTransaction), old = parseTransaction4(original);
@@ -2821,10 +3167,19 @@ var AgentReads = class {
2821
3167
  }
2822
3168
  };
2823
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
+
2824
3176
  // src/agents/zyfai/zyfai.withdraw-submission.ts
2825
3177
  var BELOW_MINIMUM = /^Idle balance (\d+(?:\.\d+)?) (USDC|WETH|ETH|EURC) is below the minimum withdrawal of (\d+(?:\.\d+)?) \2\.?$/;
2826
3178
  var listenersByClient = /* @__PURE__ */ new WeakMap();
2827
- function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
3179
+ var ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT = 1;
3180
+ var MAX_WITHDRAWAL_ID_LENGTH = 128;
3181
+ var WITHDRAWAL_PATHS = { get: "/users/withdraw/v2", post: "/users/partial-withdraw/v2" };
3182
+ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent, beforeDispatch) {
2828
3183
  const client = sdk.httpClient;
2829
3184
  if (!client || typeof client.get !== "function" || typeof client.post !== "function")
2830
3185
  return {
@@ -2844,7 +3199,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2844
3199
  const original = client[method];
2845
3200
  client[method] = async function(...args) {
2846
3201
  const [url, options] = args;
2847
- const withdrawal = method === "get" && url === "/users/withdraw" || method === "post" && url === "/users/partial-withdraw";
3202
+ const withdrawal = url === WITHDRAWAL_PATHS[method];
2848
3203
  const payload = method === "get" ? options?.params : options;
2849
3204
  const matching = withdrawal ? [...observers].filter((observer) => observer.chainId === payload?.chainId && observer.token === payload?.tokenSymbol) : [];
2850
3205
  for (const observer of matching) observer.assertCurrent?.();
@@ -2852,8 +3207,12 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2852
3207
  const request3 = {
2853
3208
  attempted: true,
2854
3209
  method: method === "get" ? "GET" : "POST",
2855
- path: method === "get" ? "/users/withdraw" : "/users/partial-withdraw"
3210
+ path: WITHDRAWAL_PATHS[method]
2856
3211
  };
3212
+ if (withdrawal) {
3213
+ for (const observer of matching) await observer.beforeDispatch?.({ ...request3 });
3214
+ for (const observer of matching) observer.assertCurrent?.();
3215
+ }
2857
3216
  let belowMinimum2;
2858
3217
  const notify2 = (outcome) => {
2859
3218
  if (outcome === "rejected" && matching.length !== 1) return;
@@ -2887,7 +3246,13 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2887
3246
  ] };
2888
3247
  }
2889
3248
  try {
2890
- return await original.apply(this, callArgs);
3249
+ const result = await original.apply(this, callArgs);
3250
+ if (withdrawal) {
3251
+ const id = result?.id;
3252
+ if (typeof id === "string" && id.length >= 1 && id.length <= MAX_WITHDRAWAL_ID_LENGTH)
3253
+ request3.providerWithdrawalId = id;
3254
+ }
3255
+ return result;
2891
3256
  } catch (error) {
2892
3257
  const message = error instanceof Error ? error.message : "";
2893
3258
  const minimum = BELOW_MINIMUM.exec(message);
@@ -2895,7 +3260,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2895
3260
  const status = error?.response?.status;
2896
3261
  if (request3.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
2897
3262
  request3.httpStatus = status;
2898
- 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";
2899
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");
2900
3265
  throw error;
2901
3266
  } finally {
@@ -2909,7 +3274,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2909
3274
  let rejected = false;
2910
3275
  let request2 = { attempted: false };
2911
3276
  let belowMinimum;
2912
- const listener = { chainId, token, assertCurrent, notify: (outcome, evidence, detail) => {
3277
+ const listener = { chainId, token, assertCurrent, beforeDispatch, notify: (outcome, evidence, detail) => {
2913
3278
  request2 = evidence;
2914
3279
  if (outcome === "attempted") submitted = true;
2915
3280
  else if (outcome === "rejected") {
@@ -3051,7 +3416,7 @@ async function submitZyfaiDeposit({ sdk, state, chain: chain2, amount, asset, sm
3051
3416
  if (!token || !(0, import_viem7.isAddress)(owner) || !(0, import_viem7.isAddress)(smartWallet) || !/^\d+$/.test(amount) || BigInt(amount) <= 0n)
3052
3417
  throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId: chain2.id, asset }, "zyfai");
3053
3418
  assertCurrent();
3054
- 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 });
3055
3420
  assertCurrent();
3056
3421
  const expectedData = (0, import_viem7.encodeFunctionData)({ abi: import_viem7.erc20Abi, functionName: "transfer", args: [smartWallet, BigInt(amount)] });
3057
3422
  const transfer = prepared?.transfer;
@@ -3134,6 +3499,13 @@ function tokenDecimals(symbol2, explicit) {
3134
3499
  return explicit;
3135
3500
  return DECIMALS_BY_SYMBOL[(symbol2 ?? "").toLowerCase()] ?? 6;
3136
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
+ }
3137
3509
  function mapWithdraw(raw2) {
3138
3510
  return {
3139
3511
  txHash: raw2.txHash,
@@ -3183,6 +3555,40 @@ function mapPendingAllocations(raw2) {
3183
3555
  }
3184
3556
  return pending2.length > 0 ? pending2 : void 0;
3185
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
+ }
3186
3592
  function mapBalances(raw2, _chainId, smartWallet) {
3187
3593
  const portfolio = raw2.portfolio;
3188
3594
  if (!portfolio?.portfolioByChain || typeof portfolio.portfolioByChain !== "object")
@@ -3241,6 +3647,9 @@ function mapBalances(raw2, _chainId, smartWallet) {
3241
3647
  const pendingAllocations = mapPendingAllocations(
3242
3648
  portfolio.staleBalances
3243
3649
  );
3650
+ const asyncWithdrawals = portfolio.pendingAsyncWithdrawals;
3651
+ const pendingWithdrawals = mapPendingWithdrawals(asyncWithdrawals);
3652
+ const recentAsyncWithdrawals = mapRecentAsyncWithdrawals(asyncWithdrawals);
3244
3653
  return {
3245
3654
  smartWallet,
3246
3655
  totalBalance: String(totalBalance),
@@ -3256,7 +3665,49 @@ function mapBalances(raw2, _chainId, smartWallet) {
3256
3665
  blockNumber: provenance.blockNumber === void 0 ? void 0 : String(provenance.blockNumber),
3257
3666
  positionsStatus: Array.isArray(portfolio.positions) ? "available" : "unavailable"
3258
3667
  })),
3259
- ...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 } : {}
3260
3711
  };
3261
3712
  }
3262
3713
  function sumTokenValues(tokens) {
@@ -3420,12 +3871,24 @@ function mapEntries(rawEntries, chainId) {
3420
3871
  const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
3421
3872
  const rawAmount2 = entry?.positions?.[0]?.amount;
3422
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;
3423
3878
  const mappedPositions = positions.filter((p) => p.protocol_name).map((p) => ({
3424
3879
  protocol: p.protocol_name,
3425
3880
  pool: p.pool ?? void 0,
3426
3881
  tokenSymbol: p.token_symbol ?? void 0,
3427
- 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
3428
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;
3429
3892
  return {
3430
3893
  agent: "zyfai",
3431
3894
  action: mappedAction,
@@ -3438,12 +3901,7 @@ function mapEntries(rawEntries, chainId) {
3438
3901
  txHashes: entry.transactionHash ? [entry.transactionHash] : [],
3439
3902
  chainId: entry.chainId,
3440
3903
  tokenSymbol: entry?.positions?.[0]?.token_symbol,
3441
- amount: hasAmount ? String(
3442
- hexToDecimal(
3443
- rawAmount2,
3444
- tokenDecimals(entry?.positions?.[0]?.token_symbol)
3445
- )
3446
- ) : void 0
3904
+ amount: transactionAmount
3447
3905
  }
3448
3906
  ],
3449
3907
  rebalanceLog
@@ -3645,9 +4103,9 @@ var isJwtExpired = (token) => {
3645
4103
  const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
3646
4104
  const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
3647
4105
  const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
3648
- const claims = JSON.parse(decoded);
3649
- if (typeof claims.exp !== "number") return false;
3650
- 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;
3651
4109
  } catch {
3652
4110
  return false;
3653
4111
  }
@@ -3888,7 +4346,7 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
3888
4346
  }
3889
4347
 
3890
4348
  // src/lib/agent-accounts.ts
3891
- var SCOPE4 = "agent-accounts";
4349
+ var SCOPE5 = "agent-accounts";
3892
4350
  var REQUEST_TIMEOUT_MS3 = 5e3;
3893
4351
  function compact3(obj) {
3894
4352
  const out = {};
@@ -3958,7 +4416,7 @@ async function fetchAgentAccounts(input) {
3958
4416
  signal
3959
4417
  });
3960
4418
  if (!res.ok) {
3961
- debugLog(SCOPE4, `fetch rejected with ${res.status}`);
4419
+ debugLog(SCOPE5, `fetch rejected with ${res.status}`);
3962
4420
  return null;
3963
4421
  }
3964
4422
  const data = unwrap3(await res.json());
@@ -3973,7 +4431,7 @@ async function fetchAgentAccounts(input) {
3973
4431
  cancel();
3974
4432
  }
3975
4433
  } catch (err) {
3976
- debugLog(SCOPE4, `fetch failed: ${describeError4(err)}`);
4434
+ debugLog(SCOPE5, `fetch failed: ${describeError4(err)}`);
3977
4435
  return null;
3978
4436
  }
3979
4437
  }
@@ -4004,7 +4462,7 @@ async function reportAgentAccount(input) {
4004
4462
  signal
4005
4463
  });
4006
4464
  if (!res.ok) {
4007
- debugLog(SCOPE4, `report rejected with ${res.status}`);
4465
+ debugLog(SCOPE5, `report rejected with ${res.status}`);
4008
4466
  return null;
4009
4467
  }
4010
4468
  const data = unwrap3(await res.json());
@@ -4016,7 +4474,7 @@ async function reportAgentAccount(input) {
4016
4474
  cancel();
4017
4475
  }
4018
4476
  } catch (err) {
4019
- debugLog(SCOPE4, `report failed: ${describeError4(err)}`);
4477
+ debugLog(SCOPE5, `report failed: ${describeError4(err)}`);
4020
4478
  return null;
4021
4479
  }
4022
4480
  }
@@ -4175,6 +4633,17 @@ var ZYFAI_LEDGER_IDLE_CACHE_MS = 6e4;
4175
4633
  var ZYFAI_LEDGER_REUSE_MS = 6e4;
4176
4634
  var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
4177
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}$/;
4178
4647
  function settleWithin(value, ms) {
4179
4648
  let timer;
4180
4649
  const limit = new Promise((resolve) => {
@@ -4184,6 +4653,28 @@ function settleWithin(value, ms) {
4184
4653
  if (timer) clearTimeout(timer);
4185
4654
  });
4186
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
+ }
4187
4678
  function belowMinimumCeiling(rejection, token, decimals) {
4188
4679
  if ((rejection.symbol === "ETH" ? "WETH" : rejection.symbol) !== token) return void 0;
4189
4680
  const ceiling = (display) => {
@@ -4207,6 +4698,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
4207
4698
  referralSource;
4208
4699
  accountReporting;
4209
4700
  id = "zyfai";
4701
+ /** This adapter can hand its withdrawals to our server (part 4). */
4702
+ supportsServerTasks = true;
4210
4703
  supportedChainIds = [8453, 42161, 1];
4211
4704
  supportedAssets = [
4212
4705
  {
@@ -4283,6 +4776,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
4283
4776
  /** A ledger read slower than this counts as "no ledger information" for that read. */
4284
4777
  ledgerBudgetMs = LEDGER_READ_BUDGET_MS;
4285
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();
4286
4791
  /** A replaced wallet session gets its own vendor client. In-flight calls
4287
4792
  * keep their original client; authentication must never change owners under
4288
4793
  * an operation that is waiting for provider reads or an HTTP response. */
@@ -4493,10 +4998,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
4493
4998
  * Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
4494
4999
  * cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
4495
5000
  * we drop the cache and try again — the second attempt has no cache entry,
4496
- * so it falls through to a real SIWE prompt. Zyfai labels every HTTP 401 with
4497
- * an `"Unauthorized: Invalid API key"` error so we can't distinguish revoked
4498
- * JWT from a misconfigured API key; we retry once either way, and if the
4499
- * 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.
4500
5006
  */
4501
5007
  async connectAccountWithRetry(provider, chainId, executionSdk = this.sdk) {
4502
5008
  try {
@@ -4533,9 +5039,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
4533
5039
  }
4534
5040
  }
4535
5041
  /** A rejected execution session must not be reused by the next foreground attempt. */
4536
- invalidateRejectedWithdrawalSession(error, state, chainId, authToken) {
5042
+ invalidateRejectedWithdrawalSession(error, state, chainId, authToken, httpStatus) {
4537
5043
  const message = error instanceof Error ? error.message : "";
4538
- 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) {
4539
5050
  const internals = this.sdk;
4540
5051
  if (typeof authToken !== "string" || internals.httpClient.authToken !== authToken || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== chainId) return;
4541
5052
  clearSession(state.walletAddress, chainId);
@@ -5143,7 +5654,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
5143
5654
  await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
5144
5655
  return redeployed.address;
5145
5656
  }
5146
- async deposit(state, chainId, amount, asset, depositCallback, onProgress, onBeforeSubmission) {
5657
+ async deposit(state, chainId, amount, asset, depositCallback, onProgress, onBeforeSubmission, context) {
5147
5658
  const validChainId = isValidChainId(chainId);
5148
5659
  const owner = state.walletAddress;
5149
5660
  const provider = state.provider;
@@ -5158,6 +5669,12 @@ var ZyfaiAgent = class _ZyfaiAgent {
5158
5669
  assertCurrent();
5159
5670
  const smartWallet = await this.ensureWalletDeployed(owner, validChainId);
5160
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 ?? {};
5161
5678
  notifyFunding(onProgress, {
5162
5679
  agentId: this.id,
5163
5680
  chainId,
@@ -5167,7 +5684,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
5167
5684
  intendedAmount: amount,
5168
5685
  transfer: "pending",
5169
5686
  registration: "pending",
5170
- delivery: "not-applicable"
5687
+ delivery: "not-applicable",
5688
+ ...mark
5171
5689
  });
5172
5690
  const txHash = depositCallback ? await depositCallback(smartWallet, validChainId, amount) : await submitZyfaiDeposit({
5173
5691
  sdk: executionSdk,
@@ -5186,7 +5704,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
5186
5704
  intendedAmount: amount,
5187
5705
  transfer: "pending",
5188
5706
  registration: "pending",
5189
- delivery: "not-applicable"
5707
+ delivery: "not-applicable",
5708
+ ...mark
5190
5709
  }) : void 0
5191
5710
  });
5192
5711
  let evidence = {
@@ -5199,7 +5718,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
5199
5718
  txHash,
5200
5719
  transfer: "pending",
5201
5720
  registration: "pending",
5202
- delivery: "not-applicable"
5721
+ delivery: "not-applicable",
5722
+ ...mark
5203
5723
  };
5204
5724
  notifyFunding(onProgress, evidence);
5205
5725
  try {
@@ -5219,11 +5739,32 @@ var ZyfaiAgent = class _ZyfaiAgent {
5219
5739
  throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
5220
5740
  if (evidence.transfer !== "confirmed") return { txHash, smartWallet, amount, evidence };
5221
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
+ }
5222
5762
  try {
5223
5763
  assertCurrent();
5224
5764
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
5225
- await (tokenAddress ? executionSdk.logDeposit(validChainId, txHash, amount, tokenAddress) : executionSdk.logDeposit(validChainId, txHash, amount));
5226
- 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 } : {} };
5227
5768
  } catch {
5228
5769
  evidence = { ...evidence, registration: "unknown" };
5229
5770
  this.notePendingRegistration(owner, validChainId, txHash, Date.now(), asset);
@@ -5343,10 +5884,69 @@ var ZyfaiAgent = class _ZyfaiAgent {
5343
5884
  throw unavailable();
5344
5885
  }
5345
5886
  }
5346
- 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) {
5347
5922
  if (amount !== void 0 && (typeof amount !== "string" || !/^[0-9]+$/.test(amount) || BigInt(amount) <= 0n))
5348
5923
  throw new OwneyError("WITHDRAW_INVALID_AMOUNT", "Withdrawal amount must be a positive integer in token base units.", void 0, this.id);
5349
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
+ });
5350
5950
  try {
5351
5951
  await this.ensureConnected(state, validChainId);
5352
5952
  } catch (error) {
@@ -5383,15 +5983,35 @@ var ZyfaiAgent = class _ZyfaiAgent {
5383
5983
  assertCurrent();
5384
5984
  assertOperation?.();
5385
5985
  };
5386
- 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);
5387
6007
  const authToken = internals.httpClient?.authToken;
5388
6008
  let raw2;
5389
- let headRead = Promise.resolve(void 0);
5390
6009
  try {
5391
6010
  for (let attempt = 0; attempt < 2; attempt++) {
5392
6011
  try {
5393
6012
  assertCurrent();
5394
6013
  headRead = this.readPayoutSearchHead(validChainId);
6014
+ settledHead = void 0;
5395
6015
  raw2 = await executionSdk.withdrawFunds(owner, validChainId, amount, token);
5396
6016
  break;
5397
6017
  } catch (error) {
@@ -5420,7 +6040,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
5420
6040
  assertSubmission();
5421
6041
  await this.recoverWithdrawalSession(state, validChainId, token, owner);
5422
6042
  observation.stop();
5423
- observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
6043
+ observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission, checkpoint2);
5424
6044
  }
5425
6045
  }
5426
6046
  if (!raw2) throw new OwneyError(
@@ -5431,7 +6051,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
5431
6051
  );
5432
6052
  } catch (error) {
5433
6053
  if (staleSessionError && observation.attempted() === false) error = staleSessionError;
5434
- this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
6054
+ this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken, observation.request()?.httpStatus);
5435
6055
  if (observation.attempted() !== void 0) {
5436
6056
  const failed = observation.attempted() === false || observation.rejected();
5437
6057
  notifyFunding(onProgress, {
@@ -5485,7 +6105,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
5485
6105
  if (!raw2.txHash) {
5486
6106
  const acceptedAt = Date.now();
5487
6107
  const agentWallet = this.smartWalletCache.get(`${owner.toLowerCase()}_${validChainId}`);
5488
- const head = await settleWithin(headRead, PAYOUT_SEARCH_HEAD_LIMIT_MS);
6108
+ const head = await searchHead();
5489
6109
  evidence = {
5490
6110
  ...evidence,
5491
6111
  ...agentWallet ? { agentWallet } : {},
@@ -5497,7 +6117,456 @@ var ZyfaiAgent = class _ZyfaiAgent {
5497
6117
  this.ledgerReads.clear();
5498
6118
  return { ...mapWithdraw(raw2), evidence };
5499
6119
  }
5500
- /** 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). */
5501
6570
  async retryDepositRegistration(state, evidence) {
5502
6571
  const executionSdk = this.sdk;
5503
6572
  const internals = executionSdk;
@@ -5508,11 +6577,49 @@ var ZyfaiAgent = class _ZyfaiAgent {
5508
6577
  throw new OwneyError("AGENT_API_ERROR", "Deposit transfer must be verified before registration recovery.");
5509
6578
  }
5510
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
+ }
5511
6593
  const tokenAddress = evidence.token === "WETH" ? WETH_ADDRESS_BY_CHAIN[cid] : void 0;
5512
- 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);
5513
6596
  this.pendingRegistrations.get(`${evidence.walletAddress.toLowerCase()}:${cid}`)?.delete(evidence.txHash.toLowerCase());
5514
6597
  this.ledgerReads.clear();
5515
- 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
+ }
6611
+ }
6612
+ /** Reads a withdrawal's lifecycle by its id. Never signs, prompts or authenticates:
6613
+ * it uses only a session the vendor client already holds, and any failure is `null`. */
6614
+ async getWithdrawalLifecycle(state, withdrawalId) {
6615
+ const internals = this.sdk;
6616
+ const client = internals.httpClient;
6617
+ if (!client?.authToken || typeof client.get !== "function" || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase()) return null;
6618
+ try {
6619
+ return mapWithdrawalLifecycle(await client.get(`/users/withdrawals/${encodeURIComponent(withdrawalId)}`));
6620
+ } catch {
6621
+ return null;
6622
+ }
5516
6623
  }
5517
6624
  // --- IAgent: Portfolio reads ---
5518
6625
  async getFundingWallet(state, chainId) {
@@ -5742,16 +6849,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
5742
6849
  }
5743
6850
  const now = Date.now();
5744
6851
  const pending2 = [...this.pendingRegistrations.get(scope)?.values() ?? []].map((entry) => entry.at);
5745
- const active2 = zyfaiHoldActive({
6852
+ const active = zyfaiHoldActive({
5746
6853
  movements,
5747
6854
  pendingRegistrations: pending2,
5748
6855
  lastCheckTimestamp: raw2?.data?.lastCheckTimestamp,
5749
6856
  lastLogDate: raw2?.data?.lastLogDate?.[String(chainId)],
5750
6857
  now
5751
6858
  });
5752
- if (active2) this.holdActiveScopes.add(scope);
6859
+ if (active) this.holdActiveScopes.add(scope);
5753
6860
  else this.holdActiveScopes.delete(scope);
5754
- if (!active2) {
6861
+ if (!active) {
5755
6862
  this.lastGoodEarnings.set(key2, raw2);
5756
6863
  return { value: raw2, active: false };
5757
6864
  }
@@ -6719,7 +7826,7 @@ function actionEntry(action) {
6719
7826
  rebalanceLog: []
6720
7827
  };
6721
7828
  }
6722
- var TX_HASH = /^0x[a-fA-F0-9]{64}$/;
7829
+ var TX_HASH3 = /^0x[a-fA-F0-9]{64}$/;
6723
7830
  function allocationLegs(value, context, requireAmount) {
6724
7831
  if (!Array.isArray(value)) return void 0;
6725
7832
  const legs = [];
@@ -6748,7 +7855,7 @@ function allocationEntry(action, context) {
6748
7855
  if (action.actionType.toLowerCase() !== "autoseek") return void 0;
6749
7856
  const hash2 = action.details?.depositTransactionHash;
6750
7857
  const withdrawHash = action.details?.withdrawTransactionHash;
6751
- if (typeof hash2 !== "string" || !TX_HASH.test(hash2)) return null;
7858
+ if (typeof hash2 !== "string" || !TX_HASH3.test(hash2)) return null;
6752
7859
  const deposits = allocationLegs(action.details?.deposits, context, true);
6753
7860
  const withdrawals = action.details?.withdrawals == null ? [] : allocationLegs(action.details.withdrawals, context, false);
6754
7861
  if (!deposits?.length || !withdrawals) return null;
@@ -6761,7 +7868,7 @@ function allocationEntry(action, context) {
6761
7868
  }));
6762
7869
  const amountRaw = deposits.reduce((total, leg) => total + leg.rawAmount, 0n);
6763
7870
  const rebalance = withdrawals.length > 0;
6764
- 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];
6765
7872
  const source = withdrawals.length === 1 ? withdrawals[0] : {
6766
7873
  protocol: [...new Set(withdrawals.map((leg) => leg.protocol))].join(" + "),
6767
7874
  pool: withdrawals.map((leg) => leg.pool).filter(Boolean).join(" + ") || void 0
@@ -7145,6 +8252,7 @@ function operationHash(operation) {
7145
8252
  }
7146
8253
  });
7147
8254
  }
8255
+ var YIELDSEEKER_WITHDRAW_SERVER_SUBMIT = 1;
7148
8256
  function sponsorError(message, safeToFallback, details = {}) {
7149
8257
  return new OwneyError(
7150
8258
  "SPONSOR_REQUEST_FAILED",
@@ -7167,6 +8275,22 @@ function assertIntentFresh(expiresAt) {
7167
8275
  { failureCode: "SPONSOR_INTENT_EXPIRED" }
7168
8276
  );
7169
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
+ }
7170
8294
  function providerReason(value) {
7171
8295
  if (typeof value !== "string") return void 0;
7172
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);
@@ -7248,6 +8372,12 @@ async function checkpoint(callback, pending2) {
7248
8372
  );
7249
8373
  }
7250
8374
  }
8375
+ function reportAccepted(onAccepted, accepted) {
8376
+ try {
8377
+ onAccepted?.(accepted);
8378
+ } catch {
8379
+ }
8380
+ }
7251
8381
  function finalPaymasterFields(value) {
7252
8382
  const result = {};
7253
8383
  for (const key2 of [
@@ -7326,15 +8456,57 @@ async function requestIntent(input) {
7326
8456
  url.searchParams.set("intent", intent.intent);
7327
8457
  return { intent, rpcUrl: url.toString() };
7328
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
+ }
7329
8506
  async function rpc(url, method, params, phase) {
7330
8507
  let response;
7331
8508
  try {
7332
- response = await fetch(url, {
7333
- method: "POST",
7334
- headers: { "content-type": "application/json" },
7335
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
7336
- signal: AbortSignal.timeout(15e3)
7337
- });
8509
+ response = await postRpc(url, method, params, RPC_TIMEOUT_MS);
7338
8510
  } catch {
7339
8511
  throw sponsorError(
7340
8512
  "Withdrawal sponsorship request status is unknown.",
@@ -7348,43 +8520,37 @@ async function rpc(url, method, params, phase) {
7348
8520
  );
7349
8521
  }
7350
8522
  const payload = await response.json().catch(() => null);
7351
- if (!response.ok || payload?.error || payload?.result === void 0) {
7352
- const rejection = payload?.error;
7353
- const diagnostics = rpcDiagnostics(rejection?.data);
7354
- const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
7355
- const details = {
7356
- statusCode: response.status,
7357
- rpcCode: rejection?.code,
7358
- rpcMethod: method,
7359
- phase,
7360
- providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
7361
- failureCode: "RPC_REJECTED",
7362
- ...diagnostics
7363
- };
7364
- if (phase === "prepare" && executionReverted)
7365
- throw new OwneyError(
7366
- "WITHDRAW_FAILED",
7367
- "Yieldseeker withdrawal simulation reverted before submission.",
7368
- {
7369
- ...details,
7370
- failureCode: "SPONSOR_EXECUTION_REVERTED",
7371
- safeToFallback: false,
7372
- rejectedBeforeSubmission: true
7373
- },
7374
- "yieldseeker"
7375
- );
7376
- 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 ?? ""));
7377
- throw sponsorError(
7378
- phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
7379
- phase === "prepare" || definitiveRejection,
7380
- {
7381
- ...details,
7382
- ...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
7383
- }
7384
- );
7385
- }
8523
+ if (!response.ok || payload?.error || payload?.result === void 0)
8524
+ throw rpcRejection(response.status, payload, method, phase);
7386
8525
  return payload.result;
7387
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
+ }
7388
8554
  async function waitForTransaction(client, txHash) {
7389
8555
  const receipt = await client.waitForTransactionReceipt({ hash: txHash, confirmations: 1 });
7390
8556
  if (receipt.status !== "success")
@@ -7466,6 +8632,7 @@ async function submitEoa(input) {
7466
8632
  operation = { ...operation, signature };
7467
8633
  await assertOwnerSession(input.state, input.owner);
7468
8634
  assertIntentFresh(input.expiresAt);
8635
+ assertPaymasterFresh(operation.paymasterAndData);
7469
8636
  const userOperation = {
7470
8637
  sender: operation.sender,
7471
8638
  nonce: operation.nonce,
@@ -7482,18 +8649,19 @@ async function submitEoa(input) {
7482
8649
  input.assertCurrent?.();
7483
8650
  await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
7484
8651
  input.assertCurrent?.();
7485
- const submittedHash = await rpc(
7486
- input.rpcUrl,
7487
- "eth_sendUserOperation",
7488
- [operation, ENTRY_POINT_V06],
7489
- "send"
7490
- );
7491
- if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
7492
- throw sponsorError(
7493
- "Bundler returned a different UserOperation hash.",
7494
- false,
7495
- { userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
7496
- );
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
+ }
7497
8665
  const deadline = Date.now() + OBSERVATION_WINDOW_MS;
7498
8666
  let lastReadError;
7499
8667
  for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
@@ -7526,6 +8694,7 @@ async function submitEoa(input) {
7526
8694
  userOpHash,
7527
8695
  phase: "observe",
7528
8696
  failureCode: "RECEIPT_UNRESOLVED",
8697
+ ...serverSubmit ? { serverSubmit } : {},
7529
8698
  ...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
7530
8699
  }
7531
8700
  );
@@ -7586,6 +8755,7 @@ async function submitSmart(input) {
7586
8755
  { callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
7587
8756
  );
7588
8757
  savePending(input.onPending, { callsId });
8758
+ reportAccepted(input.onAccepted, { callsId });
7589
8759
  const deadline = Date.now() + OBSERVATION_WINDOW_MS;
7590
8760
  for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
7591
8761
  let status;
@@ -7879,6 +9049,9 @@ var YieldseekerAgent = class {
7879
9049
  endpointReadFailures = /* @__PURE__ */ new Map();
7880
9050
  reconcileUntil = /* @__PURE__ */ new Map();
7881
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();
7882
9055
  activityRefreshUntil = /* @__PURE__ */ new Map();
7883
9056
  // null means a confirmed movement has no reliable baseline. Do not guess
7884
9057
  // settlement; keep slow reconciliation after the fast window expires.
@@ -8285,6 +9458,7 @@ var YieldseekerAgent = class {
8285
9458
  this.endpointReadFailures.clear();
8286
9459
  this.reconcileUntil.clear();
8287
9460
  this.allocationRefresh.clear();
9461
+ this.forceNextSnapshot.clear();
8288
9462
  this.activityRefreshUntil.clear();
8289
9463
  this.snapshotMovements.clear();
8290
9464
  this.movementEpochs.clear();
@@ -8398,7 +9572,7 @@ var YieldseekerAgent = class {
8398
9572
  notifyFunding(onProgress, evidence);
8399
9573
  return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
8400
9574
  }
8401
- async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation) {
9575
+ async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation, submission) {
8402
9576
  let ownerTransferAttempted = false;
8403
9577
  let evidence = {
8404
9578
  agentId: this.id,
@@ -8411,12 +9585,23 @@ var YieldseekerAgent = class {
8411
9585
  delivery: "pending"
8412
9586
  };
8413
9587
  try {
8414
- return await this.withdrawInternal(state, chainId, asset, amount, (next) => {
8415
- evidence = { ...evidence, ...next };
8416
- notifyFunding(onProgress, evidence);
8417
- }, () => {
8418
- ownerTransferAttempted = true;
8419
- }, 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
+ );
8420
9605
  } catch (error) {
8421
9606
  if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") {
8422
9607
  this.withdrawalVersionCache.clear();
@@ -8432,7 +9617,7 @@ var YieldseekerAgent = class {
8432
9617
  throw error;
8433
9618
  }
8434
9619
  }
8435
- 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) {
8436
9621
  this.assertChain(chainId);
8437
9622
  this.assertAsset(asset);
8438
9623
  if (amount !== void 0 && BigInt(amount) <= 0n) {
@@ -8564,13 +9749,13 @@ var YieldseekerAgent = class {
8564
9749
  const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
8565
9750
  const verifiedPositions = [];
8566
9751
  if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
8567
- let active2 = true;
9752
+ let active = true;
8568
9753
  const checks = this.readVaultShares(values.map((position2) => position2.vaultOption), agentWallet, blockNumber).catch(() => []);
8569
9754
  const reads = values.map(async (position2, index) => {
8570
9755
  const address = (0, import_viem17.getAddress)(position2.vaultOption.address);
8571
9756
  try {
8572
9757
  const group = (await checks).slice(index * 2, index * 2 + 2);
8573
- 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;
8574
9759
  this.assertSession(generation, chainId, asset);
8575
9760
  const [underlying, shares] = group.map((value) => value.result);
8576
9761
  if (typeof underlying !== "string" || !(0, import_viem17.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
@@ -8583,7 +9768,7 @@ var YieldseekerAgent = class {
8583
9768
  shares,
8584
9769
  blockNumber
8585
9770
  );
8586
- if (!active2 || value === void 0) return null;
9771
+ if (!active || value === void 0) return null;
8587
9772
  this.assertSession(generation, chainId, asset);
8588
9773
  verified.withdrawableAssetsRaw = value.toString();
8589
9774
  verifiedPositions.push(verified);
@@ -8600,7 +9785,7 @@ var YieldseekerAgent = class {
8600
9785
  args: [shares],
8601
9786
  blockNumber
8602
9787
  });
8603
- if (!active2 || typeof value !== "bigint" || value < 0n) return null;
9788
+ if (!active || typeof value !== "bigint" || value < 0n) return null;
8604
9789
  this.assertSession(generation, chainId, asset);
8605
9790
  if (value > 0n && !positiveValue) {
8606
9791
  positiveValue = true;
@@ -8628,19 +9813,20 @@ var YieldseekerAgent = class {
8628
9813
  }
8629
9814
  });
8630
9815
  const result = await boundedVerification(Promise.all(reads), timeoutMs);
8631
- active2 = false;
9816
+ active = false;
8632
9817
  this.assertSession(generation, chainId, asset);
8633
9818
  return {
8634
9819
  verified: result !== void 0 && result.every((position2) => position2 !== null),
8635
9820
  positions: [...verifiedPositions]
8636
9821
  };
8637
9822
  };
8638
- const refreshPositions = async () => {
9823
+ const refreshPositions = async (promptless = false) => {
8639
9824
  this.assertSession(generation, chainId, asset);
8640
9825
  const response = await this.walletRequest(
8641
9826
  state,
8642
9827
  chainId,
8643
- this.agentPath(context, "vault-positions")
9828
+ this.agentPath(context, "vault-positions"),
9829
+ { promptless }
8644
9830
  );
8645
9831
  this.assertSession(generation, chainId, asset);
8646
9832
  if (!Array.isArray(response?.vaultPositions)) throw this.invalidResponse("vault positions");
@@ -8871,6 +10057,8 @@ var YieldseekerAgent = class {
8871
10057
  agentWallet: (0, import_viem17.getAddress)(context.wallet.walletAddress),
8872
10058
  asset: metadata.address,
8873
10059
  callData: data,
10060
+ // The EOA send opts in to the server-owned send with it.
10061
+ operationId,
8874
10062
  onPending: (pending2) => {
8875
10063
  submissionEvidence = { ...submissionEvidence, ...pending2 };
8876
10064
  notifyFunding(onProgress, submissionEvidence);
@@ -8879,6 +10067,11 @@ var YieldseekerAgent = class {
8879
10067
  assertOperation?.();
8880
10068
  await onBeforeSubmission?.({ ...base7, ...pending2 });
8881
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);
8882
10075
  }
8883
10076
  });
8884
10077
  txHash = submitted.txHash;
@@ -8895,7 +10088,10 @@ var YieldseekerAgent = class {
8895
10088
  value: "0",
8896
10089
  chainId
8897
10090
  },
8898
- (hash2) => notifyFunding(onProgress, { ...base7, txHash: hash2 }),
10091
+ (hash2) => {
10092
+ submissionEvidence = { ...submissionEvidence, txHash: hash2, ownerAuthorization: "accepted" };
10093
+ notifyFunding(onProgress, submissionEvidence);
10094
+ },
8899
10095
  (value) => {
8900
10096
  receipt = value;
8901
10097
  },
@@ -8926,7 +10122,9 @@ var YieldseekerAgent = class {
8926
10122
  const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
8927
10123
  const knownAtPayout = new Set(knownPositions.keys());
8928
10124
  const [catalogVerified, knownProof] = await Promise.all([
8929
- 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) => {
8930
10128
  rememberPositions(values);
8931
10129
  return true;
8932
10130
  }).catch(() => false)),
@@ -8971,7 +10169,8 @@ var YieldseekerAgent = class {
8971
10169
  );
8972
10170
  const confirmedAmount = evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) ? BigInt(evidence.confirmedAmount) : void 0;
8973
10171
  const feeIncrease = ownerFunds.fees - initialFunds.fees;
8974
- const feeAdjustment = amount !== void 0 && receipt && initialFunds.blockNumber !== void 0 && latestUnwindBlock !== void 0 && initialFunds.blockNumber <= latestUnwindBlock && receipt.blockNumber >= latestUnwindBlock && evidence.transfer === "confirmed" && evidence.delivery === "confirmed" && confirmedAmount === payout && feeIncrease > 0n && feeIncrease <= maxFee && requested - payout === feeIncrease ? { grossAmount: requested.toString(), feeAmount: feeIncrease.toString() } : void 0;
10172
+ const feeShortfall = requested - payout;
10173
+ const feeAdjustment = amount !== void 0 && receipt && initialFunds.blockNumber !== void 0 && latestUnwindBlock !== void 0 && initialFunds.blockNumber <= latestUnwindBlock && receipt.blockNumber >= latestUnwindBlock && evidence.transfer === "confirmed" && evidence.delivery === "confirmed" && confirmedAmount === payout && feeIncrease > 0n && feeIncrease <= maxFee && feeShortfall > 0n && feeShortfall <= feeIncrease ? { grossAmount: requested.toString(), feeAmount: feeShortfall.toString() } : void 0;
8975
10174
  return {
8976
10175
  txHash,
8977
10176
  type: amount === void 0 && !completion ? "full" : "partial",
@@ -9033,14 +10232,14 @@ var YieldseekerAgent = class {
9033
10232
  }
9034
10233
  /** Recheck MAX without re-unwinding, paying out, or requesting a signature. */
9035
10234
  async verifyWithdrawalCompletion(state, evidence) {
9036
- const unknown = (reason2) => ({ status: "unavailable", reason: reason2 });
10235
+ const unknown2 = (reason2) => ({ status: "unavailable", reason: reason2 });
9037
10236
  const proof = evidence.maxCompletion;
9038
- 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");
9039
10238
  this.assertAsset(evidence.token);
9040
10239
  const asset = evidence.token;
9041
10240
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
9042
10241
  const signature = this.auth.peekToken(state, evidence.chainId);
9043
- if (!signature) return unknown("AUTHENTICATION_REQUIRED");
10242
+ if (!signature) return unknown2("AUTHENTICATION_REQUIRED");
9044
10243
  let reason = "CATALOG_UNAVAILABLE";
9045
10244
  try {
9046
10245
  return await bounded(async () => {
@@ -9048,7 +10247,7 @@ var YieldseekerAgent = class {
9048
10247
  `/users/${encodeURIComponent(proof.userId)}/agents/${encodeURIComponent(proof.agentId)}/vault-positions`,
9049
10248
  { signature }
9050
10249
  );
9051
- if (!Array.isArray(response?.vaultPositions)) return unknown("CATALOG_INVALID");
10250
+ if (!Array.isArray(response?.vaultPositions)) return unknown2("CATALOG_INVALID");
9052
10251
  const positions = this.validateWithdrawalPositions(response.vaultPositions, evidence.chainId, asset);
9053
10252
  const vaults = new Map(proof.vaults.map((address) => [address.toLowerCase(), { address }]));
9054
10253
  positions.forEach((p) => vaults.set(p.vaultOption.address.toLowerCase(), p.vaultOption));
@@ -9056,14 +10255,14 @@ var YieldseekerAgent = class {
9056
10255
  if (needsInventory || [...vaults.values()].some((option) => !option.provider)) {
9057
10256
  const catalog = await this.loadVaultOptions(asset).catch(() => []);
9058
10257
  const eligible = catalog.filter((o) => o.chainId === evidence.chainId && o.assetAddress?.toLowerCase() === metadata.address.toLowerCase() && (0, import_viem17.isAddress)(o.address));
9059
- if (needsInventory && !eligible.length) return unknown("INVENTORY_INCOMPLETE");
10258
+ if (needsInventory && !eligible.length) return unknown2("INVENTORY_INCOMPLETE");
9060
10259
  for (const option of eligible) {
9061
10260
  const key2 = option.address.toLowerCase();
9062
10261
  if (needsInventory || vaults.has(key2)) vaults.set(key2, option);
9063
10262
  }
9064
10263
  }
9065
10264
  if (vaults.size > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT || [...vaults.keys()].some((a) => !(0, import_viem17.isAddress)(a)))
9066
- return unknown("INVENTORY_INVALID");
10265
+ return unknown2("INVENTORY_INVALID");
9067
10266
  reason = "VAULT_PROOF_UNAVAILABLE";
9068
10267
  const blockNumber = BigInt(evidence.blockNumber);
9069
10268
  const options = [...vaults.values()];
@@ -9094,7 +10293,7 @@ var YieldseekerAgent = class {
9094
10293
  };
9095
10294
  }, 8e3, () => new Error("MAX proof read timed out"));
9096
10295
  } catch {
9097
- return unknown(reason);
10296
+ return unknown2(reason);
9098
10297
  }
9099
10298
  }
9100
10299
  async readVaultShares(options, wallet, blockNumber) {
@@ -9175,12 +10374,12 @@ var YieldseekerAgent = class {
9175
10374
  { asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
9176
10375
  this.id
9177
10376
  );
9178
- let active2 = true;
10377
+ let active = true;
9179
10378
  let knownProof;
9180
10379
  const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
9181
10380
  const assertReadCurrent = () => {
9182
10381
  this.assertSession(generation, chainId, context.asset);
9183
- if (!active2 || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
10382
+ if (!active || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
9184
10383
  };
9185
10384
  const read2 = async () => {
9186
10385
  const client = this.getReceiptClient();
@@ -9369,7 +10568,7 @@ var YieldseekerAgent = class {
9369
10568
  if (error instanceof OwneyError) throw error;
9370
10569
  throw unavailable();
9371
10570
  } finally {
9372
- active2 = false;
10571
+ active = false;
9373
10572
  if (timer) clearTimeout(timer);
9374
10573
  }
9375
10574
  }
@@ -9454,6 +10653,7 @@ var YieldseekerAgent = class {
9454
10653
  this.assertAsset(asset);
9455
10654
  const until = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_SNAPSHOT_WINDOW_MS);
9456
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);
9457
10657
  this.invalidateSlot(state, chainId, asset, (key2) => {
9458
10658
  const failure = this.snapshotFailures.get(key2);
9459
10659
  this.advancePortfolioVersion(key2);
@@ -9465,6 +10665,7 @@ var YieldseekerAgent = class {
9465
10665
  }
9466
10666
  this.reconcileUntil.set(key2, Math.max(this.reconcileUntil.get(key2) ?? 0, until));
9467
10667
  this.activityRefreshUntil.set(key2, Math.max(this.activityRefreshUntil.get(key2) ?? 0, activityUntil));
10668
+ if (hash2) this.forceNextSnapshot.add(key2);
9468
10669
  });
9469
10670
  }
9470
10671
  endFundingBalanceRefresh(state, chainId, asset, read2) {
@@ -10300,7 +11501,7 @@ var YieldseekerAgent = class {
10300
11501
  return this.walletRequest(
10301
11502
  state,
10302
11503
  chainId,
10303
- `${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`
11504
+ this.snapshotPath(context, forceRefresh)
10304
11505
  ).then((response) => {
10305
11506
  if (!response?.agentSnapshot) {
10306
11507
  throw this.invalidResponse("agent snapshot");
@@ -10330,6 +11531,16 @@ var YieldseekerAgent = class {
10330
11531
  throw error;
10331
11532
  });
10332
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
+ }
10333
11544
  portfolioSnapshot(state, chainId, context, contextKey) {
10334
11545
  const key2 = `snapshot:${contextKey}`;
10335
11546
  const cached = this.readCache.get(key2);
@@ -10348,6 +11559,7 @@ var YieldseekerAgent = class {
10348
11559
  const settling = inWindow && !this.deferredToLedger(contextKey);
10349
11560
  if (inWindow) this.settlementChecks.set(contextKey, (this.settlementChecks.get(contextKey) ?? 0) + 1);
10350
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);
10351
11563
  return this.requestPortfolioSnapshot(
10352
11564
  state,
10353
11565
  chainId,
@@ -10355,7 +11567,7 @@ var YieldseekerAgent = class {
10355
11567
  contextKey,
10356
11568
  version,
10357
11569
  generation,
10358
- allocationChanged || settling || ready
11570
+ allocationChanged || settling || ready || movementForce
10359
11571
  );
10360
11572
  })
10361
11573
  };
@@ -10805,7 +12017,7 @@ var YieldseekerAgent = class {
10805
12017
  }
10806
12018
  const ttl = this.portfolioCacheMs(contextKey);
10807
12019
  this.settlementChecks.set(contextKey, 1);
10808
- const force = !this.deferredToLedger(contextKey);
12020
+ const force = true;
10809
12021
  await this.cachedRead(
10810
12022
  `snapshot:${contextKey}`,
10811
12023
  ttl,
@@ -10860,13 +12072,22 @@ var YieldseekerAgent = class {
10860
12072
  options.assertCurrent?.();
10861
12073
  return this.api.request(path, { method: options.method, body: options.body, signature: signature2 });
10862
12074
  };
10863
- 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);
10864
12084
  try {
10865
12085
  return await request2(signature);
10866
12086
  } catch (error) {
10867
12087
  if (!(error instanceof YieldseekerApiError)) throw error;
10868
12088
  if (error.providerCode === "NO_USER") throw error;
10869
12089
  if (!error.isAuthenticationError) throw error;
12090
+ if (options.promptless) throw error;
10870
12091
  signature = await this.auth.refreshToken(state, chainId, signature);
10871
12092
  try {
10872
12093
  return await request2(signature);
@@ -11098,6 +12319,16 @@ init_errors();
11098
12319
 
11099
12320
  // src/lib/swap/swap-api.ts
11100
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
+ }
11101
12332
  async function request(baseUrl, apiKey, path, init) {
11102
12333
  const url = `${baseUrl}/api/v1/swap${path}`;
11103
12334
  const controller = new AbortController();
@@ -11110,7 +12341,8 @@ async function request(baseUrl, apiKey, path, init) {
11110
12341
  "Content-Type": "application/json",
11111
12342
  "x-owney-api-key": apiKey
11112
12343
  },
11113
- ...init ? { body: JSON.stringify(init.body) } : {}
12344
+ ...init ? { body: JSON.stringify(init.body) } : {},
12345
+ ...init?.redirect ? { redirect: init.redirect } : {}
11114
12346
  });
11115
12347
  if (!res.ok) {
11116
12348
  const text = await res.text().catch(() => "");
@@ -11143,7 +12375,7 @@ async function request(baseUrl, apiKey, path, init) {
11143
12375
  );
11144
12376
  }
11145
12377
  return json.data;
11146
- }, 2e4, () => new OwneyError(
12378
+ }, init?.timeoutMs ?? 2e4, () => new OwneyError(
11147
12379
  "SWAP_REQUEST_FAILED",
11148
12380
  "Swap request timed out. Its outcome may still need confirmation.",
11149
12381
  { requestTimedOut: true, phase: path === "/order" ? "submit" : "read" }
@@ -11227,8 +12459,9 @@ function createSwapApi(baseUrl, apiKey) {
11227
12459
  /**
11228
12460
  * Builds a Fusion+ order server-side and returns EIP-712 typed data.
11229
12461
  *
11230
- * Only HASHES go over the wire. The preimages never leave the browser —
11231
- * 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.
11232
12465
  */
11233
12466
  buildOrder: (params) => request(baseUrl, apiKey, "/order/build", {
11234
12467
  method: "POST",
@@ -11255,6 +12488,23 @@ function createSwapApi(baseUrl, apiKey) {
11255
12488
  method: "POST",
11256
12489
  body: { orderHash, secret }
11257
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
+ }),
11258
12508
  /**
11259
12509
  * @param chainId present only for a SAME-CHAIN order. That product keys its
11260
12510
  * orders per chain and the Fusion+ endpoint does not know them, so asking
@@ -11433,14 +12683,14 @@ init_errors();
11433
12683
 
11434
12684
  // src/lib/swap/swap.arrival.ts
11435
12685
  init_errors();
11436
- var DEFAULT_TIMEOUT_MS = 18e4;
12686
+ var DEFAULT_TIMEOUT_MS2 = 18e4;
11437
12687
  var DEFAULT_POLL_MS = 4e3;
11438
12688
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
11439
12689
  async function awaitBalanceIncrease(options) {
11440
12690
  const {
11441
12691
  readBalance,
11442
12692
  baseline,
11443
- timeoutMs = DEFAULT_TIMEOUT_MS,
12693
+ timeoutMs = DEFAULT_TIMEOUT_MS2,
11444
12694
  pollMs = DEFAULT_POLL_MS
11445
12695
  } = options;
11446
12696
  const deadline = Date.now() + timeoutMs;
@@ -11537,14 +12787,14 @@ var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
11537
12787
  var DEFAULT_POLL_MS2 = 5e3;
11538
12788
  var MAX_BACKOFF_MS = 3e4;
11539
12789
  var backoffFor = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
11540
- var DEFAULT_TIMEOUT_MS2 = 15 * 60 * 1e3;
12790
+ var DEFAULT_TIMEOUT_MS3 = 15 * 60 * 1e3;
11541
12791
  async function runFusionOrderInternal(deps, options) {
11542
12792
  const {
11543
12793
  orderHash,
11544
12794
  secrets,
11545
12795
  onStage,
11546
12796
  pollIntervalMs = DEFAULT_POLL_MS2,
11547
- timeoutMs = DEFAULT_TIMEOUT_MS2
12797
+ timeoutMs = DEFAULT_TIMEOUT_MS3
11548
12798
  } = options;
11549
12799
  const deadline = deps.now() + timeoutMs;
11550
12800
  let partial = options.hasPartialFill === true;
@@ -11625,7 +12875,7 @@ async function runFusionOrderInternal(deps, options) {
11625
12875
  }
11626
12876
  }
11627
12877
  function runFusionOrder(deps, options) {
11628
- return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options));
12878
+ return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options), { wait: options.waitForWatcher === true });
11629
12879
  }
11630
12880
 
11631
12881
  // src/lib/swap/swap.same-chain-runner.ts
@@ -11633,7 +12883,7 @@ init_errors();
11633
12883
  var DEFAULT_POLL_MS3 = 5e3;
11634
12884
  var MAX_BACKOFF_MS2 = 3e4;
11635
12885
  var backoffFor2 = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS2);
11636
- var DEFAULT_TIMEOUT_MS3 = 3 * 60 * 1e3;
12886
+ var DEFAULT_TIMEOUT_MS4 = 3 * 60 * 1e3;
11637
12887
  function explain(status) {
11638
12888
  switch (status) {
11639
12889
  case "expired":
@@ -11657,7 +12907,7 @@ async function runSameChainOrderInternal(deps, options) {
11657
12907
  orderHash,
11658
12908
  onStage,
11659
12909
  pollIntervalMs = DEFAULT_POLL_MS3,
11660
- timeoutMs = DEFAULT_TIMEOUT_MS3
12910
+ timeoutMs = DEFAULT_TIMEOUT_MS4
11661
12911
  } = options;
11662
12912
  const deadline = deps.now() + timeoutMs;
11663
12913
  let partial = options.hasPartialFill === true;
@@ -11710,7 +12960,7 @@ async function runSameChainOrderInternal(deps, options) {
11710
12960
  }
11711
12961
  }
11712
12962
  function runSameChainOrder(deps, options) {
11713
- return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options));
12963
+ return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options), { wait: options.waitForWatcher === true });
11714
12964
  }
11715
12965
 
11716
12966
  // src/lib/swap/swap.secret-store.ts
@@ -11735,6 +12985,21 @@ function saveOrder(order) {
11735
12985
  return false;
11736
12986
  }
11737
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
+ }
11738
13003
  function clearOrder(orderHash) {
11739
13004
  const store = storage5();
11740
13005
  if (!store) return;
@@ -11813,10 +13078,10 @@ async function affordableAmount(deps, quoted, reserve = 0n) {
11813
13078
  }
11814
13079
  async function executeSwap(deps, options) {
11815
13080
  const { quote, walletAddress, onStage } = options;
11816
- let active2 = true;
13081
+ let active = true;
11817
13082
  const original = deps;
11818
13083
  const assertActive = () => {
11819
- 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.");
11820
13085
  };
11821
13086
  const call = async (work, write = false) => {
11822
13087
  assertActive();
@@ -11877,7 +13142,7 @@ async function executeSwap(deps, options) {
11877
13142
  }
11878
13143
  return { received: arrived.toString(), ...result };
11879
13144
  } finally {
11880
- active2 = false;
13145
+ active = false;
11881
13146
  }
11882
13147
  }
11883
13148
  async function runClassic(deps, options) {
@@ -11996,6 +13261,7 @@ async function runSameChainFusion(deps, options, walletAddress) {
11996
13261
  }
11997
13262
  await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
11998
13263
  orderHash: built.orderHash,
13264
+ waitForWatcher: true,
11999
13265
  ...onStage ? { onStage } : {}
12000
13266
  });
12001
13267
  return { orderHash: built.orderHash, spent: amount.toString() };
@@ -12064,67 +13330,118 @@ async function runFusion(deps, options, walletAddress) {
12064
13330
  ...direction ? { direction } : {},
12065
13331
  ...permit ? { permit } : {}
12066
13332
  });
12067
- const durable = saveOrder({
12068
- orderHash: built.orderHash,
12069
- walletAddress,
12070
- secrets,
12071
- srcChainId: quote.src.chainId,
12072
- srcSymbol: quote.src.symbol,
12073
- dstChainId: quote.dst.chainId,
12074
- dstSymbol: quote.dst.symbol,
12075
- amount: amount.toString(),
12076
- createdAt: Date.now()
12077
- });
12078
- debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
12079
- let signature;
13333
+ const release = claimOrder(built.orderHash);
12080
13334
  try {
12081
- onStage?.("signing");
12082
- await deps.ensureChain(quote.src.chainId);
12083
- signature = await awaitWalletPrompt(() => deps.signTypedData(built.typedData), "sign the swap");
12084
- options.assertCurrent?.();
12085
- options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
12086
- } 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
+ }
12087
13402
  clearOrder(built.orderHash);
12088
- throw error;
12089
- }
12090
- 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) {
12091
13421
  try {
12092
- await deps.api.submitOrder({
13422
+ const answer = await bounded(() => deps.api.handoffSecrets({
13423
+ operation: handoff.operation,
13424
+ orderHash: built.orderHash,
12093
13425
  srcChainId: quote.src.chainId,
12094
- // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
12095
- // the envelope here gets a bare 500 from the relayer.
13426
+ dstChainId: quote.dst.chainId,
12096
13427
  order: built.order,
12097
- signature,
12098
- quoteId: built.quoteId,
12099
- // Single-fill orders must NOT carry secretHashes — the relayer rejects
12100
- // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
12101
- // order's hashlock, so repeating it here is redundant, and only a
12102
- // multi-fill order (a Merkle tree of hashes) needs them listed.
12103
- ...secretHashes.length > 1 ? { secretHashes } : {},
12104
- ...built.extension ? { extension: built.extension } : {}
12105
- });
12106
- } catch (error) {
12107
- throw submissionUnknown(error, built.orderHash);
12108
- }
12109
- debugLog("owney-sdk", "swap: order submitted, polling escrows");
12110
- try {
12111
- options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
12112
- } catch {
12113
- }
12114
- try {
12115
- await runFusionOrder(deps.runner, {
12116
- orderHash: built.orderHash,
12117
- secrets,
12118
- ...onStage ? { onStage } : {}
12119
- });
12120
- } catch (error) {
12121
- if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
12122
- 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;
12123
13435
  }
12124
- 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");
12125
13440
  }
12126
- clearOrder(built.orderHash);
12127
- 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;
12128
13445
  }
12129
13446
  function submissionUnknown(error, orderHash) {
12130
13447
  return new OwneyError(
@@ -12148,7 +13465,7 @@ async function awaitWithdrawalPayouts(options) {
12148
13465
  const payouts = new Map(Object.entries(options.results).map(([id, result]) => [id, { result, evidence: result.evidence }]));
12149
13466
  const timeoutMs = Math.max(0, options.timeoutMs ?? 18e4);
12150
13467
  const deadline = options.deadline ?? Date.now() + timeoutMs;
12151
- let active2 = true;
13468
+ let active = true;
12152
13469
  let timer;
12153
13470
  const incomplete = (reason, agentId) => new OwneyError(
12154
13471
  "WITHDRAW_SWAP_INCOMPLETE",
@@ -12166,7 +13483,7 @@ async function awaitWithdrawalPayouts(options) {
12166
13483
  );
12167
13484
  const assertActive = () => {
12168
13485
  options.assertCurrent();
12169
- if (!active2 || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
13486
+ if (!active || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
12170
13487
  };
12171
13488
  const pause = async () => {
12172
13489
  assertActive();
@@ -12343,7 +13660,7 @@ async function awaitWithdrawalPayouts(options) {
12343
13660
  timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), Math.max(0, deadline - Date.now()));
12344
13661
  })]);
12345
13662
  } finally {
12346
- active2 = false;
13663
+ active = false;
12347
13664
  if (timer) clearTimeout(timer);
12348
13665
  }
12349
13666
  }
@@ -12912,21 +14229,42 @@ init_errors();
12912
14229
  var import_viem22 = require("viem");
12913
14230
  init_errors();
12914
14231
  init_token_batch_family();
14232
+ var SPONSORED_DEPOSIT_SERVER_BROADCAST = 1;
12915
14233
  async function executeTokenBatch(input, initial) {
12916
14234
  let body = initial, family, ended = false;
12917
14235
  const deadlineAt = Date.now() + 9e4, controller = new AbortController();
12918
14236
  let timer;
12919
- const pending2 = () => new OwneyError(
12920
- "DEPOSIT_IN_PROGRESS",
12921
- "Deposit is awaiting confirmation. Its transaction is still being checked.",
12922
- {
12923
- sponsorPending: true,
12924
- phase: body.serializedTransaction ? "deposit" : "approval",
12925
- txHash: body.serializedTransaction ? (0, import_viem22.keccak256)(body.serializedTransaction) : body.serializedPermitTransaction ? (0, import_viem22.keccak256)(body.serializedPermitTransaction) : void 0,
12926
- notSubmitted: false,
12927
- safeToFallback: false
12928
- }
12929
- );
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
+ };
12930
14268
  const assertCurrent = () => {
12931
14269
  if (ended || Date.now() >= deadlineAt) throw pending2();
12932
14270
  input.assertCurrent?.();
@@ -12951,20 +14289,52 @@ async function executeTokenBatch(input, initial) {
12951
14289
  }
12952
14290
  return operationId ? { ...value, operationId } : value;
12953
14291
  };
12954
- const post2 = () => bounded2(
12955
- postSponsorBatchTransfer({
12956
- apiKey: input.apiKey,
12957
- baseUrl: input.baseUrl,
12958
- body: withOperationId(body),
12959
- signal: controller.signal
12960
- })
12961
- );
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
+ };
12962
14332
  const replacements = { approval: 0, deposit: 0 };
12963
14333
  const unavailable = /* @__PURE__ */ new Set();
12964
14334
  const observedApprovals = /* @__PURE__ */ new Set();
12965
14335
  const checkpointed = /* @__PURE__ */ new Set();
12966
14336
  const accounted = /* @__PURE__ */ new Set();
12967
- const remember = async (phase, bytes, hash2, predecessorHash) => {
14337
+ const remember = async (phase, bytes, hash2, predecessorHash, submissionAttempted = false) => {
12968
14338
  assertCurrent();
12969
14339
  if ((0, import_viem22.keccak256)(bytes) !== hash2) throw pending2();
12970
14340
  const tx = (0, import_viem22.parseTransaction)(bytes);
@@ -12983,7 +14353,7 @@ async function executeTokenBatch(input, initial) {
12983
14353
  nonce: String(tx.nonce),
12984
14354
  predecessorHash,
12985
14355
  preparedAt: Date.now(),
12986
- submissionAttempted: false
14356
+ submissionAttempted
12987
14357
  },
12988
14358
  assertCurrent
12989
14359
  )
@@ -12997,13 +14367,18 @@ async function executeTokenBatch(input, initial) {
12997
14367
  const markSubmission = async (phase) => {
12998
14368
  const hash2 = phase === "deposit" ? family?.current.deposit : family?.current.approval;
12999
14369
  if (!family || !hash2) throw pending2();
14370
+ const flag = serverSent.has(hash2) ? [true] : [];
13000
14371
  if (phase === "deposit" && !checkpointed.has(hash2)) {
13001
14372
  try {
13002
14373
  await bounded2(
13003
- Promise.resolve(input.onBeforeSubmission?.(hash2, reference(hash2)))
14374
+ Promise.resolve(
14375
+ input.onBeforeSubmission?.(hash2, reference(hash2), ...flag)
14376
+ )
13004
14377
  );
14378
+ checkpointed.add(hash2);
13005
14379
  } catch (error) {
13006
- 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")
13007
14382
  throw new OwneyError(error.code, error.message, {
13008
14383
  ...error.details,
13009
14384
  notSubmitted: !family.attempts.some(
@@ -13011,9 +14386,8 @@ async function executeTokenBatch(input, initial) {
13011
14386
  ),
13012
14387
  safeToFallback: false
13013
14388
  });
13014
- throw error;
14389
+ else throw error;
13015
14390
  }
13016
- checkpointed.add(hash2);
13017
14391
  }
13018
14392
  assertCurrent();
13019
14393
  const attempt = family.attempts.find((a) => a.hash === hash2);
@@ -13028,7 +14402,7 @@ async function executeTokenBatch(input, initial) {
13028
14402
  assertCurrent();
13029
14403
  if (phase === "deposit")
13030
14404
  try {
13031
- input.onSubmitted?.(hash2, reference(hash2));
14405
+ input.onSubmitted?.(hash2, reference(hash2), ...flag);
13032
14406
  } catch {
13033
14407
  }
13034
14408
  assertCurrent();
@@ -13064,12 +14438,15 @@ async function executeTokenBatch(input, initial) {
13064
14438
  }
13065
14439
  return observation.txHash;
13066
14440
  }
13067
- if (observation.status === "failed" || observation.status === "expired")
13068
- throw new OwneyError(
14441
+ if (observation.status === "failed" || observation.status === "expired") {
14442
+ const failure = new OwneyError(
13069
14443
  "SPONSOR_REQUEST_FAILED",
13070
14444
  observation.status === "expired" ? "The deposit authorization expired without transferring funds. Start a new deposit." : "Sponsored deposit transactions reverted. Please retry.",
13071
14445
  { notSubmitted: true, safeToFallback: false }
13072
14446
  );
14447
+ verifiedFailures.add(failure);
14448
+ throw failure;
14449
+ }
13073
14450
  };
13074
14451
  try {
13075
14452
  assertCurrent();
@@ -13085,6 +14462,14 @@ async function executeTokenBatch(input, initial) {
13085
14462
  tokenBatchKey(input),
13086
14463
  JSON.stringify(tokenBatchStoredBody(body))
13087
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
+ }
13088
14473
  if (family) {
13089
14474
  const winner = await observe();
13090
14475
  if (winner) return winner;
@@ -13108,6 +14493,20 @@ async function executeTokenBatch(input, initial) {
13108
14493
  result = await post2();
13109
14494
  } catch (error) {
13110
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();
13111
14510
  if (!family) {
13112
14511
  tokenBatchClear(
13113
14512
  tokenBatchKey(input),
@@ -13130,7 +14529,9 @@ async function executeTokenBatch(input, initial) {
13130
14529
  await remember(
13131
14530
  "deposit",
13132
14531
  result.serializedTransaction,
13133
- result.txHash
14532
+ result.txHash,
14533
+ void 0,
14534
+ serverSent.has(result.txHash)
13134
14535
  );
13135
14536
  needsPost = true;
13136
14537
  continue;
@@ -13206,6 +14607,10 @@ async function executeTokenBatch(input, initial) {
13206
14607
  )
13207
14608
  );
13208
14609
  }
14610
+ } catch (error) {
14611
+ if ((serverSent.size || maybeAccepted) && !(error && typeof error === "object" && verifiedFailures.has(error)))
14612
+ throw pending2();
14613
+ throw error;
13209
14614
  } finally {
13210
14615
  ended = true;
13211
14616
  if (timer) clearTimeout(timer);
@@ -13232,14 +14637,14 @@ async function releaseExpiredTokenBatch(i) {
13232
14637
  let timer;
13233
14638
  let expired = false;
13234
14639
  const check = async () => {
13235
- let active2 = read(key2);
13236
- if (active2 && i.expectedTxHash) {
13237
- const current = JSON.parse(active2);
14640
+ let active = read(key2);
14641
+ if (active && i.expectedTxHash) {
14642
+ const current = JSON.parse(active);
13238
14643
  if (!current.serializedTransaction || (0, import_viem23.keccak256)(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
13239
- active2 = void 0;
14644
+ active = void 0;
13240
14645
  }
13241
14646
  const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
13242
- 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))));
13243
14648
  if (!saved) return;
13244
14649
  const body = JSON.parse(saved);
13245
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;
@@ -13263,8 +14668,8 @@ async function releaseExpiredTokenBatch(i) {
13263
14668
  args: [i.owner, nonce >> 8n],
13264
14669
  blockNumber: block.number
13265
14670
  });
13266
- if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active2 && read(key2) !== active2) return;
13267
- 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) {
13268
14673
  const terminalKey = `${key2}:expired:${txHash.toLowerCase()}`;
13269
14674
  expiredRecords.set(terminalKey, saved);
13270
14675
  let persisted = typeof window === "undefined";
@@ -13301,15 +14706,15 @@ function clear(key2, body) {
13301
14706
  function sponsorTokenBatch(i) {
13302
14707
  const key2 = pendingKey(i);
13303
14708
  const plan = planOf(i.transfers);
13304
- const active2 = inflight.get(key2);
13305
- if (active2) {
13306
- if (active2.plan !== plan)
14709
+ const active = inflight.get(key2);
14710
+ if (active) {
14711
+ if (active.plan !== plan)
13307
14712
  return Promise.reject(
13308
14713
  new Error(
13309
14714
  "A token deposit is already in progress. Wait for its result before depositing again."
13310
14715
  )
13311
14716
  );
13312
- return active2.promise;
14717
+ return active.promise;
13313
14718
  }
13314
14719
  const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
13315
14720
  inflight.set(key2, { plan, promise });
@@ -13321,8 +14726,15 @@ async function execute(i, key2, plan) {
13321
14726
  ) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
13322
14727
  throw new Error("Invalid token deposit shares.");
13323
14728
  const send = (body) => executeTokenBatch(i, body);
14729
+ if (!i.resumeTxHash) await dropExpiredHashlessTokenBatch(i, i.pub);
13324
14730
  const family = await loadTokenBatchFamily(i, i.resumeTxHash);
13325
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
+ );
13326
14738
  if (saved) {
13327
14739
  const previous = JSON.parse(saved);
13328
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)
@@ -13432,10 +14844,13 @@ function makeSponsoredTokenCallback(deps) {
13432
14844
  pub,
13433
14845
  wallet,
13434
14846
  onApproved: deps.onApproved,
13435
- onSubmitted: (hash2, reference) => deps.onSubmitted?.(hash2, transfers, reference),
13436
- 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),
13437
14850
  assertCurrent: deps.assertCurrent,
13438
14851
  resumeTxHash: deps.resumeTxHash,
14852
+ ...deps.resumeSaved ? { resumeSaved: true } : {},
14853
+ ...deps.onAuthorization ? { onAuthorization: deps.onAuthorization } : {},
13439
14854
  operationId: deps.operationId
13440
14855
  });
13441
14856
  };
@@ -13744,6 +15159,33 @@ function sponsoredTokensFor(asset) {
13744
15159
  function shouldFallbackToUserPaid(error, asset, appCallback) {
13745
15160
  return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
13746
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;
13747
15189
  var OwneySDK = class {
13748
15190
  agents = /* @__PURE__ */ new Map();
13749
15191
  activeAgents = /* @__PURE__ */ new Set();
@@ -13772,13 +15214,21 @@ var OwneySDK = class {
13772
15214
  ledger;
13773
15215
  reservationMode;
13774
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;
13775
15222
  cachedSponsoredCallback = null;
13776
15223
  cachedWethSponsoredCallback = null;
13777
15224
  paymasterServiceUrl;
13778
15225
  cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
13779
15226
  agentReads = new AgentReads();
13780
15227
  depositFamilyReads = /* @__PURE__ */ new Map();
13781
- 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();
13782
15232
  fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
13783
15233
  activationChecks = /* @__PURE__ */ new Map();
13784
15234
  // Protect the SDK entry point as well as the mini-app hook. This owns only
@@ -13816,6 +15266,8 @@ var OwneySDK = class {
13816
15266
  });
13817
15267
  this.reservationMode = config.withdrawalReservation === "fail-closed" || config.withdrawalReservation === "off" ? config.withdrawalReservation : "fail-open";
13818
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;
13819
15271
  }
13820
15272
  /** Finishes a funding-ledger operation on success and adds its reference to
13821
15273
  * the result. The result object is copied, never mutated or replaced.
@@ -13881,16 +15333,34 @@ var OwneySDK = class {
13881
15333
  return void 0;
13882
15334
  }
13883
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
+ }
13884
15353
  closeSwapLedger(op) {
13885
15354
  if (op && this.openSwapLedgers.get(op.operationId) === op) this.openSwapLedgers.delete(op.operationId);
13886
15355
  }
13887
- /** Reports one agent's withdrawal evidence as its leg. Never throws. */
13888
- 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) {
13889
15359
  if (!op) return;
13890
15360
  try {
13891
15361
  const leg = ledgerLegFromEvidence(evidence, "withdraw");
13892
15362
  const rail = withdrawalLedgerRail(evidence);
13893
- if (leg) op.leg(rail ? { ...leg, rail } : leg);
15363
+ if (leg) op.leg({ ...rail ? { ...leg, rail } : leg, ...errorCode ? { errorCode } : {} });
13894
15364
  if (leg?.status === "confirmed") this.ledgerProviderSamples(op, evidence, "withdraw");
13895
15365
  } catch {
13896
15366
  }
@@ -14125,8 +15595,8 @@ var OwneySDK = class {
14125
15595
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
14126
15596
  }
14127
15597
  /** Builds the default USDC batch callback for the connected wallet. */
14128
- getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId) {
14129
- 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)
14130
15600
  return this.cachedSponsoredCallback;
14131
15601
  const provider = this.requireConnectedProvider();
14132
15602
  const connection = this.requireState();
@@ -14140,6 +15610,8 @@ var OwneySDK = class {
14140
15610
  onSubmitted,
14141
15611
  onBeforeSubmission,
14142
15612
  resumeTxHash,
15613
+ ...resumeSaved ? { resumeSaved: true } : {},
15614
+ ...onAuthorization ? { onAuthorization } : {},
14143
15615
  operationId,
14144
15616
  assertCurrent: () => {
14145
15617
  assertSubmissionCurrent?.();
@@ -14160,7 +15632,7 @@ var OwneySDK = class {
14160
15632
  transport: (0, import_viem25.custom)(provider)
14161
15633
  })
14162
15634
  });
14163
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId) this.cachedSponsoredCallback = callback;
15635
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization) this.cachedSponsoredCallback = callback;
14164
15636
  return callback;
14165
15637
  }
14166
15638
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
@@ -14189,8 +15661,8 @@ var OwneySDK = class {
14189
15661
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
14190
15662
  * single-use batch authorization instead of an EIP-3009 authorization.
14191
15663
  */
14192
- getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId) {
14193
- 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)
14194
15666
  return this.cachedWethSponsoredCallback;
14195
15667
  const provider = this.requireConnectedProvider();
14196
15668
  const connection = this.requireState();
@@ -14204,6 +15676,8 @@ var OwneySDK = class {
14204
15676
  onSubmitted,
14205
15677
  onBeforeSubmission,
14206
15678
  resumeTxHash,
15679
+ ...resumeSaved ? { resumeSaved: true } : {},
15680
+ ...onAuthorization ? { onAuthorization } : {},
14207
15681
  operationId,
14208
15682
  assertCurrent: () => {
14209
15683
  assertSubmissionCurrent?.();
@@ -14224,7 +15698,7 @@ var OwneySDK = class {
14224
15698
  transport: (0, import_viem25.custom)(provider)
14225
15699
  })
14226
15700
  });
14227
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId) this.cachedWethSponsoredCallback = callback;
15701
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization) this.cachedWethSponsoredCallback = callback;
14228
15702
  return callback;
14229
15703
  }
14230
15704
  getAgent(agentId) {
@@ -14238,12 +15712,12 @@ var OwneySDK = class {
14238
15712
  return [...this.getActiveAgents().keys()].sort();
14239
15713
  }
14240
15714
  getActiveAgents() {
14241
- const active2 = /* @__PURE__ */ new Map();
15715
+ const active = /* @__PURE__ */ new Map();
14242
15716
  for (const id of this.activeAgents) {
14243
15717
  const agent = this.agents.get(id);
14244
- if (agent) active2.set(id, agent);
15718
+ if (agent) active.set(id, agent);
14245
15719
  }
14246
- return active2;
15720
+ return active;
14247
15721
  }
14248
15722
  /**
14249
15723
  * Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
@@ -14342,7 +15816,13 @@ var OwneySDK = class {
14342
15816
  this.routingApiBaseUrl
14343
15817
  ) : this.rpcUrls,
14344
15818
  this.referralSource,
14345
- { 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
+ }
14346
15826
  );
14347
15827
  }
14348
15828
  if (agentId === "yieldseeker") {
@@ -14564,10 +16044,21 @@ var OwneySDK = class {
14564
16044
  } catch {
14565
16045
  }
14566
16046
  };
14567
- 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) => {
14568
16053
  for (const destination of destinations) {
14569
16054
  const evidence = prepared.get(destination.to.toLowerCase());
14570
- 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
+ });
14571
16062
  }
14572
16063
  };
14573
16064
  const onCallsProgress = (callsId, destinations, transfer) => {
@@ -14576,6 +16067,7 @@ var OwneySDK = class {
14576
16067
  if (evidence) notifyFunding(onProgress, { ...evidence, callsId, transfer });
14577
16068
  }
14578
16069
  };
16070
+ const serverTaskContext = (agent) => this.zyfaiServerTasks !== void 0 && agent.supportsServerTasks === true && ledgerOp ? { operationId: ledgerOp.operationId } : void 0;
14579
16071
  const ledgerDone = (result) => this.ledgerSucceeded(ledgerOp, result);
14580
16072
  const ledgerFailed = (error) => this.ledgerFailed(ledgerOp, error);
14581
16073
  if (BigInt(amount) === 0n) {
@@ -14611,7 +16103,7 @@ var OwneySDK = class {
14611
16103
  });
14612
16104
  await checkpoint2(evidence);
14613
16105
  } : void 0;
14614
- 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;
14615
16107
  const checkpointCalls = checkpointDestinations ? (id, destinations) => checkpointDestinations(destinations, { callsId: id }) : void 0;
14616
16108
  const checkpointPaid = checkpoint2 ? (evidence) => checkpoint2([evidence]) : void 0;
14617
16109
  const customCallback = depositCallback && checkpointDestinations ? makeVerificationAwareDepositCallback(async (to, cid, value, verification) => {
@@ -14619,9 +16111,10 @@ var OwneySDK = class {
14619
16111
  await checkpointDestinations([{ to, amount: value }], {});
14620
16112
  return depositCallback(to, cid, value);
14621
16113
  }) : depositCallback;
14622
- 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))
14623
16115
  throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
14624
- 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;
14625
16118
  try {
14626
16119
  const ledgerToken = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
14627
16120
  ledgerOp = this.startLedger({
@@ -14636,16 +16129,19 @@ var OwneySDK = class {
14636
16129
  } catch {
14637
16130
  }
14638
16131
  try {
14639
- if (options.resumeTxHash) {
16132
+ if (options.resumeTxHash || options.resumeSavedBatch) {
14640
16133
  const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
14641
- const family = token && await loadTokenBatchFamily({
16134
+ const scope = token && {
14642
16135
  apiKey: this.apiKey,
14643
16136
  baseUrl: this.routingApiBaseUrl,
14644
16137
  owner: state.walletAddress,
14645
16138
  chainId,
14646
16139
  token: token.address
14647
- }, options.resumeTxHash);
14648
- 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 };
14649
16145
  const invalid2 = () => new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit does not match this request. Check its status before depositing again.");
14650
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();
14651
16147
  const candidates = agentId ? [this.getAgent(agentId)] : this.getEligibleAgents(chainId, asset);
@@ -14665,12 +16161,10 @@ var OwneySDK = class {
14665
16161
  return {
14666
16162
  id: agent.id,
14667
16163
  amount: transfer.amount,
14668
- run: (callback) => withFailureReporting(
14669
- this.apiKey,
14670
- agent.id,
14671
- () => agent.deposit(state, chainId, transfer.amount, asset, callback, onProgress),
14672
- this.routingApiBaseUrl
14673
- )
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)
14674
16168
  };
14675
16169
  });
14676
16170
  const batchTransfer2 = getDepositBatchTransfer(effectiveCallback);
@@ -14709,7 +16203,8 @@ var OwneySDK = class {
14709
16203
  onProgress,
14710
16204
  () => paidFallbackAgents.add(agent.id),
14711
16205
  !!options.resumeTxHash,
14712
- checkpointPaid
16206
+ checkpointPaid,
16207
+ serverTaskContext(agent)
14713
16208
  ));
14714
16209
  }
14715
16210
  const totalAmount = BigInt(amount);
@@ -14771,7 +16266,10 @@ var OwneySDK = class {
14771
16266
  run: (callback) => withFailureReporting(
14772
16267
  this.apiKey,
14773
16268
  agent.id,
14774
- () => 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
+ },
14775
16273
  this.routingApiBaseUrl
14776
16274
  )
14777
16275
  })),
@@ -14812,7 +16310,8 @@ var OwneySDK = class {
14812
16310
  onProgress,
14813
16311
  () => paidFallbackAgents.add(agent.id),
14814
16312
  !!options.resumeTxHash,
14815
- checkpointPaid
16313
+ checkpointPaid,
16314
+ serverTaskContext(agent)
14816
16315
  );
14817
16316
  } catch (error) {
14818
16317
  if (Object.keys(agentResults).length === 0) throw error;
@@ -14832,7 +16331,22 @@ var OwneySDK = class {
14832
16331
  }
14833
16332
  return ledgerDone({ agentResults });
14834
16333
  } catch (error) {
14835
- 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)) {
14836
16350
  error = error instanceof OwneyError && error.code === "DEPOSIT_PARTIAL_FAILURE" ? new OwneyError(
14837
16351
  error.code,
14838
16352
  "Some deposits completed before another agent could be submitted.",
@@ -14850,6 +16364,19 @@ var OwneySDK = class {
14850
16364
  const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
14851
16365
  if (!rejected && paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
14852
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
+ }
14853
16380
  notifyFunding(onProgress, { ...evidence ?? {
14854
16381
  agentId: id,
14855
16382
  chainId,
@@ -14896,11 +16423,11 @@ var OwneySDK = class {
14896
16423
  * propagates unchanged. Scoped per agent invocation so one agent's failure
14897
16424
  * doesn't force a retry of sibling agents in a multi-agent split.
14898
16425
  */
14899
- 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) {
14900
16427
  const attempt = (callback) => withFailureReporting(
14901
16428
  this.apiKey,
14902
16429
  agent.id,
14903
- () => 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),
14904
16431
  this.routingApiBaseUrl
14905
16432
  );
14906
16433
  let approvalAttempted = false;
@@ -15197,11 +16724,36 @@ var OwneySDK = class {
15197
16724
  const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
15198
16725
  if (options.refreshBalance !== false && (verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
15199
16726
  agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil, evidence.txHash);
15200
- this.recoveredFundingReads.add(key2);
15201
- 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());
15202
16729
  }
15203
16730
  return verified;
15204
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
+ }
15205
16757
  /**
15206
16758
  * Lists on-chain payout candidates for a withdrawal the provider accepted
15207
16759
  * without a hash: ERC-20 `Transfer` logs on the evidence's token that reach
@@ -15323,22 +16875,25 @@ var OwneySDK = class {
15323
16875
  try {
15324
16876
  if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
15325
16877
  const first = evidences[0];
15326
- 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;
15327
16880
  const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
15328
16881
  const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
15329
16882
  if (!token) return false;
15330
- const family = await loadTokenBatchFamily({
16883
+ const scope = {
15331
16884
  apiKey: this.apiKey,
15332
16885
  baseUrl: this.routingApiBaseUrl,
15333
16886
  owner: state.walletAddress,
15334
16887
  chainId,
15335
16888
  token: token.address
15336
- }, first.txHash);
15337
- const transfers = family?.body.transfers;
15338
- 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;
15339
16894
  const destinations = /* @__PURE__ */ new Set();
15340
16895
  for (const e of evidences) {
15341
- 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;
15342
16897
  const agent = this.getAgent(e.agentId);
15343
16898
  const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, e.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
15344
16899
  assertCurrent();
@@ -15351,6 +16906,62 @@ var OwneySDK = class {
15351
16906
  return false;
15352
16907
  }
15353
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
+ }
15354
16965
  /** Validate signed journal membership, never just a caller-supplied family ID. */
15355
16966
  async validateDepositReplacement(previous, next) {
15356
16967
  try {
@@ -15443,6 +17054,22 @@ var OwneySDK = class {
15443
17054
  if (verified.agentId !== "zyfai") return verified;
15444
17055
  return this.getAgent("zyfai").retryDepositRegistration(state, verified);
15445
17056
  }
17057
+ /** A Zyfai withdrawal's lifecycle by the id recorded on its evidence. Never signs,
17058
+ * prompts or authenticates; `null` when unavailable or not this wallet's Zyfai withdrawal. */
17059
+ async getZyfaiWithdrawal(evidence) {
17060
+ const state = this.state;
17061
+ const id = evidence.withdrawalRequest?.providerWithdrawalId;
17062
+ if (evidence.agentId !== "zyfai" || !id || !state || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return null;
17063
+ return this.getAgent("zyfai").getWithdrawalLifecycle(state, id);
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
+ }
15446
17073
  // --- Fund operations ---
15447
17074
  /** On-chain version and ownership status for this owner's Yieldseeker agent. */
15448
17075
  async getYieldseekerWalletStatus(asset) {
@@ -16025,6 +17652,17 @@ var OwneySDK = class {
16025
17652
  onStage: (stage) => options.onStage(order.orderHash, stage)
16026
17653
  } : {}
16027
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
+ });
16028
17666
  clearOrder(order.orderHash);
16029
17667
  updateWithdrawSwap(order.orderHash, { status: "landed" });
16030
17668
  return { orderHash: order.orderHash, status: "completed" };
@@ -16195,14 +17833,16 @@ var OwneySDK = class {
16195
17833
  options.to.symbol
16196
17834
  );
16197
17835
  }
16198
- const ledgerOp = this.openSwapLedger(() => ({
17836
+ let ledgerInput;
17837
+ const ledgerOp = this.openSwapLedger(() => ledgerInput = {
16199
17838
  operationId: options.operationId,
16200
17839
  kind: "swap_deposit",
16201
17840
  owner: state.walletAddress,
16202
17841
  chainId: options.to.chainId,
16203
17842
  token: SupportedAssets.find((value) => value.chainId === options.to.chainId && value.symbol === options.to.symbol)?.address ?? "",
16204
17843
  asset: options.to.symbol.toLowerCase()
16205
- }));
17844
+ });
17845
+ const secretHandoff = this.secretHandoffFor(ledgerOp, ledgerInput);
16206
17846
  try {
16207
17847
  const api = this.swapApi();
16208
17848
  const quoted = await api.quote({
@@ -16230,6 +17870,17 @@ var OwneySDK = class {
16230
17870
  ...options.assertCurrent ? { assertCurrent: options.assertCurrent } : {},
16231
17871
  ...options.slippage === void 0 ? {} : { slippage: options.slippage },
16232
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 } : {},
16233
17884
  ...ledgerOp ? { onOrderSubmitted: (orderHash) => {
16234
17885
  swapIds.orderHash = orderHash;
16235
17886
  this.ledgerSwap(ledgerOp, quote, { status: "submitted", orderHash });
@@ -16249,6 +17900,7 @@ var OwneySDK = class {
16249
17900
  receivedDstAmountRaw: swap.received
16250
17901
  });
16251
17902
  options.onSwapProgress?.("depositing");
17903
+ const backgroundDepositId = swap.orderHash ? `${quote.dst.chainId}-${options.to.symbol}-${swap.orderHash}` : void 0;
16252
17904
  try {
16253
17905
  options.assertCurrent?.();
16254
17906
  await this.ensureSwapChain(quote.dst.chainId);
@@ -16278,10 +17930,13 @@ var OwneySDK = class {
16278
17930
  ...options.assertCurrent ? { assertSubmissionCurrent: options.assertCurrent } : {},
16279
17931
  ...ledgerOp ? { operationId: ledgerOp.operationId } : {}
16280
17932
  });
17933
+ if (backgroundDepositId) clearPendingDeposit(backgroundDepositId);
16281
17934
  return this.ledgerSucceeded(ledgerOp, { swap, deposit, deposited: amount });
16282
17935
  } catch (error) {
16283
17936
  const pending2 = {
16284
- 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()}`,
16285
17940
  received: swap.received,
16286
17941
  symbol: options.to.symbol,
16287
17942
  chainId: quote.dst.chainId,
@@ -16437,7 +18092,7 @@ var OwneySDK = class {
16437
18092
  const state = this.requireState();
16438
18093
  const activeChainId = this.requireChainId();
16439
18094
  const revision = this.connectionRevision;
16440
- let active2 = true;
18095
+ let active = true;
16441
18096
  let record2;
16442
18097
  let ledgerOp;
16443
18098
  let hadDurableRecord = false;
@@ -16445,7 +18100,7 @@ var OwneySDK = class {
16445
18100
  let confirmationDeadline = Infinity;
16446
18101
  const assertCurrent = () => {
16447
18102
  options.assertCurrent?.();
16448
- 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 });
16449
18104
  if (this.state !== state || this.connectionRevision !== revision || state.chainId !== activeChainId)
16450
18105
  throw new OwneyError("NOT_CONNECTED", "Wallet changed during withdrawal and swap.");
16451
18106
  };
@@ -16490,9 +18145,10 @@ var OwneySDK = class {
16490
18145
  const saved = options.recoveryId ? listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === options.recoveryId) : void 0;
16491
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))
16492
18147
  throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal is not ready to continue.");
18148
+ let ledgerInput;
16493
18149
  ledgerOp = this.openSwapLedger(() => {
16494
18150
  const requestedRaw = saved ? saved.recovery.requested : options.amount === void 0 ? void 0 : (0, import_viem25.parseUnits)(options.amount, asset.decimals).toString();
16495
- return {
18151
+ return ledgerInput = {
16496
18152
  operationId: saved?.operationId ?? options.operationId,
16497
18153
  kind: "withdraw_swap",
16498
18154
  owner: state.walletAddress,
@@ -16503,6 +18159,7 @@ var OwneySDK = class {
16503
18159
  ...requestedRaw === void 0 ? {} : { requestedAmountRaw: requestedRaw }
16504
18160
  };
16505
18161
  });
18162
+ const secretHandoff = this.secretHandoffFor(ledgerOp, ledgerInput);
16506
18163
  const prepareTimeout = () => new OwneyError(
16507
18164
  "WITHDRAW_SWAP_INCOMPLETE",
16508
18165
  "Could not read the source wallet. No new withdrawal was submitted.",
@@ -16562,7 +18219,7 @@ var OwneySDK = class {
16562
18219
  evidence.set(next.agentId, next);
16563
18220
  if (record2?.recovery) {
16564
18221
  record2.recovery.evidence[next.agentId] = next;
16565
- if (active2) saveWithdrawSwap(record2);
18222
+ if (active) saveWithdrawSwap(record2);
16566
18223
  else {
16567
18224
  const latest = listWithdrawSwaps(Date.now(), true).find((row) => row.recovery?.attemptId === record2.recovery.attemptId);
16568
18225
  if (latest?.recovery) updateWithdrawSwap(latest.orderHash, { recovery: {
@@ -16678,6 +18335,7 @@ var OwneySDK = class {
16678
18335
  quote,
16679
18336
  walletAddress: state.walletAddress,
16680
18337
  direction: "withdraw",
18338
+ ...secretHandoff ? { secretHandoff } : {},
16681
18339
  ...options.slippage === void 0 ? {} : { slippage: options.slippage },
16682
18340
  ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
16683
18341
  onOrderPrepared: (orderHash, details) => {
@@ -16766,7 +18424,7 @@ var OwneySDK = class {
16766
18424
  this.ledgerFailed(ledgerOp, error);
16767
18425
  throw error;
16768
18426
  } finally {
16769
- active2 = false;
18427
+ active = false;
16770
18428
  releaseSubmission();
16771
18429
  this.closeSwapLedger(ledgerOp);
16772
18430
  }
@@ -16814,11 +18472,13 @@ var OwneySDK = class {
16814
18472
  });
16815
18473
  } catch {
16816
18474
  }
16817
- const ledgerLeg = (evidence) => this.ledgerWithdrawalLeg(ledgerOp, evidence);
18475
+ const ledgerLeg = (evidence, errorCode) => this.ledgerWithdrawalLeg(ledgerOp, evidence, errorCode);
16818
18476
  let hold;
16819
18477
  let reservationRejected = false;
16820
18478
  let innerStarted = false;
16821
18479
  const reservationLegs = /* @__PURE__ */ new Map();
18480
+ const plannedLegs = /* @__PURE__ */ new Map();
18481
+ const serverLegs = /* @__PURE__ */ new Map();
16822
18482
  const reservation = {
16823
18483
  dispatching: (id) => hold?.dispatch(id),
16824
18484
  settled: (id, leg) => {
@@ -16828,11 +18488,22 @@ var OwneySDK = class {
16828
18488
  const settleHold = () => {
16829
18489
  if (!hold) return;
16830
18490
  hold.stopHeartbeat();
16831
- 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);
16832
18493
  };
16833
18494
  const runAgents = () => {
16834
18495
  innerStarted = true;
16835
- 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
+ );
16836
18507
  void inner.then(settleHold, settleHold);
16837
18508
  return inner;
16838
18509
  };
@@ -16856,7 +18527,9 @@ var OwneySDK = class {
16856
18527
  } catch (error) {
16857
18528
  if (!reservationRejected) {
16858
18529
  for (const id of ids) if (!attempted.has(id)) {
16859
- 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" } : {
16860
18533
  agentId: id,
16861
18534
  chainId,
16862
18535
  token: options.asset,
@@ -16978,12 +18651,13 @@ var OwneySDK = class {
16978
18651
  * - a throw without submission evidence: `not_submitted`;
16979
18652
  * - Zyfai "withdrawal already in progress": kept, dispatch `provider_in_progress`.
16980
18653
  */
16981
- settleWithdrawalReservation(hold, ids, attempted, legs) {
18654
+ settleWithdrawalReservation(hold, ids, attempted, legs, keep) {
16982
18655
  try {
16983
18656
  if ([...attempted].some((id) => !legs.has(id))) return;
16984
18657
  const releases = /* @__PURE__ */ new Map();
16985
18658
  for (const id of new Set(ids)) {
16986
18659
  if (!RESERVATION_AGENTS.includes(id)) continue;
18660
+ if (keep?.has(id)) continue;
16987
18661
  const leg = legs.get(id);
16988
18662
  const outcome = attempted.has(id) && leg ? reservationLegOutcome(leg) : "not_submitted";
16989
18663
  if (outcome === "keep") continue;
@@ -17014,6 +18688,31 @@ var OwneySDK = class {
17014
18688
  } catch {
17015
18689
  }
17016
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
+ }
17017
18716
  /**
17018
18717
  * Reports a withdrawal leg that was proven after its call ended (a hashless
17019
18718
  * Zyfai payout found on chain or in history) to the funding ledger, then
@@ -17063,7 +18762,36 @@ var OwneySDK = class {
17063
18762
  } catch {
17064
18763
  }
17065
18764
  }
17066
- 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()) {
17067
18795
  const { asset, amount, agentId, onAgentResult } = options;
17068
18796
  const notifyAgentResult = (id, result) => {
17069
18797
  try {
@@ -17109,7 +18837,22 @@ var OwneySDK = class {
17109
18837
  { asset, amount, agentErrors: agentErrors2, agentFailures, pendingSubmissions: pending2 }
17110
18838
  );
17111
18839
  };
17112
- 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);
17113
18856
  if (this.state !== state || this.state.chainId !== chainId)
17114
18857
  throw new OwneyError("NOT_CONNECTED", "Wallet connection changed before withdrawal submission.");
17115
18858
  attempted.add(agent.id);
@@ -17131,6 +18874,7 @@ var OwneySDK = class {
17131
18874
  submissionEvidence.set(agent.id, evidence);
17132
18875
  notifyFunding(options.onProgress, evidence);
17133
18876
  ledgerLeg?.(evidence);
18877
+ if (onAccepted && evidence.ownerAuthorization === "accepted" && evidence.transfer !== "failed" && (evidence.userOpHash || evidence.callsId || evidence.txHash)) onAccepted();
17134
18878
  };
17135
18879
  progress(evidence);
17136
18880
  adapterReported = false;
@@ -17145,14 +18889,26 @@ var OwneySDK = class {
17145
18889
  reservation?.dispatching(agent.id);
17146
18890
  } catch {
17147
18891
  }
17148
- 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(
17149
18905
  state,
17150
18906
  chainId,
17151
18907
  token,
17152
18908
  requested2,
17153
18909
  progress,
17154
18910
  options.gasMode ?? "auto",
17155
- options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0,
18911
+ onBeforeSubmission,
17156
18912
  options.assertCurrent
17157
18913
  ) : await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
17158
18914
  settled2({
@@ -17163,6 +18919,10 @@ var OwneySDK = class {
17163
18919
  } catch (error) {
17164
18920
  if (!adapterReported && !evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
17165
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");
17166
18926
  settled2({ settled: "threw", evidence });
17167
18927
  throw error;
17168
18928
  }
@@ -17188,40 +18948,151 @@ var OwneySDK = class {
17188
18948
  notifyAgentResult(agent.id, result);
17189
18949
  return result;
17190
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
+ };
17191
19032
  const eligibleAgents = this.getEligibleAgents(chainId, asset).filter((agent) => !options.excludeAgentIds?.includes(agent.id));
17192
19033
  const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
17193
19034
  if (!amount) {
17194
19035
  const results2 = {};
17195
19036
  const agentErrors2 = {};
17196
19037
  const incompleteWithdrawals = {};
17197
- for (const agent of withdrawalAgents) {
17198
- try {
17199
- results2[agent.id] = await submit(agent);
17200
- notifyAgentResult(agent.id, results2[agent.id]);
17201
- } catch (err) {
19038
+ const failed2 = (agent, err, handedOver) => {
19039
+ if (!handedOver) {
17202
19040
  if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
17203
19041
  if (this.isUserRejectedWithdrawal(err)) throw err;
17204
- if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
17205
- if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
17206
- incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
17207
- const evidence = submissionEvidence.get(agent.id);
17208
- 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) {
17209
- results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
17210
- notifyAgentResult(agent.id, results2[agent.id]);
17211
- }
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);
17212
19088
  }
17213
- console.error(`withdraw failed for agent "${agent.id}":`, err);
17214
- agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
17215
- recordAgentFailure(agent.id, err);
17216
- const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
17217
- void reportAgentFailure(
17218
- this.apiKey,
17219
- agent.id,
17220
- code,
17221
- this.routingApiBaseUrl
17222
- );
17223
19089
  }
19090
+ } catch (error) {
19091
+ if (serverLegs.size) await awaitServerHandoffs();
19092
+ await settleHandedOver2();
19093
+ throw error;
17224
19094
  }
19095
+ await settleHandedOver2();
17225
19096
  if (Object.keys(results2).length === 0) {
17226
19097
  throwUnconfirmed(agentErrors2);
17227
19098
  if (Object.keys(agentErrors2).length === 0)
@@ -17366,32 +19237,65 @@ var OwneySDK = class {
17366
19237
  const agentErrors = {
17367
19238
  ...aggregated.agentErrors ?? {}
17368
19239
  };
17369
- for (let i = 0; i < orderedPlans.length; i++) {
17370
- const p = orderedPlans[i];
17371
- assertPlan();
17372
- if (p.planned <= 0n) continue;
17373
- try {
17374
- results[p.agent.id] = await submit(p.agent, p.planned.toString());
17375
- notifyAgentResult(p.agent.id, results[p.agent.id]);
17376
- } catch (err) {
19240
+ const failed = (p, index, err, handedOver) => {
19241
+ if (!handedOver) {
17377
19242
  if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
17378
19243
  if (this.isUserRejectedWithdrawal(err)) throw err;
17379
- console.error(`withdraw failed for agent "${p.agent.id}":`, err);
17380
- agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
17381
- recordAgentFailure(p.agent.id, err);
17382
- const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
17383
- void reportAgentFailure(
17384
- this.apiKey,
17385
- p.agent.id,
17386
- code,
17387
- this.routingApiBaseUrl
17388
- );
17389
- const failedAmount = p.planned;
17390
- p.planned = 0n;
17391
- if (submissionEvidence.get(p.agent.id)?.transfer === "failed")
17392
- redistributeShare(orderedPlans, i, failedAmount);
17393
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;
17394
19297
  }
19298
+ await settleHandedOver();
17395
19299
  if (Object.keys(results).length === 0) {
17396
19300
  throwUnconfirmed(agentErrors);
17397
19301
  throw new OwneyError(
@@ -18331,12 +20235,20 @@ function createOwneySIWX(config) {
18331
20235
  0 && (module.exports = {
18332
20236
  AgentChainIncompatibleError,
18333
20237
  AgentNotFoundError,
20238
+ FUNDING_DEPOSIT_RECOVERY_REPORT,
20239
+ FUSION_SECRET_SERVER_PUBLISH,
18334
20240
  InvalidHistoryCursorError,
18335
20241
  NotConnectedError,
18336
20242
  OwneyError,
18337
20243
  OwneySDK,
18338
20244
  SDK_VERSION,
20245
+ SPONSORED_DEPOSIT_SERVER_BROADCAST,
20246
+ YIELDSEEKER_WITHDRAW_SERVER_SUBMIT,
18339
20247
  YieldseekerAgent,
20248
+ ZYFAI_REGISTRATION_NOT_SENT_CODES,
20249
+ ZYFAI_SERVER_TASKS,
20250
+ ZYFAI_WITHDRAWAL_NOT_SENT_CODES,
20251
+ ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT,
18340
20252
  clearWithdrawSwap,
18341
20253
  createOwneySIWX,
18342
20254
  listPendingDeposits,