@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.js CHANGED
@@ -8,10 +8,13 @@ import {
8
8
  PERMIT2_BATCH_ABI,
9
9
  batchArgs,
10
10
  batchTypedData,
11
+ discardHashlessTokenBatch,
12
+ dropExpiredHashlessTokenBatch,
11
13
  erc2612TypedData,
12
14
  finishTokenBatchFamily,
13
15
  loadTokenBatchFamily,
14
16
  saveTokenBatchAttempt,
17
+ savedTokenBatchBody,
15
18
  supportsSponsoredErc2612,
16
19
  tokenBatchClear,
17
20
  tokenBatchKey,
@@ -19,7 +22,7 @@ import {
19
22
  tokenBatchStoredBody,
20
23
  tokenBatchWrite,
21
24
  validateTokenBatchAttempt
22
- } from "./chunk-YBMCUL5D.js";
25
+ } from "./chunk-CW55W2WO.js";
23
26
 
24
27
  // src/lib/funding-evidence.ts
25
28
  import { decodeEventLog, decodeFunctionData, erc20Abi, parseAbi } from "viem";
@@ -356,25 +359,41 @@ async function observeTokenBatchFamily(input) {
356
359
  }
357
360
 
358
361
  // src/lib/swap/swap.observation.ts
359
- var active = /* @__PURE__ */ new Set();
360
- var isOrderObserved = (key2) => active.has(key2);
361
- async function observeOrder(key2, run) {
362
+ var claims = /* @__PURE__ */ new Map();
363
+ var watches = /* @__PURE__ */ new Map();
364
+ var isOrderObserved = (key2) => claims.has(key2) || watches.has(key2);
365
+ function claimOrder(key2) {
366
+ claims.set(key2, (claims.get(key2) ?? 0) + 1);
367
+ let released = false;
368
+ return () => {
369
+ if (released) return;
370
+ released = true;
371
+ const left = (claims.get(key2) ?? 1) - 1;
372
+ if (left > 0) claims.set(key2, left);
373
+ else claims.delete(key2);
374
+ };
375
+ }
376
+ async function observeOrder(key2, run, options = {}) {
362
377
  const busy = () => new OwneyError(
363
378
  "SWAP_REQUEST_FAILED",
364
379
  "This order is already being observed.",
365
380
  { orderHash: key2, watchActive: true, settlementUnconfirmed: true }
366
381
  );
367
- if (active.has(key2)) throw busy();
368
- active.add(key2);
382
+ if (options.wait) {
383
+ for (let other = watches.get(key2); other; other = watches.get(key2))
384
+ await other.catch(() => void 0);
385
+ } else if (watches.has(key2)) throw busy();
386
+ const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
387
+ const name = `owney.swap.observe:${key2}`;
388
+ const watch = !locks ? run() : options.wait ? locks.request(name, () => run()) : locks.request(name, { ifAvailable: true }, (lock) => {
389
+ if (!lock) throw busy();
390
+ return run();
391
+ });
392
+ watches.set(key2, watch);
369
393
  try {
370
- if (typeof navigator !== "undefined" && navigator.locks)
371
- return await navigator.locks.request(`owney.swap.observe:${key2}`, { ifAvailable: true }, (lock) => {
372
- if (!lock) throw busy();
373
- return run();
374
- });
375
- return await run();
394
+ return await watch;
376
395
  } finally {
377
- active.delete(key2);
396
+ if (watches.get(key2) === watch) watches.delete(key2);
378
397
  }
379
398
  }
380
399
 
@@ -529,7 +548,7 @@ function parseLeg(item) {
529
548
  const r = item;
530
549
  if (typeof r.legKey !== "string" || !r.legKey) return null;
531
550
  if (typeof r.status !== "string" || !r.status) return null;
532
- return {
551
+ const leg = {
533
552
  legKey: r.legKey,
534
553
  agentType: strOrNull(r.agentType),
535
554
  status: r.status,
@@ -539,8 +558,11 @@ function parseLeg(item) {
539
558
  confirmedAt: strOrNull(r.confirmedAt),
540
559
  agentWallet: strOrNull(r.agentWallet),
541
560
  providerIndexedAt: strOrNull(r.providerIndexedAt),
542
- providerBookedAt: strOrNull(r.providerBookedAt)
561
+ providerBookedAt: strOrNull(r.providerBookedAt),
562
+ providerRef: strOrNull(r.providerRef)
543
563
  };
564
+ if (r.closed === true) leg.closed = true;
565
+ return leg;
544
566
  }
545
567
  function parseRow(item) {
546
568
  if (!item || typeof item !== "object") return null;
@@ -960,7 +982,7 @@ var SnapshotHistory = class {
960
982
  };
961
983
 
962
984
  // src/lib/funding-ledger.ts
963
- var SDK_VERSION = "0.7.32-beta.1";
985
+ var SDK_VERSION = "0.7.32-beta.4";
964
986
  var SCOPE2 = "funding-ledger";
965
987
  var REQUEST_TIMEOUT_MS2 = 5e3;
966
988
  var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
@@ -1210,13 +1232,213 @@ function buildReporter(config) {
1210
1232
  } catch (err) {
1211
1233
  debugLog(SCOPE2, `event failed: ${describeError2(err)}`);
1212
1234
  }
1235
+ },
1236
+ settle(timeoutMs) {
1237
+ return new Promise((resolve) => {
1238
+ let deadline;
1239
+ const done = () => {
1240
+ if (deadline !== void 0) clearTimeout(deadline);
1241
+ resolve();
1242
+ };
1243
+ try {
1244
+ flush();
1245
+ deadline = setTimeout(done, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 0);
1246
+ unrefTimer(deadline);
1247
+ chain2.then(done, done);
1248
+ } catch (err) {
1249
+ debugLog(SCOPE2, `settle failed: ${describeError2(err)}`);
1250
+ done();
1251
+ }
1252
+ });
1213
1253
  }
1214
1254
  };
1215
1255
  }
1216
1256
  }
1217
1257
 
1258
+ // src/lib/zyfai-server-tasks.ts
1259
+ var SCOPE3 = "zyfai-server-tasks";
1260
+ var ZYFAI_SERVER_TASKS = 1;
1261
+ var ZYFAI_SERVER_TASKS_OFF_CODE = "ZYFAI_SERVER_TASKS_OFF";
1262
+ function zyfaiServerTasksEnabled(option) {
1263
+ if (typeof option !== "function") return option === true;
1264
+ try {
1265
+ return option() === true;
1266
+ } catch {
1267
+ return false;
1268
+ }
1269
+ }
1270
+ var ZYFAI_HANDOFF_FEATURE_OFF_CODES = /* @__PURE__ */ new Set([
1271
+ "FUNDING_TASK_KIND_DISABLED",
1272
+ "ZYFAI_NOT_CONFIGURED",
1273
+ "PROBE_ONLY",
1274
+ "HTTP_404",
1275
+ "HTTP_401",
1276
+ "HTTP_403",
1277
+ ZYFAI_SERVER_TASKS_OFF_CODE
1278
+ ]);
1279
+ var ZYFAI_HANDOFF_NO_TASK_CODES = /* @__PURE__ */ new Set([
1280
+ ...ZYFAI_HANDOFF_FEATURE_OFF_CODES,
1281
+ "ZYFAI_SESSION_MISMATCH",
1282
+ "NEEDS_USER_SESSION"
1283
+ ]);
1284
+ var UNCHECKED_NO_TASK_CODES = /* @__PURE__ */ new Set([
1285
+ "HTTP_401",
1286
+ "HTTP_403",
1287
+ "HTTP_404",
1288
+ ZYFAI_SERVER_TASKS_OFF_CODE
1289
+ ]);
1290
+ function provesNoServerTask(code, afterLostAnswer) {
1291
+ return ZYFAI_HANDOFF_NO_TASK_CODES.has(code) && !(afterLostAnswer && UNCHECKED_NO_TASK_CODES.has(code));
1292
+ }
1293
+ var ZYFAI_WITHDRAWAL_NOT_SENT_CODES = /* @__PURE__ */ new Set([
1294
+ "PREREQUISITE_FAILED",
1295
+ "PREREQUISITE_TIMEOUT",
1296
+ "SUPERSEDED",
1297
+ "ZYFAI_SESSION_MISMATCH",
1298
+ "ZYFAI_REDEMPTION_IN_FLIGHT",
1299
+ "NEEDS_USER_SESSION",
1300
+ "NEEDS_USER_SESSION_KEY",
1301
+ "ZYFAI_FORBIDDEN",
1302
+ "WITHDRAW_INSUFFICIENT_BALANCE",
1303
+ "WITHDRAWAL_IN_PROGRESS",
1304
+ // The task's own agent:zyfai leg was already failed or confirmed (P4-8).
1305
+ "LEG_CLOSED",
1306
+ "MAX_ATTEMPTS",
1307
+ "SECRET_UNREADABLE"
1308
+ ]);
1309
+ var ZYFAI_REGISTRATION_NOT_SENT_CODES = /* @__PURE__ */ new Set([
1310
+ "TRANSFER_NOT_SEEN",
1311
+ "DEPOSIT_NOT_SENT",
1312
+ "DEPOSIT_REVERTED",
1313
+ "TRANSFER_MISMATCH",
1314
+ "NEEDS_USER_SESSION",
1315
+ "ZYFAI_REGISTRATION_REJECTED",
1316
+ "MAX_ATTEMPTS",
1317
+ "SECRET_UNREADABLE"
1318
+ ]);
1319
+ var ZYFAI_TASK_LIFETIME_MS = {
1320
+ zyfai_withdrawal: 72e5,
1321
+ zyfai_deposit_registration: 36e5
1322
+ };
1323
+ var SESSION_MARGIN_MS = 3e5;
1324
+ var SESSION_SLACK_MS = 6e4;
1325
+ function sessionExp(session) {
1326
+ try {
1327
+ const parts = session.split(".");
1328
+ if (parts.length !== 3) return void 0;
1329
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
1330
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
1331
+ const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
1332
+ const claims2 = JSON.parse(decoded);
1333
+ const exp = isRecord(claims2) ? claims2.exp : void 0;
1334
+ return typeof exp === "number" && Number.isFinite(exp) ? exp : void 0;
1335
+ } catch {
1336
+ return void 0;
1337
+ }
1338
+ }
1339
+ function zyfaiSessionOutlivesTask(session, kind, nowMs = Date.now()) {
1340
+ const lifetimeMs = Object.prototype.hasOwnProperty.call(ZYFAI_TASK_LIFETIME_MS, kind) ? ZYFAI_TASK_LIFETIME_MS[kind] : void 0;
1341
+ if (typeof session !== "string" || lifetimeMs === void 0) return false;
1342
+ const exp = sessionExp(session);
1343
+ return exp !== void 0 && exp * 1e3 > nowMs + lifetimeMs + SESSION_MARGIN_MS + SESSION_SLACK_MS;
1344
+ }
1345
+ var DEFAULT_TIMEOUT_MS = 25e3;
1346
+ var DECLINED_STATUSES = /* @__PURE__ */ new Set([400, 401, 403, 404, 409, 429]);
1347
+ var ID = /^.{1,128}$/s;
1348
+ var TX_HASH = /^0x[0-9a-fA-F]{64}$/;
1349
+ var unknown = () => ({ type: "unknown" });
1350
+ var isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
1351
+ function requestBody(body, session) {
1352
+ const out = {
1353
+ kind: body.kind,
1354
+ operationId: body.operationId,
1355
+ owner: body.owner,
1356
+ chainId: body.chainId,
1357
+ asset: body.asset,
1358
+ amount: body.amount,
1359
+ agentWallet: body.agentWallet,
1360
+ zyfaiSession: session
1361
+ };
1362
+ if (body.after !== void 0) out.after = body.after;
1363
+ if (body.runNow !== void 0) out.runNow = body.runNow;
1364
+ return out;
1365
+ }
1366
+ function taskResult(value) {
1367
+ const result = {};
1368
+ if (!isRecord(value)) return result;
1369
+ if (typeof value.providerWithdrawalId === "string" && ID.test(value.providerWithdrawalId))
1370
+ result.providerWithdrawalId = value.providerWithdrawalId;
1371
+ if (typeof value.providerDepositId === "string" && ID.test(value.providerDepositId))
1372
+ result.providerDepositId = value.providerDepositId;
1373
+ if (typeof value.txHash === "string" && TX_HASH.test(value.txHash)) result.txHash = value.txHash;
1374
+ if (value.asyncPending === true) result.asyncPending = true;
1375
+ if (value.handedOff === "reconciler") result.handedOff = "reconciler";
1376
+ if (value.lifecycle === "failed") result.lifecycle = "failed";
1377
+ return result;
1378
+ }
1379
+ function answerOf(payload) {
1380
+ if (!isRecord(payload) || payload.success !== true || !isRecord(payload.data)) return unknown();
1381
+ const data = payload.data;
1382
+ if (data.accepted === false)
1383
+ return typeof data.code === "string" && data.code ? { type: "declined", code: data.code } : unknown();
1384
+ if (data.accepted !== true || typeof data.taskId !== "string" || !data.taskId || typeof data.state !== "string" || !data.state) return unknown();
1385
+ return {
1386
+ type: "accepted",
1387
+ taskId: data.taskId,
1388
+ state: data.state,
1389
+ ...typeof data.code === "string" && data.code ? { code: data.code } : {},
1390
+ result: taskResult(data.result)
1391
+ };
1392
+ }
1393
+ async function handOffZyfaiTask(cfg, body, session) {
1394
+ if (cfg?.zyfaiServerTasks !== true) return { type: "declined", code: ZYFAI_SERVER_TASKS_OFF_CODE };
1395
+ let timer;
1396
+ try {
1397
+ const controller = new AbortController();
1398
+ const timeoutMs = typeof cfg.timeoutMs === "number" && Number.isFinite(cfg.timeoutMs) && cfg.timeoutMs > 0 ? cfg.timeoutMs : DEFAULT_TIMEOUT_MS;
1399
+ const url = `${getRoutingApiBaseUrl(cfg.baseUrl).replace(/\/$/, "")}/api/v1/funding/zyfai-tasks`;
1400
+ const timedOut = new Promise((resolve) => {
1401
+ timer = setTimeout(() => {
1402
+ controller.abort();
1403
+ resolve("timeout");
1404
+ }, timeoutMs);
1405
+ timer.unref?.();
1406
+ });
1407
+ const exchange = (async () => {
1408
+ const res = await fetch(url, {
1409
+ method: "POST",
1410
+ headers: { "content-type": "application/json", "x-owney-api-key": cfg.apiKey },
1411
+ body: JSON.stringify(requestBody(body, session)),
1412
+ signal: controller.signal,
1413
+ // The body carries the session: never follow a redirect to another host.
1414
+ redirect: "error"
1415
+ });
1416
+ if (DECLINED_STATUSES.has(res.status)) return { type: "declined", code: `HTTP_${res.status}` };
1417
+ if (res.status !== 200) {
1418
+ debugLog(SCOPE3, `handoff answered ${res.status}`);
1419
+ return unknown();
1420
+ }
1421
+ return answerOf(await res.json());
1422
+ })();
1423
+ exchange.catch(() => {
1424
+ });
1425
+ const answer = await Promise.race([exchange, timedOut]);
1426
+ if (answer === "timeout") {
1427
+ debugLog(SCOPE3, "handoff timed out");
1428
+ return unknown();
1429
+ }
1430
+ debugLog(SCOPE3, `handoff ${answer.type}`);
1431
+ return answer;
1432
+ } catch {
1433
+ debugLog(SCOPE3, "handoff failed");
1434
+ return unknown();
1435
+ } finally {
1436
+ if (timer !== void 0) clearTimeout(timer);
1437
+ }
1438
+ }
1439
+
1218
1440
  // src/lib/withdrawal-reservation.ts
1219
- var SCOPE3 = "withdrawal-reservation";
1441
+ var SCOPE4 = "withdrawal-reservation";
1220
1442
  var CLAIM_TIMEOUT_MS = 4e3;
1221
1443
  var HEARTBEAT_INTERVAL_MS = 6e4;
1222
1444
  var HOLDER_TIMEOUT_MS = 5e3;
@@ -1314,7 +1536,7 @@ function compact2(obj) {
1314
1536
  async function claimWithdrawalReservation(cfg, input) {
1315
1537
  const result = await claimOnce(cfg, input);
1316
1538
  if (result.status === "unavailable" && result.httpStatus === 403 && input.supersedes) {
1317
- debugLog(SCOPE3, "claim 403 with supersedes: retrying without it");
1539
+ debugLog(SCOPE4, "claim 403 with supersedes: retrying without it");
1318
1540
  const { supersedes: _dropped, ...rest } = input;
1319
1541
  const retried = await claimOnce(cfg, rest);
1320
1542
  return retried.status === "claimed" ? { ...retried, supersedesDropped: true } : retried;
@@ -1341,7 +1563,7 @@ async function claimOnce(cfg, input) {
1341
1563
  res = await post(cfg, endpoint(cfg), body, CLAIM_TIMEOUT_MS);
1342
1564
  } catch (err) {
1343
1565
  const reason = err instanceof RequestTimeout ? "timeout" : "network";
1344
- debugLog(SCOPE3, `claim ${reason}: ${describeError3(err)}`);
1566
+ debugLog(SCOPE4, `claim ${reason}: ${describeError3(err)}`);
1345
1567
  return { status: "unavailable", reason };
1346
1568
  }
1347
1569
  if (!res) return { status: "unavailable", reason: "network" };
@@ -1357,7 +1579,7 @@ async function claimOnce(cfg, input) {
1357
1579
  }
1358
1580
  }
1359
1581
  if (!res.ok) {
1360
- debugLog(SCOPE3, `claim ${res.status}`);
1582
+ debugLog(SCOPE4, `claim ${res.status}`);
1361
1583
  return { status: "unavailable", reason: "http", httpStatus: res.status };
1362
1584
  }
1363
1585
  const data = unwrap2(res.body);
@@ -1392,12 +1614,12 @@ async function holderRequest(cfg, operationId, action, body, retryDelays = []) {
1392
1614
  if (res.ok) return "ok";
1393
1615
  if (res.status === 404) return "gone";
1394
1616
  if (res.status < 500 && res.status !== 429) {
1395
- debugLog(SCOPE3, `${action} rejected with ${res.status}`);
1617
+ debugLog(SCOPE4, `${action} rejected with ${res.status}`);
1396
1618
  return "rejected";
1397
1619
  }
1398
- debugLog(SCOPE3, `${action} ${res.status}`);
1620
+ debugLog(SCOPE4, `${action} ${res.status}`);
1399
1621
  } catch (err) {
1400
- debugLog(SCOPE3, `${action} failed: ${describeError3(err)}`);
1622
+ debugLog(SCOPE4, `${action} failed: ${describeError3(err)}`);
1401
1623
  }
1402
1624
  if (attempt < retryDelays.length) {
1403
1625
  await new Promise((resolve) => unrefTimer2(setTimeout(resolve, retryDelays[attempt])));
@@ -1570,6 +1792,8 @@ function ledgerLegFromEvidence(e, kind) {
1570
1792
  if (e.callsId && e.callsId.length <= 200) leg.callsId = e.callsId;
1571
1793
  if (isUint(e.confirmedAmount)) leg.confirmedAmountRaw = e.confirmedAmount;
1572
1794
  if (e.blockNumber && /^\d{1,19}$/.test(e.blockNumber)) leg.blockNumber = e.blockNumber;
1795
+ const providerRef = e.withdrawalRequest?.providerWithdrawalId || e.providerDepositId;
1796
+ if (typeof providerRef === "string" && providerRef.length >= 1 && providerRef.length <= 128) leg.providerRef = providerRef;
1573
1797
  const failure = e.withdrawalRequest?.failure;
1574
1798
  if (failure) leg.errorCode = failure.toUpperCase().replace(/-/g, "_");
1575
1799
  return leg;
@@ -1597,6 +1821,12 @@ function withdrawalLedgerRail(e) {
1597
1821
  return e.userOpHash ? "yieldseeker_userop" : e.callsId ? "yieldseeker_wallet_calls" : e.txHash ? "yieldseeker_paid" : void 0;
1598
1822
  return void 0;
1599
1823
  }
1824
+ function depositLedgerRail(e) {
1825
+ if (e.callsId) return "wallet_calls";
1826
+ if (e.depositAuthorization || e.serverBroadcast) return "permit2_batch";
1827
+ return void 0;
1828
+ }
1829
+ var FUNDING_DEPOSIT_RECOVERY_REPORT = 1;
1600
1830
  var SWAP_LEDGER_RAILS = {
1601
1831
  classic: "swap_classic",
1602
1832
  fusion: "swap_fusion",
@@ -1664,6 +1894,12 @@ var TRANSFER_EVENT = getAbiItem({
1664
1894
  abi: parseAbi2(["event Transfer(address indexed from, address indexed to, uint256 value)"]),
1665
1895
  name: "Transfer"
1666
1896
  });
1897
+ var acceptedChunk = /* @__PURE__ */ new Map();
1898
+ var acceptedChunkByClient = /* @__PURE__ */ new WeakMap();
1899
+ function providerKey(client) {
1900
+ const { chain: chain2, transport } = client;
1901
+ return typeof chain2?.id === "number" ? `${chain2.id}:${typeof transport?.url === "string" ? transport.url : ""}` : void 0;
1902
+ }
1667
1903
  function parseBlock(value) {
1668
1904
  if (value === void 0 || value === null) return void 0;
1669
1905
  return typeof value === "string" && /^\d{1,19}$/.test(value) ? BigInt(value) : null;
@@ -1688,11 +1924,12 @@ async function scanWithdrawalPayouts(client, input) {
1688
1924
  const candidates = [];
1689
1925
  if (start > end) {
1690
1926
  const kept = input.cursor ?? (start > 0n ? start - 1n : 0n);
1691
- return { candidates, scannedTo: kept.toString() };
1927
+ return { candidates, scannedTo: kept.toString(), caughtUp: true };
1692
1928
  }
1693
1929
  const tokenAddress = input.token.toLowerCase();
1694
1930
  const owner = input.owner.toLowerCase();
1695
- let size = PAYOUT_SCAN_CHUNK_BLOCKS;
1931
+ const key2 = providerKey(client);
1932
+ let size = (key2 !== void 0 ? acceptedChunk.get(key2) : acceptedChunkByClient.get(client)) ?? PAYOUT_SCAN_CHUNK_BLOCKS;
1696
1933
  let from = start;
1697
1934
  let scannedTo = start - 1n;
1698
1935
  for (let requests = 0; from <= end && requests < MAX_CHUNKS_PER_SCAN; ) {
@@ -1707,12 +1944,21 @@ async function scanWithdrawalPayouts(client, input) {
1707
1944
  toBlock: to
1708
1945
  });
1709
1946
  } catch (error) {
1710
- if (size > 1n && !isRateLimited(error) && isRangeError(error)) {
1711
- size /= 2n;
1712
- continue;
1947
+ if (!isRateLimited(error) && isRangeError(error)) {
1948
+ const requested = to - from + 1n;
1949
+ const allowed = allowedRange(error);
1950
+ const next = allowed !== void 0 && allowed < requested ? allowed : requested / 2n;
1951
+ if (next >= 1n && next < requested) {
1952
+ size = next;
1953
+ continue;
1954
+ }
1713
1955
  }
1714
1956
  throw error;
1715
1957
  }
1958
+ if (size < PAYOUT_SCAN_CHUNK_BLOCKS) {
1959
+ if (key2 !== void 0) acceptedChunk.set(key2, size);
1960
+ else acceptedChunkByClient.set(client, size);
1961
+ }
1716
1962
  check();
1717
1963
  requests++;
1718
1964
  for (const log of logs) {
@@ -1730,7 +1976,8 @@ async function scanWithdrawalPayouts(client, input) {
1730
1976
  scannedTo = to;
1731
1977
  from = to + 1n;
1732
1978
  }
1733
- return { candidates, scannedTo: (scannedTo < 0n ? 0n : scannedTo).toString() };
1979
+ const stalled = input.cursor !== void 0 && scannedTo <= input.cursor;
1980
+ return { candidates, scannedTo: (scannedTo < 0n ? 0n : scannedTo).toString(), caughtUp: from > end || stalled };
1734
1981
  }
1735
1982
  async function estimateBlockAt(client, latest, targetMs) {
1736
1983
  const head = await client.getBlock({ blockNumber: latest });
@@ -1755,6 +2002,7 @@ function causeChain(error) {
1755
2002
  chain2.push({
1756
2003
  code: value.code,
1757
2004
  status: value.status,
2005
+ data: value.data,
1758
2006
  text: [value.message, value.details, value.shortMessage].filter((part) => typeof part === "string").join(" ")
1759
2007
  });
1760
2008
  current = value.cause;
@@ -1764,6 +2012,14 @@ function causeChain(error) {
1764
2012
  function isRateLimited(error) {
1765
2013
  return causeChain(error).some((entry) => entry.status === 429 || entry.code === 429 || /rate|too many requests/i.test(entry.text));
1766
2014
  }
2015
+ function allowedRange(error) {
2016
+ for (const entry of causeChain(error)) {
2017
+ const hinted = entry.data && typeof entry.data === "object" ? entry.data.maxBlockRange : void 0;
2018
+ const value = typeof hinted === "number" ? hinted : Number(/"maxBlockRange"\s*:\s*(\d+)/.exec(entry.text)?.[1]);
2019
+ if (Number.isSafeInteger(value) && value >= 1) return BigInt(value);
2020
+ }
2021
+ return void 0;
2022
+ }
1767
2023
  function isRangeError(error) {
1768
2024
  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));
1769
2025
  }
@@ -1869,6 +2125,7 @@ async function getSponsorRelayerAddress(input) {
1869
2125
  return parsed.data.relayer;
1870
2126
  }
1871
2127
  async function postSponsorBatchTransfer(input) {
2128
+ const flaggedPrepare = input.body.serverBroadcast === true && !input.body.serializedTransaction;
1872
2129
  let res;
1873
2130
  try {
1874
2131
  res = await fetch(
@@ -1887,7 +2144,7 @@ async function postSponsorBatchTransfer(input) {
1887
2144
  throw new OwneyError(
1888
2145
  "SPONSOR_REQUEST_FAILED",
1889
2146
  "Deposit status is unknown. Retry the same amount to check it.",
1890
- { safeToFallback: false }
2147
+ { safeToFallback: false, ...flaggedPrepare ? { outcomeUnknown: true } : {} }
1891
2148
  );
1892
2149
  }
1893
2150
  const parsed = await res.json().catch(() => null);
@@ -1898,7 +2155,13 @@ async function postSponsorBatchTransfer(input) {
1898
2155
  {
1899
2156
  statusCode: res.status,
1900
2157
  safeToFallback: false,
1901
- notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
2158
+ notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true,
2159
+ // Another authorization of this operation is being sent by the
2160
+ // server (409, nothing signed).
2161
+ ...parsed?.code === "DEPOSIT_ALREADY_SENDING" ? { code: "DEPOSIT_ALREADY_SENDING" } : {},
2162
+ // No definitive answer (5xx, an unreadable or malformed body): the
2163
+ // server may have accepted it. A 4xx with a body is a refusal.
2164
+ ...flaggedPrepare && (res.status >= 500 || parsed === null || res.ok) ? { outcomeUnknown: true } : {}
1902
2165
  }
1903
2166
  );
1904
2167
  }
@@ -1915,19 +2178,19 @@ async function postSponsorBatchRecovery(input) {
1915
2178
  input.signal?.throwIfAborted();
1916
2179
  if (!response.ok || payload?.success !== true) throw failure();
1917
2180
  const value = payload.data;
1918
- const { isAddress: isAddress5, keccak256: keccak2564, parseTransaction: parseTransaction3, recoverTransactionAddress } = await import("viem");
1919
- const { validateTokenBatchAttempt: validateTokenBatchAttempt2 } = await import("./token-batch-family-EKOLMSTD.js");
2181
+ const { isAddress: isAddress6, keccak256: keccak2564, parseTransaction: parseTransaction3, recoverTransactionAddress } = await import("viem");
2182
+ const { validateTokenBatchAttempt: validateTokenBatchAttempt2 } = await import("./token-batch-family-TUGZWETV.js");
1920
2183
  const phase = input.body.phase;
1921
2184
  const original = phase === "approval" ? input.body.serializedPermitTransaction : input.body.serializedTransaction;
1922
2185
  if (!original) throw failure();
1923
2186
  const hash2 = keccak2564(original), relayer = await recoverTransactionAddress({ serializedTransaction: original });
1924
- const validHash = (v) => typeof v === "string" && /^0x[\da-f]{64}$/i.test(v);
1925
- if (!value || value.protocol !== 1 || value.phase !== phase || !validHash(value.txHash) || !isAddress5(value.relayer) || value.relayer.toLowerCase() !== relayer.toLowerCase()) throw failure();
2187
+ const validHash2 = (v) => typeof v === "string" && /^0x[\da-f]{64}$/i.test(v);
2188
+ if (!value || value.protocol !== 1 || value.phase !== phase || !validHash2(value.txHash) || !isAddress6(value.relayer) || value.relayer.toLowerCase() !== relayer.toLowerCase()) throw failure();
1926
2189
  if (value.status !== "prepared" && value.txHash.toLowerCase() !== hash2.toLowerCase()) throw failure();
1927
2190
  if (value.status === "unresolved") {
1928
2191
  if (!["nonce-not-consumed", "authorization-used", "authorization-expired", "state-unavailable", "preparation-rejected", "relayer-changed"].includes(value.reason)) throw failure();
1929
2192
  } else {
1930
- if (!("blockNumber" in value) || typeof value.blockNumber !== "string" || !/^\d{1,78}$/.test(value.blockNumber) || BigInt(value.blockNumber) >= 1n << 256n || !validHash(value.blockHash)) throw failure();
2193
+ if (!("blockNumber" in value) || typeof value.blockNumber !== "string" || !/^\d{1,78}$/.test(value.blockNumber) || BigInt(value.blockNumber) >= 1n << 256n || !validHash2(value.blockHash)) throw failure();
1931
2194
  if (value.status === "prepared") {
1932
2195
  if (value.predecessorHash?.toLowerCase() !== hash2.toLowerCase()) throw failure();
1933
2196
  const tx = parseTransaction3(value.serializedTransaction), old = parseTransaction3(original);
@@ -2225,10 +2488,19 @@ var AgentReads = class {
2225
2488
  }
2226
2489
  };
2227
2490
 
2491
+ // src/agents/zyfai/zyfai.session-errors.ts
2492
+ var SESSION_REJECTED = /(?:^|: )Unauthorized: (?:(?:Invalid or expired user session|Missing user session)\b.*|Invalid API key)$/;
2493
+ function isZyfaiSessionRejected(message) {
2494
+ return SESSION_REJECTED.test(message);
2495
+ }
2496
+
2228
2497
  // src/agents/zyfai/zyfai.withdraw-submission.ts
2229
2498
  var BELOW_MINIMUM = /^Idle balance (\d+(?:\.\d+)?) (USDC|WETH|ETH|EURC) is below the minimum withdrawal of (\d+(?:\.\d+)?) \2\.?$/;
2230
2499
  var listenersByClient = /* @__PURE__ */ new WeakMap();
2231
- function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2500
+ var ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT = 1;
2501
+ var MAX_WITHDRAWAL_ID_LENGTH = 128;
2502
+ var WITHDRAWAL_PATHS = { get: "/users/withdraw/v2", post: "/users/partial-withdraw/v2" };
2503
+ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent, beforeDispatch) {
2232
2504
  const client = sdk.httpClient;
2233
2505
  if (!client || typeof client.get !== "function" || typeof client.post !== "function")
2234
2506
  return {
@@ -2248,7 +2520,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2248
2520
  const original = client[method];
2249
2521
  client[method] = async function(...args) {
2250
2522
  const [url, options] = args;
2251
- const withdrawal = method === "get" && url === "/users/withdraw" || method === "post" && url === "/users/partial-withdraw";
2523
+ const withdrawal = url === WITHDRAWAL_PATHS[method];
2252
2524
  const payload = method === "get" ? options?.params : options;
2253
2525
  const matching = withdrawal ? [...observers].filter((observer) => observer.chainId === payload?.chainId && observer.token === payload?.tokenSymbol) : [];
2254
2526
  for (const observer of matching) observer.assertCurrent?.();
@@ -2256,8 +2528,12 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2256
2528
  const request3 = {
2257
2529
  attempted: true,
2258
2530
  method: method === "get" ? "GET" : "POST",
2259
- path: method === "get" ? "/users/withdraw" : "/users/partial-withdraw"
2531
+ path: WITHDRAWAL_PATHS[method]
2260
2532
  };
2533
+ if (withdrawal) {
2534
+ for (const observer of matching) await observer.beforeDispatch?.({ ...request3 });
2535
+ for (const observer of matching) observer.assertCurrent?.();
2536
+ }
2261
2537
  let belowMinimum2;
2262
2538
  const notify2 = (outcome) => {
2263
2539
  if (outcome === "rejected" && matching.length !== 1) return;
@@ -2291,7 +2567,13 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2291
2567
  ] };
2292
2568
  }
2293
2569
  try {
2294
- return await original.apply(this, callArgs);
2570
+ const result = await original.apply(this, callArgs);
2571
+ if (withdrawal) {
2572
+ const id = result?.id;
2573
+ if (typeof id === "string" && id.length >= 1 && id.length <= MAX_WITHDRAWAL_ID_LENGTH)
2574
+ request3.providerWithdrawalId = id;
2575
+ }
2576
+ return result;
2295
2577
  } catch (error) {
2296
2578
  const message = error instanceof Error ? error.message : "";
2297
2579
  const minimum = BELOW_MINIMUM.exec(message);
@@ -2299,7 +2581,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2299
2581
  const status = error?.response?.status;
2300
2582
  if (request3.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
2301
2583
  request3.httpStatus = status;
2302
- 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";
2584
+ 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";
2303
2585
  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");
2304
2586
  throw error;
2305
2587
  } finally {
@@ -2313,7 +2595,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
2313
2595
  let rejected = false;
2314
2596
  let request2 = { attempted: false };
2315
2597
  let belowMinimum;
2316
- const listener = { chainId, token, assertCurrent, notify: (outcome, evidence, detail) => {
2598
+ const listener = { chainId, token, assertCurrent, beforeDispatch, notify: (outcome, evidence, detail) => {
2317
2599
  request2 = evidence;
2318
2600
  if (outcome === "attempted") submitted = true;
2319
2601
  else if (outcome === "rejected") {
@@ -2454,7 +2736,7 @@ async function submitZyfaiDeposit({ sdk, state, chain: chain2, amount, asset, sm
2454
2736
  if (!token || !isAddress(owner) || !isAddress(smartWallet) || !/^\d+$/.test(amount) || BigInt(amount) <= 0n)
2455
2737
  throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId: chain2.id, asset }, "zyfai");
2456
2738
  assertCurrent();
2457
- const prepared = await sdk.prepareEnterPosition({ userAddress: owner, chainId: chain2.id, amount, asset, strategy: "aggressive" });
2739
+ const prepared = await sdk.prepareDeposit({ userAddress: owner, chainId: chain2.id, amount, asset });
2458
2740
  assertCurrent();
2459
2741
  const expectedData = encodeFunctionData({ abi: erc20Abi2, functionName: "transfer", args: [smartWallet, BigInt(amount)] });
2460
2742
  const transfer = prepared?.transfer;
@@ -2536,6 +2818,13 @@ function tokenDecimals(symbol2, explicit) {
2536
2818
  return explicit;
2537
2819
  return DECIMALS_BY_SYMBOL[(symbol2 ?? "").toLowerCase()] ?? 6;
2538
2820
  }
2821
+ function decodeDeltaAmount(raw2, decimals) {
2822
+ if (typeof raw2 !== "string") return void 0;
2823
+ if (/^0x[0-9a-f]+$/i.test(raw2)) return String(hexToDecimal(raw2, decimals));
2824
+ if (/^\d+$/.test(raw2))
2825
+ return String(hexToDecimal(BigInt(raw2).toString(16), decimals));
2826
+ return void 0;
2827
+ }
2539
2828
  function mapWithdraw(raw2) {
2540
2829
  return {
2541
2830
  txHash: raw2.txHash,
@@ -2585,6 +2874,40 @@ function mapPendingAllocations(raw2) {
2585
2874
  }
2586
2875
  return pending.length > 0 ? pending : void 0;
2587
2876
  }
2877
+ function mapPendingWithdrawals(raw2) {
2878
+ if (!Array.isArray(raw2)) return void 0;
2879
+ const pending = [];
2880
+ for (const entry of raw2) {
2881
+ if (typeof entry !== "object" || entry === null) continue;
2882
+ if (typeof entry.id !== "string" || entry.id === "") continue;
2883
+ if (entry.status !== "REQUESTED" && entry.status !== "CLAIMABLE") continue;
2884
+ if (typeof entry.chainId !== "number" || !Number.isFinite(entry.chainId)) continue;
2885
+ const asset = entry.token?.symbol;
2886
+ if (typeof asset !== "string" || asset === "" || typeof entry.amount !== "string") continue;
2887
+ let amountRaw;
2888
+ try {
2889
+ const amount = BigInt(/^0x/i.test(entry.amount) ? entry.amount : `0x${entry.amount}`);
2890
+ if (amount <= 0n) continue;
2891
+ amountRaw = amount.toString();
2892
+ } catch {
2893
+ continue;
2894
+ }
2895
+ pending.push({ id: entry.id, status: entry.status, chainId: entry.chainId, asset, amountRaw });
2896
+ }
2897
+ return pending.length > 0 ? pending : void 0;
2898
+ }
2899
+ function mapRecentAsyncWithdrawals(raw2) {
2900
+ if (!Array.isArray(raw2)) return void 0;
2901
+ const recent = [];
2902
+ for (const entry of raw2) {
2903
+ if (typeof entry !== "object" || entry === null) return void 0;
2904
+ if (entry.status === "FAILED") continue;
2905
+ const asset = entry.token?.symbol;
2906
+ 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;
2907
+ recent.push({ id: entry.id, status: entry.status, chainId: entry.chainId, asset });
2908
+ }
2909
+ return recent;
2910
+ }
2588
2911
  function mapBalances(raw2, _chainId, smartWallet) {
2589
2912
  const portfolio = raw2.portfolio;
2590
2913
  if (!portfolio?.portfolioByChain || typeof portfolio.portfolioByChain !== "object")
@@ -2643,6 +2966,9 @@ function mapBalances(raw2, _chainId, smartWallet) {
2643
2966
  const pendingAllocations = mapPendingAllocations(
2644
2967
  portfolio.staleBalances
2645
2968
  );
2969
+ const asyncWithdrawals = portfolio.pendingAsyncWithdrawals;
2970
+ const pendingWithdrawals = mapPendingWithdrawals(asyncWithdrawals);
2971
+ const recentAsyncWithdrawals = mapRecentAsyncWithdrawals(asyncWithdrawals);
2646
2972
  return {
2647
2973
  smartWallet,
2648
2974
  totalBalance: String(totalBalance),
@@ -2658,7 +2984,49 @@ function mapBalances(raw2, _chainId, smartWallet) {
2658
2984
  blockNumber: provenance.blockNumber === void 0 ? void 0 : String(provenance.blockNumber),
2659
2985
  positionsStatus: Array.isArray(portfolio.positions) ? "available" : "unavailable"
2660
2986
  })),
2661
- ...pendingAllocations ? { pendingAllocations } : {}
2987
+ ...pendingAllocations ? { pendingAllocations } : {},
2988
+ ...pendingWithdrawals ? { pendingWithdrawals } : {},
2989
+ ...recentAsyncWithdrawals ? { recentAsyncWithdrawals } : {}
2990
+ };
2991
+ }
2992
+ var TX_HASH2 = /^0x[0-9a-fA-F]{64}$/;
2993
+ var validHash = (value) => typeof value === "string" && TX_HASH2.test(value) ? value : void 0;
2994
+ var validDate = (value) => typeof value === "string" && Number.isFinite(Date.parse(value)) ? value : void 0;
2995
+ function mapWithdrawalLifecycle(raw2) {
2996
+ const body = raw2;
2997
+ if (!body || typeof body !== "object" || typeof body.status !== "string" || typeof body.syncSettled !== "boolean")
2998
+ return null;
2999
+ const syncTxHash = validHash(body.syncTxHash);
3000
+ const estimatedClaimAt = validDate(body.estimatedClaimAt);
3001
+ const asyncLegs = [];
3002
+ for (const leg of Array.isArray(body.asyncLegs) ? body.asyncLegs : []) {
3003
+ if (!leg || typeof leg !== "object" || typeof leg.id !== "string" || leg.id === "" || typeof leg.status !== "string") continue;
3004
+ const legClaimAt = validDate(leg.estimatedClaimAt);
3005
+ const claimTxHash = validHash(leg.claimTxHash);
3006
+ asyncLegs.push({
3007
+ id: leg.id,
3008
+ status: leg.status,
3009
+ ...legClaimAt ? { estimatedClaimAt: legClaimAt } : {},
3010
+ ...claimTxHash ? { claimTxHash } : {}
3011
+ });
3012
+ }
3013
+ const failedAsyncIds = Array.isArray(body.failedAsyncIds) ? body.failedAsyncIds.filter((id) => typeof id === "string" && id !== "") : [];
3014
+ return {
3015
+ status: body.status,
3016
+ syncSettled: body.syncSettled,
3017
+ ...syncTxHash ? { syncTxHash } : {},
3018
+ asyncLegs,
3019
+ failedAsyncIds,
3020
+ ...estimatedClaimAt ? { estimatedClaimAt } : {},
3021
+ hasError: typeof body.lastError === "string" && body.lastError !== ""
3022
+ };
3023
+ }
3024
+ function mapDepositPlacement(raw2) {
3025
+ const body = raw2;
3026
+ if (!body || typeof body !== "object" || typeof body.status !== "string") return null;
3027
+ return {
3028
+ status: body.status,
3029
+ ...typeof body.positionOutcome === "string" ? { positionOutcome: body.positionOutcome } : {}
2662
3030
  };
2663
3031
  }
2664
3032
  function sumTokenValues(tokens) {
@@ -2822,12 +3190,24 @@ function mapEntries(rawEntries, chainId) {
2822
3190
  const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
2823
3191
  const rawAmount2 = entry?.positions?.[0]?.amount;
2824
3192
  const hasAmount = mappedAction !== "Withdraw" && typeof rawAmount2 === "string" && rawAmount2 !== "" && rawAmount2 !== "0x" && rawAmount2 !== "0x0" && rawAmount2 !== "0";
3193
+ const rawDelta = mappedAction === "Deposit" ? decodeDeltaAmount(
3194
+ entry?.positions?.[0]?.deltaAmount,
3195
+ tokenDecimals(entry?.positions?.[0]?.token_symbol)
3196
+ ) : void 0;
2825
3197
  const mappedPositions = positions.filter((p) => p.protocol_name).map((p) => ({
2826
3198
  protocol: p.protocol_name,
2827
3199
  pool: p.pool ?? void 0,
2828
3200
  tokenSymbol: p.token_symbol ?? void 0,
2829
- amount: mappedAction !== "Withdraw" && typeof p.amount === "string" && p.amount !== "" && p.amount !== "0x" ? String(hexToDecimal(p.amount, tokenDecimals(p.token_symbol))) : void 0
3201
+ amount: mappedAction !== "Withdraw" && typeof p.amount === "string" && p.amount !== "" && p.amount !== "0x" ? String(hexToDecimal(p.amount, tokenDecimals(p.token_symbol))) : void 0,
3202
+ deltaAmount: mappedAction !== "Withdraw" ? decodeDeltaAmount(p.deltaAmount, tokenDecimals(p.token_symbol)) : void 0
2830
3203
  }));
3204
+ const reportedAmount = hasAmount ? String(
3205
+ hexToDecimal(
3206
+ rawAmount2,
3207
+ tokenDecimals(entry?.positions?.[0]?.token_symbol)
3208
+ )
3209
+ ) : void 0;
3210
+ const transactionAmount = rawDelta === void 0 ? reportedAmount : Number(rawDelta) > 0 ? rawDelta : void 0;
2831
3211
  return {
2832
3212
  agent: "zyfai",
2833
3213
  action: mappedAction,
@@ -2840,12 +3220,7 @@ function mapEntries(rawEntries, chainId) {
2840
3220
  txHashes: entry.transactionHash ? [entry.transactionHash] : [],
2841
3221
  chainId: entry.chainId,
2842
3222
  tokenSymbol: entry?.positions?.[0]?.token_symbol,
2843
- amount: hasAmount ? String(
2844
- hexToDecimal(
2845
- rawAmount2,
2846
- tokenDecimals(entry?.positions?.[0]?.token_symbol)
2847
- )
2848
- ) : void 0
3223
+ amount: transactionAmount
2849
3224
  }
2850
3225
  ],
2851
3226
  rebalanceLog
@@ -3044,9 +3419,9 @@ var isJwtExpired = (token) => {
3044
3419
  const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
3045
3420
  const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
3046
3421
  const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
3047
- const claims = JSON.parse(decoded);
3048
- if (typeof claims.exp !== "number") return false;
3049
- return Date.now() / 1e3 >= claims.exp - 30;
3422
+ const claims2 = JSON.parse(decoded);
3423
+ if (typeof claims2.exp !== "number") return false;
3424
+ return Date.now() / 1e3 >= claims2.exp - 30;
3050
3425
  } catch {
3051
3426
  return false;
3052
3427
  }
@@ -3287,7 +3662,7 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
3287
3662
  }
3288
3663
 
3289
3664
  // src/lib/agent-accounts.ts
3290
- var SCOPE4 = "agent-accounts";
3665
+ var SCOPE5 = "agent-accounts";
3291
3666
  var REQUEST_TIMEOUT_MS3 = 5e3;
3292
3667
  function compact3(obj) {
3293
3668
  const out = {};
@@ -3357,7 +3732,7 @@ async function fetchAgentAccounts(input) {
3357
3732
  signal
3358
3733
  });
3359
3734
  if (!res.ok) {
3360
- debugLog(SCOPE4, `fetch rejected with ${res.status}`);
3735
+ debugLog(SCOPE5, `fetch rejected with ${res.status}`);
3361
3736
  return null;
3362
3737
  }
3363
3738
  const data = unwrap3(await res.json());
@@ -3372,7 +3747,7 @@ async function fetchAgentAccounts(input) {
3372
3747
  cancel();
3373
3748
  }
3374
3749
  } catch (err) {
3375
- debugLog(SCOPE4, `fetch failed: ${describeError4(err)}`);
3750
+ debugLog(SCOPE5, `fetch failed: ${describeError4(err)}`);
3376
3751
  return null;
3377
3752
  }
3378
3753
  }
@@ -3403,7 +3778,7 @@ async function reportAgentAccount(input) {
3403
3778
  signal
3404
3779
  });
3405
3780
  if (!res.ok) {
3406
- debugLog(SCOPE4, `report rejected with ${res.status}`);
3781
+ debugLog(SCOPE5, `report rejected with ${res.status}`);
3407
3782
  return null;
3408
3783
  }
3409
3784
  const data = unwrap3(await res.json());
@@ -3415,7 +3790,7 @@ async function reportAgentAccount(input) {
3415
3790
  cancel();
3416
3791
  }
3417
3792
  } catch (err) {
3418
- debugLog(SCOPE4, `report failed: ${describeError4(err)}`);
3793
+ debugLog(SCOPE5, `report failed: ${describeError4(err)}`);
3419
3794
  return null;
3420
3795
  }
3421
3796
  }
@@ -3576,6 +3951,17 @@ var ZYFAI_LEDGER_IDLE_CACHE_MS = 6e4;
3576
3951
  var ZYFAI_LEDGER_REUSE_MS = 6e4;
3577
3952
  var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
3578
3953
  var PAYOUT_SEARCH_HEAD_LIMIT_MS = 2e3;
3954
+ var WITHDRAW_HANDOFF_TIMEOUT_MS = 25e3;
3955
+ var PLANNED_HANDOFF_TIMEOUT_MS = 8e3;
3956
+ var PLANNED_HANDOFFS_LIMIT = 32;
3957
+ var REGISTRATION_HANDOFF_TIMEOUT_MS = 8e3;
3958
+ var REGISTRATION_NUDGE_TIMEOUT_MS = 25e3;
3959
+ var REGISTRATION_AMOUNT = /^[1-9][0-9]{0,77}$/;
3960
+ var TASK_CODE = /^[A-Z0-9_]{1,64}$/;
3961
+ var SERVER_LEG_CLEARED_CODE = "ZYFAI_LEG_CLEARED";
3962
+ var SESSION_ENDING_CODE = "ZYFAI_SESSION_ENDING";
3963
+ var SERVER_LEG_STATES_LIMIT = 2e3;
3964
+ var ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
3579
3965
  function settleWithin(value, ms) {
3580
3966
  let timer;
3581
3967
  const limit = new Promise((resolve) => {
@@ -3585,6 +3971,28 @@ function settleWithin(value, ms) {
3585
3971
  if (timer) clearTimeout(timer);
3586
3972
  });
3587
3973
  }
3974
+ function depositIdFromLog(logged) {
3975
+ const id = logged?.deposit?.id;
3976
+ return typeof id === "string" && id.length >= 1 && id.length <= 128 ? id : void 0;
3977
+ }
3978
+ function registrationOutcome(answer, knownTask) {
3979
+ if (answer.type === "unknown") return { kind: "unknown" };
3980
+ if (answer.type === "declined") {
3981
+ return !knownTask && provesNoServerTask(answer.code, true) ? { kind: "local" } : { kind: "unknown", code: answer.code };
3982
+ }
3983
+ if (answer.result.providerDepositId) return { kind: "confirmed", providerDepositId: answer.result.providerDepositId };
3984
+ if (answer.state === "failed" && answer.code && ZYFAI_REGISTRATION_NOT_SENT_CODES.has(answer.code)) return { kind: "local" };
3985
+ if (answer.state === "queued" || answer.state === "running") return { kind: "pending" };
3986
+ return { kind: "unknown", code: answer.code ?? answer.state.toUpperCase(), accepted: true };
3987
+ }
3988
+ function registrationOutcomeUnknown(code) {
3989
+ const error = new OwneyError(
3990
+ "AGENT_API_ERROR",
3991
+ "Zyfai registration by the server has no known outcome, so this browser does not register the deposit again.",
3992
+ { status: 409, ...code && TASK_CODE.test(code) ? { reason: code } : {} }
3993
+ );
3994
+ return Object.assign(error, { status: 409 });
3995
+ }
3588
3996
  function belowMinimumCeiling(rejection, token, decimals) {
3589
3997
  if ((rejection.symbol === "ETH" ? "WETH" : rejection.symbol) !== token) return void 0;
3590
3998
  const ceiling = (display) => {
@@ -3608,6 +4016,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
3608
4016
  referralSource;
3609
4017
  accountReporting;
3610
4018
  id = "zyfai";
4019
+ /** This adapter can hand its withdrawals to our server (part 4). */
4020
+ supportsServerTasks = true;
3611
4021
  supportedChainIds = [8453, 42161, 1];
3612
4022
  supportedAssets = [
3613
4023
  {
@@ -3684,6 +4094,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
3684
4094
  /** A ledger read slower than this counts as "no ledger information" for that read. */
3685
4095
  ledgerBudgetMs = LEDGER_READ_BUDGET_MS;
3686
4096
  balanceSnapshots = /* @__PURE__ */ new WeakMap();
4097
+ /** operationId -> this session's plan-time handoff of that withdrawal (part 4),
4098
+ * read once by its nudge: the `after` legs and the planned share the task
4099
+ * was handed off with ("MAX" or digits). Who owns the leg is
4100
+ * `serverLegStates`, never this record. */
4101
+ plannedHandoffs = /* @__PURE__ */ new Map();
4102
+ /** operationId -> the server ownership of its Zyfai withdrawal leg (T9-I5).
4103
+ * The one source for "may this browser send it" (`mayWithdrawLocally`). */
4104
+ serverLegStates = /* @__PURE__ */ new Map();
4105
+ /** Deposit operations whose registration create was sent in this session:
4106
+ * a retry of one is always handed off again, whatever its session's
4107
+ * lifetime (F-I1(a)), so the server answers the task it may hold. */
4108
+ requestedRegistrations = /* @__PURE__ */ new Set();
3687
4109
  /** A replaced wallet session gets its own vendor client. In-flight calls
3688
4110
  * keep their original client; authentication must never change owners under
3689
4111
  * an operation that is waiting for provider reads or an HTTP response. */
@@ -3894,10 +4316,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
3894
4316
  * Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
3895
4317
  * cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
3896
4318
  * we drop the cache and try again — the second attempt has no cache entry,
3897
- * so it falls through to a real SIWE prompt. Zyfai labels every HTTP 401 with
3898
- * an `"Unauthorized: Invalid API key"` error so we can't distinguish revoked
3899
- * JWT from a misconfigured API key; we retry once either way, and if the
3900
- * second attempt still fails the original error propagates.
4319
+ * so it falls through to a real SIWE prompt. @zyfai/sdk 0.10.0 labels every
4320
+ * HTTP 401 as an invalid/expired or missing user session ("Unauthorized: …"),
4321
+ * which does not separate a revoked JWT from a misconfigured API key; we
4322
+ * retry once either way, and if the second attempt still fails the original
4323
+ * error propagates.
3901
4324
  */
3902
4325
  async connectAccountWithRetry(provider, chainId, executionSdk = this.sdk) {
3903
4326
  try {
@@ -3934,9 +4357,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
3934
4357
  }
3935
4358
  }
3936
4359
  /** A rejected execution session must not be reused by the next foreground attempt. */
3937
- invalidateRejectedWithdrawalSession(error, state, chainId, authToken) {
4360
+ invalidateRejectedWithdrawalSession(error, state, chainId, authToken, httpStatus) {
3938
4361
  const message = error instanceof Error ? error.message : "";
3939
- if (!/(?:^|: )Unauthorized: Invalid API key$/.test(message)) return;
4362
+ if (!isZyfaiSessionRejected(message) || httpStatus !== void 0 && httpStatus !== 401) return;
4363
+ this.dropWithdrawalSession(state, chainId, authToken);
4364
+ }
4365
+ /** Drops the execution session a withdrawal used (Zyfai rejected it), unless
4366
+ * it has already changed. The next foreground connection signs in again. */
4367
+ dropWithdrawalSession(state, chainId, authToken) {
3940
4368
  const internals = this.sdk;
3941
4369
  if (typeof authToken !== "string" || internals.httpClient.authToken !== authToken || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== chainId) return;
3942
4370
  clearSession(state.walletAddress, chainId);
@@ -4544,7 +4972,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
4544
4972
  await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
4545
4973
  return redeployed.address;
4546
4974
  }
4547
- async deposit(state, chainId, amount, asset, depositCallback, onProgress, onBeforeSubmission) {
4975
+ async deposit(state, chainId, amount, asset, depositCallback, onProgress, onBeforeSubmission, context) {
4548
4976
  const validChainId = isValidChainId(chainId);
4549
4977
  const owner = state.walletAddress;
4550
4978
  const provider = state.provider;
@@ -4559,6 +4987,12 @@ var ZyfaiAgent = class _ZyfaiAgent {
4559
4987
  assertCurrent();
4560
4988
  const smartWallet = await this.ensureWalletDeployed(owner, validChainId);
4561
4989
  assertCurrent();
4990
+ let server;
4991
+ if (this.accountReporting.owneyApiKey && typeof context?.operationId === "string" && context.operationId && this.serverTasksOn(this.requestedRegistrations.has(context.operationId))) {
4992
+ server = await this.handOffDepositRegistration(owner, validChainId, asset, amount, smartWallet, context.operationId);
4993
+ assertCurrent();
4994
+ }
4995
+ const mark = server ?? {};
4562
4996
  notifyFunding(onProgress, {
4563
4997
  agentId: this.id,
4564
4998
  chainId,
@@ -4568,7 +5002,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
4568
5002
  intendedAmount: amount,
4569
5003
  transfer: "pending",
4570
5004
  registration: "pending",
4571
- delivery: "not-applicable"
5005
+ delivery: "not-applicable",
5006
+ ...mark
4572
5007
  });
4573
5008
  const txHash = depositCallback ? await depositCallback(smartWallet, validChainId, amount) : await submitZyfaiDeposit({
4574
5009
  sdk: executionSdk,
@@ -4587,7 +5022,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
4587
5022
  intendedAmount: amount,
4588
5023
  transfer: "pending",
4589
5024
  registration: "pending",
4590
- delivery: "not-applicable"
5025
+ delivery: "not-applicable",
5026
+ ...mark
4591
5027
  }) : void 0
4592
5028
  });
4593
5029
  let evidence = {
@@ -4600,7 +5036,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
4600
5036
  txHash,
4601
5037
  transfer: "pending",
4602
5038
  registration: "pending",
4603
- delivery: "not-applicable"
5039
+ delivery: "not-applicable",
5040
+ ...mark
4604
5041
  };
4605
5042
  notifyFunding(onProgress, evidence);
4606
5043
  try {
@@ -4620,11 +5057,32 @@ var ZyfaiAgent = class _ZyfaiAgent {
4620
5057
  throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
4621
5058
  if (evidence.transfer !== "confirmed") return { txHash, smartWallet, amount, evidence };
4622
5059
  this.ledgerReads.clear();
5060
+ if (server) {
5061
+ let outcome;
5062
+ try {
5063
+ assertCurrent();
5064
+ outcome = await this.nudgeDepositRegistration(evidence, this.executionSession());
5065
+ } catch {
5066
+ outcome = { kind: "unknown" };
5067
+ }
5068
+ if (outcome.kind !== "local") {
5069
+ evidence = outcome.kind === "confirmed" ? { ...evidence, registration: "confirmed", providerDepositId: outcome.providerDepositId, serverTask: true } : {
5070
+ ...evidence,
5071
+ registration: outcome.kind,
5072
+ ...outcome.kind === "pending" || outcome.accepted ? { serverTask: true } : {}
5073
+ };
5074
+ if (outcome.kind !== "confirmed") this.notePendingRegistration(owner, validChainId, txHash, Date.now(), asset);
5075
+ notifyFunding(onProgress, evidence);
5076
+ return { txHash, smartWallet, amount, evidence };
5077
+ }
5078
+ evidence = { ...evidence, serverTask: false };
5079
+ }
4623
5080
  try {
4624
5081
  assertCurrent();
4625
5082
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
4626
- await (tokenAddress ? executionSdk.logDeposit(validChainId, txHash, amount, tokenAddress) : executionSdk.logDeposit(validChainId, txHash, amount));
4627
- evidence = { ...evidence, registration: "confirmed" };
5083
+ const logged = await (tokenAddress ? executionSdk.logDeposit(validChainId, txHash, amount, tokenAddress) : executionSdk.logDeposit(validChainId, txHash, amount));
5084
+ const providerDepositId = depositIdFromLog(logged);
5085
+ evidence = { ...evidence, registration: "confirmed", ...providerDepositId ? { providerDepositId } : {} };
4628
5086
  } catch {
4629
5087
  evidence = { ...evidence, registration: "unknown" };
4630
5088
  this.notePendingRegistration(owner, validChainId, txHash, Date.now(), asset);
@@ -4744,10 +5202,69 @@ var ZyfaiAgent = class _ZyfaiAgent {
4744
5202
  throw unavailable();
4745
5203
  }
4746
5204
  }
4747
- async withdraw(state, chainId, token, amount, onProgress, _gasMode, _onBeforeSubmission, assertOperation) {
5205
+ /**
5206
+ * Settles Zyfai's own wallet prompts before another agent's owner signature:
5207
+ * the SIWE login and, for a legacy wallet the login reported without a
5208
+ * session key, that key (attempted once per session, like activation). Reads
5209
+ * only the login's cached flags; never withdraws. A rejected prompt throws;
5210
+ * any other session-key failure is left to `withdraw`'s own recovery.
5211
+ */
5212
+ async prepareWithdrawal(state, chainId, _token) {
5213
+ const validChainId = isValidChainId(chainId);
5214
+ await this.ensureConnected(state, validChainId);
5215
+ await this.ensureWithdrawalSessionKey(state.walletAddress, validChainId);
5216
+ }
5217
+ /** The session key a legacy wallet's login reported missing, created once per
5218
+ * session (one prompt). A rejected prompt throws; any other failure is left
5219
+ * to the withdrawal itself. */
5220
+ async ensureWithdrawalSessionKey(owner, validChainId) {
5221
+ const internals = this.sdk;
5222
+ const key2 = `${owner.toLowerCase()}:${validChainId}`;
5223
+ if (internals.isPredeployed !== false || internals.hasActiveSessionKey !== false || this.sessionKeyAttempts.has(key2)) return;
5224
+ this.sessionKeyAttempts.add(key2);
5225
+ try {
5226
+ await this.sdk.createSessionKey(owner, validChainId);
5227
+ } catch (error) {
5228
+ const code = error?.code;
5229
+ if (code === 4001 || code === "ACTION_REJECTED" || error instanceof Error && /user (rejected|denied)|request rejected|action rejected/i.test(error.message)) {
5230
+ this.sessionKeyAttempts.delete(key2);
5231
+ throw error;
5232
+ }
5233
+ console.warn(
5234
+ "[zyfai] session key before withdrawal skipped (non-fatal):",
5235
+ error instanceof Error ? error.message : String(error)
5236
+ );
5237
+ }
5238
+ }
5239
+ async withdraw(state, chainId, token, amount, onProgress, _gasMode, onBeforeSubmission, assertOperation, context) {
4748
5240
  if (amount !== void 0 && (typeof amount !== "string" || !/^[0-9]+$/.test(amount) || BigInt(amount) <= 0n))
4749
5241
  throw new OwneyError("WITHDRAW_INVALID_AMOUNT", "Withdrawal amount must be a positive integer in token base units.", void 0, this.id);
4750
5242
  const validChainId = isValidChainId(chainId);
5243
+ if (this.accountReporting.owneyApiKey && typeof context?.operationId === "string" && context.operationId && // T10-I1: a leg whose mark was cleared is never handed off again.
5244
+ this.serverLegStates.get(context.operationId) !== "cleared" && // F-I2(iii): the opt-in, read now, unless the leg was already handed
5245
+ // off (planned, requested or accepted).
5246
+ this.serverTasksOn(context.planned === true || this.serverLegStates.has(context.operationId))) {
5247
+ const served = await this.withdrawThroughServer(
5248
+ state,
5249
+ validChainId,
5250
+ token,
5251
+ amount,
5252
+ onProgress,
5253
+ onBeforeSubmission,
5254
+ assertOperation,
5255
+ { operationId: context.operationId, planned: context.planned === true }
5256
+ );
5257
+ if (served) return served;
5258
+ }
5259
+ if (typeof context?.operationId === "string" && context.operationId && !this.mayWithdrawLocally(context.operationId))
5260
+ this.serverLegUnresolved(onProgress, {
5261
+ agentId: this.id,
5262
+ chainId,
5263
+ token,
5264
+ walletAddress: state.walletAddress,
5265
+ intendedAmount: amount ?? "MAX",
5266
+ registration: "not-applicable"
5267
+ });
4751
5268
  try {
4752
5269
  await this.ensureConnected(state, validChainId);
4753
5270
  } catch (error) {
@@ -4784,15 +5301,35 @@ var ZyfaiAgent = class _ZyfaiAgent {
4784
5301
  assertCurrent();
4785
5302
  assertOperation?.();
4786
5303
  };
4787
- let observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
5304
+ let headRead = Promise.resolve(void 0);
5305
+ let settledHead;
5306
+ const searchHead = () => settledHead ??= settleWithin(headRead, PAYOUT_SEARCH_HEAD_LIMIT_MS);
5307
+ const checkpoint2 = onBeforeSubmission ? async (request2) => {
5308
+ const head = await searchHead();
5309
+ const agentWallet = this.smartWalletCache.get(`${owner.toLowerCase()}_${validChainId}`);
5310
+ await onBeforeSubmission({
5311
+ agentId: this.id,
5312
+ chainId,
5313
+ token,
5314
+ walletAddress: state.walletAddress,
5315
+ intendedAmount: amount ?? "MAX",
5316
+ transfer: "pending",
5317
+ registration: "not-applicable",
5318
+ delivery: "pending",
5319
+ withdrawalRequest: request2,
5320
+ ...agentWallet ? { agentWallet } : {},
5321
+ ...head !== void 0 ? { payoutSearch: { fromBlock: head.toString() } } : {}
5322
+ });
5323
+ } : void 0;
5324
+ let observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission, checkpoint2);
4788
5325
  const authToken = internals.httpClient?.authToken;
4789
5326
  let raw2;
4790
- let headRead = Promise.resolve(void 0);
4791
5327
  try {
4792
5328
  for (let attempt = 0; attempt < 2; attempt++) {
4793
5329
  try {
4794
5330
  assertCurrent();
4795
5331
  headRead = this.readPayoutSearchHead(validChainId);
5332
+ settledHead = void 0;
4796
5333
  raw2 = await executionSdk.withdrawFunds(owner, validChainId, amount, token);
4797
5334
  break;
4798
5335
  } catch (error) {
@@ -4821,7 +5358,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
4821
5358
  assertSubmission();
4822
5359
  await this.recoverWithdrawalSession(state, validChainId, token, owner);
4823
5360
  observation.stop();
4824
- observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
5361
+ observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission, checkpoint2);
4825
5362
  }
4826
5363
  }
4827
5364
  if (!raw2) throw new OwneyError(
@@ -4832,7 +5369,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
4832
5369
  );
4833
5370
  } catch (error) {
4834
5371
  if (staleSessionError && observation.attempted() === false) error = staleSessionError;
4835
- this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
5372
+ this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken, observation.request()?.httpStatus);
4836
5373
  if (observation.attempted() !== void 0) {
4837
5374
  const failed = observation.attempted() === false || observation.rejected();
4838
5375
  notifyFunding(onProgress, {
@@ -4886,7 +5423,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
4886
5423
  if (!raw2.txHash) {
4887
5424
  const acceptedAt = Date.now();
4888
5425
  const agentWallet = this.smartWalletCache.get(`${owner.toLowerCase()}_${validChainId}`);
4889
- const head = await settleWithin(headRead, PAYOUT_SEARCH_HEAD_LIMIT_MS);
5426
+ const head = await searchHead();
4890
5427
  evidence = {
4891
5428
  ...evidence,
4892
5429
  ...agentWallet ? { agentWallet } : {},
@@ -4898,7 +5435,456 @@ var ZyfaiAgent = class _ZyfaiAgent {
4898
5435
  this.ledgerReads.clear();
4899
5436
  return { ...mapWithdraw(raw2), evidence };
4900
5437
  }
4901
- /** Registration recovery never signs, transfers, activates, or refreshes authentication. */
5438
+ /**
5439
+ * Hands a planned withdrawal leg to our server at plan time (part 4), before
5440
+ * the earlier wallet-approval legs (`after`) are signed. The server sends it
5441
+ * once they are accepted; the browser nudges it at that point
5442
+ * (`withdraw(…, { operationId, planned: true })`). Uses the current execution
5443
+ * client's session, after `ensureConnected`. Never throws, never prompts
5444
+ * beyond that connection, and never withdraws locally.
5445
+ */
5446
+ async handOffPlannedWithdrawal(state, chainId, token, amount, ctx) {
5447
+ const { owneyApiKey, routingApiBaseUrl } = this.accountReporting;
5448
+ if (!this.serverTasksOn(this.serverLegStates.has(ctx.operationId)))
5449
+ return { type: "declined", code: ZYFAI_SERVER_TASKS_OFF_CODE };
5450
+ if (this.serverLegStates.get(ctx.operationId) === "cleared") return { type: "declined", code: SERVER_LEG_CLEARED_CODE };
5451
+ const noSession = { type: "declined", code: "NO_SESSION" };
5452
+ if (!owneyApiKey) return noSession;
5453
+ let validChainId;
5454
+ let session;
5455
+ let agentWallet;
5456
+ try {
5457
+ validChainId = isValidChainId(chainId);
5458
+ await this.ensureConnected(state, validChainId);
5459
+ session = this.executionSession();
5460
+ if (!session) return noSession;
5461
+ if (this.serverLegStates.get(ctx.operationId) === void 0 && !zyfaiSessionOutlivesTask(session, "zyfai_withdrawal")) return { type: "declined", code: SESSION_ENDING_CODE };
5462
+ agentWallet = await this.handoffAgentWallet(state.walletAddress, validChainId);
5463
+ } catch {
5464
+ return noSession;
5465
+ }
5466
+ if (!agentWallet) return { type: "declined", code: "NO_AGENT_WALLET" };
5467
+ try {
5468
+ ctx.beforeRequest?.();
5469
+ } catch {
5470
+ }
5471
+ const afterLostAnswer = this.serverLegStates.get(ctx.operationId) === "requested";
5472
+ this.setServerLegState(ctx.operationId, "requested");
5473
+ let answer = await handOffZyfaiTask(
5474
+ { apiKey: owneyApiKey, baseUrl: routingApiBaseUrl, timeoutMs: PLANNED_HANDOFF_TIMEOUT_MS, zyfaiServerTasks: true },
5475
+ {
5476
+ kind: "zyfai_withdrawal",
5477
+ operationId: ctx.operationId,
5478
+ owner: state.walletAddress,
5479
+ chainId: validChainId,
5480
+ asset: token,
5481
+ amount,
5482
+ agentWallet,
5483
+ after: ctx.after
5484
+ },
5485
+ session
5486
+ );
5487
+ if (answer.type === "declined" && ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code) && !provesNoServerTask(answer.code, afterLostAnswer)) answer = { type: "unknown" };
5488
+ if (answer.type === "accepted" || answer.type === "declined" && answer.code === "LEG_ALREADY_SENT")
5489
+ this.setServerLegState(ctx.operationId, "accepted");
5490
+ if (answer.type === "declined") {
5491
+ if (answer.code !== "LEG_ALREADY_SENT" && this.serverLegStates.get(ctx.operationId) === "accepted")
5492
+ return { type: "unknown" };
5493
+ if (provesNoServerTask(answer.code, afterLostAnswer)) this.clearServerLeg(ctx.operationId, "no-task-decline");
5494
+ return answer;
5495
+ }
5496
+ this.plannedHandoffs.delete(ctx.operationId);
5497
+ this.plannedHandoffs.set(ctx.operationId, { after: [...ctx.after], amount });
5498
+ while (this.plannedHandoffs.size > PLANNED_HANDOFFS_LIMIT)
5499
+ this.plannedHandoffs.delete(this.plannedHandoffs.keys().next().value);
5500
+ return answer;
5501
+ }
5502
+ /**
5503
+ * F-I2(iii): whether the server path may run. The caller's opt-in is read
5504
+ * now (a function is called) as an operation's first handoff would start.
5505
+ * An operation this session already handed off (`handedOff`: a planned leg,
5506
+ * a leg requested or accepted, a registration already sent) keeps going
5507
+ * through the server even after the opt-in turns off: its nudges, re-runs
5508
+ * and retries, so the server answers the task it may hold and the browser
5509
+ * never sends it too. Without any opt-in (P4-0) nothing was handed off.
5510
+ */
5511
+ serverTasksOn(handedOff = false) {
5512
+ const option = this.accountReporting.zyfaiServerTasks;
5513
+ if (handedOff) return option === true || typeof option === "function";
5514
+ return zyfaiServerTasksEnabled(option);
5515
+ }
5516
+ /** True when this browser may send the operation's Zyfai withdrawal leg
5517
+ * itself: it never handed it off, or the server proved it has no task. */
5518
+ mayWithdrawLocally(operationId) {
5519
+ const current = this.serverLegStates.get(operationId);
5520
+ return current === void 0 || current === "cleared";
5521
+ }
5522
+ /** Moves the leg's server ownership forward: absent -> requested ->
5523
+ * accepted. "accepted" is sticky, and "cleared" is final (such a leg is
5524
+ * never handed off again). Clearing goes through `clearServerLeg`. */
5525
+ setServerLegState(operationId, next) {
5526
+ const current = this.serverLegStates.get(operationId);
5527
+ if (current === "accepted" || current === "cleared" || current === next) return;
5528
+ this.serverLegStates.set(operationId, next);
5529
+ while (this.serverLegStates.size > SERVER_LEG_STATES_LIMIT)
5530
+ this.serverLegStates.delete(this.serverLegStates.keys().next().value);
5531
+ }
5532
+ /** Clears the leg on a proof that the server has no task: a no-task decline
5533
+ * after a request, or nothing ever requested (T8-R4b). Never from accepted.
5534
+ * Returns whether the leg is cleared; the caller then emits
5535
+ * `serverTask: false` and may send locally (T10-I1, T9-I5). */
5536
+ clearServerLeg(operationId, proof) {
5537
+ const current = this.serverLegStates.get(operationId);
5538
+ if (current === "cleared") return true;
5539
+ if (proof === "no-task-decline" ? current !== "requested" : current !== void 0) return false;
5540
+ this.serverLegStates.set(operationId, "cleared");
5541
+ while (this.serverLegStates.size > SERVER_LEG_STATES_LIMIT)
5542
+ this.serverLegStates.delete(this.serverLegStates.keys().next().value);
5543
+ return true;
5544
+ }
5545
+ /** T9-I5: the server may hold this leg's task, so this browser never sends
5546
+ * it. The leg is reported failed with `serverTask: "unknown"` (never
5547
+ * cleared; a re-run hands it off again), and `error`, or an unresolved-leg
5548
+ * error, is thrown. */
5549
+ serverLegUnresolved(onProgress, base7, error) {
5550
+ notifyFunding(onProgress, {
5551
+ ...base7,
5552
+ transfer: "failed",
5553
+ delivery: "failed",
5554
+ withdrawalRequest: { attempted: false },
5555
+ serverTask: "unknown"
5556
+ });
5557
+ throw error ?? new OwneyError(
5558
+ "WITHDRAW_FAILED",
5559
+ "The server may still send this Zyfai withdrawal, so this browser does not send it.",
5560
+ { reason: "ZYFAI_SERVER_LEG_UNRESOLVED", serverTask: true, rejectedBeforeSubmission: true },
5561
+ this.id
5562
+ );
5563
+ }
5564
+ /** The current execution client's session. Never logged or returned. */
5565
+ executionSession() {
5566
+ const token = this.sdk.httpClient?.authToken;
5567
+ return typeof token === "string" && token ? token : void 0;
5568
+ }
5569
+ /** The owner's Zyfai wallet for a handoff (read-only), or undefined. */
5570
+ async handoffAgentWallet(owner, chainId) {
5571
+ if (!ADDRESS_PATTERN.test(owner)) return void 0;
5572
+ try {
5573
+ const wallet = await this.readSmartWallet(owner, chainId);
5574
+ return typeof wallet === "string" && ADDRESS_PATTERN.test(wallet) ? wallet : void 0;
5575
+ } catch {
5576
+ return void 0;
5577
+ }
5578
+ }
5579
+ /**
5580
+ * Part 4: hands this withdrawal to our server, or nudges the task a plan-time
5581
+ * handoff created (`planned`). The server is the one sender from then on:
5582
+ * this never calls Zyfai's withdraw route, and never sends again after an
5583
+ * ambiguous answer. Returns undefined only when nothing was handed off, or
5584
+ * when the server proved no task exists (a decline in
5585
+ * `ZYFAI_HANDOFF_NO_TASK_CODES`, rulings P4-11, T7-R1, T8-C1 and F-I1(b);
5586
+ * after a lost answer only the server's own declines, F-I2(i)); today's
5587
+ * local path then runs, after the handoff's mark was cleared (T8-C2).
5588
+ */
5589
+ async withdrawThroughServer(state, chainId, token, requestedAmount, onProgress, onBeforeSubmission, assertOperation, context) {
5590
+ const owner = state.walletAddress;
5591
+ const operationId = context.operationId;
5592
+ const plan = context.planned ? this.plannedHandoffs.get(operationId) : void 0;
5593
+ if (context.planned) this.plannedHandoffs.delete(operationId);
5594
+ if (context.planned && this.serverLegStates.get(operationId) === void 0)
5595
+ this.setServerLegState(operationId, "accepted");
5596
+ const planLost = context.planned && this.serverLegStates.get(operationId) === "requested";
5597
+ const serverOwned = () => context.planned || this.serverLegStates.get(operationId) === "accepted";
5598
+ const amount = plan?.amount === "MAX" ? void 0 : plan && /^[1-9][0-9]*$/.test(plan.amount) ? plan.amount : requestedAmount;
5599
+ const route = amount ? { attempted: true, method: "POST", path: "/users/partial-withdraw/v2" } : { attempted: true, method: "GET", path: "/users/withdraw/v2" };
5600
+ const base7 = {
5601
+ agentId: this.id,
5602
+ chainId,
5603
+ token,
5604
+ walletAddress: owner,
5605
+ intendedAmount: amount ?? "MAX",
5606
+ registration: "not-applicable"
5607
+ };
5608
+ let head;
5609
+ const searchHead = () => head ??= settleWithin(this.readPayoutSearchHead(chainId), PAYOUT_SEARCH_HEAD_LIMIT_MS);
5610
+ const accept = async (serverTask, agentWallet2, txHash, providerWithdrawalId) => {
5611
+ const withdrawalRequest = { ...route, ...providerWithdrawalId ? { providerWithdrawalId } : {} };
5612
+ let evidence;
5613
+ if (txHash) {
5614
+ evidence = { ...base7, txHash, transfer: "pending", delivery: "pending", withdrawalRequest, serverTask };
5615
+ } else {
5616
+ const acceptedAt = Date.now();
5617
+ const fromBlock = await searchHead();
5618
+ evidence = {
5619
+ ...base7,
5620
+ transfer: "pending",
5621
+ delivery: "pending",
5622
+ ...agentWallet2 ? { agentWallet: agentWallet2 } : {},
5623
+ withdrawalRequest: { ...withdrawalRequest, acceptedAt },
5624
+ ...fromBlock !== void 0 ? { payoutSearch: { fromBlock: fromBlock.toString() } } : {},
5625
+ serverTask
5626
+ };
5627
+ }
5628
+ notifyFunding(onProgress, evidence);
5629
+ this.ledgerReads.clear();
5630
+ return { ...mapWithdraw({
5631
+ success: true,
5632
+ message: "Withdrawal handed to the server",
5633
+ txHash,
5634
+ type: amount ? "partial" : "full",
5635
+ amount: amount || "all"
5636
+ }), evidence };
5637
+ };
5638
+ try {
5639
+ await this.ensureConnected(state, chainId);
5640
+ } catch (error) {
5641
+ if (serverOwned()) return accept("unknown", void 0);
5642
+ if (!this.mayWithdrawLocally(operationId)) this.serverLegUnresolved(onProgress, base7, error);
5643
+ notifyFunding(onProgress, { ...base7, transfer: "failed", delivery: "failed" });
5644
+ throw error;
5645
+ }
5646
+ const generation = this.connectionGeneration;
5647
+ const internals = this.sdk;
5648
+ const session = this.executionSession();
5649
+ if (session && !context.planned && this.serverLegStates.get(operationId) === void 0 && !zyfaiSessionOutlivesTask(session, "zyfai_withdrawal")) return void 0;
5650
+ const agentWallet = session ? await this.handoffAgentWallet(owner, chainId) : void 0;
5651
+ if (!session || !agentWallet) {
5652
+ if (serverOwned()) return accept("unknown", agentWallet);
5653
+ if (!this.mayWithdrawLocally(operationId)) this.serverLegUnresolved(onProgress, base7);
5654
+ return void 0;
5655
+ }
5656
+ const assertSubmission = () => {
5657
+ const account = internals.walletClient?.account?.address;
5658
+ if (generation !== this.connectionGeneration || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || state.chainId !== null && state.chainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
5659
+ throw new OwneyError(
5660
+ "NOT_CONNECTED",
5661
+ "Wallet changed before Zyfai withdrawal submission.",
5662
+ { phase: "prepare", rejectedBeforeSubmission: true },
5663
+ this.id
5664
+ );
5665
+ assertOperation?.();
5666
+ };
5667
+ try {
5668
+ if (!context.planned) await this.ensureWithdrawalSessionKey(owner, chainId);
5669
+ assertSubmission();
5670
+ const fromBlock = await searchHead();
5671
+ await onBeforeSubmission?.({
5672
+ ...base7,
5673
+ transfer: "pending",
5674
+ delivery: "pending",
5675
+ withdrawalRequest: { ...route },
5676
+ agentWallet,
5677
+ ...fromBlock !== void 0 ? { payoutSearch: { fromBlock: fromBlock.toString() } } : {},
5678
+ serverTask: true
5679
+ });
5680
+ assertSubmission();
5681
+ } catch (error) {
5682
+ if (serverOwned()) return accept("unknown", agentWallet);
5683
+ if (!this.clearServerLeg(operationId, "never-requested")) this.serverLegUnresolved(onProgress, base7, error);
5684
+ notifyFunding(onProgress, {
5685
+ ...base7,
5686
+ transfer: "failed",
5687
+ delivery: "failed",
5688
+ withdrawalRequest: { attempted: false },
5689
+ serverTask: false
5690
+ });
5691
+ throw error;
5692
+ }
5693
+ const cfg = {
5694
+ apiKey: this.accountReporting.owneyApiKey,
5695
+ baseUrl: this.accountReporting.routingApiBaseUrl,
5696
+ timeoutMs: WITHDRAW_HANDOFF_TIMEOUT_MS,
5697
+ zyfaiServerTasks: true
5698
+ };
5699
+ const body = {
5700
+ kind: "zyfai_withdrawal",
5701
+ operationId: context.operationId,
5702
+ owner,
5703
+ chainId,
5704
+ asset: token,
5705
+ amount: amount ?? "MAX",
5706
+ agentWallet,
5707
+ ...plan?.after.length ? { after: plan.after } : {},
5708
+ ...context.planned ? { runNow: true } : {}
5709
+ };
5710
+ const requestedBefore = this.serverLegStates.get(operationId) === "requested";
5711
+ let afterUnknown = planLost;
5712
+ this.setServerLegState(operationId, "requested");
5713
+ let answer = await handOffZyfaiTask(cfg, body, session);
5714
+ if (answer.type === "unknown" && !context.planned) {
5715
+ afterUnknown = true;
5716
+ answer = await handOffZyfaiTask(cfg, body, session);
5717
+ }
5718
+ if (answer.type === "declined" && ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code) && !provesNoServerTask(answer.code, requestedBefore || afterUnknown)) answer = { type: "unknown" };
5719
+ if (answer.type === "accepted" || answer.type === "declined" && answer.code === "LEG_ALREADY_SENT")
5720
+ this.setServerLegState(operationId, "accepted");
5721
+ if (answer.type === "declined") {
5722
+ if (ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code) && this.clearServerLeg(operationId, "no-task-decline")) {
5723
+ const cleared = { ...base7, transfer: "pending", delivery: "pending", serverTask: false };
5724
+ notifyFunding(onProgress, cleared);
5725
+ try {
5726
+ await onBeforeSubmission?.({ ...cleared, withdrawalRequest: { attempted: false } });
5727
+ } catch (error) {
5728
+ notifyFunding(onProgress, { ...cleared, transfer: "failed", delivery: "failed", withdrawalRequest: { attempted: false } });
5729
+ throw error;
5730
+ }
5731
+ return void 0;
5732
+ }
5733
+ if (this.serverLegStates.get(operationId) === "accepted" || afterUnknown) return accept("unknown", agentWallet);
5734
+ return this.serverWithdrawalNotSent(answer.code, state, chainId, token, session, onProgress, base7, route, "unknown");
5735
+ }
5736
+ if (answer.type === "unknown") return accept("unknown", agentWallet);
5737
+ if (answer.state === "failed" && answer.code && ZYFAI_WITHDRAWAL_NOT_SENT_CODES.has(answer.code))
5738
+ return this.serverWithdrawalNotSent(answer.code, state, chainId, token, session, onProgress, base7, route, true);
5739
+ if (answer.state === "failed" || answer.state === "expired") return accept("unknown", agentWallet);
5740
+ return accept(true, agentWallet, answer.result.txHash, answer.result.providerWithdrawalId);
5741
+ }
5742
+ /** A server answer proving the withdrawal was never sent: today's matching
5743
+ * error, with evidence transfer and delivery `failed`. "Already in progress"
5744
+ * keeps the hold: failure `withdrawal-in-progress`, never rejectedBeforeSubmission.
5745
+ * `serverTask`: true for a task that ended unsent (the server owns the leg's
5746
+ * close), "unknown" for a declined handoff whose code does not prove no task
5747
+ * (never false: such a leg is never cleared, ruling T9-I3). */
5748
+ serverWithdrawalNotSent(code, state, chainId, token, session, onProgress, base7, route, serverTask) {
5749
+ let failure;
5750
+ let error;
5751
+ const sessionUnavailable = () => new OwneyError(
5752
+ "AGENT_AUTH_FAILED",
5753
+ "Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
5754
+ { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token, serverTask: true, rejectedBeforeSubmission: true },
5755
+ this.id
5756
+ );
5757
+ switch (code) {
5758
+ case "NEEDS_USER_SESSION":
5759
+ failure = "unauthorized";
5760
+ error = sessionUnavailable();
5761
+ this.dropWithdrawalSession(state, chainId, session);
5762
+ break;
5763
+ case "NEEDS_USER_SESSION_KEY":
5764
+ failure = "session-key-missing";
5765
+ error = sessionUnavailable();
5766
+ this.rearmWithdrawalSessionKey(state.walletAddress, chainId);
5767
+ break;
5768
+ case "WITHDRAW_INSUFFICIENT_BALANCE":
5769
+ failure = "insufficient-balance";
5770
+ error = new OwneyError(
5771
+ "WITHDRAW_INSUFFICIENT_BALANCE",
5772
+ "No funds remain in Zyfai for this asset.",
5773
+ { available: "0", chainId, token, rejectedBeforeSubmission: true },
5774
+ this.id
5775
+ );
5776
+ break;
5777
+ case "WITHDRAWAL_IN_PROGRESS":
5778
+ case "ZYFAI_REDEMPTION_IN_FLIGHT":
5779
+ failure = "withdrawal-in-progress";
5780
+ error = new OwneyError(
5781
+ "WITHDRAW_IN_PROGRESS",
5782
+ "A Zyfai withdrawal is already in progress.",
5783
+ { reason: code, chainId, token, serverTask: true },
5784
+ this.id
5785
+ );
5786
+ break;
5787
+ default:
5788
+ failure = code === "ZYFAI_FORBIDDEN" ? "forbidden" : "other";
5789
+ error = new OwneyError(
5790
+ "WITHDRAW_FAILED",
5791
+ "The server did not send the Zyfai withdrawal.",
5792
+ { reason: code, serverTask: true, rejectedBeforeSubmission: true },
5793
+ this.id
5794
+ );
5795
+ }
5796
+ notifyFunding(onProgress, {
5797
+ ...base7,
5798
+ transfer: "failed",
5799
+ delivery: "failed",
5800
+ serverTask,
5801
+ withdrawalRequest: { ...route, failure }
5802
+ });
5803
+ throw error;
5804
+ }
5805
+ /** Zyfai has no session key for this wallet: for a legacy (not pre-deployed)
5806
+ * wallet, forget the login's "active" flag and this session's attempt, so the
5807
+ * next `ensureWithdrawalSessionKey` prompts once. Sends and prompts nothing. */
5808
+ rearmWithdrawalSessionKey(owner, chainId) {
5809
+ const internals = this.sdk;
5810
+ if (internals.isPredeployed !== false || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId) return;
5811
+ internals.hasActiveSessionKey = false;
5812
+ this.sessionKeyAttempts.delete(`${owner.toLowerCase()}:${chainId}`);
5813
+ }
5814
+ /** The routing API's handoff config. Only called with the opt-in and the key. */
5815
+ registrationHandoffConfig(timeoutMs) {
5816
+ return {
5817
+ apiKey: this.accountReporting.owneyApiKey,
5818
+ baseUrl: this.accountReporting.routingApiBaseUrl,
5819
+ timeoutMs,
5820
+ zyfaiServerTasks: true
5821
+ };
5822
+ }
5823
+ /**
5824
+ * Part 4: hands a deposit's registration to our server before its transfer.
5825
+ * Undefined when nothing was handed off, or when the server proved it has no
5826
+ * task (a decline in `ZYFAI_HANDOFF_NO_TASK_CODES`, rulings P4-11, T7-R1 and
5827
+ * T8-F2; for a retried create only the server's own declines, F-I2(i)):
5828
+ * today's local registration then runs. Any other answer means the
5829
+ * server holds the registration, or may hold it (an unknown answer, or
5830
+ * another decline), so the browser never registers it except by the
5831
+ * not-sent fallback. Never throws; never logs or returns the session.
5832
+ */
5833
+ async handOffDepositRegistration(owner, chainId, asset, amount, agentWallet, operationId) {
5834
+ const session = this.executionSession();
5835
+ if (!session || !ADDRESS_PATTERN.test(owner) || !ADDRESS_PATTERN.test(agentWallet) || !REGISTRATION_AMOUNT.test(amount) || asset !== "USDC" && asset !== "WETH") return void 0;
5836
+ const retried = this.requestedRegistrations.has(operationId);
5837
+ if (!retried) {
5838
+ if (!zyfaiSessionOutlivesTask(session, "zyfai_deposit_registration")) return void 0;
5839
+ this.requestedRegistrations.add(operationId);
5840
+ while (this.requestedRegistrations.size > SERVER_LEG_STATES_LIMIT)
5841
+ this.requestedRegistrations.delete(this.requestedRegistrations.values().next().value);
5842
+ }
5843
+ const answer = await handOffZyfaiTask(
5844
+ this.registrationHandoffConfig(REGISTRATION_HANDOFF_TIMEOUT_MS),
5845
+ { kind: "zyfai_deposit_registration", operationId, owner, chainId, asset, amount, agentWallet },
5846
+ session
5847
+ );
5848
+ if (answer.type === "declined" && provesNoServerTask(answer.code, retried)) return void 0;
5849
+ if (answer.type === "accepted") return { serverTask: true, serverTaskOperationId: operationId };
5850
+ const lost = answer.type === "unknown" || ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code);
5851
+ return { serverTask: "unknown", serverTaskOperationId: operationId, serverTaskCreate: lost ? "lost" : "declined" };
5852
+ }
5853
+ /**
5854
+ * Nudges the server's registration task of this deposit evidence (with
5855
+ * `serverTask`) and reads what its answer proves. Without the opt-in (read
5856
+ * now, unless this session sent the registration's create, F-I2(iii)), the
5857
+ * key, a session or the task's identity no nudge is sent and the outcome is
5858
+ * unknown. Never throws; never registers locally.
5859
+ */
5860
+ async nudgeDepositRegistration(evidence, session) {
5861
+ const { owneyApiKey } = this.accountReporting;
5862
+ const operationId = evidence.serverTaskOperationId;
5863
+ const agentWallet = evidence.agentWallet;
5864
+ const chainId = evidence.chainId;
5865
+ 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)
5866
+ return { kind: "unknown" };
5867
+ const answer = await handOffZyfaiTask(
5868
+ this.registrationHandoffConfig(REGISTRATION_NUDGE_TIMEOUT_MS),
5869
+ {
5870
+ kind: "zyfai_deposit_registration",
5871
+ operationId,
5872
+ owner: evidence.walletAddress,
5873
+ chainId,
5874
+ asset: evidence.token,
5875
+ amount: evidence.intendedAmount,
5876
+ agentWallet,
5877
+ runNow: true
5878
+ },
5879
+ session
5880
+ );
5881
+ return registrationOutcome(answer, evidence.serverTask === true || evidence.serverTaskCreate !== "lost");
5882
+ }
5883
+ /** Registration recovery never signs, transfers, activates, or refreshes authentication.
5884
+ * For a deposit the server registers (`serverTask`), it nudges the server's
5885
+ * task instead: still pending gives a transient error, and an unknown or
5886
+ * expired outcome the status-409 error. It registers locally only when the
5887
+ * server proved it never sent the registration (ruling A4). */
4902
5888
  async retryDepositRegistration(state, evidence) {
4903
5889
  const executionSdk = this.sdk;
4904
5890
  const internals = executionSdk;
@@ -4909,11 +5895,49 @@ var ZyfaiAgent = class _ZyfaiAgent {
4909
5895
  throw new OwneyError("AGENT_API_ERROR", "Deposit transfer must be verified before registration recovery.");
4910
5896
  }
4911
5897
  const cid = isValidChainId(evidence.chainId);
5898
+ let cleared = {};
5899
+ if (evidence.serverTask === true || evidence.serverTask === "unknown") {
5900
+ const outcome = await this.nudgeDepositRegistration(evidence, internals.httpClient.authToken ?? void 0);
5901
+ if (outcome.kind === "confirmed") {
5902
+ this.pendingRegistrations.get(`${evidence.walletAddress.toLowerCase()}:${cid}`)?.delete(evidence.txHash.toLowerCase());
5903
+ this.ledgerReads.clear();
5904
+ return { ...evidence, registration: "confirmed", providerDepositId: outcome.providerDepositId, serverTask: true };
5905
+ }
5906
+ if (outcome.kind === "pending")
5907
+ throw new OwneyError("AGENT_API_ERROR", "Zyfai registration by the server is temporarily unavailable.");
5908
+ if (outcome.kind === "unknown") throw registrationOutcomeUnknown(outcome.code);
5909
+ cleared = { serverTask: false };
5910
+ }
4912
5911
  const tokenAddress = evidence.token === "WETH" ? WETH_ADDRESS_BY_CHAIN[cid] : void 0;
4913
- await executionSdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
5912
+ const logged = await executionSdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
5913
+ const providerDepositId = depositIdFromLog(logged);
4914
5914
  this.pendingRegistrations.get(`${evidence.walletAddress.toLowerCase()}:${cid}`)?.delete(evidence.txHash.toLowerCase());
4915
5915
  this.ledgerReads.clear();
4916
- return { ...evidence, registration: "confirmed" };
5916
+ return { ...evidence, ...cleared, registration: "confirmed", ...providerDepositId ? { providerDepositId } : {} };
5917
+ }
5918
+ /** Reads a deposit's placement status by its id. Never signs, prompts or authenticates:
5919
+ * it uses only a session the vendor client already holds, and any failure is `null`. */
5920
+ async getDepositPlacement(state, depositId) {
5921
+ const internals = this.sdk;
5922
+ const client = internals.httpClient;
5923
+ if (!client?.authToken || typeof client.get !== "function" || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase()) return null;
5924
+ try {
5925
+ return mapDepositPlacement(await client.get(`/users/deposits/${encodeURIComponent(depositId)}/v2`));
5926
+ } catch {
5927
+ return null;
5928
+ }
5929
+ }
5930
+ /** Reads a withdrawal's lifecycle by its id. Never signs, prompts or authenticates:
5931
+ * it uses only a session the vendor client already holds, and any failure is `null`. */
5932
+ async getWithdrawalLifecycle(state, withdrawalId) {
5933
+ const internals = this.sdk;
5934
+ const client = internals.httpClient;
5935
+ if (!client?.authToken || typeof client.get !== "function" || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase()) return null;
5936
+ try {
5937
+ return mapWithdrawalLifecycle(await client.get(`/users/withdrawals/${encodeURIComponent(withdrawalId)}`));
5938
+ } catch {
5939
+ return null;
5940
+ }
4917
5941
  }
4918
5942
  // --- IAgent: Portfolio reads ---
4919
5943
  async getFundingWallet(state, chainId) {
@@ -5143,16 +6167,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
5143
6167
  }
5144
6168
  const now = Date.now();
5145
6169
  const pending = [...this.pendingRegistrations.get(scope)?.values() ?? []].map((entry) => entry.at);
5146
- const active2 = zyfaiHoldActive({
6170
+ const active = zyfaiHoldActive({
5147
6171
  movements,
5148
6172
  pendingRegistrations: pending,
5149
6173
  lastCheckTimestamp: raw2?.data?.lastCheckTimestamp,
5150
6174
  lastLogDate: raw2?.data?.lastLogDate?.[String(chainId)],
5151
6175
  now
5152
6176
  });
5153
- if (active2) this.holdActiveScopes.add(scope);
6177
+ if (active) this.holdActiveScopes.add(scope);
5154
6178
  else this.holdActiveScopes.delete(scope);
5155
- if (!active2) {
6179
+ if (!active) {
5156
6180
  this.lastGoodEarnings.set(key2, raw2);
5157
6181
  return { value: raw2, active: false };
5158
6182
  }
@@ -6135,7 +7159,7 @@ function actionEntry(action) {
6135
7159
  rebalanceLog: []
6136
7160
  };
6137
7161
  }
6138
- var TX_HASH = /^0x[a-fA-F0-9]{64}$/;
7162
+ var TX_HASH3 = /^0x[a-fA-F0-9]{64}$/;
6139
7163
  function allocationLegs(value, context, requireAmount) {
6140
7164
  if (!Array.isArray(value)) return void 0;
6141
7165
  const legs = [];
@@ -6164,7 +7188,7 @@ function allocationEntry(action, context) {
6164
7188
  if (action.actionType.toLowerCase() !== "autoseek") return void 0;
6165
7189
  const hash2 = action.details?.depositTransactionHash;
6166
7190
  const withdrawHash = action.details?.withdrawTransactionHash;
6167
- if (typeof hash2 !== "string" || !TX_HASH.test(hash2)) return null;
7191
+ if (typeof hash2 !== "string" || !TX_HASH3.test(hash2)) return null;
6168
7192
  const deposits = allocationLegs(action.details?.deposits, context, true);
6169
7193
  const withdrawals = action.details?.withdrawals == null ? [] : allocationLegs(action.details.withdrawals, context, false);
6170
7194
  if (!deposits?.length || !withdrawals) return null;
@@ -6177,7 +7201,7 @@ function allocationEntry(action, context) {
6177
7201
  }));
6178
7202
  const amountRaw = deposits.reduce((total, leg) => total + leg.rawAmount, 0n);
6179
7203
  const rebalance = withdrawals.length > 0;
6180
- const hashes = rebalance && typeof withdrawHash === "string" && TX_HASH.test(withdrawHash) && withdrawHash.toLowerCase() !== hash2.toLowerCase() ? [hash2, withdrawHash] : [hash2];
7204
+ const hashes = rebalance && typeof withdrawHash === "string" && TX_HASH3.test(withdrawHash) && withdrawHash.toLowerCase() !== hash2.toLowerCase() ? [hash2, withdrawHash] : [hash2];
6181
7205
  const source = withdrawals.length === 1 ? withdrawals[0] : {
6182
7206
  protocol: [...new Set(withdrawals.map((leg) => leg.protocol))].join(" + "),
6183
7207
  pool: withdrawals.map((leg) => leg.pool).filter(Boolean).join(" + ") || void 0
@@ -6569,6 +7593,7 @@ function operationHash(operation) {
6569
7593
  }
6570
7594
  });
6571
7595
  }
7596
+ var YIELDSEEKER_WITHDRAW_SERVER_SUBMIT = 1;
6572
7597
  function sponsorError(message, safeToFallback, details = {}) {
6573
7598
  return new OwneyError(
6574
7599
  "SPONSOR_REQUEST_FAILED",
@@ -6591,6 +7616,22 @@ function assertIntentFresh(expiresAt) {
6591
7616
  { failureCode: "SPONSOR_INTENT_EXPIRED" }
6592
7617
  );
6593
7618
  }
7619
+ var PAYMASTER_EXPIRY_MARGIN_SECONDS = 30;
7620
+ function paymasterValidUntil(paymasterAndData) {
7621
+ if (paymasterAndData.length < 2 + 64) return void 0;
7622
+ const validUntil = Number.parseInt(paymasterAndData.slice(42, 54), 16);
7623
+ const now = Date.now() / 1e3;
7624
+ return validUntil > now - 86400 && validUntil < now + 86400 ? validUntil : void 0;
7625
+ }
7626
+ function assertPaymasterFresh(paymasterAndData) {
7627
+ const validUntil = paymasterValidUntil(paymasterAndData);
7628
+ if (validUntil !== void 0 && Date.now() / 1e3 + PAYMASTER_EXPIRY_MARGIN_SECONDS >= validUntil)
7629
+ throw sponsorError(
7630
+ "The withdrawal sponsorship expired while waiting for the signature. Please try again.",
7631
+ true,
7632
+ { failureCode: "SPONSOR_INTENT_EXPIRED" }
7633
+ );
7634
+ }
6594
7635
  function providerReason(value) {
6595
7636
  if (typeof value !== "string") return void 0;
6596
7637
  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);
@@ -6672,6 +7713,12 @@ async function checkpoint(callback, pending) {
6672
7713
  );
6673
7714
  }
6674
7715
  }
7716
+ function reportAccepted(onAccepted, accepted) {
7717
+ try {
7718
+ onAccepted?.(accepted);
7719
+ } catch {
7720
+ }
7721
+ }
6675
7722
  function finalPaymasterFields(value) {
6676
7723
  const result = {};
6677
7724
  for (const key2 of [
@@ -6750,15 +7797,57 @@ async function requestIntent(input) {
6750
7797
  url.searchParams.set("intent", intent.intent);
6751
7798
  return { intent, rpcUrl: url.toString() };
6752
7799
  }
7800
+ var RPC_TIMEOUT_MS = 15e3;
7801
+ var SERVER_SUBMIT_SEND_TIMEOUT_MS = 25e3;
7802
+ function postRpc(url, method, params, timeoutMs) {
7803
+ return fetch(url, {
7804
+ method: "POST",
7805
+ headers: { "content-type": "application/json" },
7806
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
7807
+ signal: AbortSignal.timeout(timeoutMs)
7808
+ });
7809
+ }
7810
+ function rpcRejection(status, payload, method, phase) {
7811
+ const rejection = payload?.error;
7812
+ const diagnostics = rpcDiagnostics(rejection?.data);
7813
+ const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
7814
+ const details = {
7815
+ statusCode: status,
7816
+ rpcCode: rejection?.code,
7817
+ rpcMethod: method,
7818
+ phase,
7819
+ providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
7820
+ failureCode: "RPC_REJECTED",
7821
+ ...diagnostics
7822
+ };
7823
+ if (phase === "prepare" && executionReverted)
7824
+ return new OwneyError(
7825
+ "WITHDRAW_FAILED",
7826
+ "Yieldseeker withdrawal simulation reverted before submission.",
7827
+ {
7828
+ ...details,
7829
+ failureCode: "SPONSOR_EXECUTION_REVERTED",
7830
+ safeToFallback: false,
7831
+ rejectedBeforeSubmission: true
7832
+ },
7833
+ "yieldseeker"
7834
+ );
7835
+ 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
7836
+ // bundler never accepted it. AA2x stays ambiguous (AA25 can mean it ran).
7837
+ /\bAA3\d\b/.test(rejection.message ?? "") || /maxFeePerGas.*(?:too low|lower than|less than|must be at least)/i.test(rejection.message ?? ""));
7838
+ return sponsorError(
7839
+ phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
7840
+ phase === "prepare" || definitiveRejection,
7841
+ {
7842
+ ...details,
7843
+ ...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
7844
+ }
7845
+ );
7846
+ }
6753
7847
  async function rpc(url, method, params, phase) {
6754
7848
  let response;
6755
7849
  try {
6756
- response = await fetch(url, {
6757
- method: "POST",
6758
- headers: { "content-type": "application/json" },
6759
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
6760
- signal: AbortSignal.timeout(15e3)
6761
- });
7850
+ response = await postRpc(url, method, params, RPC_TIMEOUT_MS);
6762
7851
  } catch {
6763
7852
  throw sponsorError(
6764
7853
  "Withdrawal sponsorship request status is unknown.",
@@ -6772,43 +7861,37 @@ async function rpc(url, method, params, phase) {
6772
7861
  );
6773
7862
  }
6774
7863
  const payload = await response.json().catch(() => null);
6775
- if (!response.ok || payload?.error || payload?.result === void 0) {
6776
- const rejection = payload?.error;
6777
- const diagnostics = rpcDiagnostics(rejection?.data);
6778
- const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
6779
- const details = {
6780
- statusCode: response.status,
6781
- rpcCode: rejection?.code,
6782
- rpcMethod: method,
6783
- phase,
6784
- providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
6785
- failureCode: "RPC_REJECTED",
6786
- ...diagnostics
6787
- };
6788
- if (phase === "prepare" && executionReverted)
6789
- throw new OwneyError(
6790
- "WITHDRAW_FAILED",
6791
- "Yieldseeker withdrawal simulation reverted before submission.",
6792
- {
6793
- ...details,
6794
- failureCode: "SPONSOR_EXECUTION_REVERTED",
6795
- safeToFallback: false,
6796
- rejectedBeforeSubmission: true
6797
- },
6798
- "yieldseeker"
6799
- );
6800
- 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 ?? ""));
6801
- throw sponsorError(
6802
- phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
6803
- phase === "prepare" || definitiveRejection,
6804
- {
6805
- ...details,
6806
- ...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
6807
- }
6808
- );
6809
- }
7864
+ if (!response.ok || payload?.error || payload?.result === void 0)
7865
+ throw rpcRejection(response.status, payload, method, phase);
6810
7866
  return payload.result;
6811
7867
  }
7868
+ async function sendSignedUserOperation(rpcUrl, operation, operationId) {
7869
+ const method = "eth_sendUserOperation";
7870
+ const params = [operation, ENTRY_POINT_V06];
7871
+ if (!operationId) return { type: "accepted", hash: await rpc(rpcUrl, method, params, "send"), serverSubmit: false };
7872
+ const url = new URL(rpcUrl);
7873
+ url.searchParams.set("operationId", operationId);
7874
+ url.searchParams.set("serverSubmit", "1");
7875
+ let response;
7876
+ try {
7877
+ response = await postRpc(url.toString(), method, params, SERVER_SUBMIT_SEND_TIMEOUT_MS);
7878
+ } catch {
7879
+ return { type: "pending", serverSubmit: "unknown" };
7880
+ }
7881
+ const payload = await response.json().catch(() => null);
7882
+ if (!payload) return { type: "pending", serverSubmit: "unknown" };
7883
+ if (response.ok && !payload.error && payload.result !== void 0)
7884
+ return {
7885
+ type: "accepted",
7886
+ hash: payload.result,
7887
+ serverSubmit: payload.owney?.serverSubmit === true && payload.owney.state === "accepted"
7888
+ };
7889
+ const rejection = rpcRejection(response.status, payload, method, "send");
7890
+ if (rejection.details?.rejectedBeforeSubmission === true) throw rejection;
7891
+ if (payload.error?.data?.serverSubmit === true) return { type: "pending", serverSubmit: true };
7892
+ if (response.status >= 500) return { type: "pending", serverSubmit: "unknown" };
7893
+ throw rejection;
7894
+ }
6812
7895
  async function waitForTransaction(client, txHash) {
6813
7896
  const receipt = await client.waitForTransactionReceipt({ hash: txHash, confirmations: 1 });
6814
7897
  if (receipt.status !== "success")
@@ -6890,6 +7973,7 @@ async function submitEoa(input) {
6890
7973
  operation = { ...operation, signature };
6891
7974
  await assertOwnerSession(input.state, input.owner);
6892
7975
  assertIntentFresh(input.expiresAt);
7976
+ assertPaymasterFresh(operation.paymasterAndData);
6893
7977
  const userOperation = {
6894
7978
  sender: operation.sender,
6895
7979
  nonce: operation.nonce,
@@ -6906,18 +7990,19 @@ async function submitEoa(input) {
6906
7990
  input.assertCurrent?.();
6907
7991
  await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
6908
7992
  input.assertCurrent?.();
6909
- const submittedHash = await rpc(
6910
- input.rpcUrl,
6911
- "eth_sendUserOperation",
6912
- [operation, ENTRY_POINT_V06],
6913
- "send"
6914
- );
6915
- if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
6916
- throw sponsorError(
6917
- "Bundler returned a different UserOperation hash.",
6918
- false,
6919
- { userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
6920
- );
7993
+ const sent = await sendSignedUserOperation(input.rpcUrl, operation, input.operationId);
7994
+ const serverSubmit = sent.type === "pending" ? sent.serverSubmit : sent.serverSubmit ? true : void 0;
7995
+ if (serverSubmit) savePending(input.onPending, { userOpHash, serverSubmit });
7996
+ if (sent.type === "accepted") {
7997
+ const submittedHash = sent.hash;
7998
+ if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
7999
+ throw sponsorError(
8000
+ "Bundler returned a different UserOperation hash.",
8001
+ false,
8002
+ { userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
8003
+ );
8004
+ reportAccepted(input.onAccepted, { userOpHash });
8005
+ }
6921
8006
  const deadline = Date.now() + OBSERVATION_WINDOW_MS;
6922
8007
  let lastReadError;
6923
8008
  for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
@@ -6950,6 +8035,7 @@ async function submitEoa(input) {
6950
8035
  userOpHash,
6951
8036
  phase: "observe",
6952
8037
  failureCode: "RECEIPT_UNRESOLVED",
8038
+ ...serverSubmit ? { serverSubmit } : {},
6953
8039
  ...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
6954
8040
  }
6955
8041
  );
@@ -7010,6 +8096,7 @@ async function submitSmart(input) {
7010
8096
  { callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
7011
8097
  );
7012
8098
  savePending(input.onPending, { callsId });
8099
+ reportAccepted(input.onAccepted, { callsId });
7013
8100
  const deadline = Date.now() + OBSERVATION_WINDOW_MS;
7014
8101
  for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
7015
8102
  let status;
@@ -7303,6 +8390,9 @@ var YieldseekerAgent = class {
7303
8390
  endpointReadFailures = /* @__PURE__ */ new Map();
7304
8391
  reconcileUntil = /* @__PURE__ */ new Map();
7305
8392
  allocationRefresh = /* @__PURE__ */ new Set();
8393
+ /** Contexts whose next permitted snapshot read is forced once: a funding
8394
+ * invalidation with a tx hash (receipt recovery, a re-armed refresh). */
8395
+ forceNextSnapshot = /* @__PURE__ */ new Set();
7306
8396
  activityRefreshUntil = /* @__PURE__ */ new Map();
7307
8397
  // null means a confirmed movement has no reliable baseline. Do not guess
7308
8398
  // settlement; keep slow reconciliation after the fast window expires.
@@ -7709,6 +8799,7 @@ var YieldseekerAgent = class {
7709
8799
  this.endpointReadFailures.clear();
7710
8800
  this.reconcileUntil.clear();
7711
8801
  this.allocationRefresh.clear();
8802
+ this.forceNextSnapshot.clear();
7712
8803
  this.activityRefreshUntil.clear();
7713
8804
  this.snapshotMovements.clear();
7714
8805
  this.movementEpochs.clear();
@@ -7822,7 +8913,7 @@ var YieldseekerAgent = class {
7822
8913
  notifyFunding(onProgress, evidence);
7823
8914
  return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
7824
8915
  }
7825
- async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation) {
8916
+ async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation, submission) {
7826
8917
  let ownerTransferAttempted = false;
7827
8918
  let evidence = {
7828
8919
  agentId: this.id,
@@ -7835,12 +8926,23 @@ var YieldseekerAgent = class {
7835
8926
  delivery: "pending"
7836
8927
  };
7837
8928
  try {
7838
- return await this.withdrawInternal(state, chainId, asset, amount, (next) => {
7839
- evidence = { ...evidence, ...next };
7840
- notifyFunding(onProgress, evidence);
7841
- }, () => {
7842
- ownerTransferAttempted = true;
7843
- }, gasMode, onBeforeSubmission, assertOperation);
8929
+ return await this.withdrawInternal(
8930
+ state,
8931
+ chainId,
8932
+ asset,
8933
+ amount,
8934
+ (next) => {
8935
+ evidence = { ...evidence, ...next };
8936
+ notifyFunding(onProgress, evidence);
8937
+ },
8938
+ () => {
8939
+ ownerTransferAttempted = true;
8940
+ },
8941
+ gasMode,
8942
+ onBeforeSubmission,
8943
+ assertOperation,
8944
+ submission?.operationId
8945
+ );
7844
8946
  } catch (error) {
7845
8947
  if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") {
7846
8948
  this.withdrawalVersionCache.clear();
@@ -7856,7 +8958,7 @@ var YieldseekerAgent = class {
7856
8958
  throw error;
7857
8959
  }
7858
8960
  }
7859
- async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission, assertOperation) {
8961
+ async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission, assertOperation, operationId) {
7860
8962
  this.assertChain(chainId);
7861
8963
  this.assertAsset(asset);
7862
8964
  if (amount !== void 0 && BigInt(amount) <= 0n) {
@@ -7988,13 +9090,13 @@ var YieldseekerAgent = class {
7988
9090
  const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
7989
9091
  const verifiedPositions = [];
7990
9092
  if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
7991
- let active2 = true;
9093
+ let active = true;
7992
9094
  const checks = this.readVaultShares(values.map((position2) => position2.vaultOption), agentWallet, blockNumber).catch(() => []);
7993
9095
  const reads = values.map(async (position2, index) => {
7994
9096
  const address = getAddress6(position2.vaultOption.address);
7995
9097
  try {
7996
9098
  const group = (await checks).slice(index * 2, index * 2 + 2);
7997
- if (!active2 || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
9099
+ if (!active || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
7998
9100
  this.assertSession(generation, chainId, asset);
7999
9101
  const [underlying, shares] = group.map((value) => value.result);
8000
9102
  if (typeof underlying !== "string" || !isAddress4(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
@@ -8007,7 +9109,7 @@ var YieldseekerAgent = class {
8007
9109
  shares,
8008
9110
  blockNumber
8009
9111
  );
8010
- if (!active2 || value === void 0) return null;
9112
+ if (!active || value === void 0) return null;
8011
9113
  this.assertSession(generation, chainId, asset);
8012
9114
  verified.withdrawableAssetsRaw = value.toString();
8013
9115
  verifiedPositions.push(verified);
@@ -8024,7 +9126,7 @@ var YieldseekerAgent = class {
8024
9126
  args: [shares],
8025
9127
  blockNumber
8026
9128
  });
8027
- if (!active2 || typeof value !== "bigint" || value < 0n) return null;
9129
+ if (!active || typeof value !== "bigint" || value < 0n) return null;
8028
9130
  this.assertSession(generation, chainId, asset);
8029
9131
  if (value > 0n && !positiveValue) {
8030
9132
  positiveValue = true;
@@ -8052,19 +9154,20 @@ var YieldseekerAgent = class {
8052
9154
  }
8053
9155
  });
8054
9156
  const result = await boundedVerification(Promise.all(reads), timeoutMs);
8055
- active2 = false;
9157
+ active = false;
8056
9158
  this.assertSession(generation, chainId, asset);
8057
9159
  return {
8058
9160
  verified: result !== void 0 && result.every((position2) => position2 !== null),
8059
9161
  positions: [...verifiedPositions]
8060
9162
  };
8061
9163
  };
8062
- const refreshPositions = async () => {
9164
+ const refreshPositions = async (promptless = false) => {
8063
9165
  this.assertSession(generation, chainId, asset);
8064
9166
  const response = await this.walletRequest(
8065
9167
  state,
8066
9168
  chainId,
8067
- this.agentPath(context, "vault-positions")
9169
+ this.agentPath(context, "vault-positions"),
9170
+ { promptless }
8068
9171
  );
8069
9172
  this.assertSession(generation, chainId, asset);
8070
9173
  if (!Array.isArray(response?.vaultPositions)) throw this.invalidResponse("vault positions");
@@ -8295,6 +9398,8 @@ var YieldseekerAgent = class {
8295
9398
  agentWallet: getAddress6(context.wallet.walletAddress),
8296
9399
  asset: metadata.address,
8297
9400
  callData: data,
9401
+ // The EOA send opts in to the server-owned send with it.
9402
+ operationId,
8298
9403
  onPending: (pending) => {
8299
9404
  submissionEvidence = { ...submissionEvidence, ...pending };
8300
9405
  notifyFunding(onProgress, submissionEvidence);
@@ -8303,6 +9408,11 @@ var YieldseekerAgent = class {
8303
9408
  assertOperation?.();
8304
9409
  await onBeforeSubmission?.({ ...base7, ...pending });
8305
9410
  onOwnerTransferAttempted?.();
9411
+ },
9412
+ // The owner's signed operation was accepted: another agent may start.
9413
+ onAccepted: (accepted) => {
9414
+ submissionEvidence = { ...submissionEvidence, ...accepted, ownerAuthorization: "accepted" };
9415
+ notifyFunding(onProgress, submissionEvidence);
8306
9416
  }
8307
9417
  });
8308
9418
  txHash = submitted.txHash;
@@ -8319,7 +9429,10 @@ var YieldseekerAgent = class {
8319
9429
  value: "0",
8320
9430
  chainId
8321
9431
  },
8322
- (hash2) => notifyFunding(onProgress, { ...base7, txHash: hash2 }),
9432
+ (hash2) => {
9433
+ submissionEvidence = { ...submissionEvidence, txHash: hash2, ownerAuthorization: "accepted" };
9434
+ notifyFunding(onProgress, submissionEvidence);
9435
+ },
8323
9436
  (value) => {
8324
9437
  receipt = value;
8325
9438
  },
@@ -8350,7 +9463,9 @@ var YieldseekerAgent = class {
8350
9463
  const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
8351
9464
  const knownAtPayout = new Set(knownPositions.keys());
8352
9465
  const [catalogVerified, knownProof] = await Promise.all([
8353
- boundedVerification(refreshPositions().then((values) => {
9466
+ // Never a sign-in prompt here: a refused or missing cached sign-in
9467
+ // leaves the catalog unverified, the existing unverified-MAX outcome.
9468
+ boundedVerification(refreshPositions(true).then((values) => {
8354
9469
  rememberPositions(values);
8355
9470
  return true;
8356
9471
  }).catch(() => false)),
@@ -8395,7 +9510,8 @@ var YieldseekerAgent = class {
8395
9510
  );
8396
9511
  const confirmedAmount = evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) ? BigInt(evidence.confirmedAmount) : void 0;
8397
9512
  const feeIncrease = ownerFunds.fees - initialFunds.fees;
8398
- 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;
9513
+ const feeShortfall = requested - payout;
9514
+ 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;
8399
9515
  return {
8400
9516
  txHash,
8401
9517
  type: amount === void 0 && !completion ? "full" : "partial",
@@ -8457,14 +9573,14 @@ var YieldseekerAgent = class {
8457
9573
  }
8458
9574
  /** Recheck MAX without re-unwinding, paying out, or requesting a signature. */
8459
9575
  async verifyWithdrawalCompletion(state, evidence) {
8460
- const unknown = (reason2) => ({ status: "unavailable", reason: reason2 });
9576
+ const unknown2 = (reason2) => ({ status: "unavailable", reason: reason2 });
8461
9577
  const proof = evidence.maxCompletion;
8462
- if (evidence.agentId !== this.id || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.chainId !== 8453 || !proof || !evidence.agentWallet || !isAddress4(evidence.agentWallet) || !evidence.blockNumber || !/^\d+$/.test(evidence.blockNumber) || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || !this.isTransactionHash(evidence.txHash)) return unknown("PAYOUT_PROOF_UNAVAILABLE");
9578
+ if (evidence.agentId !== this.id || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.chainId !== 8453 || !proof || !evidence.agentWallet || !isAddress4(evidence.agentWallet) || !evidence.blockNumber || !/^\d+$/.test(evidence.blockNumber) || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || !this.isTransactionHash(evidence.txHash)) return unknown2("PAYOUT_PROOF_UNAVAILABLE");
8463
9579
  this.assertAsset(evidence.token);
8464
9580
  const asset = evidence.token;
8465
9581
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
8466
9582
  const signature = this.auth.peekToken(state, evidence.chainId);
8467
- if (!signature) return unknown("AUTHENTICATION_REQUIRED");
9583
+ if (!signature) return unknown2("AUTHENTICATION_REQUIRED");
8468
9584
  let reason = "CATALOG_UNAVAILABLE";
8469
9585
  try {
8470
9586
  return await bounded(async () => {
@@ -8472,7 +9588,7 @@ var YieldseekerAgent = class {
8472
9588
  `/users/${encodeURIComponent(proof.userId)}/agents/${encodeURIComponent(proof.agentId)}/vault-positions`,
8473
9589
  { signature }
8474
9590
  );
8475
- if (!Array.isArray(response?.vaultPositions)) return unknown("CATALOG_INVALID");
9591
+ if (!Array.isArray(response?.vaultPositions)) return unknown2("CATALOG_INVALID");
8476
9592
  const positions = this.validateWithdrawalPositions(response.vaultPositions, evidence.chainId, asset);
8477
9593
  const vaults = new Map(proof.vaults.map((address) => [address.toLowerCase(), { address }]));
8478
9594
  positions.forEach((p) => vaults.set(p.vaultOption.address.toLowerCase(), p.vaultOption));
@@ -8480,14 +9596,14 @@ var YieldseekerAgent = class {
8480
9596
  if (needsInventory || [...vaults.values()].some((option) => !option.provider)) {
8481
9597
  const catalog = await this.loadVaultOptions(asset).catch(() => []);
8482
9598
  const eligible = catalog.filter((o) => o.chainId === evidence.chainId && o.assetAddress?.toLowerCase() === metadata.address.toLowerCase() && isAddress4(o.address));
8483
- if (needsInventory && !eligible.length) return unknown("INVENTORY_INCOMPLETE");
9599
+ if (needsInventory && !eligible.length) return unknown2("INVENTORY_INCOMPLETE");
8484
9600
  for (const option of eligible) {
8485
9601
  const key2 = option.address.toLowerCase();
8486
9602
  if (needsInventory || vaults.has(key2)) vaults.set(key2, option);
8487
9603
  }
8488
9604
  }
8489
9605
  if (vaults.size > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT || [...vaults.keys()].some((a) => !isAddress4(a)))
8490
- return unknown("INVENTORY_INVALID");
9606
+ return unknown2("INVENTORY_INVALID");
8491
9607
  reason = "VAULT_PROOF_UNAVAILABLE";
8492
9608
  const blockNumber = BigInt(evidence.blockNumber);
8493
9609
  const options = [...vaults.values()];
@@ -8518,7 +9634,7 @@ var YieldseekerAgent = class {
8518
9634
  };
8519
9635
  }, 8e3, () => new Error("MAX proof read timed out"));
8520
9636
  } catch {
8521
- return unknown(reason);
9637
+ return unknown2(reason);
8522
9638
  }
8523
9639
  }
8524
9640
  async readVaultShares(options, wallet, blockNumber) {
@@ -8599,12 +9715,12 @@ var YieldseekerAgent = class {
8599
9715
  { asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
8600
9716
  this.id
8601
9717
  );
8602
- let active2 = true;
9718
+ let active = true;
8603
9719
  let knownProof;
8604
9720
  const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
8605
9721
  const assertReadCurrent = () => {
8606
9722
  this.assertSession(generation, chainId, context.asset);
8607
- if (!active2 || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
9723
+ if (!active || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
8608
9724
  };
8609
9725
  const read2 = async () => {
8610
9726
  const client = this.getReceiptClient();
@@ -8793,7 +9909,7 @@ var YieldseekerAgent = class {
8793
9909
  if (error instanceof OwneyError) throw error;
8794
9910
  throw unavailable();
8795
9911
  } finally {
8796
- active2 = false;
9912
+ active = false;
8797
9913
  if (timer) clearTimeout(timer);
8798
9914
  }
8799
9915
  }
@@ -8878,6 +9994,7 @@ var YieldseekerAgent = class {
8878
9994
  this.assertAsset(asset);
8879
9995
  const until = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_SNAPSHOT_WINDOW_MS);
8880
9996
  const activityUntil = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
9997
+ const hash2 = typeof txHash === "string" && /^0x[0-9a-fA-F]{64}$/.test(txHash);
8881
9998
  this.invalidateSlot(state, chainId, asset, (key2) => {
8882
9999
  const failure = this.snapshotFailures.get(key2);
8883
10000
  this.advancePortfolioVersion(key2);
@@ -8889,6 +10006,7 @@ var YieldseekerAgent = class {
8889
10006
  }
8890
10007
  this.reconcileUntil.set(key2, Math.max(this.reconcileUntil.get(key2) ?? 0, until));
8891
10008
  this.activityRefreshUntil.set(key2, Math.max(this.activityRefreshUntil.get(key2) ?? 0, activityUntil));
10009
+ if (hash2) this.forceNextSnapshot.add(key2);
8892
10010
  });
8893
10011
  }
8894
10012
  endFundingBalanceRefresh(state, chainId, asset, read2) {
@@ -9724,7 +10842,7 @@ var YieldseekerAgent = class {
9724
10842
  return this.walletRequest(
9725
10843
  state,
9726
10844
  chainId,
9727
- `${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`
10845
+ this.snapshotPath(context, forceRefresh)
9728
10846
  ).then((response) => {
9729
10847
  if (!response?.agentSnapshot) {
9730
10848
  throw this.invalidResponse("agent snapshot");
@@ -9754,6 +10872,16 @@ var YieldseekerAgent = class {
9754
10872
  throw error;
9755
10873
  });
9756
10874
  }
10875
+ snapshotPath(context, forceRefresh) {
10876
+ return `${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`;
10877
+ }
10878
+ /** True while `walletRequest` would refuse a read of `path`: this user's
10879
+ * rate-limit cooldown or that endpoint's outage cooldown. */
10880
+ readCoolingDown(state, chainId, path) {
10881
+ const userKey = this.userKey(state, chainId);
10882
+ const now = Date.now();
10883
+ return (this.standardReadFailures.get(userKey)?.retryAt ?? 0) > now || (this.endpointReadFailures.get(`${userKey}:${path}`)?.retryAt ?? 0) > now;
10884
+ }
9757
10885
  portfolioSnapshot(state, chainId, context, contextKey) {
9758
10886
  const key2 = `snapshot:${contextKey}`;
9759
10887
  const cached = this.readCache.get(key2);
@@ -9772,6 +10900,7 @@ var YieldseekerAgent = class {
9772
10900
  const settling = inWindow && !this.deferredToLedger(contextKey);
9773
10901
  if (inWindow) this.settlementChecks.set(contextKey, (this.settlementChecks.get(contextKey) ?? 0) + 1);
9774
10902
  const ready = this.readyForces.delete(contextKey) && this.noteReadyForce(contextKey);
10903
+ const movementForce = this.forceNextSnapshot.has(contextKey) && !this.readCoolingDown(state, chainId, this.snapshotPath(context, true)) && this.forceNextSnapshot.delete(contextKey);
9775
10904
  return this.requestPortfolioSnapshot(
9776
10905
  state,
9777
10906
  chainId,
@@ -9779,7 +10908,7 @@ var YieldseekerAgent = class {
9779
10908
  contextKey,
9780
10909
  version,
9781
10910
  generation,
9782
- allocationChanged || settling || ready
10911
+ allocationChanged || settling || ready || movementForce
9783
10912
  );
9784
10913
  })
9785
10914
  };
@@ -10229,7 +11358,7 @@ var YieldseekerAgent = class {
10229
11358
  }
10230
11359
  const ttl = this.portfolioCacheMs(contextKey);
10231
11360
  this.settlementChecks.set(contextKey, 1);
10232
- const force = !this.deferredToLedger(contextKey);
11361
+ const force = true;
10233
11362
  await this.cachedRead(
10234
11363
  `snapshot:${contextKey}`,
10235
11364
  ttl,
@@ -10284,13 +11413,22 @@ var YieldseekerAgent = class {
10284
11413
  options.assertCurrent?.();
10285
11414
  return this.api.request(path, { method: options.method, body: options.body, signature: signature2 });
10286
11415
  };
10287
- let signature = await this.auth.getToken(state, chainId);
11416
+ const cached = options.promptless ? this.auth.peekToken(state, chainId) : void 0;
11417
+ if (options.promptless && !cached)
11418
+ throw new OwneyError(
11419
+ "AGENT_AUTH_FAILED",
11420
+ "No Yieldseeker sign-in is available without a prompt.",
11421
+ { reason: "YIELDSEEKER_SIGN_IN_REQUIRED" },
11422
+ this.id
11423
+ );
11424
+ let signature = cached ?? await this.auth.getToken(state, chainId);
10288
11425
  try {
10289
11426
  return await request2(signature);
10290
11427
  } catch (error) {
10291
11428
  if (!(error instanceof YieldseekerApiError)) throw error;
10292
11429
  if (error.providerCode === "NO_USER") throw error;
10293
11430
  if (!error.isAuthenticationError) throw error;
11431
+ if (options.promptless) throw error;
10294
11432
  signature = await this.auth.refreshToken(state, chainId, signature);
10295
11433
  try {
10296
11434
  return await request2(signature);
@@ -10518,6 +11656,16 @@ var YieldseekerAgent = class {
10518
11656
  };
10519
11657
 
10520
11658
  // src/lib/swap/swap-api.ts
11659
+ var FUSION_SECRET_SERVER_PUBLISH = 1;
11660
+ var FUSION_SECRET_HANDOFF_TIMEOUT_MS = 5e3;
11661
+ function handoffFailure(error) {
11662
+ const statusCode2 = error instanceof OwneyError && typeof error.details?.statusCode === "number" ? error.details.statusCode : void 0;
11663
+ return new OwneyError(
11664
+ error instanceof OwneyError ? error.code : "SWAP_REQUEST_FAILED",
11665
+ "The swap secret handoff did not complete.",
11666
+ statusCode2 === void 0 ? {} : { statusCode: statusCode2 }
11667
+ );
11668
+ }
10521
11669
  async function request(baseUrl, apiKey, path, init) {
10522
11670
  const url = `${baseUrl}/api/v1/swap${path}`;
10523
11671
  const controller = new AbortController();
@@ -10530,7 +11678,8 @@ async function request(baseUrl, apiKey, path, init) {
10530
11678
  "Content-Type": "application/json",
10531
11679
  "x-owney-api-key": apiKey
10532
11680
  },
10533
- ...init ? { body: JSON.stringify(init.body) } : {}
11681
+ ...init ? { body: JSON.stringify(init.body) } : {},
11682
+ ...init?.redirect ? { redirect: init.redirect } : {}
10534
11683
  });
10535
11684
  if (!res.ok) {
10536
11685
  const text = await res.text().catch(() => "");
@@ -10563,7 +11712,7 @@ async function request(baseUrl, apiKey, path, init) {
10563
11712
  );
10564
11713
  }
10565
11714
  return json.data;
10566
- }, 2e4, () => new OwneyError(
11715
+ }, init?.timeoutMs ?? 2e4, () => new OwneyError(
10567
11716
  "SWAP_REQUEST_FAILED",
10568
11717
  "Swap request timed out. Its outcome may still need confirmation.",
10569
11718
  { requestTimedOut: true, phase: path === "/order" ? "submit" : "read" }
@@ -10647,8 +11796,9 @@ function createSwapApi(baseUrl, apiKey) {
10647
11796
  /**
10648
11797
  * Builds a Fusion+ order server-side and returns EIP-712 typed data.
10649
11798
  *
10650
- * Only HASHES go over the wire. The preimages never leave the browser —
10651
- * see swap.secrets.
11799
+ * Only HASHES go over the wire. The preimages leave the browser only
11800
+ * through `handoffSecrets`, and only with the caller's opt-in — see
11801
+ * swap.secrets.
10652
11802
  */
10653
11803
  buildOrder: (params) => request(baseUrl, apiKey, "/order/build", {
10654
11804
  method: "POST",
@@ -10675,6 +11825,23 @@ function createSwapApi(baseUrl, apiKey) {
10675
11825
  method: "POST",
10676
11826
  body: { orderHash, secret }
10677
11827
  }),
11828
+ /**
11829
+ * Hands a signed Fusion+ order's secrets to our routing API, which checks
11830
+ * them against the order and publishes each one only once 1inch reports
11831
+ * its destination escrow ready (part 5). The browser keeps publishing too;
11832
+ * a double publish is a no-op upstream.
11833
+ *
11834
+ * Only our own API: a redirect is refused, since the body carries the
11835
+ * preimages. Bounded to 5 s.
11836
+ */
11837
+ handoffSecrets: (body) => request(baseUrl, apiKey, "/order/secrets", {
11838
+ method: "POST",
11839
+ body,
11840
+ redirect: "error",
11841
+ timeoutMs: FUSION_SECRET_HANDOFF_TIMEOUT_MS
11842
+ }).catch((error) => {
11843
+ throw handoffFailure(error);
11844
+ }),
10678
11845
  /**
10679
11846
  * @param chainId present only for a SAME-CHAIN order. That product keys its
10680
11847
  * orders per chain and the Fusion+ endpoint does not know them, so asking
@@ -10858,14 +12025,14 @@ function swapChain(chainId) {
10858
12025
  }
10859
12026
 
10860
12027
  // src/lib/swap/swap.arrival.ts
10861
- var DEFAULT_TIMEOUT_MS = 18e4;
12028
+ var DEFAULT_TIMEOUT_MS2 = 18e4;
10862
12029
  var DEFAULT_POLL_MS = 4e3;
10863
12030
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
10864
12031
  async function awaitBalanceIncrease(options) {
10865
12032
  const {
10866
12033
  readBalance,
10867
12034
  baseline,
10868
- timeoutMs = DEFAULT_TIMEOUT_MS,
12035
+ timeoutMs = DEFAULT_TIMEOUT_MS2,
10869
12036
  pollMs = DEFAULT_POLL_MS
10870
12037
  } = options;
10871
12038
  const deadline = Date.now() + timeoutMs;
@@ -10959,14 +12126,14 @@ var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
10959
12126
  var DEFAULT_POLL_MS2 = 5e3;
10960
12127
  var MAX_BACKOFF_MS = 3e4;
10961
12128
  var backoffFor = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
10962
- var DEFAULT_TIMEOUT_MS2 = 15 * 60 * 1e3;
12129
+ var DEFAULT_TIMEOUT_MS3 = 15 * 60 * 1e3;
10963
12130
  async function runFusionOrderInternal(deps, options) {
10964
12131
  const {
10965
12132
  orderHash,
10966
12133
  secrets,
10967
12134
  onStage,
10968
12135
  pollIntervalMs = DEFAULT_POLL_MS2,
10969
- timeoutMs = DEFAULT_TIMEOUT_MS2
12136
+ timeoutMs = DEFAULT_TIMEOUT_MS3
10970
12137
  } = options;
10971
12138
  const deadline = deps.now() + timeoutMs;
10972
12139
  let partial = options.hasPartialFill === true;
@@ -11047,14 +12214,14 @@ async function runFusionOrderInternal(deps, options) {
11047
12214
  }
11048
12215
  }
11049
12216
  function runFusionOrder(deps, options) {
11050
- return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options));
12217
+ return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options), { wait: options.waitForWatcher === true });
11051
12218
  }
11052
12219
 
11053
12220
  // src/lib/swap/swap.same-chain-runner.ts
11054
12221
  var DEFAULT_POLL_MS3 = 5e3;
11055
12222
  var MAX_BACKOFF_MS2 = 3e4;
11056
12223
  var backoffFor2 = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS2);
11057
- var DEFAULT_TIMEOUT_MS3 = 3 * 60 * 1e3;
12224
+ var DEFAULT_TIMEOUT_MS4 = 3 * 60 * 1e3;
11058
12225
  function explain(status) {
11059
12226
  switch (status) {
11060
12227
  case "expired":
@@ -11078,7 +12245,7 @@ async function runSameChainOrderInternal(deps, options) {
11078
12245
  orderHash,
11079
12246
  onStage,
11080
12247
  pollIntervalMs = DEFAULT_POLL_MS3,
11081
- timeoutMs = DEFAULT_TIMEOUT_MS3
12248
+ timeoutMs = DEFAULT_TIMEOUT_MS4
11082
12249
  } = options;
11083
12250
  const deadline = deps.now() + timeoutMs;
11084
12251
  let partial = options.hasPartialFill === true;
@@ -11131,7 +12298,7 @@ async function runSameChainOrderInternal(deps, options) {
11131
12298
  }
11132
12299
  }
11133
12300
  function runSameChainOrder(deps, options) {
11134
- return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options));
12301
+ return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options), { wait: options.waitForWatcher === true });
11135
12302
  }
11136
12303
 
11137
12304
  // src/lib/swap/swap.secret-store.ts
@@ -11156,6 +12323,21 @@ function saveOrder(order) {
11156
12323
  return false;
11157
12324
  }
11158
12325
  }
12326
+ function loadOrder(orderHash) {
12327
+ const store = storage5();
12328
+ if (!store) return null;
12329
+ try {
12330
+ const raw2 = store.getItem(keyFor2(orderHash));
12331
+ if (!raw2) return null;
12332
+ const parsed = JSON.parse(raw2);
12333
+ if (!Array.isArray(parsed.secrets) || parsed.secrets.length === 0) {
12334
+ return null;
12335
+ }
12336
+ return parsed;
12337
+ } catch {
12338
+ return null;
12339
+ }
12340
+ }
11159
12341
  function clearOrder(orderHash) {
11160
12342
  const store = storage5();
11161
12343
  if (!store) return;
@@ -11234,10 +12416,10 @@ async function affordableAmount(deps, quoted, reserve = 0n) {
11234
12416
  }
11235
12417
  async function executeSwap(deps, options) {
11236
12418
  const { quote, walletAddress, onStage } = options;
11237
- let active2 = true;
12419
+ let active = true;
11238
12420
  const original = deps;
11239
12421
  const assertActive = () => {
11240
- if (!active2) throw new OwneyError("SWAP_REQUEST_FAILED", "This swap attempt is no longer active.");
12422
+ if (!active) throw new OwneyError("SWAP_REQUEST_FAILED", "This swap attempt is no longer active.");
11241
12423
  };
11242
12424
  const call = async (work, write = false) => {
11243
12425
  assertActive();
@@ -11298,7 +12480,7 @@ async function executeSwap(deps, options) {
11298
12480
  }
11299
12481
  return { received: arrived.toString(), ...result };
11300
12482
  } finally {
11301
- active2 = false;
12483
+ active = false;
11302
12484
  }
11303
12485
  }
11304
12486
  async function runClassic(deps, options) {
@@ -11417,6 +12599,7 @@ async function runSameChainFusion(deps, options, walletAddress) {
11417
12599
  }
11418
12600
  await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
11419
12601
  orderHash: built.orderHash,
12602
+ waitForWatcher: true,
11420
12603
  ...onStage ? { onStage } : {}
11421
12604
  });
11422
12605
  return { orderHash: built.orderHash, spent: amount.toString() };
@@ -11485,67 +12668,118 @@ async function runFusion(deps, options, walletAddress) {
11485
12668
  ...direction ? { direction } : {},
11486
12669
  ...permit ? { permit } : {}
11487
12670
  });
11488
- const durable = saveOrder({
11489
- orderHash: built.orderHash,
11490
- walletAddress,
11491
- secrets,
11492
- srcChainId: quote.src.chainId,
11493
- srcSymbol: quote.src.symbol,
11494
- dstChainId: quote.dst.chainId,
11495
- dstSymbol: quote.dst.symbol,
11496
- amount: amount.toString(),
11497
- createdAt: Date.now()
11498
- });
11499
- debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
11500
- let signature;
12671
+ const release = claimOrder(built.orderHash);
11501
12672
  try {
11502
- onStage?.("signing");
11503
- await deps.ensureChain(quote.src.chainId);
11504
- signature = await awaitWalletPrompt(() => deps.signTypedData(built.typedData), "sign the swap");
11505
- options.assertCurrent?.();
11506
- options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
11507
- } catch (error) {
12673
+ const stored = {
12674
+ orderHash: built.orderHash,
12675
+ walletAddress,
12676
+ secrets,
12677
+ srcChainId: quote.src.chainId,
12678
+ srcSymbol: quote.src.symbol,
12679
+ dstChainId: quote.dst.chainId,
12680
+ dstSymbol: quote.dst.symbol,
12681
+ amount: amount.toString(),
12682
+ createdAt: Date.now(),
12683
+ ...options.pendingDeposit ? { pendingDeposit: withinOrderMinimum(options.pendingDeposit, built.dstAmountMin) } : {}
12684
+ };
12685
+ const durable = saveOrder(stored);
12686
+ debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
12687
+ let signature;
12688
+ try {
12689
+ onStage?.("signing");
12690
+ await deps.ensureChain(quote.src.chainId);
12691
+ signature = await awaitWalletPrompt(() => deps.signTypedData(built.typedData), "sign the swap");
12692
+ options.assertCurrent?.();
12693
+ options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
12694
+ } catch (error) {
12695
+ clearOrder(built.orderHash);
12696
+ throw error;
12697
+ }
12698
+ if (options.secretHandoff && deps.api.handoffSecrets) {
12699
+ if (await tryHandoff(deps, options.secretHandoff, built, quote, secretHashes, secrets))
12700
+ saveOrder({ ...stored, serverSecrets: true });
12701
+ }
12702
+ debugLog("owney-sdk", "swap: signed, submitting to relayer");
12703
+ try {
12704
+ await deps.api.submitOrder({
12705
+ srcChainId: quote.src.chainId,
12706
+ // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
12707
+ // the envelope here gets a bare 500 from the relayer.
12708
+ order: built.order,
12709
+ signature,
12710
+ quoteId: built.quoteId,
12711
+ // Single-fill orders must NOT carry secretHashes — the relayer rejects
12712
+ // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
12713
+ // order's hashlock, so repeating it here is redundant, and only a
12714
+ // multi-fill order (a Merkle tree of hashes) needs them listed.
12715
+ ...secretHashes.length > 1 ? { secretHashes } : {},
12716
+ ...built.extension ? { extension: built.extension } : {}
12717
+ });
12718
+ } catch (error) {
12719
+ throw submissionUnknown(error, built.orderHash);
12720
+ }
12721
+ debugLog("owney-sdk", "swap: order submitted, polling escrows");
12722
+ try {
12723
+ options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
12724
+ } catch {
12725
+ }
12726
+ try {
12727
+ await runFusionOrder(deps.runner, {
12728
+ orderHash: built.orderHash,
12729
+ // This run owns the order: wait out a watcher another tab started.
12730
+ waitForWatcher: true,
12731
+ secrets,
12732
+ ...onStage ? { onStage } : {}
12733
+ });
12734
+ } catch (error) {
12735
+ if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
12736
+ clearOrder(built.orderHash);
12737
+ }
12738
+ throw error;
12739
+ }
11508
12740
  clearOrder(built.orderHash);
11509
- throw error;
11510
- }
11511
- debugLog("owney-sdk", "swap: signed, submitting to relayer");
12741
+ return { orderHash: built.orderHash, spent: amount.toString() };
12742
+ } finally {
12743
+ release();
12744
+ }
12745
+ }
12746
+ var HANDOFF_DECLINE_CODES = /* @__PURE__ */ new Set([
12747
+ "FUNDING_TASK_KIND_DISABLED",
12748
+ "SECRETS_INVALID",
12749
+ "SECRET_HASH_MISMATCH",
12750
+ "ORDER_UNREADABLE",
12751
+ "ORDER_HASH_MISMATCH",
12752
+ "HASHLOCK_MISMATCH",
12753
+ "MAKER_MISMATCH",
12754
+ "CHAIN_MISMATCH",
12755
+ "OPERATION_INVALID",
12756
+ "ORDER_CONFLICT"
12757
+ ]);
12758
+ async function tryHandoff(deps, handoff, built, quote, secretHashes, secrets) {
11512
12759
  try {
11513
- await deps.api.submitOrder({
12760
+ const answer = await bounded(() => deps.api.handoffSecrets({
12761
+ operation: handoff.operation,
12762
+ orderHash: built.orderHash,
11514
12763
  srcChainId: quote.src.chainId,
11515
- // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
11516
- // the envelope here gets a bare 500 from the relayer.
12764
+ dstChainId: quote.dst.chainId,
11517
12765
  order: built.order,
11518
- signature,
11519
- quoteId: built.quoteId,
11520
- // Single-fill orders must NOT carry secretHashes — the relayer rejects
11521
- // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
11522
- // order's hashlock, so repeating it here is redundant, and only a
11523
- // multi-fill order (a Merkle tree of hashes) needs them listed.
11524
- ...secretHashes.length > 1 ? { secretHashes } : {},
11525
- ...built.extension ? { extension: built.extension } : {}
11526
- });
11527
- } catch (error) {
11528
- throw submissionUnknown(error, built.orderHash);
11529
- }
11530
- debugLog("owney-sdk", "swap: order submitted, polling escrows");
11531
- try {
11532
- options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
11533
- } catch {
11534
- }
11535
- try {
11536
- await runFusionOrder(deps.runner, {
11537
- orderHash: built.orderHash,
11538
- secrets,
11539
- ...onStage ? { onStage } : {}
11540
- });
11541
- } catch (error) {
11542
- if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
11543
- clearOrder(built.orderHash);
12766
+ extension: built.extension,
12767
+ secretHashes,
12768
+ fusionSecrets: secrets
12769
+ }), FUSION_SECRET_HANDOFF_TIMEOUT_MS, () => new Error("secret handoff timed out"));
12770
+ if (answer?.accepted === true) {
12771
+ debugLog("owney-sdk", "swap: secrets handed to the server", { orderHash: built.orderHash });
12772
+ return true;
11544
12773
  }
11545
- throw error;
12774
+ const code = typeof answer?.code === "string" && HANDOFF_DECLINE_CODES.has(answer.code) ? answer.code : "OTHER";
12775
+ debugLog("owney-sdk", "swap: secret handoff declined, the browser publishes", { code });
12776
+ } catch {
12777
+ debugLog("owney-sdk", "swap: secret handoff unavailable, the browser publishes");
11546
12778
  }
11547
- clearOrder(built.orderHash);
11548
- return { orderHash: built.orderHash, spent: amount.toString() };
12779
+ return false;
12780
+ }
12781
+ function withinOrderMinimum(deposit, orderMinimum) {
12782
+ return typeof orderMinimum === "string" && /^\d+$/.test(orderMinimum) && /^\d+$/.test(deposit.received) && BigInt(orderMinimum) < BigInt(deposit.received) ? { ...deposit, received: orderMinimum } : deposit;
11549
12783
  }
11550
12784
  function submissionUnknown(error, orderHash) {
11551
12785
  return new OwneyError(
@@ -11568,7 +12802,7 @@ async function awaitWithdrawalPayouts(options) {
11568
12802
  const payouts = new Map(Object.entries(options.results).map(([id, result]) => [id, { result, evidence: result.evidence }]));
11569
12803
  const timeoutMs = Math.max(0, options.timeoutMs ?? 18e4);
11570
12804
  const deadline = options.deadline ?? Date.now() + timeoutMs;
11571
- let active2 = true;
12805
+ let active = true;
11572
12806
  let timer;
11573
12807
  const incomplete = (reason, agentId) => new OwneyError(
11574
12808
  "WITHDRAW_SWAP_INCOMPLETE",
@@ -11586,7 +12820,7 @@ async function awaitWithdrawalPayouts(options) {
11586
12820
  );
11587
12821
  const assertActive = () => {
11588
12822
  options.assertCurrent();
11589
- if (!active2 || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
12823
+ if (!active || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
11590
12824
  };
11591
12825
  const pause = async () => {
11592
12826
  assertActive();
@@ -11763,7 +12997,7 @@ async function awaitWithdrawalPayouts(options) {
11763
12997
  timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), Math.max(0, deadline - Date.now()));
11764
12998
  })]);
11765
12999
  } finally {
11766
- active2 = false;
13000
+ active = false;
11767
13001
  if (timer) clearTimeout(timer);
11768
13002
  }
11769
13003
  }
@@ -12331,7 +13565,8 @@ import {
12331
13565
  createPublicClient as createPublicClient4,
12332
13566
  createWalletClient as createWalletClient5,
12333
13567
  custom as custom5,
12334
- erc20Abi as erc20Abi7
13568
+ erc20Abi as erc20Abi7,
13569
+ isAddress as isAddress5
12335
13570
  } from "viem";
12336
13571
  import { base as base6, arbitrum as arbitrum3, mainnet as mainnet3 } from "viem/chains";
12337
13572
 
@@ -12340,21 +13575,42 @@ import {
12340
13575
  keccak256 as keccak2562,
12341
13576
  parseTransaction
12342
13577
  } from "viem";
13578
+ var SPONSORED_DEPOSIT_SERVER_BROADCAST = 1;
12343
13579
  async function executeTokenBatch(input, initial) {
12344
13580
  let body = initial, family, ended = false;
12345
13581
  const deadlineAt = Date.now() + 9e4, controller = new AbortController();
12346
13582
  let timer;
12347
- const pending = () => new OwneyError(
12348
- "DEPOSIT_IN_PROGRESS",
12349
- "Deposit is awaiting confirmation. Its transaction is still being checked.",
12350
- {
12351
- sponsorPending: true,
12352
- phase: body.serializedTransaction ? "deposit" : "approval",
12353
- txHash: body.serializedTransaction ? keccak2562(body.serializedTransaction) : body.serializedPermitTransaction ? keccak2562(body.serializedPermitTransaction) : void 0,
12354
- notSubmitted: false,
12355
- safeToFallback: false
12356
- }
12357
- );
13583
+ const serverSent = /* @__PURE__ */ new Set();
13584
+ let accepted;
13585
+ let maybeAccepted = !!input.resumeSaved, alreadySending = false;
13586
+ const verifiedFailures = /* @__PURE__ */ new WeakSet();
13587
+ const pending = () => {
13588
+ const deposit = body.serializedTransaction ? keccak2562(body.serializedTransaction) : accepted;
13589
+ const unknown2 = !deposit && maybeAccepted;
13590
+ const deadline = /^\d{1,15}$/.test(body.deadline) ? Number(body.deadline) : void 0;
13591
+ return new OwneyError(
13592
+ "DEPOSIT_IN_PROGRESS",
13593
+ "Deposit is awaiting confirmation. Its transaction is still being checked.",
13594
+ {
13595
+ sponsorPending: true,
13596
+ phase: deposit || unknown2 ? "deposit" : "approval",
13597
+ txHash: deposit ?? (body.serializedPermitTransaction && !unknown2 ? keccak2562(body.serializedPermitTransaction) : void 0),
13598
+ notSubmitted: false,
13599
+ safeToFallback: false,
13600
+ // Only while the current deposit is the one the server will send.
13601
+ ...deposit && serverSent.has(deposit) ? { serverBroadcast: true } : unknown2 ? { serverBroadcast: "unknown" } : {},
13602
+ ...unknown2 && alreadySending ? { code: "DEPOSIT_ALREADY_SENDING" } : {},
13603
+ // The Permit2 deadline (seconds): after it no deposit of this
13604
+ // authorization can execute, which bounds an unknown outcome. Never
13605
+ // after a 409 DEPOSIT_ALREADY_SENDING: this refused authorization is
13606
+ // not the deposit being sent.
13607
+ ...unknown2 && !alreadySending && deadline !== void 0 ? { authorizationDeadline: deadline } : {},
13608
+ // Its Permit2 nonce: with the deadline, the chain shows whether any
13609
+ // deposit of this authorization executed.
13610
+ ...unknown2 && !alreadySending || deposit && serverSent.has(deposit) ? { permit2Nonce: body.nonce } : {}
13611
+ }
13612
+ );
13613
+ };
12358
13614
  const assertCurrent = () => {
12359
13615
  if (ended || Date.now() >= deadlineAt) throw pending();
12360
13616
  input.assertCurrent?.();
@@ -12379,20 +13635,52 @@ async function executeTokenBatch(input, initial) {
12379
13635
  }
12380
13636
  return operationId ? { ...value, operationId } : value;
12381
13637
  };
12382
- const post2 = () => bounded2(
12383
- postSponsorBatchTransfer({
12384
- apiKey: input.apiKey,
12385
- baseUrl: input.baseUrl,
12386
- body: withOperationId(body),
12387
- signal: controller.signal
12388
- })
12389
- );
13638
+ const withServerBroadcast = (value) => {
13639
+ const request2 = withOperationId(value);
13640
+ return request2.operationId ? { ...request2, serverBroadcast: true } : request2;
13641
+ };
13642
+ const post2 = async () => {
13643
+ const request2 = withServerBroadcast(body);
13644
+ const flaggedPrepare = request2.serverBroadcast === true && !request2.serializedTransaction && !(request2.erc2612Permit && !request2.serializedPermitTransaction);
13645
+ let result;
13646
+ try {
13647
+ result = await Promise.race([
13648
+ postSponsorBatchTransfer({
13649
+ apiKey: input.apiKey,
13650
+ baseUrl: input.baseUrl,
13651
+ body: request2,
13652
+ signal: controller.signal
13653
+ }),
13654
+ timeout
13655
+ ]);
13656
+ } catch (error) {
13657
+ const details = error instanceof OwneyError ? error.details : void 0;
13658
+ const sending = details?.code === "DEPOSIT_ALREADY_SENDING";
13659
+ if (flaggedPrepare && (ended || !(error instanceof OwneyError) || details?.outcomeUnknown === true || sending)) {
13660
+ maybeAccepted = true;
13661
+ alreadySending = sending;
13662
+ }
13663
+ throw error;
13664
+ }
13665
+ maybeAccepted = false;
13666
+ alreadySending = false;
13667
+ if (result.serverBroadcast === true && result.serializedTransaction && !body.serializedTransaction) {
13668
+ serverSent.add(result.txHash);
13669
+ accepted = result.txHash;
13670
+ try {
13671
+ input.onSubmitted?.(result.txHash, reference(result.txHash), true);
13672
+ } catch {
13673
+ }
13674
+ }
13675
+ assertCurrent();
13676
+ return result;
13677
+ };
12390
13678
  const replacements = { approval: 0, deposit: 0 };
12391
13679
  const unavailable = /* @__PURE__ */ new Set();
12392
13680
  const observedApprovals = /* @__PURE__ */ new Set();
12393
13681
  const checkpointed = /* @__PURE__ */ new Set();
12394
13682
  const accounted = /* @__PURE__ */ new Set();
12395
- const remember = async (phase, bytes, hash2, predecessorHash) => {
13683
+ const remember = async (phase, bytes, hash2, predecessorHash, submissionAttempted = false) => {
12396
13684
  assertCurrent();
12397
13685
  if (keccak2562(bytes) !== hash2) throw pending();
12398
13686
  const tx = parseTransaction(bytes);
@@ -12411,7 +13699,7 @@ async function executeTokenBatch(input, initial) {
12411
13699
  nonce: String(tx.nonce),
12412
13700
  predecessorHash,
12413
13701
  preparedAt: Date.now(),
12414
- submissionAttempted: false
13702
+ submissionAttempted
12415
13703
  },
12416
13704
  assertCurrent
12417
13705
  )
@@ -12425,13 +13713,18 @@ async function executeTokenBatch(input, initial) {
12425
13713
  const markSubmission = async (phase) => {
12426
13714
  const hash2 = phase === "deposit" ? family?.current.deposit : family?.current.approval;
12427
13715
  if (!family || !hash2) throw pending();
13716
+ const flag = serverSent.has(hash2) ? [true] : [];
12428
13717
  if (phase === "deposit" && !checkpointed.has(hash2)) {
12429
13718
  try {
12430
13719
  await bounded2(
12431
- Promise.resolve(input.onBeforeSubmission?.(hash2, reference(hash2)))
13720
+ Promise.resolve(
13721
+ input.onBeforeSubmission?.(hash2, reference(hash2), ...flag)
13722
+ )
12432
13723
  );
13724
+ checkpointed.add(hash2);
12433
13725
  } catch (error) {
12434
- if (error instanceof OwneyError && error.code === "DEPOSIT_CHECKPOINT_FAILED")
13726
+ if (serverSent.has(hash2)) {
13727
+ } else if (error instanceof OwneyError && error.code === "DEPOSIT_CHECKPOINT_FAILED")
12435
13728
  throw new OwneyError(error.code, error.message, {
12436
13729
  ...error.details,
12437
13730
  notSubmitted: !family.attempts.some(
@@ -12439,9 +13732,8 @@ async function executeTokenBatch(input, initial) {
12439
13732
  ),
12440
13733
  safeToFallback: false
12441
13734
  });
12442
- throw error;
13735
+ else throw error;
12443
13736
  }
12444
- checkpointed.add(hash2);
12445
13737
  }
12446
13738
  assertCurrent();
12447
13739
  const attempt = family.attempts.find((a) => a.hash === hash2);
@@ -12456,7 +13748,7 @@ async function executeTokenBatch(input, initial) {
12456
13748
  assertCurrent();
12457
13749
  if (phase === "deposit")
12458
13750
  try {
12459
- input.onSubmitted?.(hash2, reference(hash2));
13751
+ input.onSubmitted?.(hash2, reference(hash2), ...flag);
12460
13752
  } catch {
12461
13753
  }
12462
13754
  assertCurrent();
@@ -12492,12 +13784,15 @@ async function executeTokenBatch(input, initial) {
12492
13784
  }
12493
13785
  return observation.txHash;
12494
13786
  }
12495
- if (observation.status === "failed" || observation.status === "expired")
12496
- throw new OwneyError(
13787
+ if (observation.status === "failed" || observation.status === "expired") {
13788
+ const failure = new OwneyError(
12497
13789
  "SPONSOR_REQUEST_FAILED",
12498
13790
  observation.status === "expired" ? "The deposit authorization expired without transferring funds. Start a new deposit." : "Sponsored deposit transactions reverted. Please retry.",
12499
13791
  { notSubmitted: true, safeToFallback: false }
12500
13792
  );
13793
+ verifiedFailures.add(failure);
13794
+ throw failure;
13795
+ }
12501
13796
  };
12502
13797
  try {
12503
13798
  assertCurrent();
@@ -12513,6 +13808,14 @@ async function executeTokenBatch(input, initial) {
12513
13808
  tokenBatchKey(input),
12514
13809
  JSON.stringify(tokenBatchStoredBody(body))
12515
13810
  );
13811
+ if (/^\d{1,78}$/.test(body.nonce) && /^\d{1,15}$/.test(body.deadline))
13812
+ try {
13813
+ input.onAuthorization?.({
13814
+ permit2Nonce: body.nonce,
13815
+ authorizationDeadline: Number(body.deadline)
13816
+ });
13817
+ } catch {
13818
+ }
12516
13819
  if (family) {
12517
13820
  const winner = await observe();
12518
13821
  if (winner) return winner;
@@ -12536,6 +13839,20 @@ async function executeTokenBatch(input, initial) {
12536
13839
  result = await post2();
12537
13840
  } catch (error) {
12538
13841
  assertCurrent();
13842
+ if (maybeAccepted && alreadySending) {
13843
+ if (family)
13844
+ finishTokenBatchFamily(input, family, {
13845
+ kind: "failed",
13846
+ blockNumber: "0"
13847
+ });
13848
+ else
13849
+ tokenBatchClear(
13850
+ tokenBatchKey(input),
13851
+ JSON.stringify(tokenBatchStoredBody(body))
13852
+ );
13853
+ throw pending();
13854
+ }
13855
+ if (maybeAccepted && !family) throw pending();
12539
13856
  if (!family) {
12540
13857
  tokenBatchClear(
12541
13858
  tokenBatchKey(input),
@@ -12558,7 +13875,9 @@ async function executeTokenBatch(input, initial) {
12558
13875
  await remember(
12559
13876
  "deposit",
12560
13877
  result.serializedTransaction,
12561
- result.txHash
13878
+ result.txHash,
13879
+ void 0,
13880
+ serverSent.has(result.txHash)
12562
13881
  );
12563
13882
  needsPost = true;
12564
13883
  continue;
@@ -12634,6 +13953,10 @@ async function executeTokenBatch(input, initial) {
12634
13953
  )
12635
13954
  );
12636
13955
  }
13956
+ } catch (error) {
13957
+ if ((serverSent.size || maybeAccepted) && !(error && typeof error === "object" && verifiedFailures.has(error)))
13958
+ throw pending();
13959
+ throw error;
12637
13960
  } finally {
12638
13961
  ended = true;
12639
13962
  if (timer) clearTimeout(timer);
@@ -12663,14 +13986,14 @@ async function releaseExpiredTokenBatch(i) {
12663
13986
  let timer;
12664
13987
  let expired = false;
12665
13988
  const check = async () => {
12666
- let active2 = read(key2);
12667
- if (active2 && i.expectedTxHash) {
12668
- const current = JSON.parse(active2);
13989
+ let active = read(key2);
13990
+ if (active && i.expectedTxHash) {
13991
+ const current = JSON.parse(active);
12669
13992
  if (!current.serializedTransaction || keccak2563(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
12670
- active2 = void 0;
13993
+ active = void 0;
12671
13994
  }
12672
13995
  const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
12673
- const saved = active2 ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
13996
+ const saved = active ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
12674
13997
  if (!saved) return;
12675
13998
  const body = JSON.parse(saved);
12676
13999
  if (!body.serializedTransaction || body.chainId !== i.chainId || !isAddressEqual2(body.from, i.owner) || !isAddressEqual2(body.token, i.token) || !body.transfers?.length) return;
@@ -12694,8 +14017,8 @@ async function releaseExpiredTokenBatch(i) {
12694
14017
  args: [i.owner, nonce >> 8n],
12695
14018
  blockNumber: block.number
12696
14019
  });
12697
- if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active2 && read(key2) !== active2) return;
12698
- if (active2) {
14020
+ if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active && read(key2) !== active) return;
14021
+ if (active) {
12699
14022
  const terminalKey = `${key2}:expired:${txHash.toLowerCase()}`;
12700
14023
  expiredRecords.set(terminalKey, saved);
12701
14024
  let persisted = typeof window === "undefined";
@@ -12732,15 +14055,15 @@ function clear(key2, body) {
12732
14055
  function sponsorTokenBatch(i) {
12733
14056
  const key2 = pendingKey(i);
12734
14057
  const plan = planOf(i.transfers);
12735
- const active2 = inflight.get(key2);
12736
- if (active2) {
12737
- if (active2.plan !== plan)
14058
+ const active = inflight.get(key2);
14059
+ if (active) {
14060
+ if (active.plan !== plan)
12738
14061
  return Promise.reject(
12739
14062
  new Error(
12740
14063
  "A token deposit is already in progress. Wait for its result before depositing again."
12741
14064
  )
12742
14065
  );
12743
- return active2.promise;
14066
+ return active.promise;
12744
14067
  }
12745
14068
  const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
12746
14069
  inflight.set(key2, { plan, promise });
@@ -12752,8 +14075,15 @@ async function execute(i, key2, plan) {
12752
14075
  ) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
12753
14076
  throw new Error("Invalid token deposit shares.");
12754
14077
  const send = (body) => executeTokenBatch(i, body);
14078
+ if (!i.resumeTxHash) await dropExpiredHashlessTokenBatch(i, i.pub);
12755
14079
  const family = await loadTokenBatchFamily(i, i.resumeTxHash);
12756
14080
  const saved = family ? JSON.stringify(family.body) : read(key2);
14081
+ if (!saved && i.resumeSaved)
14082
+ throw new OwneyError(
14083
+ "DEPOSIT_IN_PROGRESS",
14084
+ "The saved deposit is no longer available. Check its status before depositing again.",
14085
+ { notSubmitted: false, safeToFallback: false }
14086
+ );
12757
14087
  if (saved) {
12758
14088
  const previous = JSON.parse(saved);
12759
14089
  if (previous.chainId !== i.chainId || !isAddressEqual2(previous.from, i.owner) || !isAddressEqual2(previous.token, i.token) || planOf(previous.transfers) !== plan)
@@ -12863,10 +14193,13 @@ function makeSponsoredTokenCallback(deps) {
12863
14193
  pub,
12864
14194
  wallet,
12865
14195
  onApproved: deps.onApproved,
12866
- onSubmitted: (hash2, reference) => deps.onSubmitted?.(hash2, transfers, reference),
12867
- onBeforeSubmission: (hash2, reference) => deps.onBeforeSubmission?.(hash2, transfers, reference),
14196
+ // Rest arguments keep the callbacks' arity: the flag appears only when set.
14197
+ onSubmitted: (hash2, ...rest) => deps.onSubmitted?.(hash2, transfers, ...rest),
14198
+ onBeforeSubmission: (hash2, ...rest) => deps.onBeforeSubmission?.(hash2, transfers, ...rest),
12868
14199
  assertCurrent: deps.assertCurrent,
12869
14200
  resumeTxHash: deps.resumeTxHash,
14201
+ ...deps.resumeSaved ? { resumeSaved: true } : {},
14202
+ ...deps.onAuthorization ? { onAuthorization: deps.onAuthorization } : {},
12870
14203
  operationId: deps.operationId
12871
14204
  });
12872
14205
  };
@@ -13173,6 +14506,33 @@ function sponsoredTokensFor(asset) {
13173
14506
  function shouldFallbackToUserPaid(error, asset, appCallback) {
13174
14507
  return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
13175
14508
  }
14509
+ function serverBroadcastOf(error) {
14510
+ const flag = (value) => {
14511
+ const details = value?.details;
14512
+ return details?.serverBroadcast === true || details?.serverBroadcast === "unknown" ? details.serverBroadcast : void 0;
14513
+ };
14514
+ return flag(error) ?? flag(error?.details?.cause);
14515
+ }
14516
+ function authorizationDeadlineOf(error) {
14517
+ const read2 = (value) => {
14518
+ const deadline = value?.details?.authorizationDeadline;
14519
+ return typeof deadline === "number" && Number.isSafeInteger(deadline) && deadline > 0 ? deadline : void 0;
14520
+ };
14521
+ return read2(error) ?? read2(error?.details?.cause);
14522
+ }
14523
+ function alreadySendingOf(error) {
14524
+ const code = (value) => value?.details?.code;
14525
+ return code(error) === "DEPOSIT_ALREADY_SENDING" || code(error?.details?.cause) === "DEPOSIT_ALREADY_SENDING";
14526
+ }
14527
+ function permit2NonceOf(error) {
14528
+ const read2 = (value) => {
14529
+ const nonce = value?.details?.permit2Nonce;
14530
+ return typeof nonce === "string" && /^\d{1,78}$/.test(nonce) ? nonce : void 0;
14531
+ };
14532
+ return read2(error) ?? read2(error?.details?.cause);
14533
+ }
14534
+ var serverMayOwn = (leg) => leg !== void 0 && leg.settled !== null;
14535
+ var SERVER_HANDOFF_WAIT_MS = 1e4;
13176
14536
  var OwneySDK = class {
13177
14537
  agents = /* @__PURE__ */ new Map();
13178
14538
  activeAgents = /* @__PURE__ */ new Set();
@@ -13201,13 +14561,21 @@ var OwneySDK = class {
13201
14561
  ledger;
13202
14562
  reservationMode;
13203
14563
  reservationTransport;
14564
+ /** The caller's Zyfai server tasks opt-in: `true`, a function read each
14565
+ * time a handoff would start (F-I2(iii)), or undefined (off). */
14566
+ zyfaiServerTasks;
14567
+ /** The caller opted in to the Fusion+ secret handoff (`fusionSecretHandoff: true`). */
14568
+ fusionSecretHandoff;
13204
14569
  cachedSponsoredCallback = null;
13205
14570
  cachedWethSponsoredCallback = null;
13206
14571
  paymasterServiceUrl;
13207
14572
  cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
13208
14573
  agentReads = new AgentReads();
13209
14574
  depositFamilyReads = /* @__PURE__ */ new Map();
13210
- recoveredFundingReads = /* @__PURE__ */ new Set();
14575
+ /** Movements (owner, chain, agent, token, tx hash) whose balance refresh this
14576
+ * session started: true when it armed the refresh, false when its deadline
14577
+ * had passed (it only dropped the cache and forced one snapshot read). */
14578
+ recoveredFundingReads = /* @__PURE__ */ new Map();
13211
14579
  fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
13212
14580
  activationChecks = /* @__PURE__ */ new Map();
13213
14581
  // Protect the SDK entry point as well as the mini-app hook. This owns only
@@ -13245,6 +14613,8 @@ var OwneySDK = class {
13245
14613
  });
13246
14614
  this.reservationMode = config.withdrawalReservation === "fail-closed" || config.withdrawalReservation === "off" ? config.withdrawalReservation : "fail-open";
13247
14615
  this.reservationTransport = { apiKey: config.apiKey, baseUrl: config.routingApiBaseUrl, appName: config.appName };
14616
+ this.zyfaiServerTasks = config.zyfaiServerTasks === true || typeof config.zyfaiServerTasks === "function" ? config.zyfaiServerTasks : void 0;
14617
+ this.fusionSecretHandoff = config.fusionSecretHandoff === true;
13248
14618
  }
13249
14619
  /** Finishes a funding-ledger operation on success and adds its reference to
13250
14620
  * the result. The result object is copied, never mutated or replaced.
@@ -13310,16 +14680,34 @@ var OwneySDK = class {
13310
14680
  return void 0;
13311
14681
  }
13312
14682
  }
14683
+ /**
14684
+ * Part 5: the swap's ledger identity, for the executor's Fusion+ secret
14685
+ * handoff. Only with the `fusionSecretHandoff` opt-in and an open operation;
14686
+ * otherwise undefined, and the executor sends nothing. Owner and token are
14687
+ * lowercased, as the ledger reports them.
14688
+ */
14689
+ secretHandoffFor(op, input) {
14690
+ if (!this.fusionSecretHandoff || !op || !input || input.kind !== "swap_deposit" && input.kind !== "withdraw_swap")
14691
+ return void 0;
14692
+ return { operation: {
14693
+ clientOperationId: op.operationId,
14694
+ kind: input.kind,
14695
+ owner: input.owner.toLowerCase(),
14696
+ chainId: input.chainId,
14697
+ token: input.token.toLowerCase()
14698
+ } };
14699
+ }
13313
14700
  closeSwapLedger(op) {
13314
14701
  if (op && this.openSwapLedgers.get(op.operationId) === op) this.openSwapLedgers.delete(op.operationId);
13315
14702
  }
13316
- /** Reports one agent's withdrawal evidence as its leg. Never throws. */
13317
- ledgerWithdrawalLeg(op, evidence) {
14703
+ /** Reports one agent's withdrawal evidence as its leg (with `errorCode`, when
14704
+ * given). Never throws. */
14705
+ ledgerWithdrawalLeg(op, evidence, errorCode) {
13318
14706
  if (!op) return;
13319
14707
  try {
13320
14708
  const leg = ledgerLegFromEvidence(evidence, "withdraw");
13321
14709
  const rail = withdrawalLedgerRail(evidence);
13322
- if (leg) op.leg(rail ? { ...leg, rail } : leg);
14710
+ if (leg) op.leg({ ...rail ? { ...leg, rail } : leg, ...errorCode ? { errorCode } : {} });
13323
14711
  if (leg?.status === "confirmed") this.ledgerProviderSamples(op, evidence, "withdraw");
13324
14712
  } catch {
13325
14713
  }
@@ -13554,8 +14942,8 @@ var OwneySDK = class {
13554
14942
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
13555
14943
  }
13556
14944
  /** Builds the default USDC batch callback for the connected wallet. */
13557
- getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId) {
13558
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && this.cachedSponsoredCallback)
14945
+ getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId, resumeSaved, onAuthorization) {
14946
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization && this.cachedSponsoredCallback)
13559
14947
  return this.cachedSponsoredCallback;
13560
14948
  const provider = this.requireConnectedProvider();
13561
14949
  const connection = this.requireState();
@@ -13569,6 +14957,8 @@ var OwneySDK = class {
13569
14957
  onSubmitted,
13570
14958
  onBeforeSubmission,
13571
14959
  resumeTxHash,
14960
+ ...resumeSaved ? { resumeSaved: true } : {},
14961
+ ...onAuthorization ? { onAuthorization } : {},
13572
14962
  operationId,
13573
14963
  assertCurrent: () => {
13574
14964
  assertSubmissionCurrent?.();
@@ -13589,7 +14979,7 @@ var OwneySDK = class {
13589
14979
  transport: custom5(provider)
13590
14980
  })
13591
14981
  });
13592
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId) this.cachedSponsoredCallback = callback;
14982
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization) this.cachedSponsoredCallback = callback;
13593
14983
  return callback;
13594
14984
  }
13595
14985
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
@@ -13618,8 +15008,8 @@ var OwneySDK = class {
13618
15008
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
13619
15009
  * single-use batch authorization instead of an EIP-3009 authorization.
13620
15010
  */
13621
- getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId) {
13622
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && this.cachedWethSponsoredCallback)
15011
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId, resumeSaved, onAuthorization) {
15012
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization && this.cachedWethSponsoredCallback)
13623
15013
  return this.cachedWethSponsoredCallback;
13624
15014
  const provider = this.requireConnectedProvider();
13625
15015
  const connection = this.requireState();
@@ -13633,6 +15023,8 @@ var OwneySDK = class {
13633
15023
  onSubmitted,
13634
15024
  onBeforeSubmission,
13635
15025
  resumeTxHash,
15026
+ ...resumeSaved ? { resumeSaved: true } : {},
15027
+ ...onAuthorization ? { onAuthorization } : {},
13636
15028
  operationId,
13637
15029
  assertCurrent: () => {
13638
15030
  assertSubmissionCurrent?.();
@@ -13653,7 +15045,7 @@ var OwneySDK = class {
13653
15045
  transport: custom5(provider)
13654
15046
  })
13655
15047
  });
13656
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId) this.cachedWethSponsoredCallback = callback;
15048
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization) this.cachedWethSponsoredCallback = callback;
13657
15049
  return callback;
13658
15050
  }
13659
15051
  getAgent(agentId) {
@@ -13667,12 +15059,12 @@ var OwneySDK = class {
13667
15059
  return [...this.getActiveAgents().keys()].sort();
13668
15060
  }
13669
15061
  getActiveAgents() {
13670
- const active2 = /* @__PURE__ */ new Map();
15062
+ const active = /* @__PURE__ */ new Map();
13671
15063
  for (const id of this.activeAgents) {
13672
15064
  const agent = this.agents.get(id);
13673
- if (agent) active2.set(id, agent);
15065
+ if (agent) active.set(id, agent);
13674
15066
  }
13675
- return active2;
15067
+ return active;
13676
15068
  }
13677
15069
  /**
13678
15070
  * Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
@@ -13771,7 +15163,13 @@ var OwneySDK = class {
13771
15163
  this.routingApiBaseUrl
13772
15164
  ) : this.rpcUrls,
13773
15165
  this.referralSource,
13774
- { owneyApiKey: this.apiKey, routingApiBaseUrl: this.routingApiBaseUrl }
15166
+ {
15167
+ owneyApiKey: this.apiKey,
15168
+ routingApiBaseUrl: this.routingApiBaseUrl,
15169
+ // Only an explicit opt-in lets the agent hand the Zyfai session to
15170
+ // Owney; a function is passed as it is and read per handoff.
15171
+ ...this.zyfaiServerTasks !== void 0 ? { zyfaiServerTasks: this.zyfaiServerTasks } : {}
15172
+ }
13775
15173
  );
13776
15174
  }
13777
15175
  if (agentId === "yieldseeker") {
@@ -13993,10 +15391,21 @@ var OwneySDK = class {
13993
15391
  } catch {
13994
15392
  }
13995
15393
  };
13996
- const onSubmitted = (hash2, destinations, depositAuthorization) => {
15394
+ let permit;
15395
+ const onAuthorization = (value) => {
15396
+ permit = value;
15397
+ };
15398
+ const permitFields = () => permit ? { permit2Nonce: permit.permit2Nonce, authorizationDeadline: permit.authorizationDeadline } : {};
15399
+ const onSubmitted = (hash2, destinations, depositAuthorization, serverBroadcast) => {
13997
15400
  for (const destination of destinations) {
13998
15401
  const evidence = prepared.get(destination.to.toLowerCase());
13999
- if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash2, ...depositAuthorization ? { depositAuthorization } : {}, transfer: "pending" });
15402
+ if (evidence) notifyFunding(onProgress, {
15403
+ ...evidence,
15404
+ txHash: hash2,
15405
+ ...depositAuthorization ? { depositAuthorization } : {},
15406
+ ...serverBroadcast ? { serverBroadcast, ...permitFields() } : {},
15407
+ transfer: "pending"
15408
+ });
14000
15409
  }
14001
15410
  };
14002
15411
  const onCallsProgress = (callsId, destinations, transfer) => {
@@ -14005,6 +15414,7 @@ var OwneySDK = class {
14005
15414
  if (evidence) notifyFunding(onProgress, { ...evidence, callsId, transfer });
14006
15415
  }
14007
15416
  };
15417
+ const serverTaskContext = (agent) => this.zyfaiServerTasks !== void 0 && agent.supportsServerTasks === true && ledgerOp ? { operationId: ledgerOp.operationId } : void 0;
14008
15418
  const ledgerDone = (result) => this.ledgerSucceeded(ledgerOp, result);
14009
15419
  const ledgerFailed = (error) => this.ledgerFailed(ledgerOp, error);
14010
15420
  if (BigInt(amount) === 0n) {
@@ -14040,7 +15450,7 @@ var OwneySDK = class {
14040
15450
  });
14041
15451
  await checkpoint2(evidence);
14042
15452
  } : void 0;
14043
- const checkpointHash = checkpointDestinations ? (hash2, destinations, depositAuthorization) => checkpointDestinations(destinations, { txHash: hash2, depositAuthorization }) : void 0;
15453
+ const checkpointHash = checkpointDestinations ? (hash2, destinations, depositAuthorization, serverBroadcast) => checkpointDestinations(destinations, { txHash: hash2, depositAuthorization, ...serverBroadcast ? { serverBroadcast, ...permitFields() } : {} }) : void 0;
14044
15454
  const checkpointCalls = checkpointDestinations ? (id, destinations) => checkpointDestinations(destinations, { callsId: id }) : void 0;
14045
15455
  const checkpointPaid = checkpoint2 ? (evidence) => checkpoint2([evidence]) : void 0;
14046
15456
  const customCallback = depositCallback && checkpointDestinations ? makeVerificationAwareDepositCallback(async (to, cid, value, verification) => {
@@ -14048,9 +15458,10 @@ var OwneySDK = class {
14048
15458
  await checkpointDestinations([{ to, amount: value }], {});
14049
15459
  return depositCallback(to, cid, value);
14050
15460
  }) : depositCallback;
14051
- if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
15461
+ if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)) || options.resumeSavedBatch && (options.resumeTxHash || depositCallback || this.paymasterServiceUrl))
14052
15462
  throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
14053
- 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));
15463
+ 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));
15464
+ let resumedDeadline;
14054
15465
  try {
14055
15466
  const ledgerToken = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
14056
15467
  ledgerOp = this.startLedger({
@@ -14065,16 +15476,19 @@ var OwneySDK = class {
14065
15476
  } catch {
14066
15477
  }
14067
15478
  try {
14068
- if (options.resumeTxHash) {
15479
+ if (options.resumeTxHash || options.resumeSavedBatch) {
14069
15480
  const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
14070
- const family = token && await loadTokenBatchFamily({
15481
+ const scope = token && {
14071
15482
  apiKey: this.apiKey,
14072
15483
  baseUrl: this.routingApiBaseUrl,
14073
15484
  owner: state.walletAddress,
14074
15485
  chainId,
14075
15486
  token: token.address
14076
- }, options.resumeTxHash);
14077
- const pending = family && { transfers: family.body.transfers };
15487
+ };
15488
+ const family = scope && (options.resumeTxHash ? await loadTokenBatchFamily(scope, options.resumeTxHash) : { body: await savedTokenBatchBody(scope, this.getPaidRpcClient(chainId)) });
15489
+ if (options.resumeSavedBatch && family?.body && /^\d{1,15}$/.test(family.body.deadline))
15490
+ resumedDeadline = Number(family.body.deadline);
15491
+ const pending = family?.body && { transfers: family.body.transfers };
14078
15492
  const invalid2 = () => new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit does not match this request. Check its status before depositing again.");
14079
15493
  if (!pending || pending.transfers.some((transfer) => !/^\d+$/.test(transfer.amount) || BigInt(transfer.amount) <= 0n) || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n) !== BigInt(amount)) throw invalid2();
14080
15494
  const candidates = agentId ? [this.getAgent(agentId)] : this.getEligibleAgents(chainId, asset);
@@ -14094,12 +15508,10 @@ var OwneySDK = class {
14094
15508
  return {
14095
15509
  id: agent.id,
14096
15510
  amount: transfer.amount,
14097
- run: (callback) => withFailureReporting(
14098
- this.apiKey,
14099
- agent.id,
14100
- () => agent.deposit(state, chainId, transfer.amount, asset, callback, onProgress),
14101
- this.routingApiBaseUrl
14102
- )
15511
+ run: (callback) => withFailureReporting(this.apiKey, agent.id, () => {
15512
+ const context = serverTaskContext(agent);
15513
+ return context ? agent.deposit(state, chainId, transfer.amount, asset, callback, onProgress, void 0, context) : agent.deposit(state, chainId, transfer.amount, asset, callback, onProgress);
15514
+ }, this.routingApiBaseUrl)
14103
15515
  };
14104
15516
  });
14105
15517
  const batchTransfer2 = getDepositBatchTransfer(effectiveCallback);
@@ -14138,7 +15550,8 @@ var OwneySDK = class {
14138
15550
  onProgress,
14139
15551
  () => paidFallbackAgents.add(agent.id),
14140
15552
  !!options.resumeTxHash,
14141
- checkpointPaid
15553
+ checkpointPaid,
15554
+ serverTaskContext(agent)
14142
15555
  ));
14143
15556
  }
14144
15557
  const totalAmount = BigInt(amount);
@@ -14200,7 +15613,10 @@ var OwneySDK = class {
14200
15613
  run: (callback) => withFailureReporting(
14201
15614
  this.apiKey,
14202
15615
  agent.id,
14203
- () => agent.deposit(state, chainId, amount2, asset, callback, onProgress),
15616
+ () => {
15617
+ const context = serverTaskContext(agent);
15618
+ return context ? agent.deposit(state, chainId, amount2, asset, callback, onProgress, void 0, context) : agent.deposit(state, chainId, amount2, asset, callback, onProgress);
15619
+ },
14204
15620
  this.routingApiBaseUrl
14205
15621
  )
14206
15622
  })),
@@ -14241,7 +15657,8 @@ var OwneySDK = class {
14241
15657
  onProgress,
14242
15658
  () => paidFallbackAgents.add(agent.id),
14243
15659
  !!options.resumeTxHash,
14244
- checkpointPaid
15660
+ checkpointPaid,
15661
+ serverTaskContext(agent)
14245
15662
  );
14246
15663
  } catch (error) {
14247
15664
  if (Object.keys(agentResults).length === 0) throw error;
@@ -14261,7 +15678,22 @@ var OwneySDK = class {
14261
15678
  }
14262
15679
  return ledgerDone({ agentResults });
14263
15680
  } catch (error) {
14264
- if (checkpointFailure && !(error instanceof OwneyError && error.details?.notSubmitted === false)) {
15681
+ if (options.resumeSavedBatch && !serverBroadcastOf(error))
15682
+ error = new OwneyError(
15683
+ "DEPOSIT_IN_PROGRESS",
15684
+ "Deposit is awaiting confirmation. Its transaction is still being checked.",
15685
+ {
15686
+ sponsorPending: true,
15687
+ phase: "deposit",
15688
+ notSubmitted: false,
15689
+ safeToFallback: false,
15690
+ serverBroadcast: "unknown",
15691
+ ...resumedDeadline !== void 0 ? { authorizationDeadline: resumedDeadline } : {},
15692
+ cause: error
15693
+ }
15694
+ );
15695
+ const serverMaySend = serverBroadcastOf(error);
15696
+ if (checkpointFailure && !serverMaySend && !(error instanceof OwneyError && error.details?.notSubmitted === false)) {
14265
15697
  error = error instanceof OwneyError && error.code === "DEPOSIT_PARTIAL_FAILURE" ? new OwneyError(
14266
15698
  error.code,
14267
15699
  "Some deposits completed before another agent could be submitted.",
@@ -14279,6 +15711,19 @@ var OwneySDK = class {
14279
15711
  const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
14280
15712
  if (!rejected && paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
14281
15713
  if (!rejected && (evidence?.txHash || evidence?.userOpHash || evidence?.callsId)) continue;
15714
+ if (serverMaySend) {
15715
+ const refused = alreadySendingOf(error);
15716
+ const deadline = refused ? void 0 : authorizationDeadlineOf(error) ?? permit?.authorizationDeadline;
15717
+ const nonce = refused ? void 0 : permit2NonceOf(error) ?? permit?.permit2Nonce;
15718
+ if (evidence) notifyFunding(onProgress, {
15719
+ ...evidence,
15720
+ serverBroadcast: evidence.serverBroadcast ?? "unknown",
15721
+ ...deadline !== void 0 && !evidence.authorizationDeadline ? { authorizationDeadline: deadline } : {},
15722
+ ...nonce !== void 0 && !evidence.permit2Nonce ? { permit2Nonce: nonce } : {},
15723
+ transfer: "pending"
15724
+ });
15725
+ continue;
15726
+ }
14282
15727
  notifyFunding(onProgress, { ...evidence ?? {
14283
15728
  agentId: id,
14284
15729
  chainId,
@@ -14325,11 +15770,11 @@ var OwneySDK = class {
14325
15770
  * propagates unchanged. Scoped per agent invocation so one agent's failure
14326
15771
  * doesn't force a retry of sibling agents in a multi-agent split.
14327
15772
  */
14328
- async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false, onBeforeSubmission) {
15773
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false, onBeforeSubmission, serverTaskContext) {
14329
15774
  const attempt = (callback) => withFailureReporting(
14330
15775
  this.apiKey,
14331
15776
  agent.id,
14332
- () => 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),
15777
+ () => 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),
14333
15778
  this.routingApiBaseUrl
14334
15779
  );
14335
15780
  let approvalAttempted = false;
@@ -14626,11 +16071,36 @@ var OwneySDK = class {
14626
16071
  const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
14627
16072
  if (options.refreshBalance !== false && (verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
14628
16073
  agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil, evidence.txHash);
14629
- this.recoveredFundingReads.add(key2);
14630
- if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.values().next().value);
16074
+ const until = options.balanceRefreshUntil;
16075
+ this.noteFundingRefresh(key2, until === void 0 || until > Date.now());
14631
16076
  }
14632
16077
  return verified;
14633
16078
  }
16079
+ noteFundingRefresh(key2, armed) {
16080
+ this.recoveredFundingReads.delete(key2);
16081
+ this.recoveredFundingReads.set(key2, armed);
16082
+ if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.keys().next().value);
16083
+ }
16084
+ /**
16085
+ * Re-arms an agent's balance refresh after a confirmed deposit or withdrawal
16086
+ * the caller already verified (e.g. after a page reload, which loses the
16087
+ * refresh the SDK started in memory). Same refresh as receipt recovery
16088
+ * (`inspectFunding`), without a receipt read, and shared with it: once per
16089
+ * movement (`txHash`) in this SDK session, unless that recovery came after
16090
+ * its own deadline and so armed nothing. `until` bounds it (the agent caps
16091
+ * it further). Returns false when the agent has no such refresh or `until`
16092
+ * has passed; throws when not connected or on another chain. Never prompts.
16093
+ */
16094
+ refreshAgentBalanceAfterMovement(input) {
16095
+ const { state, chainId } = this.captureReadScope(input.chainId);
16096
+ const agent = this.getAgent(input.agentId);
16097
+ if (typeof agent.invalidateFundingRead !== "function" || !Number.isFinite(input.until) || input.until <= Date.now()) return false;
16098
+ const key2 = input.txHash ? `${state.walletAddress.toLowerCase()}:${chainId}:${input.agentId}:${input.token}:${input.txHash}` : void 0;
16099
+ if (key2 && this.recoveredFundingReads.get(key2) === true) return true;
16100
+ agent.invalidateFundingRead(state, chainId, input.token, input.until, input.txHash);
16101
+ if (key2) this.noteFundingRefresh(key2, true);
16102
+ return true;
16103
+ }
14634
16104
  /**
14635
16105
  * Lists on-chain payout candidates for a withdrawal the provider accepted
14636
16106
  * without a hash: ERC-20 `Transfer` logs on the evidence's token that reach
@@ -14752,22 +16222,25 @@ var OwneySDK = class {
14752
16222
  try {
14753
16223
  if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
14754
16224
  const first = evidences[0];
14755
- if (!first.txHash) return false;
16225
+ const saved = !first.txHash && evidences.every((e) => !e.txHash && (e.serverBroadcast === "unknown" || e.serverBroadcast === true) && typeof e.authorizationDeadline === "number");
16226
+ if (!first.txHash && !saved) return false;
14756
16227
  const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
14757
16228
  const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
14758
16229
  if (!token) return false;
14759
- const family = await loadTokenBatchFamily({
16230
+ const scope = {
14760
16231
  apiKey: this.apiKey,
14761
16232
  baseUrl: this.routingApiBaseUrl,
14762
16233
  owner: state.walletAddress,
14763
16234
  chainId,
14764
16235
  token: token.address
14765
- }, first.txHash);
14766
- const transfers = family?.body.transfers;
14767
- if (!family || !transfers || transfers.length !== evidences.length || transfers.reduce((n, t) => n + BigInt(t.amount), 0n).toString() !== amount) return false;
16236
+ };
16237
+ const family = saved ? void 0 : await loadTokenBatchFamily(scope, first.txHash);
16238
+ const savedBody = saved ? await savedTokenBatchBody(scope, this.getPaidRpcClient(chainId)) : void 0;
16239
+ const transfers = saved ? savedBody?.transfers : family?.body.transfers;
16240
+ if (!family && !savedBody || !transfers || transfers.length !== evidences.length || transfers.reduce((n, t) => n + BigInt(t.amount), 0n).toString() !== amount) return false;
14768
16241
  const destinations = /* @__PURE__ */ new Set();
14769
16242
  for (const e of evidences) {
14770
- 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;
16243
+ 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;
14771
16244
  const agent = this.getAgent(e.agentId);
14772
16245
  const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, e.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
14773
16246
  assertCurrent();
@@ -14780,6 +16253,62 @@ var OwneySDK = class {
14780
16253
  return false;
14781
16254
  }
14782
16255
  }
16256
+ /** Whether a Permit2 deposit authorization can still execute and whether it
16257
+ * did: `expired` when the finalized block is more than 300 s past
16258
+ * `deadline` (chain time), `used` when the owner's Permit2 nonce bit is set
16259
+ * at that block. Null when anything cannot be read. Never throws. */
16260
+ async checkDepositAuthorization(input) {
16261
+ try {
16262
+ const { chainId, owner, permit2Nonce, deadline } = input;
16263
+ if (!SupportedAssets.some((asset) => asset.chainId === chainId) || !isAddress5(owner) || typeof permit2Nonce !== "string" || !/^\d{1,78}$/.test(permit2Nonce) || !Number.isSafeInteger(deadline) || deadline <= 0) return null;
16264
+ const nonce = BigInt(permit2Nonce);
16265
+ if (nonce >= 1n << 256n) return null;
16266
+ const pub = this.getPaidRpcClient(chainId);
16267
+ const block = await pub.getBlock({ blockTag: "finalized" });
16268
+ if (typeof block?.timestamp !== "bigint" || typeof block.number !== "bigint") return null;
16269
+ const bitmap = await pub.readContract({
16270
+ address: BATCH_PERMIT2_ADDRESS,
16271
+ abi: PERMIT2_BATCH_ABI,
16272
+ functionName: "nonceBitmap",
16273
+ args: [owner, nonce >> 8n],
16274
+ blockNumber: block.number
16275
+ });
16276
+ if (typeof bitmap !== "bigint" || bitmap < 0n) return null;
16277
+ return { expired: block.timestamp > BigInt(deadline) + 300n, used: (bitmap & 1n << (nonce & 255n)) !== 0n };
16278
+ } catch {
16279
+ return null;
16280
+ }
16281
+ }
16282
+ /** Drops the saved signed body of a hashless deposit the server may have
16283
+ * been sending (`serverBroadcast` 'unknown'/true, no hash) once the app has
16284
+ * settled it another way: by the ledger's hash or its expired deadline.
16285
+ * Only the body with this Permit2 deadline and these exact shares is
16286
+ * dropped, so it never resumes or blocks a different deposit. Never throws. */
16287
+ async discardSavedDepositBatch(evidences) {
16288
+ try {
16289
+ if (this.paymasterServiceUrl || !evidences.length) return false;
16290
+ const first = evidences[0];
16291
+ const deadline = first.authorizationDeadline;
16292
+ if (typeof deadline !== "number" || !Number.isSafeInteger(deadline) || deadline <= 0) return false;
16293
+ const { state, chainId } = this.captureReadScope(first.chainId);
16294
+ const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
16295
+ if (!token) return false;
16296
+ 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;
16297
+ return await discardHashlessTokenBatch(
16298
+ {
16299
+ apiKey: this.apiKey,
16300
+ baseUrl: this.routingApiBaseUrl,
16301
+ owner: state.walletAddress,
16302
+ chainId,
16303
+ token: token.address
16304
+ },
16305
+ String(deadline),
16306
+ (body) => evidences.every((e) => body.transfers.some((t) => t.to.toLowerCase() === e.agentWallet.toLowerCase() && t.amount === e.intendedAmount))
16307
+ );
16308
+ } catch {
16309
+ return false;
16310
+ }
16311
+ }
14783
16312
  /** Validate signed journal membership, never just a caller-supplied family ID. */
14784
16313
  async validateDepositReplacement(previous, next) {
14785
16314
  try {
@@ -14872,6 +16401,22 @@ var OwneySDK = class {
14872
16401
  if (verified.agentId !== "zyfai") return verified;
14873
16402
  return this.getAgent("zyfai").retryDepositRegistration(state, verified);
14874
16403
  }
16404
+ /** A Zyfai withdrawal's lifecycle by the id recorded on its evidence. Never signs,
16405
+ * prompts or authenticates; `null` when unavailable or not this wallet's Zyfai withdrawal. */
16406
+ async getZyfaiWithdrawal(evidence) {
16407
+ const state = this.state;
16408
+ const id = evidence.withdrawalRequest?.providerWithdrawalId;
16409
+ if (evidence.agentId !== "zyfai" || !id || !state || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return null;
16410
+ return this.getAgent("zyfai").getWithdrawalLifecycle(state, id);
16411
+ }
16412
+ /** A Zyfai deposit's placement status by the id recorded on its evidence. Never signs,
16413
+ * prompts or authenticates; `null` when unavailable or not this wallet's Zyfai deposit. */
16414
+ async getZyfaiDeposit(evidence) {
16415
+ const state = this.state;
16416
+ const id = evidence.providerDepositId;
16417
+ if (evidence.agentId !== "zyfai" || !id || !state || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return null;
16418
+ return this.getAgent("zyfai").getDepositPlacement(state, id);
16419
+ }
14875
16420
  // --- Fund operations ---
14876
16421
  /** On-chain version and ownership status for this owner's Yieldseeker agent. */
14877
16422
  async getYieldseekerWalletStatus(asset) {
@@ -15454,6 +16999,17 @@ var OwneySDK = class {
15454
16999
  onStage: (stage) => options.onStage(order.orderHash, stage)
15455
17000
  } : {}
15456
17001
  });
17002
+ const deposit = order.pendingDeposit;
17003
+ if (deposit && typeof deposit.received === "string" && typeof deposit.symbol === "string" && typeof deposit.chainId === "number" && loadOrder(order.orderHash))
17004
+ savePendingDeposit({
17005
+ id: `${deposit.chainId}-${deposit.symbol}-${order.orderHash}`,
17006
+ received: deposit.received,
17007
+ symbol: deposit.symbol,
17008
+ chainId: deposit.chainId,
17009
+ createdAt: Date.now(),
17010
+ ...typeof deposit.agentId === "string" ? { agentId: deposit.agentId } : {},
17011
+ ...typeof deposit.operationId === "string" ? { operationId: deposit.operationId } : {}
17012
+ });
15457
17013
  clearOrder(order.orderHash);
15458
17014
  updateWithdrawSwap(order.orderHash, { status: "landed" });
15459
17015
  return { orderHash: order.orderHash, status: "completed" };
@@ -15624,14 +17180,16 @@ var OwneySDK = class {
15624
17180
  options.to.symbol
15625
17181
  );
15626
17182
  }
15627
- const ledgerOp = this.openSwapLedger(() => ({
17183
+ let ledgerInput;
17184
+ const ledgerOp = this.openSwapLedger(() => ledgerInput = {
15628
17185
  operationId: options.operationId,
15629
17186
  kind: "swap_deposit",
15630
17187
  owner: state.walletAddress,
15631
17188
  chainId: options.to.chainId,
15632
17189
  token: SupportedAssets.find((value) => value.chainId === options.to.chainId && value.symbol === options.to.symbol)?.address ?? "",
15633
17190
  asset: options.to.symbol.toLowerCase()
15634
- }));
17191
+ });
17192
+ const secretHandoff = this.secretHandoffFor(ledgerOp, ledgerInput);
15635
17193
  try {
15636
17194
  const api = this.swapApi();
15637
17195
  const quoted = await api.quote({
@@ -15659,6 +17217,17 @@ var OwneySDK = class {
15659
17217
  ...options.assertCurrent ? { assertCurrent: options.assertCurrent } : {},
15660
17218
  ...options.slippage === void 0 ? {} : { slippage: options.slippage },
15661
17219
  ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
17220
+ // Stored with a cross-chain order, so a resume that finishes the swap
17221
+ // can still lead to the deposit (ROUT-625). The guaranteed minimum,
17222
+ // never the quoted amount.
17223
+ pendingDeposit: {
17224
+ received: quote.dstAmountMin,
17225
+ symbol: options.to.symbol,
17226
+ chainId: quote.dst.chainId,
17227
+ ...options.agentId ? { agentId: options.agentId } : {},
17228
+ ...ledgerOp ? { operationId: ledgerOp.operationId } : {}
17229
+ },
17230
+ ...secretHandoff ? { secretHandoff } : {},
15662
17231
  ...ledgerOp ? { onOrderSubmitted: (orderHash) => {
15663
17232
  swapIds.orderHash = orderHash;
15664
17233
  this.ledgerSwap(ledgerOp, quote, { status: "submitted", orderHash });
@@ -15678,6 +17247,7 @@ var OwneySDK = class {
15678
17247
  receivedDstAmountRaw: swap.received
15679
17248
  });
15680
17249
  options.onSwapProgress?.("depositing");
17250
+ const backgroundDepositId = swap.orderHash ? `${quote.dst.chainId}-${options.to.symbol}-${swap.orderHash}` : void 0;
15681
17251
  try {
15682
17252
  options.assertCurrent?.();
15683
17253
  await this.ensureSwapChain(quote.dst.chainId);
@@ -15707,10 +17277,13 @@ var OwneySDK = class {
15707
17277
  ...options.assertCurrent ? { assertSubmissionCurrent: options.assertCurrent } : {},
15708
17278
  ...ledgerOp ? { operationId: ledgerOp.operationId } : {}
15709
17279
  });
17280
+ if (backgroundDepositId) clearPendingDeposit(backgroundDepositId);
15710
17281
  return this.ledgerSucceeded(ledgerOp, { swap, deposit, deposited: amount });
15711
17282
  } catch (error) {
15712
17283
  const pending = {
15713
- id: `${quote.dst.chainId}-${options.to.symbol}-${Date.now()}`,
17284
+ // Keyed by the order when there is one, so it replaces any record a
17285
+ // background resume wrote for the same order rather than doubling it.
17286
+ id: backgroundDepositId ?? `${quote.dst.chainId}-${options.to.symbol}-${Date.now()}`,
15714
17287
  received: swap.received,
15715
17288
  symbol: options.to.symbol,
15716
17289
  chainId: quote.dst.chainId,
@@ -15866,7 +17439,7 @@ var OwneySDK = class {
15866
17439
  const state = this.requireState();
15867
17440
  const activeChainId = this.requireChainId();
15868
17441
  const revision = this.connectionRevision;
15869
- let active2 = true;
17442
+ let active = true;
15870
17443
  let record2;
15871
17444
  let ledgerOp;
15872
17445
  let hadDurableRecord = false;
@@ -15874,7 +17447,7 @@ var OwneySDK = class {
15874
17447
  let confirmationDeadline = Infinity;
15875
17448
  const assertCurrent = () => {
15876
17449
  options.assertCurrent?.();
15877
- if (!active2) throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal attempt is no longer active.", { swapSubmitted: false });
17450
+ if (!active) throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal attempt is no longer active.", { swapSubmitted: false });
15878
17451
  if (this.state !== state || this.connectionRevision !== revision || state.chainId !== activeChainId)
15879
17452
  throw new OwneyError("NOT_CONNECTED", "Wallet changed during withdrawal and swap.");
15880
17453
  };
@@ -15919,9 +17492,10 @@ var OwneySDK = class {
15919
17492
  const saved = options.recoveryId ? listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === options.recoveryId) : void 0;
15920
17493
  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))
15921
17494
  throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal is not ready to continue.");
17495
+ let ledgerInput;
15922
17496
  ledgerOp = this.openSwapLedger(() => {
15923
17497
  const requestedRaw = saved ? saved.recovery.requested : options.amount === void 0 ? void 0 : parseUnits4(options.amount, asset.decimals).toString();
15924
- return {
17498
+ return ledgerInput = {
15925
17499
  operationId: saved?.operationId ?? options.operationId,
15926
17500
  kind: "withdraw_swap",
15927
17501
  owner: state.walletAddress,
@@ -15932,6 +17506,7 @@ var OwneySDK = class {
15932
17506
  ...requestedRaw === void 0 ? {} : { requestedAmountRaw: requestedRaw }
15933
17507
  };
15934
17508
  });
17509
+ const secretHandoff = this.secretHandoffFor(ledgerOp, ledgerInput);
15935
17510
  const prepareTimeout = () => new OwneyError(
15936
17511
  "WITHDRAW_SWAP_INCOMPLETE",
15937
17512
  "Could not read the source wallet. No new withdrawal was submitted.",
@@ -15991,7 +17566,7 @@ var OwneySDK = class {
15991
17566
  evidence.set(next.agentId, next);
15992
17567
  if (record2?.recovery) {
15993
17568
  record2.recovery.evidence[next.agentId] = next;
15994
- if (active2) saveWithdrawSwap(record2);
17569
+ if (active) saveWithdrawSwap(record2);
15995
17570
  else {
15996
17571
  const latest = listWithdrawSwaps(Date.now(), true).find((row) => row.recovery?.attemptId === record2.recovery.attemptId);
15997
17572
  if (latest?.recovery) updateWithdrawSwap(latest.orderHash, { recovery: {
@@ -16107,6 +17682,7 @@ var OwneySDK = class {
16107
17682
  quote,
16108
17683
  walletAddress: state.walletAddress,
16109
17684
  direction: "withdraw",
17685
+ ...secretHandoff ? { secretHandoff } : {},
16110
17686
  ...options.slippage === void 0 ? {} : { slippage: options.slippage },
16111
17687
  ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
16112
17688
  onOrderPrepared: (orderHash, details) => {
@@ -16195,7 +17771,7 @@ var OwneySDK = class {
16195
17771
  this.ledgerFailed(ledgerOp, error);
16196
17772
  throw error;
16197
17773
  } finally {
16198
- active2 = false;
17774
+ active = false;
16199
17775
  releaseSubmission();
16200
17776
  this.closeSwapLedger(ledgerOp);
16201
17777
  }
@@ -16243,11 +17819,13 @@ var OwneySDK = class {
16243
17819
  });
16244
17820
  } catch {
16245
17821
  }
16246
- const ledgerLeg = (evidence) => this.ledgerWithdrawalLeg(ledgerOp, evidence);
17822
+ const ledgerLeg = (evidence, errorCode) => this.ledgerWithdrawalLeg(ledgerOp, evidence, errorCode);
16247
17823
  let hold;
16248
17824
  let reservationRejected = false;
16249
17825
  let innerStarted = false;
16250
17826
  const reservationLegs = /* @__PURE__ */ new Map();
17827
+ const plannedLegs = /* @__PURE__ */ new Map();
17828
+ const serverLegs = /* @__PURE__ */ new Map();
16251
17829
  const reservation = {
16252
17830
  dispatching: (id) => hold?.dispatch(id),
16253
17831
  settled: (id, leg) => {
@@ -16257,11 +17835,22 @@ var OwneySDK = class {
16257
17835
  const settleHold = () => {
16258
17836
  if (!hold) return;
16259
17837
  hold.stopHeartbeat();
16260
- this.settleWithdrawalReservation(hold, ids, attempted, reservationLegs);
17838
+ const serverOwned = new Set(ids.filter((id) => !attempted.has(id) && serverMayOwn(serverLegs.get(id))));
17839
+ this.settleWithdrawalReservation(hold, ids, attempted, reservationLegs, serverOwned);
16261
17840
  };
16262
17841
  const runAgents = () => {
16263
17842
  innerStarted = true;
16264
- const inner = this.withdrawFromAgents(options, state, chainId, attempted, ledgerLeg, reservation);
17843
+ const inner = this.withdrawFromAgents(
17844
+ options,
17845
+ state,
17846
+ chainId,
17847
+ attempted,
17848
+ ledgerLeg,
17849
+ reservation,
17850
+ plannedLegs,
17851
+ (timeoutMs) => ledgerOp?.settle?.(timeoutMs),
17852
+ serverLegs
17853
+ );
16265
17854
  void inner.then(settleHold, settleHold);
16266
17855
  return inner;
16267
17856
  };
@@ -16285,7 +17874,9 @@ var OwneySDK = class {
16285
17874
  } catch (error) {
16286
17875
  if (!reservationRejected) {
16287
17876
  for (const id of ids) if (!attempted.has(id)) {
16288
- notifyFunding(options.onProgress, {
17877
+ if (serverMayOwn(serverLegs.get(id))) continue;
17878
+ const planned = plannedLegs.get(id);
17879
+ notifyFunding(options.onProgress, planned ? { ...planned, transfer: "failed", delivery: "failed" } : {
16289
17880
  agentId: id,
16290
17881
  chainId,
16291
17882
  token: options.asset,
@@ -16407,12 +17998,13 @@ var OwneySDK = class {
16407
17998
  * - a throw without submission evidence: `not_submitted`;
16408
17999
  * - Zyfai "withdrawal already in progress": kept, dispatch `provider_in_progress`.
16409
18000
  */
16410
- settleWithdrawalReservation(hold, ids, attempted, legs) {
18001
+ settleWithdrawalReservation(hold, ids, attempted, legs, keep) {
16411
18002
  try {
16412
18003
  if ([...attempted].some((id) => !legs.has(id))) return;
16413
18004
  const releases = /* @__PURE__ */ new Map();
16414
18005
  for (const id of new Set(ids)) {
16415
18006
  if (!RESERVATION_AGENTS.includes(id)) continue;
18007
+ if (keep?.has(id)) continue;
16416
18008
  const leg = legs.get(id);
16417
18009
  const outcome = attempted.has(id) && leg ? reservationLegOutcome(leg) : "not_submitted";
16418
18010
  if (outcome === "keep") continue;
@@ -16443,6 +18035,31 @@ var OwneySDK = class {
16443
18035
  } catch {
16444
18036
  }
16445
18037
  }
18038
+ /**
18039
+ * Reports a withdrawal step the app closed as never sent (a reload before its
18040
+ * request was saved) as a failed ledger leg with `NOT_SUBMITTED`, so other
18041
+ * devices and later recovery stop treating it as in flight. Refuses any step
18042
+ * with a hash or a saved request. Never touches the hold; never throws.
18043
+ */
18044
+ async reportWithdrawalNotSubmitted(operationId, evidence) {
18045
+ try {
18046
+ if (typeof operationId !== "string" || !operationId || !evidence || evidence.transfer !== "failed" || evidence.txHash || evidence.userOpHash || evidence.callsId || evidence.withdrawalRequest?.attempted === true) return;
18047
+ const leg = ledgerLegFromEvidence(evidence, "withdraw");
18048
+ if (leg?.status !== "failed") return;
18049
+ const token = SupportedAssets.find((value) => value.chainId === evidence.chainId && value.symbol === evidence.token);
18050
+ const op = this.ledger.resume({
18051
+ operationId,
18052
+ kind: "withdraw",
18053
+ owner: evidence.walletAddress,
18054
+ chainId: evidence.chainId,
18055
+ token: token?.address ?? "",
18056
+ asset: String(evidence.token).toLowerCase()
18057
+ });
18058
+ op.leg({ ...leg, errorCode: "NOT_SUBMITTED" });
18059
+ op.finish({});
18060
+ } catch {
18061
+ }
18062
+ }
16446
18063
  /**
16447
18064
  * Reports a withdrawal leg that was proven after its call ended (a hashless
16448
18065
  * Zyfai payout found on chain or in history) to the funding ledger, then
@@ -16492,7 +18109,36 @@ var OwneySDK = class {
16492
18109
  } catch {
16493
18110
  }
16494
18111
  }
16495
- async withdrawFromAgents(options, state, chainId, attempted, ledgerLeg, reservation) {
18112
+ /**
18113
+ * Reports a deposit leg that the app's recovery verified on chain after its
18114
+ * call ended to the funding ledger. Only evidence whose transfer is confirmed
18115
+ * with a transaction hash is reported; anything else is ignored. The ledger
18116
+ * PATCHes the existing operation (creating it only on a 404) and the server
18117
+ * re-verifies the hash. Deposits hold no reservation, so nothing is released.
18118
+ * Never throws.
18119
+ */
18120
+ async reportDepositRecovery(operationId, evidence) {
18121
+ try {
18122
+ if (typeof operationId !== "string" || !operationId || !evidence || evidence.transfer !== "confirmed") return;
18123
+ const leg = ledgerLegFromEvidence(evidence, "deposit");
18124
+ if (leg?.status !== "confirmed" || !leg.txHash) return;
18125
+ const rail = depositLedgerRail(evidence);
18126
+ const token = SupportedAssets.find((value) => value.chainId === evidence.chainId && value.symbol === evidence.token);
18127
+ const op = this.ledger.resume({
18128
+ operationId,
18129
+ kind: "deposit",
18130
+ owner: evidence.walletAddress,
18131
+ chainId: evidence.chainId,
18132
+ token: token?.address ?? "",
18133
+ asset: String(evidence.token).toLowerCase()
18134
+ });
18135
+ op.leg(rail ? { ...leg, rail } : leg);
18136
+ op.finish({});
18137
+ this.ledgerProviderSamples(op, evidence, "deposit");
18138
+ } catch {
18139
+ }
18140
+ }
18141
+ async withdrawFromAgents(options, state, chainId, attempted, ledgerLeg, reservation, plannedLegs, settleLedger, serverLegs = /* @__PURE__ */ new Map()) {
16496
18142
  const { asset, amount, agentId, onAgentResult } = options;
16497
18143
  const notifyAgentResult = (id, result) => {
16498
18144
  try {
@@ -16538,7 +18184,22 @@ var OwneySDK = class {
16538
18184
  { asset, amount, agentErrors: agentErrors2, agentFailures, pendingSubmissions: pending }
16539
18185
  );
16540
18186
  };
16541
- const submit = async (agent, requested2) => {
18187
+ const awaitServerHandoffs = async () => {
18188
+ const pending = [...serverLegs.values()].filter((leg) => leg.settled === void 0).map((leg) => leg.answer);
18189
+ if (!pending.length) return;
18190
+ let timer;
18191
+ await Promise.race([
18192
+ Promise.all(pending),
18193
+ new Promise((resolve) => {
18194
+ timer = setTimeout(resolve, SERVER_HANDOFF_WAIT_MS);
18195
+ })
18196
+ ]);
18197
+ clearTimeout(timer);
18198
+ };
18199
+ const submit = async (agent, requested2, onAccepted) => {
18200
+ const plannedTask = serverLegs.get(agent.id);
18201
+ const serverTask = plannedTask ? await plannedTask.answer : void 0;
18202
+ if (serverTask) await settleLedger?.(3e3);
16542
18203
  if (this.state !== state || this.state.chainId !== chainId)
16543
18204
  throw new OwneyError("NOT_CONNECTED", "Wallet connection changed before withdrawal submission.");
16544
18205
  attempted.add(agent.id);
@@ -16560,6 +18221,7 @@ var OwneySDK = class {
16560
18221
  submissionEvidence.set(agent.id, evidence);
16561
18222
  notifyFunding(options.onProgress, evidence);
16562
18223
  ledgerLeg?.(evidence);
18224
+ if (onAccepted && evidence.ownerAuthorization === "accepted" && evidence.transfer !== "failed" && (evidence.userOpHash || evidence.callsId || evidence.txHash)) onAccepted();
16563
18225
  };
16564
18226
  progress(evidence);
16565
18227
  adapterReported = false;
@@ -16574,14 +18236,26 @@ var OwneySDK = class {
16574
18236
  reservation?.dispatching(agent.id);
16575
18237
  } catch {
16576
18238
  }
16577
- const result = options.onBeforeSubmission || options.assertCurrent ? await agent.withdraw(
18239
+ const context = typeof options.operationId === "string" ? { operationId: options.operationId, ...serverTask ? { planned: true } : {} } : void 0;
18240
+ const onBeforeSubmission = options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0;
18241
+ const result = context ? await agent.withdraw(
18242
+ state,
18243
+ chainId,
18244
+ token,
18245
+ requested2,
18246
+ progress,
18247
+ options.gasMode ?? "auto",
18248
+ onBeforeSubmission,
18249
+ options.assertCurrent,
18250
+ context
18251
+ ) : options.onBeforeSubmission || options.assertCurrent ? await agent.withdraw(
16578
18252
  state,
16579
18253
  chainId,
16580
18254
  token,
16581
18255
  requested2,
16582
18256
  progress,
16583
18257
  options.gasMode ?? "auto",
16584
- options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0,
18258
+ onBeforeSubmission,
16585
18259
  options.assertCurrent
16586
18260
  ) : await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
16587
18261
  settled({
@@ -16592,6 +18266,10 @@ var OwneySDK = class {
16592
18266
  } catch (error) {
16593
18267
  if (!adapterReported && !evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
16594
18268
  progress({ ...evidence, transfer: "failed", delivery: "failed" });
18269
+ else if (error instanceof OwneyError && error.details?.rejectedBeforeSubmission === true && evidence.transfer !== "confirmed")
18270
+ progress({ ...evidence, transfer: "failed", delivery: "failed" });
18271
+ 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)
18272
+ ledgerLeg?.(evidence, "WITHDRAW_INSUFFICIENT_BALANCE");
16595
18273
  settled({ settled: "threw", evidence });
16596
18274
  throw error;
16597
18275
  }
@@ -16617,40 +18295,151 @@ var OwneySDK = class {
16617
18295
  notifyAgentResult(agent.id, result);
16618
18296
  return result;
16619
18297
  }
18298
+ const dispatch = async (agent, requested2) => {
18299
+ let accept;
18300
+ const accepted = new Promise((resolve) => {
18301
+ accept = () => resolve(true);
18302
+ });
18303
+ const outcome = submit(agent, requested2, agent.withdrawalRequiresWalletApproval ? accept : void 0);
18304
+ const handedOver = await Promise.race([accepted, outcome.then(() => false, () => false)]);
18305
+ return { outcome, handedOver };
18306
+ };
18307
+ const prepareLaterAgents = async (agents) => {
18308
+ if (!agents.some((agent) => agent.withdrawalRequiresWalletApproval)) return;
18309
+ for (const agent of agents) {
18310
+ if (agent.withdrawalRequiresWalletApproval || typeof agent.prepareWithdrawal !== "function") continue;
18311
+ try {
18312
+ await agent.prepareWithdrawal(state, chainId, token);
18313
+ } catch (error) {
18314
+ if (this.isUserRejectedWithdrawal(error)) throw error;
18315
+ console.warn(`withdraw preparation failed for agent "${agent.id}":`, error);
18316
+ }
18317
+ }
18318
+ };
18319
+ const recordPlan = (legs) => {
18320
+ if (legs.length < 2) return;
18321
+ const handOffs = [];
18322
+ for (const [index, [agent, intendedAmount]] of legs.entries()) {
18323
+ const planned = {
18324
+ agentId: agent.id,
18325
+ chainId,
18326
+ token,
18327
+ walletAddress: state.walletAddress,
18328
+ intendedAmount,
18329
+ transfer: "pending",
18330
+ registration: "not-applicable",
18331
+ delivery: "pending",
18332
+ planned: true
18333
+ };
18334
+ plannedLegs?.set(planned.agentId, planned);
18335
+ notifyFunding(options.onProgress, planned);
18336
+ const after = legs.slice(0, index).filter(([earlier]) => earlier.withdrawalRequiresWalletApproval).map(([earlier]) => `agent:${earlier.id}`);
18337
+ if (agent.supportsServerTasks === true && typeof agent.handOffPlannedWithdrawal === "function" && after.length && zyfaiServerTasksEnabled(this.zyfaiServerTasks)) handOffs.push([agent, planned, after]);
18338
+ }
18339
+ const operationId = options.operationId;
18340
+ if (typeof operationId !== "string" || !operationId) return;
18341
+ for (const [agent, planned, after] of handOffs) {
18342
+ const leg = { answer: Promise.resolve(void 0), share: planned.intendedAmount };
18343
+ leg.answer = (async () => {
18344
+ let serverTask;
18345
+ let noTask = false;
18346
+ try {
18347
+ const answer = await agent.handOffPlannedWithdrawal(
18348
+ state,
18349
+ chainId,
18350
+ token,
18351
+ planned.intendedAmount,
18352
+ {
18353
+ operationId,
18354
+ after,
18355
+ // T10-I1: saved before the request leaves, as the dispatch
18356
+ // handoff's checkpoint is, so a reload while the server
18357
+ // answers finds a leg the server may own.
18358
+ beforeRequest: () => notifyFunding(options.onProgress, { ...planned, serverTask: "unknown" })
18359
+ }
18360
+ );
18361
+ serverTask = answer.type === "accepted" ? true : answer.type === "unknown" || answer.code === "LEG_ALREADY_SENT" ? "unknown" : void 0;
18362
+ noTask = answer.type === "declined" && ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code);
18363
+ } catch {
18364
+ serverTask = "unknown";
18365
+ }
18366
+ leg.settled = serverTask ?? null;
18367
+ if (serverTask) {
18368
+ const marked = { ...planned, serverTask };
18369
+ plannedLegs?.set(planned.agentId, marked);
18370
+ notifyFunding(options.onProgress, marked);
18371
+ } else if (noTask) {
18372
+ notifyFunding(options.onProgress, { ...planned, serverTask: false });
18373
+ }
18374
+ return serverTask;
18375
+ })();
18376
+ serverLegs.set(planned.agentId, leg);
18377
+ }
18378
+ };
16620
18379
  const eligibleAgents = this.getEligibleAgents(chainId, asset).filter((agent) => !options.excludeAgentIds?.includes(agent.id));
16621
18380
  const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
16622
18381
  if (!amount) {
16623
18382
  const results2 = {};
16624
18383
  const agentErrors2 = {};
16625
18384
  const incompleteWithdrawals = {};
16626
- for (const agent of withdrawalAgents) {
16627
- try {
16628
- results2[agent.id] = await submit(agent);
16629
- notifyAgentResult(agent.id, results2[agent.id]);
16630
- } catch (err) {
18385
+ const failed2 = (agent, err, handedOver) => {
18386
+ if (!handedOver) {
16631
18387
  if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
16632
18388
  if (this.isUserRejectedWithdrawal(err)) throw err;
16633
- if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") continue;
16634
- if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
16635
- incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
16636
- const evidence = submissionEvidence.get(agent.id);
16637
- 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) {
16638
- results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
16639
- notifyAgentResult(agent.id, results2[agent.id]);
16640
- }
18389
+ }
18390
+ if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") return;
18391
+ if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
18392
+ incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
18393
+ const evidence = submissionEvidence.get(agent.id);
18394
+ 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) {
18395
+ results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
18396
+ notifyAgentResult(agent.id, results2[agent.id]);
18397
+ }
18398
+ }
18399
+ console.error(`withdraw failed for agent "${agent.id}":`, err);
18400
+ agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
18401
+ recordAgentFailure(agent.id, err);
18402
+ const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
18403
+ void reportAgentFailure(
18404
+ this.apiKey,
18405
+ agent.id,
18406
+ code,
18407
+ this.routingApiBaseUrl
18408
+ );
18409
+ };
18410
+ const handedOverLegs2 = [];
18411
+ const settleHandedOver2 = async () => {
18412
+ for (const { agent, outcome } of handedOverLegs2.splice(0)) {
18413
+ try {
18414
+ results2[agent.id] = await outcome;
18415
+ notifyAgentResult(agent.id, results2[agent.id]);
18416
+ } catch (err) {
18417
+ failed2(agent, err, true);
18418
+ }
18419
+ }
18420
+ };
18421
+ await prepareLaterAgents(withdrawalAgents);
18422
+ recordPlan(withdrawalAgents.map((agent) => [agent, "MAX"]));
18423
+ try {
18424
+ for (const agent of withdrawalAgents) {
18425
+ const leg = await dispatch(agent);
18426
+ if (leg.handedOver) {
18427
+ handedOverLegs2.push({ agent, outcome: leg.outcome });
18428
+ continue;
18429
+ }
18430
+ try {
18431
+ results2[agent.id] = await leg.outcome;
18432
+ notifyAgentResult(agent.id, results2[agent.id]);
18433
+ } catch (err) {
18434
+ failed2(agent, err, false);
16641
18435
  }
16642
- console.error(`withdraw failed for agent "${agent.id}":`, err);
16643
- agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
16644
- recordAgentFailure(agent.id, err);
16645
- const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
16646
- void reportAgentFailure(
16647
- this.apiKey,
16648
- agent.id,
16649
- code,
16650
- this.routingApiBaseUrl
16651
- );
16652
18436
  }
18437
+ } catch (error) {
18438
+ if (serverLegs.size) await awaitServerHandoffs();
18439
+ await settleHandedOver2();
18440
+ throw error;
16653
18441
  }
18442
+ await settleHandedOver2();
16654
18443
  if (Object.keys(results2).length === 0) {
16655
18444
  throwUnconfirmed(agentErrors2);
16656
18445
  if (Object.keys(agentErrors2).length === 0)
@@ -16795,32 +18584,65 @@ var OwneySDK = class {
16795
18584
  const agentErrors = {
16796
18585
  ...aggregated.agentErrors ?? {}
16797
18586
  };
16798
- for (let i = 0; i < orderedPlans.length; i++) {
16799
- const p = orderedPlans[i];
16800
- assertPlan();
16801
- if (p.planned <= 0n) continue;
16802
- try {
16803
- results[p.agent.id] = await submit(p.agent, p.planned.toString());
16804
- notifyAgentResult(p.agent.id, results[p.agent.id]);
16805
- } catch (err) {
18587
+ const failed = (p, index, err, handedOver) => {
18588
+ if (!handedOver) {
16806
18589
  if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
16807
18590
  if (this.isUserRejectedWithdrawal(err)) throw err;
16808
- console.error(`withdraw failed for agent "${p.agent.id}":`, err);
16809
- agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
16810
- recordAgentFailure(p.agent.id, err);
16811
- const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
16812
- void reportAgentFailure(
16813
- this.apiKey,
16814
- p.agent.id,
16815
- code,
16816
- this.routingApiBaseUrl
16817
- );
16818
- const failedAmount = p.planned;
16819
- p.planned = 0n;
16820
- if (submissionEvidence.get(p.agent.id)?.transfer === "failed")
16821
- redistributeShare(orderedPlans, i, failedAmount);
16822
18591
  }
18592
+ console.error(`withdraw failed for agent "${p.agent.id}":`, err);
18593
+ agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
18594
+ recordAgentFailure(p.agent.id, err);
18595
+ const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
18596
+ void reportAgentFailure(
18597
+ this.apiKey,
18598
+ p.agent.id,
18599
+ code,
18600
+ this.routingApiBaseUrl
18601
+ );
18602
+ const failedAmount = p.planned;
18603
+ p.planned = 0n;
18604
+ if (!handedOver && submissionEvidence.get(p.agent.id)?.transfer === "failed")
18605
+ redistributeShare(orderedPlans, index, failedAmount);
18606
+ };
18607
+ const handedOverLegs = [];
18608
+ const settleHandedOver = async () => {
18609
+ for (const { plan, index, outcome } of handedOverLegs.splice(0)) {
18610
+ try {
18611
+ results[plan.agent.id] = await outcome;
18612
+ notifyAgentResult(plan.agent.id, results[plan.agent.id]);
18613
+ } catch (err) {
18614
+ failed(plan, index, err, true);
18615
+ }
18616
+ }
18617
+ };
18618
+ const dispatchedPlans = orderedPlans.filter((plan) => plan.planned > 0n);
18619
+ await prepareLaterAgents(dispatchedPlans.map((plan) => plan.agent));
18620
+ recordPlan(dispatchedPlans.map((plan) => [plan.agent, plan.planned.toString()]));
18621
+ try {
18622
+ for (let i = 0; i < orderedPlans.length; i++) {
18623
+ const p = orderedPlans[i];
18624
+ assertPlan();
18625
+ if (p.planned <= 0n) continue;
18626
+ const serverLeg = serverLegs.get(p.agent.id);
18627
+ if (serverLeg && await serverLeg.answer && /^[1-9][0-9]*$/.test(serverLeg.share)) p.planned = BigInt(serverLeg.share);
18628
+ const leg = await dispatch(p.agent, p.planned.toString());
18629
+ if (leg.handedOver) {
18630
+ handedOverLegs.push({ plan: p, index: i, outcome: leg.outcome });
18631
+ continue;
18632
+ }
18633
+ try {
18634
+ results[p.agent.id] = await leg.outcome;
18635
+ notifyAgentResult(p.agent.id, results[p.agent.id]);
18636
+ } catch (err) {
18637
+ failed(p, i, err, false);
18638
+ }
18639
+ }
18640
+ } catch (error) {
18641
+ if (serverLegs.size) await awaitServerHandoffs();
18642
+ await settleHandedOver();
18643
+ throw error;
16823
18644
  }
18645
+ await settleHandedOver();
16824
18646
  if (Object.keys(results).length === 0) {
16825
18647
  throwUnconfirmed(agentErrors);
16826
18648
  throw new OwneyError(
@@ -17756,12 +19578,20 @@ function createOwneySIWX(config) {
17756
19578
  export {
17757
19579
  AgentChainIncompatibleError,
17758
19580
  AgentNotFoundError,
19581
+ FUNDING_DEPOSIT_RECOVERY_REPORT,
19582
+ FUSION_SECRET_SERVER_PUBLISH,
17759
19583
  InvalidHistoryCursorError,
17760
19584
  NotConnectedError,
17761
19585
  OwneyError,
17762
19586
  OwneySDK,
17763
19587
  SDK_VERSION,
19588
+ SPONSORED_DEPOSIT_SERVER_BROADCAST,
19589
+ YIELDSEEKER_WITHDRAW_SERVER_SUBMIT,
17764
19590
  YieldseekerAgent,
19591
+ ZYFAI_REGISTRATION_NOT_SENT_CODES,
19592
+ ZYFAI_SERVER_TASKS,
19593
+ ZYFAI_WITHDRAWAL_NOT_SENT_CODES,
19594
+ ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT,
17765
19595
  clearWithdrawSwap,
17766
19596
  createOwneySIWX,
17767
19597
  listPendingDeposits,