@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/{chunk-YBMCUL5D.js → chunk-CW55W2WO.js} +83 -5
- package/dist/index.cjs +2309 -397
- package/dist/index.d.cts +374 -6
- package/dist/index.d.ts +374 -6
- package/dist/index.js +2221 -391
- package/dist/{token-batch-family-EKOLMSTD.js → token-batch-family-TUGZWETV.js} +7 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -209,9 +209,12 @@ var init_permit2_batch = __esm({
|
|
|
209
209
|
// src/lib/token-batch-family.ts
|
|
210
210
|
var token_batch_family_exports = {};
|
|
211
211
|
__export(token_batch_family_exports, {
|
|
212
|
+
discardHashlessTokenBatch: () => discardHashlessTokenBatch,
|
|
213
|
+
dropExpiredHashlessTokenBatch: () => dropExpiredHashlessTokenBatch,
|
|
212
214
|
finishTokenBatchFamily: () => finishTokenBatchFamily,
|
|
213
215
|
loadTokenBatchFamily: () => loadTokenBatchFamily,
|
|
214
216
|
saveTokenBatchAttempt: () => saveTokenBatchAttempt,
|
|
217
|
+
savedTokenBatchBody: () => savedTokenBatchBody,
|
|
215
218
|
tokenBatchAuthorizationId: () => tokenBatchAuthorizationId,
|
|
216
219
|
tokenBatchCall: () => tokenBatchCall,
|
|
217
220
|
tokenBatchClear: () => tokenBatchClear,
|
|
@@ -423,6 +426,9 @@ function merge(base7, incoming) {
|
|
|
423
426
|
async function loadTokenBatchFamily(scope, expectedHash, allowMissing = false) {
|
|
424
427
|
try {
|
|
425
428
|
const key2 = tokenBatchKey(scope), records = /* @__PURE__ */ new Map();
|
|
429
|
+
for (const sidecar of [...unfinished.keys()])
|
|
430
|
+
if (sidecar.startsWith(`${key2}:family:`) && !tokenBatchRead(sidecar))
|
|
431
|
+
unfinished.delete(sidecar);
|
|
426
432
|
for (const k of keys().filter((k2) => k2.startsWith(`${key2}:family:`))) {
|
|
427
433
|
const raw2 = tokenBatchRead(k);
|
|
428
434
|
if (!raw2) continue;
|
|
@@ -450,6 +456,19 @@ async function loadTokenBatchFamily(scope, expectedHash, allowMissing = false) {
|
|
|
450
456
|
);
|
|
451
457
|
}
|
|
452
458
|
}
|
|
459
|
+
const marked = /* @__PURE__ */ new Set();
|
|
460
|
+
for (const [id, family] of [...records]) {
|
|
461
|
+
const sidecar = `${key2}:family:${id}`;
|
|
462
|
+
const terminal = unfinished.get(sidecar);
|
|
463
|
+
if (!terminal) continue;
|
|
464
|
+
if (family.terminal) {
|
|
465
|
+
unfinished.delete(sidecar);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
finishTokenBatchFamily(scope, family, terminal);
|
|
469
|
+
marked.add(id);
|
|
470
|
+
records.set(id, { ...family, terminal });
|
|
471
|
+
}
|
|
453
472
|
if (expectedHash) {
|
|
454
473
|
let found = [...records.values()].filter(
|
|
455
474
|
(f) => f.attempts.some((a) => same(a.hash, expectedHash))
|
|
@@ -467,16 +486,69 @@ async function loadTokenBatchFamily(scope, expectedHash, allowMissing = false) {
|
|
|
467
486
|
if (found.length !== 1) throw pending();
|
|
468
487
|
return found[0];
|
|
469
488
|
}
|
|
470
|
-
const
|
|
471
|
-
(f) => !f.terminal || f.authorizationId === projectedId
|
|
489
|
+
const active = [...records.values()].filter(
|
|
490
|
+
(f) => !f.terminal || f.authorizationId === projectedId && !marked.has(f.authorizationId)
|
|
472
491
|
);
|
|
473
|
-
if (
|
|
474
|
-
return
|
|
492
|
+
if (active.length > 1) throw pending();
|
|
493
|
+
return active[0];
|
|
475
494
|
} catch (error) {
|
|
476
495
|
if (error instanceof OwneyError) throw error;
|
|
477
496
|
throw pending();
|
|
478
497
|
}
|
|
479
498
|
}
|
|
499
|
+
async function dropExpiredHashlessTokenBatch(scope, pub) {
|
|
500
|
+
try {
|
|
501
|
+
const key2 = tokenBatchKey(scope);
|
|
502
|
+
const family = await loadTokenBatchFamily(scope);
|
|
503
|
+
const raw2 = family ? void 0 : tokenBatchRead(key2);
|
|
504
|
+
const body = family ? family.terminal ? void 0 : family.body : raw2 ? JSON.parse(raw2) : void 0;
|
|
505
|
+
if (!body || body.serializedTransaction || family?.attempts.some((a) => a.phase === "deposit") || !uint(body.deadline))
|
|
506
|
+
return false;
|
|
507
|
+
const block = await pub.getBlock();
|
|
508
|
+
if (typeof block?.timestamp !== "bigint" || block.timestamp <= BigInt(body.deadline))
|
|
509
|
+
return false;
|
|
510
|
+
if (family)
|
|
511
|
+
finishTokenBatchFamily(scope, family, {
|
|
512
|
+
kind: "expired",
|
|
513
|
+
blockNumber: typeof block.number === "bigint" ? block.number.toString() : "0"
|
|
514
|
+
});
|
|
515
|
+
else tokenBatchClear(key2, raw2);
|
|
516
|
+
return true;
|
|
517
|
+
} catch {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
async function discardHashlessTokenBatch(scope, deadline, matches) {
|
|
522
|
+
try {
|
|
523
|
+
const key2 = tokenBatchKey(scope);
|
|
524
|
+
const family = await loadTokenBatchFamily(scope);
|
|
525
|
+
const raw2 = family ? void 0 : tokenBatchRead(key2);
|
|
526
|
+
const body = family ? family.terminal ? void 0 : family.body : raw2 ? JSON.parse(raw2) : void 0;
|
|
527
|
+
if (!body || body.serializedTransaction || family?.attempts.some((a) => a.phase === "deposit") || body.deadline !== deadline || body.chainId !== scope.chainId || !same(body.from, scope.owner) || !same(body.token, scope.token) || !matches(body))
|
|
528
|
+
return false;
|
|
529
|
+
if (family)
|
|
530
|
+
finishTokenBatchFamily(scope, family, { kind: "failed", blockNumber: "0" });
|
|
531
|
+
else tokenBatchClear(key2, raw2);
|
|
532
|
+
return true;
|
|
533
|
+
} catch {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async function savedTokenBatchBody(scope, pub) {
|
|
538
|
+
try {
|
|
539
|
+
if (pub) await dropExpiredHashlessTokenBatch(scope, pub);
|
|
540
|
+
const family = await loadTokenBatchFamily(scope);
|
|
541
|
+
if (family) return family.terminal ? void 0 : family.body;
|
|
542
|
+
const raw2 = tokenBatchRead(tokenBatchKey(scope));
|
|
543
|
+
if (!raw2) return void 0;
|
|
544
|
+
const body = JSON.parse(raw2);
|
|
545
|
+
if (body?.chainId !== scope.chainId || typeof body.from !== "string" || typeof body.token !== "string" || !same(body.from, scope.owner) || !same(body.token, scope.token) || !uint(body.deadline) || !Array.isArray(body.transfers) || !body.transfers.length)
|
|
546
|
+
return void 0;
|
|
547
|
+
return body;
|
|
548
|
+
} catch {
|
|
549
|
+
return void 0;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
480
552
|
async function saveTokenBatchAttempt(scope, body, attempt, assertCurrent = () => {
|
|
481
553
|
}) {
|
|
482
554
|
try {
|
|
@@ -535,17 +607,22 @@ function finishTokenBatchFamily(scope, family, terminal) {
|
|
|
535
607
|
return;
|
|
536
608
|
const evidence = terminalOf(terminal, current.attempts);
|
|
537
609
|
if (!evidence) return;
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
610
|
+
try {
|
|
611
|
+
tokenBatchWrite(
|
|
612
|
+
sidecar,
|
|
613
|
+
JSON.stringify({ ...current, terminal: evidence })
|
|
614
|
+
);
|
|
615
|
+
unfinished.delete(sidecar);
|
|
616
|
+
} catch {
|
|
617
|
+
unfinished.set(sidecar, evidence);
|
|
618
|
+
}
|
|
542
619
|
const projected = tokenBatchRead(key2);
|
|
543
620
|
if (projected && tokenBatchAuthorizationId(JSON.parse(projected), family.relayer) === family.authorizationId)
|
|
544
621
|
tokenBatchClear(key2, projected);
|
|
545
622
|
} catch {
|
|
546
623
|
}
|
|
547
624
|
}
|
|
548
|
-
var import_viem2, memory, settled, hashPattern, uint, same, tokenBatchKey, tokenBatchAuthorizationId, tokenBatchStoredBody, approvalAbi;
|
|
625
|
+
var import_viem2, memory, unfinished, settled, hashPattern, uint, same, tokenBatchKey, tokenBatchAuthorizationId, tokenBatchStoredBody, approvalAbi;
|
|
549
626
|
var init_token_batch_family = __esm({
|
|
550
627
|
"src/lib/token-batch-family.ts"() {
|
|
551
628
|
"use strict";
|
|
@@ -553,6 +630,7 @@ var init_token_batch_family = __esm({
|
|
|
553
630
|
init_errors();
|
|
554
631
|
init_permit2_batch();
|
|
555
632
|
memory = /* @__PURE__ */ new Map();
|
|
633
|
+
unfinished = /* @__PURE__ */ new Map();
|
|
556
634
|
settled = /* @__PURE__ */ new Map();
|
|
557
635
|
hashPattern = /^0x[\da-f]{64}$/i;
|
|
558
636
|
uint = (value) => typeof value === "string" && /^\d{1,78}$/.test(value) && BigInt(value) < 1n << 256n;
|
|
@@ -579,12 +657,20 @@ var index_exports = {};
|
|
|
579
657
|
__export(index_exports, {
|
|
580
658
|
AgentChainIncompatibleError: () => AgentChainIncompatibleError,
|
|
581
659
|
AgentNotFoundError: () => AgentNotFoundError,
|
|
660
|
+
FUNDING_DEPOSIT_RECOVERY_REPORT: () => FUNDING_DEPOSIT_RECOVERY_REPORT,
|
|
661
|
+
FUSION_SECRET_SERVER_PUBLISH: () => FUSION_SECRET_SERVER_PUBLISH,
|
|
582
662
|
InvalidHistoryCursorError: () => InvalidHistoryCursorError,
|
|
583
663
|
NotConnectedError: () => NotConnectedError,
|
|
584
664
|
OwneyError: () => OwneyError,
|
|
585
665
|
OwneySDK: () => OwneySDK,
|
|
586
666
|
SDK_VERSION: () => SDK_VERSION,
|
|
667
|
+
SPONSORED_DEPOSIT_SERVER_BROADCAST: () => SPONSORED_DEPOSIT_SERVER_BROADCAST,
|
|
668
|
+
YIELDSEEKER_WITHDRAW_SERVER_SUBMIT: () => YIELDSEEKER_WITHDRAW_SERVER_SUBMIT,
|
|
587
669
|
YieldseekerAgent: () => YieldseekerAgent,
|
|
670
|
+
ZYFAI_REGISTRATION_NOT_SENT_CODES: () => ZYFAI_REGISTRATION_NOT_SENT_CODES,
|
|
671
|
+
ZYFAI_SERVER_TASKS: () => ZYFAI_SERVER_TASKS,
|
|
672
|
+
ZYFAI_WITHDRAWAL_NOT_SENT_CODES: () => ZYFAI_WITHDRAWAL_NOT_SENT_CODES,
|
|
673
|
+
ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT: () => ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT,
|
|
588
674
|
clearWithdrawSwap: () => clearWithdrawSwap,
|
|
589
675
|
createOwneySIWX: () => createOwneySIWX,
|
|
590
676
|
listPendingDeposits: () => listPendingDeposits,
|
|
@@ -940,25 +1026,41 @@ async function observeTokenBatchFamily(input) {
|
|
|
940
1026
|
|
|
941
1027
|
// src/lib/swap/swap.observation.ts
|
|
942
1028
|
init_errors();
|
|
943
|
-
var
|
|
944
|
-
var
|
|
945
|
-
|
|
1029
|
+
var claims = /* @__PURE__ */ new Map();
|
|
1030
|
+
var watches = /* @__PURE__ */ new Map();
|
|
1031
|
+
var isOrderObserved = (key2) => claims.has(key2) || watches.has(key2);
|
|
1032
|
+
function claimOrder(key2) {
|
|
1033
|
+
claims.set(key2, (claims.get(key2) ?? 0) + 1);
|
|
1034
|
+
let released = false;
|
|
1035
|
+
return () => {
|
|
1036
|
+
if (released) return;
|
|
1037
|
+
released = true;
|
|
1038
|
+
const left = (claims.get(key2) ?? 1) - 1;
|
|
1039
|
+
if (left > 0) claims.set(key2, left);
|
|
1040
|
+
else claims.delete(key2);
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
async function observeOrder(key2, run, options = {}) {
|
|
946
1044
|
const busy = () => new OwneyError(
|
|
947
1045
|
"SWAP_REQUEST_FAILED",
|
|
948
1046
|
"This order is already being observed.",
|
|
949
1047
|
{ orderHash: key2, watchActive: true, settlementUnconfirmed: true }
|
|
950
1048
|
);
|
|
951
|
-
if (
|
|
952
|
-
|
|
1049
|
+
if (options.wait) {
|
|
1050
|
+
for (let other = watches.get(key2); other; other = watches.get(key2))
|
|
1051
|
+
await other.catch(() => void 0);
|
|
1052
|
+
} else if (watches.has(key2)) throw busy();
|
|
1053
|
+
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
1054
|
+
const name = `owney.swap.observe:${key2}`;
|
|
1055
|
+
const watch = !locks ? run() : options.wait ? locks.request(name, () => run()) : locks.request(name, { ifAvailable: true }, (lock) => {
|
|
1056
|
+
if (!lock) throw busy();
|
|
1057
|
+
return run();
|
|
1058
|
+
});
|
|
1059
|
+
watches.set(key2, watch);
|
|
953
1060
|
try {
|
|
954
|
-
|
|
955
|
-
return await navigator.locks.request(`owney.swap.observe:${key2}`, { ifAvailable: true }, (lock) => {
|
|
956
|
-
if (!lock) throw busy();
|
|
957
|
-
return run();
|
|
958
|
-
});
|
|
959
|
-
return await run();
|
|
1061
|
+
return await watch;
|
|
960
1062
|
} finally {
|
|
961
|
-
|
|
1063
|
+
if (watches.get(key2) === watch) watches.delete(key2);
|
|
962
1064
|
}
|
|
963
1065
|
}
|
|
964
1066
|
|
|
@@ -1117,7 +1219,7 @@ function parseLeg(item) {
|
|
|
1117
1219
|
const r = item;
|
|
1118
1220
|
if (typeof r.legKey !== "string" || !r.legKey) return null;
|
|
1119
1221
|
if (typeof r.status !== "string" || !r.status) return null;
|
|
1120
|
-
|
|
1222
|
+
const leg = {
|
|
1121
1223
|
legKey: r.legKey,
|
|
1122
1224
|
agentType: strOrNull(r.agentType),
|
|
1123
1225
|
status: r.status,
|
|
@@ -1127,8 +1229,11 @@ function parseLeg(item) {
|
|
|
1127
1229
|
confirmedAt: strOrNull(r.confirmedAt),
|
|
1128
1230
|
agentWallet: strOrNull(r.agentWallet),
|
|
1129
1231
|
providerIndexedAt: strOrNull(r.providerIndexedAt),
|
|
1130
|
-
providerBookedAt: strOrNull(r.providerBookedAt)
|
|
1232
|
+
providerBookedAt: strOrNull(r.providerBookedAt),
|
|
1233
|
+
providerRef: strOrNull(r.providerRef)
|
|
1131
1234
|
};
|
|
1235
|
+
if (r.closed === true) leg.closed = true;
|
|
1236
|
+
return leg;
|
|
1132
1237
|
}
|
|
1133
1238
|
function parseRow(item) {
|
|
1134
1239
|
if (!item || typeof item !== "object") return null;
|
|
@@ -1548,7 +1653,7 @@ var SnapshotHistory = class {
|
|
|
1548
1653
|
};
|
|
1549
1654
|
|
|
1550
1655
|
// src/lib/funding-ledger.ts
|
|
1551
|
-
var SDK_VERSION = "0.7.32-beta.
|
|
1656
|
+
var SDK_VERSION = "0.7.32-beta.4";
|
|
1552
1657
|
var SCOPE2 = "funding-ledger";
|
|
1553
1658
|
var REQUEST_TIMEOUT_MS2 = 5e3;
|
|
1554
1659
|
var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
|
|
@@ -1798,13 +1903,213 @@ function buildReporter(config) {
|
|
|
1798
1903
|
} catch (err) {
|
|
1799
1904
|
debugLog(SCOPE2, `event failed: ${describeError2(err)}`);
|
|
1800
1905
|
}
|
|
1906
|
+
},
|
|
1907
|
+
settle(timeoutMs) {
|
|
1908
|
+
return new Promise((resolve) => {
|
|
1909
|
+
let deadline;
|
|
1910
|
+
const done = () => {
|
|
1911
|
+
if (deadline !== void 0) clearTimeout(deadline);
|
|
1912
|
+
resolve();
|
|
1913
|
+
};
|
|
1914
|
+
try {
|
|
1915
|
+
flush();
|
|
1916
|
+
deadline = setTimeout(done, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 0);
|
|
1917
|
+
unrefTimer(deadline);
|
|
1918
|
+
chain2.then(done, done);
|
|
1919
|
+
} catch (err) {
|
|
1920
|
+
debugLog(SCOPE2, `settle failed: ${describeError2(err)}`);
|
|
1921
|
+
done();
|
|
1922
|
+
}
|
|
1923
|
+
});
|
|
1801
1924
|
}
|
|
1802
1925
|
};
|
|
1803
1926
|
}
|
|
1804
1927
|
}
|
|
1805
1928
|
|
|
1929
|
+
// src/lib/zyfai-server-tasks.ts
|
|
1930
|
+
var SCOPE3 = "zyfai-server-tasks";
|
|
1931
|
+
var ZYFAI_SERVER_TASKS = 1;
|
|
1932
|
+
var ZYFAI_SERVER_TASKS_OFF_CODE = "ZYFAI_SERVER_TASKS_OFF";
|
|
1933
|
+
function zyfaiServerTasksEnabled(option) {
|
|
1934
|
+
if (typeof option !== "function") return option === true;
|
|
1935
|
+
try {
|
|
1936
|
+
return option() === true;
|
|
1937
|
+
} catch {
|
|
1938
|
+
return false;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
var ZYFAI_HANDOFF_FEATURE_OFF_CODES = /* @__PURE__ */ new Set([
|
|
1942
|
+
"FUNDING_TASK_KIND_DISABLED",
|
|
1943
|
+
"ZYFAI_NOT_CONFIGURED",
|
|
1944
|
+
"PROBE_ONLY",
|
|
1945
|
+
"HTTP_404",
|
|
1946
|
+
"HTTP_401",
|
|
1947
|
+
"HTTP_403",
|
|
1948
|
+
ZYFAI_SERVER_TASKS_OFF_CODE
|
|
1949
|
+
]);
|
|
1950
|
+
var ZYFAI_HANDOFF_NO_TASK_CODES = /* @__PURE__ */ new Set([
|
|
1951
|
+
...ZYFAI_HANDOFF_FEATURE_OFF_CODES,
|
|
1952
|
+
"ZYFAI_SESSION_MISMATCH",
|
|
1953
|
+
"NEEDS_USER_SESSION"
|
|
1954
|
+
]);
|
|
1955
|
+
var UNCHECKED_NO_TASK_CODES = /* @__PURE__ */ new Set([
|
|
1956
|
+
"HTTP_401",
|
|
1957
|
+
"HTTP_403",
|
|
1958
|
+
"HTTP_404",
|
|
1959
|
+
ZYFAI_SERVER_TASKS_OFF_CODE
|
|
1960
|
+
]);
|
|
1961
|
+
function provesNoServerTask(code, afterLostAnswer) {
|
|
1962
|
+
return ZYFAI_HANDOFF_NO_TASK_CODES.has(code) && !(afterLostAnswer && UNCHECKED_NO_TASK_CODES.has(code));
|
|
1963
|
+
}
|
|
1964
|
+
var ZYFAI_WITHDRAWAL_NOT_SENT_CODES = /* @__PURE__ */ new Set([
|
|
1965
|
+
"PREREQUISITE_FAILED",
|
|
1966
|
+
"PREREQUISITE_TIMEOUT",
|
|
1967
|
+
"SUPERSEDED",
|
|
1968
|
+
"ZYFAI_SESSION_MISMATCH",
|
|
1969
|
+
"ZYFAI_REDEMPTION_IN_FLIGHT",
|
|
1970
|
+
"NEEDS_USER_SESSION",
|
|
1971
|
+
"NEEDS_USER_SESSION_KEY",
|
|
1972
|
+
"ZYFAI_FORBIDDEN",
|
|
1973
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
1974
|
+
"WITHDRAWAL_IN_PROGRESS",
|
|
1975
|
+
// The task's own agent:zyfai leg was already failed or confirmed (P4-8).
|
|
1976
|
+
"LEG_CLOSED",
|
|
1977
|
+
"MAX_ATTEMPTS",
|
|
1978
|
+
"SECRET_UNREADABLE"
|
|
1979
|
+
]);
|
|
1980
|
+
var ZYFAI_REGISTRATION_NOT_SENT_CODES = /* @__PURE__ */ new Set([
|
|
1981
|
+
"TRANSFER_NOT_SEEN",
|
|
1982
|
+
"DEPOSIT_NOT_SENT",
|
|
1983
|
+
"DEPOSIT_REVERTED",
|
|
1984
|
+
"TRANSFER_MISMATCH",
|
|
1985
|
+
"NEEDS_USER_SESSION",
|
|
1986
|
+
"ZYFAI_REGISTRATION_REJECTED",
|
|
1987
|
+
"MAX_ATTEMPTS",
|
|
1988
|
+
"SECRET_UNREADABLE"
|
|
1989
|
+
]);
|
|
1990
|
+
var ZYFAI_TASK_LIFETIME_MS = {
|
|
1991
|
+
zyfai_withdrawal: 72e5,
|
|
1992
|
+
zyfai_deposit_registration: 36e5
|
|
1993
|
+
};
|
|
1994
|
+
var SESSION_MARGIN_MS = 3e5;
|
|
1995
|
+
var SESSION_SLACK_MS = 6e4;
|
|
1996
|
+
function sessionExp(session) {
|
|
1997
|
+
try {
|
|
1998
|
+
const parts = session.split(".");
|
|
1999
|
+
if (parts.length !== 3) return void 0;
|
|
2000
|
+
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
2001
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
2002
|
+
const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
|
|
2003
|
+
const claims2 = JSON.parse(decoded);
|
|
2004
|
+
const exp = isRecord(claims2) ? claims2.exp : void 0;
|
|
2005
|
+
return typeof exp === "number" && Number.isFinite(exp) ? exp : void 0;
|
|
2006
|
+
} catch {
|
|
2007
|
+
return void 0;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
function zyfaiSessionOutlivesTask(session, kind, nowMs = Date.now()) {
|
|
2011
|
+
const lifetimeMs = Object.prototype.hasOwnProperty.call(ZYFAI_TASK_LIFETIME_MS, kind) ? ZYFAI_TASK_LIFETIME_MS[kind] : void 0;
|
|
2012
|
+
if (typeof session !== "string" || lifetimeMs === void 0) return false;
|
|
2013
|
+
const exp = sessionExp(session);
|
|
2014
|
+
return exp !== void 0 && exp * 1e3 > nowMs + lifetimeMs + SESSION_MARGIN_MS + SESSION_SLACK_MS;
|
|
2015
|
+
}
|
|
2016
|
+
var DEFAULT_TIMEOUT_MS = 25e3;
|
|
2017
|
+
var DECLINED_STATUSES = /* @__PURE__ */ new Set([400, 401, 403, 404, 409, 429]);
|
|
2018
|
+
var ID = /^.{1,128}$/s;
|
|
2019
|
+
var TX_HASH = /^0x[0-9a-fA-F]{64}$/;
|
|
2020
|
+
var unknown = () => ({ type: "unknown" });
|
|
2021
|
+
var isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
2022
|
+
function requestBody(body, session) {
|
|
2023
|
+
const out = {
|
|
2024
|
+
kind: body.kind,
|
|
2025
|
+
operationId: body.operationId,
|
|
2026
|
+
owner: body.owner,
|
|
2027
|
+
chainId: body.chainId,
|
|
2028
|
+
asset: body.asset,
|
|
2029
|
+
amount: body.amount,
|
|
2030
|
+
agentWallet: body.agentWallet,
|
|
2031
|
+
zyfaiSession: session
|
|
2032
|
+
};
|
|
2033
|
+
if (body.after !== void 0) out.after = body.after;
|
|
2034
|
+
if (body.runNow !== void 0) out.runNow = body.runNow;
|
|
2035
|
+
return out;
|
|
2036
|
+
}
|
|
2037
|
+
function taskResult(value) {
|
|
2038
|
+
const result = {};
|
|
2039
|
+
if (!isRecord(value)) return result;
|
|
2040
|
+
if (typeof value.providerWithdrawalId === "string" && ID.test(value.providerWithdrawalId))
|
|
2041
|
+
result.providerWithdrawalId = value.providerWithdrawalId;
|
|
2042
|
+
if (typeof value.providerDepositId === "string" && ID.test(value.providerDepositId))
|
|
2043
|
+
result.providerDepositId = value.providerDepositId;
|
|
2044
|
+
if (typeof value.txHash === "string" && TX_HASH.test(value.txHash)) result.txHash = value.txHash;
|
|
2045
|
+
if (value.asyncPending === true) result.asyncPending = true;
|
|
2046
|
+
if (value.handedOff === "reconciler") result.handedOff = "reconciler";
|
|
2047
|
+
if (value.lifecycle === "failed") result.lifecycle = "failed";
|
|
2048
|
+
return result;
|
|
2049
|
+
}
|
|
2050
|
+
function answerOf(payload) {
|
|
2051
|
+
if (!isRecord(payload) || payload.success !== true || !isRecord(payload.data)) return unknown();
|
|
2052
|
+
const data = payload.data;
|
|
2053
|
+
if (data.accepted === false)
|
|
2054
|
+
return typeof data.code === "string" && data.code ? { type: "declined", code: data.code } : unknown();
|
|
2055
|
+
if (data.accepted !== true || typeof data.taskId !== "string" || !data.taskId || typeof data.state !== "string" || !data.state) return unknown();
|
|
2056
|
+
return {
|
|
2057
|
+
type: "accepted",
|
|
2058
|
+
taskId: data.taskId,
|
|
2059
|
+
state: data.state,
|
|
2060
|
+
...typeof data.code === "string" && data.code ? { code: data.code } : {},
|
|
2061
|
+
result: taskResult(data.result)
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
async function handOffZyfaiTask(cfg, body, session) {
|
|
2065
|
+
if (cfg?.zyfaiServerTasks !== true) return { type: "declined", code: ZYFAI_SERVER_TASKS_OFF_CODE };
|
|
2066
|
+
let timer;
|
|
2067
|
+
try {
|
|
2068
|
+
const controller = new AbortController();
|
|
2069
|
+
const timeoutMs = typeof cfg.timeoutMs === "number" && Number.isFinite(cfg.timeoutMs) && cfg.timeoutMs > 0 ? cfg.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
2070
|
+
const url = `${getRoutingApiBaseUrl(cfg.baseUrl).replace(/\/$/, "")}/api/v1/funding/zyfai-tasks`;
|
|
2071
|
+
const timedOut = new Promise((resolve) => {
|
|
2072
|
+
timer = setTimeout(() => {
|
|
2073
|
+
controller.abort();
|
|
2074
|
+
resolve("timeout");
|
|
2075
|
+
}, timeoutMs);
|
|
2076
|
+
timer.unref?.();
|
|
2077
|
+
});
|
|
2078
|
+
const exchange = (async () => {
|
|
2079
|
+
const res = await fetch(url, {
|
|
2080
|
+
method: "POST",
|
|
2081
|
+
headers: { "content-type": "application/json", "x-owney-api-key": cfg.apiKey },
|
|
2082
|
+
body: JSON.stringify(requestBody(body, session)),
|
|
2083
|
+
signal: controller.signal,
|
|
2084
|
+
// The body carries the session: never follow a redirect to another host.
|
|
2085
|
+
redirect: "error"
|
|
2086
|
+
});
|
|
2087
|
+
if (DECLINED_STATUSES.has(res.status)) return { type: "declined", code: `HTTP_${res.status}` };
|
|
2088
|
+
if (res.status !== 200) {
|
|
2089
|
+
debugLog(SCOPE3, `handoff answered ${res.status}`);
|
|
2090
|
+
return unknown();
|
|
2091
|
+
}
|
|
2092
|
+
return answerOf(await res.json());
|
|
2093
|
+
})();
|
|
2094
|
+
exchange.catch(() => {
|
|
2095
|
+
});
|
|
2096
|
+
const answer = await Promise.race([exchange, timedOut]);
|
|
2097
|
+
if (answer === "timeout") {
|
|
2098
|
+
debugLog(SCOPE3, "handoff timed out");
|
|
2099
|
+
return unknown();
|
|
2100
|
+
}
|
|
2101
|
+
debugLog(SCOPE3, `handoff ${answer.type}`);
|
|
2102
|
+
return answer;
|
|
2103
|
+
} catch {
|
|
2104
|
+
debugLog(SCOPE3, "handoff failed");
|
|
2105
|
+
return unknown();
|
|
2106
|
+
} finally {
|
|
2107
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
|
|
1806
2111
|
// src/lib/withdrawal-reservation.ts
|
|
1807
|
-
var
|
|
2112
|
+
var SCOPE4 = "withdrawal-reservation";
|
|
1808
2113
|
var CLAIM_TIMEOUT_MS = 4e3;
|
|
1809
2114
|
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
1810
2115
|
var HOLDER_TIMEOUT_MS = 5e3;
|
|
@@ -1902,7 +2207,7 @@ function compact2(obj) {
|
|
|
1902
2207
|
async function claimWithdrawalReservation(cfg, input) {
|
|
1903
2208
|
const result = await claimOnce(cfg, input);
|
|
1904
2209
|
if (result.status === "unavailable" && result.httpStatus === 403 && input.supersedes) {
|
|
1905
|
-
debugLog(
|
|
2210
|
+
debugLog(SCOPE4, "claim 403 with supersedes: retrying without it");
|
|
1906
2211
|
const { supersedes: _dropped, ...rest } = input;
|
|
1907
2212
|
const retried = await claimOnce(cfg, rest);
|
|
1908
2213
|
return retried.status === "claimed" ? { ...retried, supersedesDropped: true } : retried;
|
|
@@ -1929,7 +2234,7 @@ async function claimOnce(cfg, input) {
|
|
|
1929
2234
|
res = await post(cfg, endpoint(cfg), body, CLAIM_TIMEOUT_MS);
|
|
1930
2235
|
} catch (err) {
|
|
1931
2236
|
const reason = err instanceof RequestTimeout ? "timeout" : "network";
|
|
1932
|
-
debugLog(
|
|
2237
|
+
debugLog(SCOPE4, `claim ${reason}: ${describeError3(err)}`);
|
|
1933
2238
|
return { status: "unavailable", reason };
|
|
1934
2239
|
}
|
|
1935
2240
|
if (!res) return { status: "unavailable", reason: "network" };
|
|
@@ -1945,7 +2250,7 @@ async function claimOnce(cfg, input) {
|
|
|
1945
2250
|
}
|
|
1946
2251
|
}
|
|
1947
2252
|
if (!res.ok) {
|
|
1948
|
-
debugLog(
|
|
2253
|
+
debugLog(SCOPE4, `claim ${res.status}`);
|
|
1949
2254
|
return { status: "unavailable", reason: "http", httpStatus: res.status };
|
|
1950
2255
|
}
|
|
1951
2256
|
const data = unwrap2(res.body);
|
|
@@ -1980,12 +2285,12 @@ async function holderRequest(cfg, operationId, action, body, retryDelays = []) {
|
|
|
1980
2285
|
if (res.ok) return "ok";
|
|
1981
2286
|
if (res.status === 404) return "gone";
|
|
1982
2287
|
if (res.status < 500 && res.status !== 429) {
|
|
1983
|
-
debugLog(
|
|
2288
|
+
debugLog(SCOPE4, `${action} rejected with ${res.status}`);
|
|
1984
2289
|
return "rejected";
|
|
1985
2290
|
}
|
|
1986
|
-
debugLog(
|
|
2291
|
+
debugLog(SCOPE4, `${action} ${res.status}`);
|
|
1987
2292
|
} catch (err) {
|
|
1988
|
-
debugLog(
|
|
2293
|
+
debugLog(SCOPE4, `${action} failed: ${describeError3(err)}`);
|
|
1989
2294
|
}
|
|
1990
2295
|
if (attempt < retryDelays.length) {
|
|
1991
2296
|
await new Promise((resolve) => unrefTimer2(setTimeout(resolve, retryDelays[attempt])));
|
|
@@ -2158,6 +2463,8 @@ function ledgerLegFromEvidence(e, kind) {
|
|
|
2158
2463
|
if (e.callsId && e.callsId.length <= 200) leg.callsId = e.callsId;
|
|
2159
2464
|
if (isUint(e.confirmedAmount)) leg.confirmedAmountRaw = e.confirmedAmount;
|
|
2160
2465
|
if (e.blockNumber && /^\d{1,19}$/.test(e.blockNumber)) leg.blockNumber = e.blockNumber;
|
|
2466
|
+
const providerRef = e.withdrawalRequest?.providerWithdrawalId || e.providerDepositId;
|
|
2467
|
+
if (typeof providerRef === "string" && providerRef.length >= 1 && providerRef.length <= 128) leg.providerRef = providerRef;
|
|
2161
2468
|
const failure = e.withdrawalRequest?.failure;
|
|
2162
2469
|
if (failure) leg.errorCode = failure.toUpperCase().replace(/-/g, "_");
|
|
2163
2470
|
return leg;
|
|
@@ -2185,6 +2492,12 @@ function withdrawalLedgerRail(e) {
|
|
|
2185
2492
|
return e.userOpHash ? "yieldseeker_userop" : e.callsId ? "yieldseeker_wallet_calls" : e.txHash ? "yieldseeker_paid" : void 0;
|
|
2186
2493
|
return void 0;
|
|
2187
2494
|
}
|
|
2495
|
+
function depositLedgerRail(e) {
|
|
2496
|
+
if (e.callsId) return "wallet_calls";
|
|
2497
|
+
if (e.depositAuthorization || e.serverBroadcast) return "permit2_batch";
|
|
2498
|
+
return void 0;
|
|
2499
|
+
}
|
|
2500
|
+
var FUNDING_DEPOSIT_RECOVERY_REPORT = 1;
|
|
2188
2501
|
var SWAP_LEDGER_RAILS = {
|
|
2189
2502
|
classic: "swap_classic",
|
|
2190
2503
|
fusion: "swap_fusion",
|
|
@@ -2252,6 +2565,12 @@ var TRANSFER_EVENT = (0, import_viem4.getAbiItem)({
|
|
|
2252
2565
|
abi: (0, import_viem4.parseAbi)(["event Transfer(address indexed from, address indexed to, uint256 value)"]),
|
|
2253
2566
|
name: "Transfer"
|
|
2254
2567
|
});
|
|
2568
|
+
var acceptedChunk = /* @__PURE__ */ new Map();
|
|
2569
|
+
var acceptedChunkByClient = /* @__PURE__ */ new WeakMap();
|
|
2570
|
+
function providerKey(client) {
|
|
2571
|
+
const { chain: chain2, transport } = client;
|
|
2572
|
+
return typeof chain2?.id === "number" ? `${chain2.id}:${typeof transport?.url === "string" ? transport.url : ""}` : void 0;
|
|
2573
|
+
}
|
|
2255
2574
|
function parseBlock(value) {
|
|
2256
2575
|
if (value === void 0 || value === null) return void 0;
|
|
2257
2576
|
return typeof value === "string" && /^\d{1,19}$/.test(value) ? BigInt(value) : null;
|
|
@@ -2276,11 +2595,12 @@ async function scanWithdrawalPayouts(client, input) {
|
|
|
2276
2595
|
const candidates = [];
|
|
2277
2596
|
if (start > end) {
|
|
2278
2597
|
const kept = input.cursor ?? (start > 0n ? start - 1n : 0n);
|
|
2279
|
-
return { candidates, scannedTo: kept.toString() };
|
|
2598
|
+
return { candidates, scannedTo: kept.toString(), caughtUp: true };
|
|
2280
2599
|
}
|
|
2281
2600
|
const tokenAddress = input.token.toLowerCase();
|
|
2282
2601
|
const owner = input.owner.toLowerCase();
|
|
2283
|
-
|
|
2602
|
+
const key2 = providerKey(client);
|
|
2603
|
+
let size = (key2 !== void 0 ? acceptedChunk.get(key2) : acceptedChunkByClient.get(client)) ?? PAYOUT_SCAN_CHUNK_BLOCKS;
|
|
2284
2604
|
let from = start;
|
|
2285
2605
|
let scannedTo = start - 1n;
|
|
2286
2606
|
for (let requests = 0; from <= end && requests < MAX_CHUNKS_PER_SCAN; ) {
|
|
@@ -2295,12 +2615,21 @@ async function scanWithdrawalPayouts(client, input) {
|
|
|
2295
2615
|
toBlock: to
|
|
2296
2616
|
});
|
|
2297
2617
|
} catch (error) {
|
|
2298
|
-
if (
|
|
2299
|
-
|
|
2300
|
-
|
|
2618
|
+
if (!isRateLimited(error) && isRangeError(error)) {
|
|
2619
|
+
const requested = to - from + 1n;
|
|
2620
|
+
const allowed = allowedRange(error);
|
|
2621
|
+
const next = allowed !== void 0 && allowed < requested ? allowed : requested / 2n;
|
|
2622
|
+
if (next >= 1n && next < requested) {
|
|
2623
|
+
size = next;
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2301
2626
|
}
|
|
2302
2627
|
throw error;
|
|
2303
2628
|
}
|
|
2629
|
+
if (size < PAYOUT_SCAN_CHUNK_BLOCKS) {
|
|
2630
|
+
if (key2 !== void 0) acceptedChunk.set(key2, size);
|
|
2631
|
+
else acceptedChunkByClient.set(client, size);
|
|
2632
|
+
}
|
|
2304
2633
|
check();
|
|
2305
2634
|
requests++;
|
|
2306
2635
|
for (const log of logs) {
|
|
@@ -2318,7 +2647,8 @@ async function scanWithdrawalPayouts(client, input) {
|
|
|
2318
2647
|
scannedTo = to;
|
|
2319
2648
|
from = to + 1n;
|
|
2320
2649
|
}
|
|
2321
|
-
|
|
2650
|
+
const stalled = input.cursor !== void 0 && scannedTo <= input.cursor;
|
|
2651
|
+
return { candidates, scannedTo: (scannedTo < 0n ? 0n : scannedTo).toString(), caughtUp: from > end || stalled };
|
|
2322
2652
|
}
|
|
2323
2653
|
async function estimateBlockAt(client, latest, targetMs) {
|
|
2324
2654
|
const head = await client.getBlock({ blockNumber: latest });
|
|
@@ -2343,6 +2673,7 @@ function causeChain(error) {
|
|
|
2343
2673
|
chain2.push({
|
|
2344
2674
|
code: value.code,
|
|
2345
2675
|
status: value.status,
|
|
2676
|
+
data: value.data,
|
|
2346
2677
|
text: [value.message, value.details, value.shortMessage].filter((part) => typeof part === "string").join(" ")
|
|
2347
2678
|
});
|
|
2348
2679
|
current = value.cause;
|
|
@@ -2352,6 +2683,14 @@ function causeChain(error) {
|
|
|
2352
2683
|
function isRateLimited(error) {
|
|
2353
2684
|
return causeChain(error).some((entry) => entry.status === 429 || entry.code === 429 || /rate|too many requests/i.test(entry.text));
|
|
2354
2685
|
}
|
|
2686
|
+
function allowedRange(error) {
|
|
2687
|
+
for (const entry of causeChain(error)) {
|
|
2688
|
+
const hinted = entry.data && typeof entry.data === "object" ? entry.data.maxBlockRange : void 0;
|
|
2689
|
+
const value = typeof hinted === "number" ? hinted : Number(/"maxBlockRange"\s*:\s*(\d+)/.exec(entry.text)?.[1]);
|
|
2690
|
+
if (Number.isSafeInteger(value) && value >= 1) return BigInt(value);
|
|
2691
|
+
}
|
|
2692
|
+
return void 0;
|
|
2693
|
+
}
|
|
2355
2694
|
function isRangeError(error) {
|
|
2356
2695
|
return causeChain(error).some((entry) => entry.code === -32005 || /block range|range (is )?too (large|wide|big)|range limit|exceed(s|ed)? .*(range|limit|results)|too many (results|logs|blocks)|response size|query returned more than|max(imum)? .*blocks/i.test(entry.text));
|
|
2357
2696
|
}
|
|
@@ -2461,6 +2800,7 @@ async function getSponsorRelayerAddress(input) {
|
|
|
2461
2800
|
return parsed.data.relayer;
|
|
2462
2801
|
}
|
|
2463
2802
|
async function postSponsorBatchTransfer(input) {
|
|
2803
|
+
const flaggedPrepare = input.body.serverBroadcast === true && !input.body.serializedTransaction;
|
|
2464
2804
|
let res;
|
|
2465
2805
|
try {
|
|
2466
2806
|
res = await fetch(
|
|
@@ -2479,7 +2819,7 @@ async function postSponsorBatchTransfer(input) {
|
|
|
2479
2819
|
throw new OwneyError(
|
|
2480
2820
|
"SPONSOR_REQUEST_FAILED",
|
|
2481
2821
|
"Deposit status is unknown. Retry the same amount to check it.",
|
|
2482
|
-
{ safeToFallback: false }
|
|
2822
|
+
{ safeToFallback: false, ...flaggedPrepare ? { outcomeUnknown: true } : {} }
|
|
2483
2823
|
);
|
|
2484
2824
|
}
|
|
2485
2825
|
const parsed = await res.json().catch(() => null);
|
|
@@ -2490,7 +2830,13 @@ async function postSponsorBatchTransfer(input) {
|
|
|
2490
2830
|
{
|
|
2491
2831
|
statusCode: res.status,
|
|
2492
2832
|
safeToFallback: false,
|
|
2493
|
-
notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
|
|
2833
|
+
notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true,
|
|
2834
|
+
// Another authorization of this operation is being sent by the
|
|
2835
|
+
// server (409, nothing signed).
|
|
2836
|
+
...parsed?.code === "DEPOSIT_ALREADY_SENDING" ? { code: "DEPOSIT_ALREADY_SENDING" } : {},
|
|
2837
|
+
// No definitive answer (5xx, an unreadable or malformed body): the
|
|
2838
|
+
// server may have accepted it. A 4xx with a body is a refusal.
|
|
2839
|
+
...flaggedPrepare && (res.status >= 500 || parsed === null || res.ok) ? { outcomeUnknown: true } : {}
|
|
2494
2840
|
}
|
|
2495
2841
|
);
|
|
2496
2842
|
}
|
|
@@ -2507,19 +2853,19 @@ async function postSponsorBatchRecovery(input) {
|
|
|
2507
2853
|
input.signal?.throwIfAborted();
|
|
2508
2854
|
if (!response.ok || payload?.success !== true) throw failure();
|
|
2509
2855
|
const value = payload.data;
|
|
2510
|
-
const { isAddress:
|
|
2856
|
+
const { isAddress: isAddress7, keccak256: keccak2565, parseTransaction: parseTransaction4, recoverTransactionAddress: recoverTransactionAddress2 } = await import("viem");
|
|
2511
2857
|
const { validateTokenBatchAttempt: validateTokenBatchAttempt2 } = await Promise.resolve().then(() => (init_token_batch_family(), token_batch_family_exports));
|
|
2512
2858
|
const phase = input.body.phase;
|
|
2513
2859
|
const original = phase === "approval" ? input.body.serializedPermitTransaction : input.body.serializedTransaction;
|
|
2514
2860
|
if (!original) throw failure();
|
|
2515
2861
|
const hash2 = keccak2565(original), relayer = await recoverTransactionAddress2({ serializedTransaction: original });
|
|
2516
|
-
const
|
|
2517
|
-
if (!value || value.protocol !== 1 || value.phase !== phase || !
|
|
2862
|
+
const validHash2 = (v) => typeof v === "string" && /^0x[\da-f]{64}$/i.test(v);
|
|
2863
|
+
if (!value || value.protocol !== 1 || value.phase !== phase || !validHash2(value.txHash) || !isAddress7(value.relayer) || value.relayer.toLowerCase() !== relayer.toLowerCase()) throw failure();
|
|
2518
2864
|
if (value.status !== "prepared" && value.txHash.toLowerCase() !== hash2.toLowerCase()) throw failure();
|
|
2519
2865
|
if (value.status === "unresolved") {
|
|
2520
2866
|
if (!["nonce-not-consumed", "authorization-used", "authorization-expired", "state-unavailable", "preparation-rejected", "relayer-changed"].includes(value.reason)) throw failure();
|
|
2521
2867
|
} else {
|
|
2522
|
-
if (!("blockNumber" in value) || typeof value.blockNumber !== "string" || !/^\d{1,78}$/.test(value.blockNumber) || BigInt(value.blockNumber) >= 1n << 256n || !
|
|
2868
|
+
if (!("blockNumber" in value) || typeof value.blockNumber !== "string" || !/^\d{1,78}$/.test(value.blockNumber) || BigInt(value.blockNumber) >= 1n << 256n || !validHash2(value.blockHash)) throw failure();
|
|
2523
2869
|
if (value.status === "prepared") {
|
|
2524
2870
|
if (value.predecessorHash?.toLowerCase() !== hash2.toLowerCase()) throw failure();
|
|
2525
2871
|
const tx = parseTransaction4(value.serializedTransaction), old = parseTransaction4(original);
|
|
@@ -2821,10 +3167,19 @@ var AgentReads = class {
|
|
|
2821
3167
|
}
|
|
2822
3168
|
};
|
|
2823
3169
|
|
|
3170
|
+
// src/agents/zyfai/zyfai.session-errors.ts
|
|
3171
|
+
var SESSION_REJECTED = /(?:^|: )Unauthorized: (?:(?:Invalid or expired user session|Missing user session)\b.*|Invalid API key)$/;
|
|
3172
|
+
function isZyfaiSessionRejected(message) {
|
|
3173
|
+
return SESSION_REJECTED.test(message);
|
|
3174
|
+
}
|
|
3175
|
+
|
|
2824
3176
|
// src/agents/zyfai/zyfai.withdraw-submission.ts
|
|
2825
3177
|
var BELOW_MINIMUM = /^Idle balance (\d+(?:\.\d+)?) (USDC|WETH|ETH|EURC) is below the minimum withdrawal of (\d+(?:\.\d+)?) \2\.?$/;
|
|
2826
3178
|
var listenersByClient = /* @__PURE__ */ new WeakMap();
|
|
2827
|
-
|
|
3179
|
+
var ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT = 1;
|
|
3180
|
+
var MAX_WITHDRAWAL_ID_LENGTH = 128;
|
|
3181
|
+
var WITHDRAWAL_PATHS = { get: "/users/withdraw/v2", post: "/users/partial-withdraw/v2" };
|
|
3182
|
+
function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent, beforeDispatch) {
|
|
2828
3183
|
const client = sdk.httpClient;
|
|
2829
3184
|
if (!client || typeof client.get !== "function" || typeof client.post !== "function")
|
|
2830
3185
|
return {
|
|
@@ -2844,7 +3199,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
2844
3199
|
const original = client[method];
|
|
2845
3200
|
client[method] = async function(...args) {
|
|
2846
3201
|
const [url, options] = args;
|
|
2847
|
-
const withdrawal =
|
|
3202
|
+
const withdrawal = url === WITHDRAWAL_PATHS[method];
|
|
2848
3203
|
const payload = method === "get" ? options?.params : options;
|
|
2849
3204
|
const matching = withdrawal ? [...observers].filter((observer) => observer.chainId === payload?.chainId && observer.token === payload?.tokenSymbol) : [];
|
|
2850
3205
|
for (const observer of matching) observer.assertCurrent?.();
|
|
@@ -2852,8 +3207,12 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
2852
3207
|
const request3 = {
|
|
2853
3208
|
attempted: true,
|
|
2854
3209
|
method: method === "get" ? "GET" : "POST",
|
|
2855
|
-
path: method
|
|
3210
|
+
path: WITHDRAWAL_PATHS[method]
|
|
2856
3211
|
};
|
|
3212
|
+
if (withdrawal) {
|
|
3213
|
+
for (const observer of matching) await observer.beforeDispatch?.({ ...request3 });
|
|
3214
|
+
for (const observer of matching) observer.assertCurrent?.();
|
|
3215
|
+
}
|
|
2857
3216
|
let belowMinimum2;
|
|
2858
3217
|
const notify2 = (outcome) => {
|
|
2859
3218
|
if (outcome === "rejected" && matching.length !== 1) return;
|
|
@@ -2887,7 +3246,13 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
2887
3246
|
] };
|
|
2888
3247
|
}
|
|
2889
3248
|
try {
|
|
2890
|
-
|
|
3249
|
+
const result = await original.apply(this, callArgs);
|
|
3250
|
+
if (withdrawal) {
|
|
3251
|
+
const id = result?.id;
|
|
3252
|
+
if (typeof id === "string" && id.length >= 1 && id.length <= MAX_WITHDRAWAL_ID_LENGTH)
|
|
3253
|
+
request3.providerWithdrawalId = id;
|
|
3254
|
+
}
|
|
3255
|
+
return result;
|
|
2891
3256
|
} catch (error) {
|
|
2892
3257
|
const message = error instanceof Error ? error.message : "";
|
|
2893
3258
|
const minimum = BELOW_MINIMUM.exec(message);
|
|
@@ -2895,7 +3260,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
2895
3260
|
const status = error?.response?.status;
|
|
2896
3261
|
if (request3.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
|
|
2897
3262
|
request3.httpStatus = status;
|
|
2898
|
-
request3.failure = message ===
|
|
3263
|
+
request3.failure = isZyfaiSessionRejected(message) && (request3.httpStatus === void 0 || request3.httpStatus === 401) ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : request3.httpStatus === 400 && message === "No balance to withdraw" ? "insufficient-balance" : minimum ? "insufficient-balance" : /^User doesn't have any active session key\.?$/.test(message) ? "session-key-missing" : method === "post" && request3.httpStatus === 400 && message === "Partial withdrawal already in progress" ? "withdrawal-in-progress" : message === "Network error: Unable to reach the server" ? "network" : "other";
|
|
2899
3264
|
if (["unauthorized", "forbidden", "rate-limited", "insufficient-balance", "session-key-missing", "withdrawal-in-progress"].includes(request3.failure) && (request3.httpStatus === void 0 || request3.httpStatus >= 400 && request3.httpStatus < 500)) notify2("rejected");
|
|
2900
3265
|
throw error;
|
|
2901
3266
|
} finally {
|
|
@@ -2909,7 +3274,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
|
|
|
2909
3274
|
let rejected = false;
|
|
2910
3275
|
let request2 = { attempted: false };
|
|
2911
3276
|
let belowMinimum;
|
|
2912
|
-
const listener = { chainId, token, assertCurrent, notify: (outcome, evidence, detail) => {
|
|
3277
|
+
const listener = { chainId, token, assertCurrent, beforeDispatch, notify: (outcome, evidence, detail) => {
|
|
2913
3278
|
request2 = evidence;
|
|
2914
3279
|
if (outcome === "attempted") submitted = true;
|
|
2915
3280
|
else if (outcome === "rejected") {
|
|
@@ -3051,7 +3416,7 @@ async function submitZyfaiDeposit({ sdk, state, chain: chain2, amount, asset, sm
|
|
|
3051
3416
|
if (!token || !(0, import_viem7.isAddress)(owner) || !(0, import_viem7.isAddress)(smartWallet) || !/^\d+$/.test(amount) || BigInt(amount) <= 0n)
|
|
3052
3417
|
throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId: chain2.id, asset }, "zyfai");
|
|
3053
3418
|
assertCurrent();
|
|
3054
|
-
const prepared = await sdk.
|
|
3419
|
+
const prepared = await sdk.prepareDeposit({ userAddress: owner, chainId: chain2.id, amount, asset });
|
|
3055
3420
|
assertCurrent();
|
|
3056
3421
|
const expectedData = (0, import_viem7.encodeFunctionData)({ abi: import_viem7.erc20Abi, functionName: "transfer", args: [smartWallet, BigInt(amount)] });
|
|
3057
3422
|
const transfer = prepared?.transfer;
|
|
@@ -3134,6 +3499,13 @@ function tokenDecimals(symbol2, explicit) {
|
|
|
3134
3499
|
return explicit;
|
|
3135
3500
|
return DECIMALS_BY_SYMBOL[(symbol2 ?? "").toLowerCase()] ?? 6;
|
|
3136
3501
|
}
|
|
3502
|
+
function decodeDeltaAmount(raw2, decimals) {
|
|
3503
|
+
if (typeof raw2 !== "string") return void 0;
|
|
3504
|
+
if (/^0x[0-9a-f]+$/i.test(raw2)) return String(hexToDecimal(raw2, decimals));
|
|
3505
|
+
if (/^\d+$/.test(raw2))
|
|
3506
|
+
return String(hexToDecimal(BigInt(raw2).toString(16), decimals));
|
|
3507
|
+
return void 0;
|
|
3508
|
+
}
|
|
3137
3509
|
function mapWithdraw(raw2) {
|
|
3138
3510
|
return {
|
|
3139
3511
|
txHash: raw2.txHash,
|
|
@@ -3183,6 +3555,40 @@ function mapPendingAllocations(raw2) {
|
|
|
3183
3555
|
}
|
|
3184
3556
|
return pending2.length > 0 ? pending2 : void 0;
|
|
3185
3557
|
}
|
|
3558
|
+
function mapPendingWithdrawals(raw2) {
|
|
3559
|
+
if (!Array.isArray(raw2)) return void 0;
|
|
3560
|
+
const pending2 = [];
|
|
3561
|
+
for (const entry of raw2) {
|
|
3562
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
3563
|
+
if (typeof entry.id !== "string" || entry.id === "") continue;
|
|
3564
|
+
if (entry.status !== "REQUESTED" && entry.status !== "CLAIMABLE") continue;
|
|
3565
|
+
if (typeof entry.chainId !== "number" || !Number.isFinite(entry.chainId)) continue;
|
|
3566
|
+
const asset = entry.token?.symbol;
|
|
3567
|
+
if (typeof asset !== "string" || asset === "" || typeof entry.amount !== "string") continue;
|
|
3568
|
+
let amountRaw;
|
|
3569
|
+
try {
|
|
3570
|
+
const amount = BigInt(/^0x/i.test(entry.amount) ? entry.amount : `0x${entry.amount}`);
|
|
3571
|
+
if (amount <= 0n) continue;
|
|
3572
|
+
amountRaw = amount.toString();
|
|
3573
|
+
} catch {
|
|
3574
|
+
continue;
|
|
3575
|
+
}
|
|
3576
|
+
pending2.push({ id: entry.id, status: entry.status, chainId: entry.chainId, asset, amountRaw });
|
|
3577
|
+
}
|
|
3578
|
+
return pending2.length > 0 ? pending2 : void 0;
|
|
3579
|
+
}
|
|
3580
|
+
function mapRecentAsyncWithdrawals(raw2) {
|
|
3581
|
+
if (!Array.isArray(raw2)) return void 0;
|
|
3582
|
+
const recent = [];
|
|
3583
|
+
for (const entry of raw2) {
|
|
3584
|
+
if (typeof entry !== "object" || entry === null) return void 0;
|
|
3585
|
+
if (entry.status === "FAILED") continue;
|
|
3586
|
+
const asset = entry.token?.symbol;
|
|
3587
|
+
if (typeof entry.id !== "string" || entry.id === "" || typeof entry.status !== "string" || entry.status === "" || typeof entry.chainId !== "number" || !Number.isFinite(entry.chainId) || typeof asset !== "string" || asset === "") return void 0;
|
|
3588
|
+
recent.push({ id: entry.id, status: entry.status, chainId: entry.chainId, asset });
|
|
3589
|
+
}
|
|
3590
|
+
return recent;
|
|
3591
|
+
}
|
|
3186
3592
|
function mapBalances(raw2, _chainId, smartWallet) {
|
|
3187
3593
|
const portfolio = raw2.portfolio;
|
|
3188
3594
|
if (!portfolio?.portfolioByChain || typeof portfolio.portfolioByChain !== "object")
|
|
@@ -3241,6 +3647,9 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
3241
3647
|
const pendingAllocations = mapPendingAllocations(
|
|
3242
3648
|
portfolio.staleBalances
|
|
3243
3649
|
);
|
|
3650
|
+
const asyncWithdrawals = portfolio.pendingAsyncWithdrawals;
|
|
3651
|
+
const pendingWithdrawals = mapPendingWithdrawals(asyncWithdrawals);
|
|
3652
|
+
const recentAsyncWithdrawals = mapRecentAsyncWithdrawals(asyncWithdrawals);
|
|
3244
3653
|
return {
|
|
3245
3654
|
smartWallet,
|
|
3246
3655
|
totalBalance: String(totalBalance),
|
|
@@ -3256,7 +3665,49 @@ function mapBalances(raw2, _chainId, smartWallet) {
|
|
|
3256
3665
|
blockNumber: provenance.blockNumber === void 0 ? void 0 : String(provenance.blockNumber),
|
|
3257
3666
|
positionsStatus: Array.isArray(portfolio.positions) ? "available" : "unavailable"
|
|
3258
3667
|
})),
|
|
3259
|
-
...pendingAllocations ? { pendingAllocations } : {}
|
|
3668
|
+
...pendingAllocations ? { pendingAllocations } : {},
|
|
3669
|
+
...pendingWithdrawals ? { pendingWithdrawals } : {},
|
|
3670
|
+
...recentAsyncWithdrawals ? { recentAsyncWithdrawals } : {}
|
|
3671
|
+
};
|
|
3672
|
+
}
|
|
3673
|
+
var TX_HASH2 = /^0x[0-9a-fA-F]{64}$/;
|
|
3674
|
+
var validHash = (value) => typeof value === "string" && TX_HASH2.test(value) ? value : void 0;
|
|
3675
|
+
var validDate = (value) => typeof value === "string" && Number.isFinite(Date.parse(value)) ? value : void 0;
|
|
3676
|
+
function mapWithdrawalLifecycle(raw2) {
|
|
3677
|
+
const body = raw2;
|
|
3678
|
+
if (!body || typeof body !== "object" || typeof body.status !== "string" || typeof body.syncSettled !== "boolean")
|
|
3679
|
+
return null;
|
|
3680
|
+
const syncTxHash = validHash(body.syncTxHash);
|
|
3681
|
+
const estimatedClaimAt = validDate(body.estimatedClaimAt);
|
|
3682
|
+
const asyncLegs = [];
|
|
3683
|
+
for (const leg of Array.isArray(body.asyncLegs) ? body.asyncLegs : []) {
|
|
3684
|
+
if (!leg || typeof leg !== "object" || typeof leg.id !== "string" || leg.id === "" || typeof leg.status !== "string") continue;
|
|
3685
|
+
const legClaimAt = validDate(leg.estimatedClaimAt);
|
|
3686
|
+
const claimTxHash = validHash(leg.claimTxHash);
|
|
3687
|
+
asyncLegs.push({
|
|
3688
|
+
id: leg.id,
|
|
3689
|
+
status: leg.status,
|
|
3690
|
+
...legClaimAt ? { estimatedClaimAt: legClaimAt } : {},
|
|
3691
|
+
...claimTxHash ? { claimTxHash } : {}
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
3694
|
+
const failedAsyncIds = Array.isArray(body.failedAsyncIds) ? body.failedAsyncIds.filter((id) => typeof id === "string" && id !== "") : [];
|
|
3695
|
+
return {
|
|
3696
|
+
status: body.status,
|
|
3697
|
+
syncSettled: body.syncSettled,
|
|
3698
|
+
...syncTxHash ? { syncTxHash } : {},
|
|
3699
|
+
asyncLegs,
|
|
3700
|
+
failedAsyncIds,
|
|
3701
|
+
...estimatedClaimAt ? { estimatedClaimAt } : {},
|
|
3702
|
+
hasError: typeof body.lastError === "string" && body.lastError !== ""
|
|
3703
|
+
};
|
|
3704
|
+
}
|
|
3705
|
+
function mapDepositPlacement(raw2) {
|
|
3706
|
+
const body = raw2;
|
|
3707
|
+
if (!body || typeof body !== "object" || typeof body.status !== "string") return null;
|
|
3708
|
+
return {
|
|
3709
|
+
status: body.status,
|
|
3710
|
+
...typeof body.positionOutcome === "string" ? { positionOutcome: body.positionOutcome } : {}
|
|
3260
3711
|
};
|
|
3261
3712
|
}
|
|
3262
3713
|
function sumTokenValues(tokens) {
|
|
@@ -3420,12 +3871,24 @@ function mapEntries(rawEntries, chainId) {
|
|
|
3420
3871
|
const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
|
|
3421
3872
|
const rawAmount2 = entry?.positions?.[0]?.amount;
|
|
3422
3873
|
const hasAmount = mappedAction !== "Withdraw" && typeof rawAmount2 === "string" && rawAmount2 !== "" && rawAmount2 !== "0x" && rawAmount2 !== "0x0" && rawAmount2 !== "0";
|
|
3874
|
+
const rawDelta = mappedAction === "Deposit" ? decodeDeltaAmount(
|
|
3875
|
+
entry?.positions?.[0]?.deltaAmount,
|
|
3876
|
+
tokenDecimals(entry?.positions?.[0]?.token_symbol)
|
|
3877
|
+
) : void 0;
|
|
3423
3878
|
const mappedPositions = positions.filter((p) => p.protocol_name).map((p) => ({
|
|
3424
3879
|
protocol: p.protocol_name,
|
|
3425
3880
|
pool: p.pool ?? void 0,
|
|
3426
3881
|
tokenSymbol: p.token_symbol ?? void 0,
|
|
3427
|
-
amount: mappedAction !== "Withdraw" && typeof p.amount === "string" && p.amount !== "" && p.amount !== "0x" ? String(hexToDecimal(p.amount, tokenDecimals(p.token_symbol))) : void 0
|
|
3882
|
+
amount: mappedAction !== "Withdraw" && typeof p.amount === "string" && p.amount !== "" && p.amount !== "0x" ? String(hexToDecimal(p.amount, tokenDecimals(p.token_symbol))) : void 0,
|
|
3883
|
+
deltaAmount: mappedAction !== "Withdraw" ? decodeDeltaAmount(p.deltaAmount, tokenDecimals(p.token_symbol)) : void 0
|
|
3428
3884
|
}));
|
|
3885
|
+
const reportedAmount = hasAmount ? String(
|
|
3886
|
+
hexToDecimal(
|
|
3887
|
+
rawAmount2,
|
|
3888
|
+
tokenDecimals(entry?.positions?.[0]?.token_symbol)
|
|
3889
|
+
)
|
|
3890
|
+
) : void 0;
|
|
3891
|
+
const transactionAmount = rawDelta === void 0 ? reportedAmount : Number(rawDelta) > 0 ? rawDelta : void 0;
|
|
3429
3892
|
return {
|
|
3430
3893
|
agent: "zyfai",
|
|
3431
3894
|
action: mappedAction,
|
|
@@ -3438,12 +3901,7 @@ function mapEntries(rawEntries, chainId) {
|
|
|
3438
3901
|
txHashes: entry.transactionHash ? [entry.transactionHash] : [],
|
|
3439
3902
|
chainId: entry.chainId,
|
|
3440
3903
|
tokenSymbol: entry?.positions?.[0]?.token_symbol,
|
|
3441
|
-
amount:
|
|
3442
|
-
hexToDecimal(
|
|
3443
|
-
rawAmount2,
|
|
3444
|
-
tokenDecimals(entry?.positions?.[0]?.token_symbol)
|
|
3445
|
-
)
|
|
3446
|
-
) : void 0
|
|
3904
|
+
amount: transactionAmount
|
|
3447
3905
|
}
|
|
3448
3906
|
],
|
|
3449
3907
|
rebalanceLog
|
|
@@ -3645,9 +4103,9 @@ var isJwtExpired = (token) => {
|
|
|
3645
4103
|
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
3646
4104
|
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
3647
4105
|
const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
|
|
3648
|
-
const
|
|
3649
|
-
if (typeof
|
|
3650
|
-
return Date.now() / 1e3 >=
|
|
4106
|
+
const claims2 = JSON.parse(decoded);
|
|
4107
|
+
if (typeof claims2.exp !== "number") return false;
|
|
4108
|
+
return Date.now() / 1e3 >= claims2.exp - 30;
|
|
3651
4109
|
} catch {
|
|
3652
4110
|
return false;
|
|
3653
4111
|
}
|
|
@@ -3888,7 +4346,7 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
|
|
|
3888
4346
|
}
|
|
3889
4347
|
|
|
3890
4348
|
// src/lib/agent-accounts.ts
|
|
3891
|
-
var
|
|
4349
|
+
var SCOPE5 = "agent-accounts";
|
|
3892
4350
|
var REQUEST_TIMEOUT_MS3 = 5e3;
|
|
3893
4351
|
function compact3(obj) {
|
|
3894
4352
|
const out = {};
|
|
@@ -3958,7 +4416,7 @@ async function fetchAgentAccounts(input) {
|
|
|
3958
4416
|
signal
|
|
3959
4417
|
});
|
|
3960
4418
|
if (!res.ok) {
|
|
3961
|
-
debugLog(
|
|
4419
|
+
debugLog(SCOPE5, `fetch rejected with ${res.status}`);
|
|
3962
4420
|
return null;
|
|
3963
4421
|
}
|
|
3964
4422
|
const data = unwrap3(await res.json());
|
|
@@ -3973,7 +4431,7 @@ async function fetchAgentAccounts(input) {
|
|
|
3973
4431
|
cancel();
|
|
3974
4432
|
}
|
|
3975
4433
|
} catch (err) {
|
|
3976
|
-
debugLog(
|
|
4434
|
+
debugLog(SCOPE5, `fetch failed: ${describeError4(err)}`);
|
|
3977
4435
|
return null;
|
|
3978
4436
|
}
|
|
3979
4437
|
}
|
|
@@ -4004,7 +4462,7 @@ async function reportAgentAccount(input) {
|
|
|
4004
4462
|
signal
|
|
4005
4463
|
});
|
|
4006
4464
|
if (!res.ok) {
|
|
4007
|
-
debugLog(
|
|
4465
|
+
debugLog(SCOPE5, `report rejected with ${res.status}`);
|
|
4008
4466
|
return null;
|
|
4009
4467
|
}
|
|
4010
4468
|
const data = unwrap3(await res.json());
|
|
@@ -4016,7 +4474,7 @@ async function reportAgentAccount(input) {
|
|
|
4016
4474
|
cancel();
|
|
4017
4475
|
}
|
|
4018
4476
|
} catch (err) {
|
|
4019
|
-
debugLog(
|
|
4477
|
+
debugLog(SCOPE5, `report failed: ${describeError4(err)}`);
|
|
4020
4478
|
return null;
|
|
4021
4479
|
}
|
|
4022
4480
|
}
|
|
@@ -4175,6 +4633,17 @@ var ZYFAI_LEDGER_IDLE_CACHE_MS = 6e4;
|
|
|
4175
4633
|
var ZYFAI_LEDGER_REUSE_MS = 6e4;
|
|
4176
4634
|
var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
|
|
4177
4635
|
var PAYOUT_SEARCH_HEAD_LIMIT_MS = 2e3;
|
|
4636
|
+
var WITHDRAW_HANDOFF_TIMEOUT_MS = 25e3;
|
|
4637
|
+
var PLANNED_HANDOFF_TIMEOUT_MS = 8e3;
|
|
4638
|
+
var PLANNED_HANDOFFS_LIMIT = 32;
|
|
4639
|
+
var REGISTRATION_HANDOFF_TIMEOUT_MS = 8e3;
|
|
4640
|
+
var REGISTRATION_NUDGE_TIMEOUT_MS = 25e3;
|
|
4641
|
+
var REGISTRATION_AMOUNT = /^[1-9][0-9]{0,77}$/;
|
|
4642
|
+
var TASK_CODE = /^[A-Z0-9_]{1,64}$/;
|
|
4643
|
+
var SERVER_LEG_CLEARED_CODE = "ZYFAI_LEG_CLEARED";
|
|
4644
|
+
var SESSION_ENDING_CODE = "ZYFAI_SESSION_ENDING";
|
|
4645
|
+
var SERVER_LEG_STATES_LIMIT = 2e3;
|
|
4646
|
+
var ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
|
|
4178
4647
|
function settleWithin(value, ms) {
|
|
4179
4648
|
let timer;
|
|
4180
4649
|
const limit = new Promise((resolve) => {
|
|
@@ -4184,6 +4653,28 @@ function settleWithin(value, ms) {
|
|
|
4184
4653
|
if (timer) clearTimeout(timer);
|
|
4185
4654
|
});
|
|
4186
4655
|
}
|
|
4656
|
+
function depositIdFromLog(logged) {
|
|
4657
|
+
const id = logged?.deposit?.id;
|
|
4658
|
+
return typeof id === "string" && id.length >= 1 && id.length <= 128 ? id : void 0;
|
|
4659
|
+
}
|
|
4660
|
+
function registrationOutcome(answer, knownTask) {
|
|
4661
|
+
if (answer.type === "unknown") return { kind: "unknown" };
|
|
4662
|
+
if (answer.type === "declined") {
|
|
4663
|
+
return !knownTask && provesNoServerTask(answer.code, true) ? { kind: "local" } : { kind: "unknown", code: answer.code };
|
|
4664
|
+
}
|
|
4665
|
+
if (answer.result.providerDepositId) return { kind: "confirmed", providerDepositId: answer.result.providerDepositId };
|
|
4666
|
+
if (answer.state === "failed" && answer.code && ZYFAI_REGISTRATION_NOT_SENT_CODES.has(answer.code)) return { kind: "local" };
|
|
4667
|
+
if (answer.state === "queued" || answer.state === "running") return { kind: "pending" };
|
|
4668
|
+
return { kind: "unknown", code: answer.code ?? answer.state.toUpperCase(), accepted: true };
|
|
4669
|
+
}
|
|
4670
|
+
function registrationOutcomeUnknown(code) {
|
|
4671
|
+
const error = new OwneyError(
|
|
4672
|
+
"AGENT_API_ERROR",
|
|
4673
|
+
"Zyfai registration by the server has no known outcome, so this browser does not register the deposit again.",
|
|
4674
|
+
{ status: 409, ...code && TASK_CODE.test(code) ? { reason: code } : {} }
|
|
4675
|
+
);
|
|
4676
|
+
return Object.assign(error, { status: 409 });
|
|
4677
|
+
}
|
|
4187
4678
|
function belowMinimumCeiling(rejection, token, decimals) {
|
|
4188
4679
|
if ((rejection.symbol === "ETH" ? "WETH" : rejection.symbol) !== token) return void 0;
|
|
4189
4680
|
const ceiling = (display) => {
|
|
@@ -4207,6 +4698,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
4207
4698
|
referralSource;
|
|
4208
4699
|
accountReporting;
|
|
4209
4700
|
id = "zyfai";
|
|
4701
|
+
/** This adapter can hand its withdrawals to our server (part 4). */
|
|
4702
|
+
supportsServerTasks = true;
|
|
4210
4703
|
supportedChainIds = [8453, 42161, 1];
|
|
4211
4704
|
supportedAssets = [
|
|
4212
4705
|
{
|
|
@@ -4283,6 +4776,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
4283
4776
|
/** A ledger read slower than this counts as "no ledger information" for that read. */
|
|
4284
4777
|
ledgerBudgetMs = LEDGER_READ_BUDGET_MS;
|
|
4285
4778
|
balanceSnapshots = /* @__PURE__ */ new WeakMap();
|
|
4779
|
+
/** operationId -> this session's plan-time handoff of that withdrawal (part 4),
|
|
4780
|
+
* read once by its nudge: the `after` legs and the planned share the task
|
|
4781
|
+
* was handed off with ("MAX" or digits). Who owns the leg is
|
|
4782
|
+
* `serverLegStates`, never this record. */
|
|
4783
|
+
plannedHandoffs = /* @__PURE__ */ new Map();
|
|
4784
|
+
/** operationId -> the server ownership of its Zyfai withdrawal leg (T9-I5).
|
|
4785
|
+
* The one source for "may this browser send it" (`mayWithdrawLocally`). */
|
|
4786
|
+
serverLegStates = /* @__PURE__ */ new Map();
|
|
4787
|
+
/** Deposit operations whose registration create was sent in this session:
|
|
4788
|
+
* a retry of one is always handed off again, whatever its session's
|
|
4789
|
+
* lifetime (F-I1(a)), so the server answers the task it may hold. */
|
|
4790
|
+
requestedRegistrations = /* @__PURE__ */ new Set();
|
|
4286
4791
|
/** A replaced wallet session gets its own vendor client. In-flight calls
|
|
4287
4792
|
* keep their original client; authentication must never change owners under
|
|
4288
4793
|
* an operation that is waiting for provider reads or an HTTP response. */
|
|
@@ -4493,10 +4998,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
4493
4998
|
* Wraps `sdk.connectAccount` with a one-shot retry on auth errors. If the
|
|
4494
4999
|
* cached JWT is rejected by the backend (revoked, signing-key rotated, etc.)
|
|
4495
5000
|
* we drop the cache and try again — the second attempt has no cache entry,
|
|
4496
|
-
* so it falls through to a real SIWE prompt.
|
|
4497
|
-
*
|
|
4498
|
-
* JWT from a misconfigured API key; we
|
|
4499
|
-
* second attempt still fails the original
|
|
5001
|
+
* so it falls through to a real SIWE prompt. @zyfai/sdk 0.10.0 labels every
|
|
5002
|
+
* HTTP 401 as an invalid/expired or missing user session ("Unauthorized: …"),
|
|
5003
|
+
* which does not separate a revoked JWT from a misconfigured API key; we
|
|
5004
|
+
* retry once either way, and if the second attempt still fails the original
|
|
5005
|
+
* error propagates.
|
|
4500
5006
|
*/
|
|
4501
5007
|
async connectAccountWithRetry(provider, chainId, executionSdk = this.sdk) {
|
|
4502
5008
|
try {
|
|
@@ -4533,9 +5039,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
4533
5039
|
}
|
|
4534
5040
|
}
|
|
4535
5041
|
/** A rejected execution session must not be reused by the next foreground attempt. */
|
|
4536
|
-
invalidateRejectedWithdrawalSession(error, state, chainId, authToken) {
|
|
5042
|
+
invalidateRejectedWithdrawalSession(error, state, chainId, authToken, httpStatus) {
|
|
4537
5043
|
const message = error instanceof Error ? error.message : "";
|
|
4538
|
-
if (
|
|
5044
|
+
if (!isZyfaiSessionRejected(message) || httpStatus !== void 0 && httpStatus !== 401) return;
|
|
5045
|
+
this.dropWithdrawalSession(state, chainId, authToken);
|
|
5046
|
+
}
|
|
5047
|
+
/** Drops the execution session a withdrawal used (Zyfai rejected it), unless
|
|
5048
|
+
* it has already changed. The next foreground connection signs in again. */
|
|
5049
|
+
dropWithdrawalSession(state, chainId, authToken) {
|
|
4539
5050
|
const internals = this.sdk;
|
|
4540
5051
|
if (typeof authToken !== "string" || internals.httpClient.authToken !== authToken || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || this.connectedChainId !== chainId) return;
|
|
4541
5052
|
clearSession(state.walletAddress, chainId);
|
|
@@ -5143,7 +5654,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5143
5654
|
await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
|
|
5144
5655
|
return redeployed.address;
|
|
5145
5656
|
}
|
|
5146
|
-
async deposit(state, chainId, amount, asset, depositCallback, onProgress, onBeforeSubmission) {
|
|
5657
|
+
async deposit(state, chainId, amount, asset, depositCallback, onProgress, onBeforeSubmission, context) {
|
|
5147
5658
|
const validChainId = isValidChainId(chainId);
|
|
5148
5659
|
const owner = state.walletAddress;
|
|
5149
5660
|
const provider = state.provider;
|
|
@@ -5158,6 +5669,12 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5158
5669
|
assertCurrent();
|
|
5159
5670
|
const smartWallet = await this.ensureWalletDeployed(owner, validChainId);
|
|
5160
5671
|
assertCurrent();
|
|
5672
|
+
let server;
|
|
5673
|
+
if (this.accountReporting.owneyApiKey && typeof context?.operationId === "string" && context.operationId && this.serverTasksOn(this.requestedRegistrations.has(context.operationId))) {
|
|
5674
|
+
server = await this.handOffDepositRegistration(owner, validChainId, asset, amount, smartWallet, context.operationId);
|
|
5675
|
+
assertCurrent();
|
|
5676
|
+
}
|
|
5677
|
+
const mark = server ?? {};
|
|
5161
5678
|
notifyFunding(onProgress, {
|
|
5162
5679
|
agentId: this.id,
|
|
5163
5680
|
chainId,
|
|
@@ -5167,7 +5684,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5167
5684
|
intendedAmount: amount,
|
|
5168
5685
|
transfer: "pending",
|
|
5169
5686
|
registration: "pending",
|
|
5170
|
-
delivery: "not-applicable"
|
|
5687
|
+
delivery: "not-applicable",
|
|
5688
|
+
...mark
|
|
5171
5689
|
});
|
|
5172
5690
|
const txHash = depositCallback ? await depositCallback(smartWallet, validChainId, amount) : await submitZyfaiDeposit({
|
|
5173
5691
|
sdk: executionSdk,
|
|
@@ -5186,7 +5704,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5186
5704
|
intendedAmount: amount,
|
|
5187
5705
|
transfer: "pending",
|
|
5188
5706
|
registration: "pending",
|
|
5189
|
-
delivery: "not-applicable"
|
|
5707
|
+
delivery: "not-applicable",
|
|
5708
|
+
...mark
|
|
5190
5709
|
}) : void 0
|
|
5191
5710
|
});
|
|
5192
5711
|
let evidence = {
|
|
@@ -5199,7 +5718,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5199
5718
|
txHash,
|
|
5200
5719
|
transfer: "pending",
|
|
5201
5720
|
registration: "pending",
|
|
5202
|
-
delivery: "not-applicable"
|
|
5721
|
+
delivery: "not-applicable",
|
|
5722
|
+
...mark
|
|
5203
5723
|
};
|
|
5204
5724
|
notifyFunding(onProgress, evidence);
|
|
5205
5725
|
try {
|
|
@@ -5219,11 +5739,32 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5219
5739
|
throw new OwneyError("AGENT_TRANSACTION_REVERTED", "Zyfai deposit transaction reverted.", { txHash }, this.id);
|
|
5220
5740
|
if (evidence.transfer !== "confirmed") return { txHash, smartWallet, amount, evidence };
|
|
5221
5741
|
this.ledgerReads.clear();
|
|
5742
|
+
if (server) {
|
|
5743
|
+
let outcome;
|
|
5744
|
+
try {
|
|
5745
|
+
assertCurrent();
|
|
5746
|
+
outcome = await this.nudgeDepositRegistration(evidence, this.executionSession());
|
|
5747
|
+
} catch {
|
|
5748
|
+
outcome = { kind: "unknown" };
|
|
5749
|
+
}
|
|
5750
|
+
if (outcome.kind !== "local") {
|
|
5751
|
+
evidence = outcome.kind === "confirmed" ? { ...evidence, registration: "confirmed", providerDepositId: outcome.providerDepositId, serverTask: true } : {
|
|
5752
|
+
...evidence,
|
|
5753
|
+
registration: outcome.kind,
|
|
5754
|
+
...outcome.kind === "pending" || outcome.accepted ? { serverTask: true } : {}
|
|
5755
|
+
};
|
|
5756
|
+
if (outcome.kind !== "confirmed") this.notePendingRegistration(owner, validChainId, txHash, Date.now(), asset);
|
|
5757
|
+
notifyFunding(onProgress, evidence);
|
|
5758
|
+
return { txHash, smartWallet, amount, evidence };
|
|
5759
|
+
}
|
|
5760
|
+
evidence = { ...evidence, serverTask: false };
|
|
5761
|
+
}
|
|
5222
5762
|
try {
|
|
5223
5763
|
assertCurrent();
|
|
5224
5764
|
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
5225
|
-
await (tokenAddress ? executionSdk.logDeposit(validChainId, txHash, amount, tokenAddress) : executionSdk.logDeposit(validChainId, txHash, amount));
|
|
5226
|
-
|
|
5765
|
+
const logged = await (tokenAddress ? executionSdk.logDeposit(validChainId, txHash, amount, tokenAddress) : executionSdk.logDeposit(validChainId, txHash, amount));
|
|
5766
|
+
const providerDepositId = depositIdFromLog(logged);
|
|
5767
|
+
evidence = { ...evidence, registration: "confirmed", ...providerDepositId ? { providerDepositId } : {} };
|
|
5227
5768
|
} catch {
|
|
5228
5769
|
evidence = { ...evidence, registration: "unknown" };
|
|
5229
5770
|
this.notePendingRegistration(owner, validChainId, txHash, Date.now(), asset);
|
|
@@ -5343,10 +5884,69 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5343
5884
|
throw unavailable();
|
|
5344
5885
|
}
|
|
5345
5886
|
}
|
|
5346
|
-
|
|
5887
|
+
/**
|
|
5888
|
+
* Settles Zyfai's own wallet prompts before another agent's owner signature:
|
|
5889
|
+
* the SIWE login and, for a legacy wallet the login reported without a
|
|
5890
|
+
* session key, that key (attempted once per session, like activation). Reads
|
|
5891
|
+
* only the login's cached flags; never withdraws. A rejected prompt throws;
|
|
5892
|
+
* any other session-key failure is left to `withdraw`'s own recovery.
|
|
5893
|
+
*/
|
|
5894
|
+
async prepareWithdrawal(state, chainId, _token) {
|
|
5895
|
+
const validChainId = isValidChainId(chainId);
|
|
5896
|
+
await this.ensureConnected(state, validChainId);
|
|
5897
|
+
await this.ensureWithdrawalSessionKey(state.walletAddress, validChainId);
|
|
5898
|
+
}
|
|
5899
|
+
/** The session key a legacy wallet's login reported missing, created once per
|
|
5900
|
+
* session (one prompt). A rejected prompt throws; any other failure is left
|
|
5901
|
+
* to the withdrawal itself. */
|
|
5902
|
+
async ensureWithdrawalSessionKey(owner, validChainId) {
|
|
5903
|
+
const internals = this.sdk;
|
|
5904
|
+
const key2 = `${owner.toLowerCase()}:${validChainId}`;
|
|
5905
|
+
if (internals.isPredeployed !== false || internals.hasActiveSessionKey !== false || this.sessionKeyAttempts.has(key2)) return;
|
|
5906
|
+
this.sessionKeyAttempts.add(key2);
|
|
5907
|
+
try {
|
|
5908
|
+
await this.sdk.createSessionKey(owner, validChainId);
|
|
5909
|
+
} catch (error) {
|
|
5910
|
+
const code = error?.code;
|
|
5911
|
+
if (code === 4001 || code === "ACTION_REJECTED" || error instanceof Error && /user (rejected|denied)|request rejected|action rejected/i.test(error.message)) {
|
|
5912
|
+
this.sessionKeyAttempts.delete(key2);
|
|
5913
|
+
throw error;
|
|
5914
|
+
}
|
|
5915
|
+
console.warn(
|
|
5916
|
+
"[zyfai] session key before withdrawal skipped (non-fatal):",
|
|
5917
|
+
error instanceof Error ? error.message : String(error)
|
|
5918
|
+
);
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
async withdraw(state, chainId, token, amount, onProgress, _gasMode, onBeforeSubmission, assertOperation, context) {
|
|
5347
5922
|
if (amount !== void 0 && (typeof amount !== "string" || !/^[0-9]+$/.test(amount) || BigInt(amount) <= 0n))
|
|
5348
5923
|
throw new OwneyError("WITHDRAW_INVALID_AMOUNT", "Withdrawal amount must be a positive integer in token base units.", void 0, this.id);
|
|
5349
5924
|
const validChainId = isValidChainId(chainId);
|
|
5925
|
+
if (this.accountReporting.owneyApiKey && typeof context?.operationId === "string" && context.operationId && // T10-I1: a leg whose mark was cleared is never handed off again.
|
|
5926
|
+
this.serverLegStates.get(context.operationId) !== "cleared" && // F-I2(iii): the opt-in, read now, unless the leg was already handed
|
|
5927
|
+
// off (planned, requested or accepted).
|
|
5928
|
+
this.serverTasksOn(context.planned === true || this.serverLegStates.has(context.operationId))) {
|
|
5929
|
+
const served = await this.withdrawThroughServer(
|
|
5930
|
+
state,
|
|
5931
|
+
validChainId,
|
|
5932
|
+
token,
|
|
5933
|
+
amount,
|
|
5934
|
+
onProgress,
|
|
5935
|
+
onBeforeSubmission,
|
|
5936
|
+
assertOperation,
|
|
5937
|
+
{ operationId: context.operationId, planned: context.planned === true }
|
|
5938
|
+
);
|
|
5939
|
+
if (served) return served;
|
|
5940
|
+
}
|
|
5941
|
+
if (typeof context?.operationId === "string" && context.operationId && !this.mayWithdrawLocally(context.operationId))
|
|
5942
|
+
this.serverLegUnresolved(onProgress, {
|
|
5943
|
+
agentId: this.id,
|
|
5944
|
+
chainId,
|
|
5945
|
+
token,
|
|
5946
|
+
walletAddress: state.walletAddress,
|
|
5947
|
+
intendedAmount: amount ?? "MAX",
|
|
5948
|
+
registration: "not-applicable"
|
|
5949
|
+
});
|
|
5350
5950
|
try {
|
|
5351
5951
|
await this.ensureConnected(state, validChainId);
|
|
5352
5952
|
} catch (error) {
|
|
@@ -5383,15 +5983,35 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5383
5983
|
assertCurrent();
|
|
5384
5984
|
assertOperation?.();
|
|
5385
5985
|
};
|
|
5386
|
-
let
|
|
5986
|
+
let headRead = Promise.resolve(void 0);
|
|
5987
|
+
let settledHead;
|
|
5988
|
+
const searchHead = () => settledHead ??= settleWithin(headRead, PAYOUT_SEARCH_HEAD_LIMIT_MS);
|
|
5989
|
+
const checkpoint2 = onBeforeSubmission ? async (request2) => {
|
|
5990
|
+
const head = await searchHead();
|
|
5991
|
+
const agentWallet = this.smartWalletCache.get(`${owner.toLowerCase()}_${validChainId}`);
|
|
5992
|
+
await onBeforeSubmission({
|
|
5993
|
+
agentId: this.id,
|
|
5994
|
+
chainId,
|
|
5995
|
+
token,
|
|
5996
|
+
walletAddress: state.walletAddress,
|
|
5997
|
+
intendedAmount: amount ?? "MAX",
|
|
5998
|
+
transfer: "pending",
|
|
5999
|
+
registration: "not-applicable",
|
|
6000
|
+
delivery: "pending",
|
|
6001
|
+
withdrawalRequest: request2,
|
|
6002
|
+
...agentWallet ? { agentWallet } : {},
|
|
6003
|
+
...head !== void 0 ? { payoutSearch: { fromBlock: head.toString() } } : {}
|
|
6004
|
+
});
|
|
6005
|
+
} : void 0;
|
|
6006
|
+
let observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission, checkpoint2);
|
|
5387
6007
|
const authToken = internals.httpClient?.authToken;
|
|
5388
6008
|
let raw2;
|
|
5389
|
-
let headRead = Promise.resolve(void 0);
|
|
5390
6009
|
try {
|
|
5391
6010
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5392
6011
|
try {
|
|
5393
6012
|
assertCurrent();
|
|
5394
6013
|
headRead = this.readPayoutSearchHead(validChainId);
|
|
6014
|
+
settledHead = void 0;
|
|
5395
6015
|
raw2 = await executionSdk.withdrawFunds(owner, validChainId, amount, token);
|
|
5396
6016
|
break;
|
|
5397
6017
|
} catch (error) {
|
|
@@ -5420,7 +6040,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5420
6040
|
assertSubmission();
|
|
5421
6041
|
await this.recoverWithdrawalSession(state, validChainId, token, owner);
|
|
5422
6042
|
observation.stop();
|
|
5423
|
-
observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
|
|
6043
|
+
observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission, checkpoint2);
|
|
5424
6044
|
}
|
|
5425
6045
|
}
|
|
5426
6046
|
if (!raw2) throw new OwneyError(
|
|
@@ -5431,7 +6051,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5431
6051
|
);
|
|
5432
6052
|
} catch (error) {
|
|
5433
6053
|
if (staleSessionError && observation.attempted() === false) error = staleSessionError;
|
|
5434
|
-
this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken);
|
|
6054
|
+
this.invalidateRejectedWithdrawalSession(error, state, validChainId, authToken, observation.request()?.httpStatus);
|
|
5435
6055
|
if (observation.attempted() !== void 0) {
|
|
5436
6056
|
const failed = observation.attempted() === false || observation.rejected();
|
|
5437
6057
|
notifyFunding(onProgress, {
|
|
@@ -5485,7 +6105,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5485
6105
|
if (!raw2.txHash) {
|
|
5486
6106
|
const acceptedAt = Date.now();
|
|
5487
6107
|
const agentWallet = this.smartWalletCache.get(`${owner.toLowerCase()}_${validChainId}`);
|
|
5488
|
-
const head = await
|
|
6108
|
+
const head = await searchHead();
|
|
5489
6109
|
evidence = {
|
|
5490
6110
|
...evidence,
|
|
5491
6111
|
...agentWallet ? { agentWallet } : {},
|
|
@@ -5497,7 +6117,456 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5497
6117
|
this.ledgerReads.clear();
|
|
5498
6118
|
return { ...mapWithdraw(raw2), evidence };
|
|
5499
6119
|
}
|
|
5500
|
-
/**
|
|
6120
|
+
/**
|
|
6121
|
+
* Hands a planned withdrawal leg to our server at plan time (part 4), before
|
|
6122
|
+
* the earlier wallet-approval legs (`after`) are signed. The server sends it
|
|
6123
|
+
* once they are accepted; the browser nudges it at that point
|
|
6124
|
+
* (`withdraw(…, { operationId, planned: true })`). Uses the current execution
|
|
6125
|
+
* client's session, after `ensureConnected`. Never throws, never prompts
|
|
6126
|
+
* beyond that connection, and never withdraws locally.
|
|
6127
|
+
*/
|
|
6128
|
+
async handOffPlannedWithdrawal(state, chainId, token, amount, ctx) {
|
|
6129
|
+
const { owneyApiKey, routingApiBaseUrl } = this.accountReporting;
|
|
6130
|
+
if (!this.serverTasksOn(this.serverLegStates.has(ctx.operationId)))
|
|
6131
|
+
return { type: "declined", code: ZYFAI_SERVER_TASKS_OFF_CODE };
|
|
6132
|
+
if (this.serverLegStates.get(ctx.operationId) === "cleared") return { type: "declined", code: SERVER_LEG_CLEARED_CODE };
|
|
6133
|
+
const noSession = { type: "declined", code: "NO_SESSION" };
|
|
6134
|
+
if (!owneyApiKey) return noSession;
|
|
6135
|
+
let validChainId;
|
|
6136
|
+
let session;
|
|
6137
|
+
let agentWallet;
|
|
6138
|
+
try {
|
|
6139
|
+
validChainId = isValidChainId(chainId);
|
|
6140
|
+
await this.ensureConnected(state, validChainId);
|
|
6141
|
+
session = this.executionSession();
|
|
6142
|
+
if (!session) return noSession;
|
|
6143
|
+
if (this.serverLegStates.get(ctx.operationId) === void 0 && !zyfaiSessionOutlivesTask(session, "zyfai_withdrawal")) return { type: "declined", code: SESSION_ENDING_CODE };
|
|
6144
|
+
agentWallet = await this.handoffAgentWallet(state.walletAddress, validChainId);
|
|
6145
|
+
} catch {
|
|
6146
|
+
return noSession;
|
|
6147
|
+
}
|
|
6148
|
+
if (!agentWallet) return { type: "declined", code: "NO_AGENT_WALLET" };
|
|
6149
|
+
try {
|
|
6150
|
+
ctx.beforeRequest?.();
|
|
6151
|
+
} catch {
|
|
6152
|
+
}
|
|
6153
|
+
const afterLostAnswer = this.serverLegStates.get(ctx.operationId) === "requested";
|
|
6154
|
+
this.setServerLegState(ctx.operationId, "requested");
|
|
6155
|
+
let answer = await handOffZyfaiTask(
|
|
6156
|
+
{ apiKey: owneyApiKey, baseUrl: routingApiBaseUrl, timeoutMs: PLANNED_HANDOFF_TIMEOUT_MS, zyfaiServerTasks: true },
|
|
6157
|
+
{
|
|
6158
|
+
kind: "zyfai_withdrawal",
|
|
6159
|
+
operationId: ctx.operationId,
|
|
6160
|
+
owner: state.walletAddress,
|
|
6161
|
+
chainId: validChainId,
|
|
6162
|
+
asset: token,
|
|
6163
|
+
amount,
|
|
6164
|
+
agentWallet,
|
|
6165
|
+
after: ctx.after
|
|
6166
|
+
},
|
|
6167
|
+
session
|
|
6168
|
+
);
|
|
6169
|
+
if (answer.type === "declined" && ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code) && !provesNoServerTask(answer.code, afterLostAnswer)) answer = { type: "unknown" };
|
|
6170
|
+
if (answer.type === "accepted" || answer.type === "declined" && answer.code === "LEG_ALREADY_SENT")
|
|
6171
|
+
this.setServerLegState(ctx.operationId, "accepted");
|
|
6172
|
+
if (answer.type === "declined") {
|
|
6173
|
+
if (answer.code !== "LEG_ALREADY_SENT" && this.serverLegStates.get(ctx.operationId) === "accepted")
|
|
6174
|
+
return { type: "unknown" };
|
|
6175
|
+
if (provesNoServerTask(answer.code, afterLostAnswer)) this.clearServerLeg(ctx.operationId, "no-task-decline");
|
|
6176
|
+
return answer;
|
|
6177
|
+
}
|
|
6178
|
+
this.plannedHandoffs.delete(ctx.operationId);
|
|
6179
|
+
this.plannedHandoffs.set(ctx.operationId, { after: [...ctx.after], amount });
|
|
6180
|
+
while (this.plannedHandoffs.size > PLANNED_HANDOFFS_LIMIT)
|
|
6181
|
+
this.plannedHandoffs.delete(this.plannedHandoffs.keys().next().value);
|
|
6182
|
+
return answer;
|
|
6183
|
+
}
|
|
6184
|
+
/**
|
|
6185
|
+
* F-I2(iii): whether the server path may run. The caller's opt-in is read
|
|
6186
|
+
* now (a function is called) as an operation's first handoff would start.
|
|
6187
|
+
* An operation this session already handed off (`handedOff`: a planned leg,
|
|
6188
|
+
* a leg requested or accepted, a registration already sent) keeps going
|
|
6189
|
+
* through the server even after the opt-in turns off: its nudges, re-runs
|
|
6190
|
+
* and retries, so the server answers the task it may hold and the browser
|
|
6191
|
+
* never sends it too. Without any opt-in (P4-0) nothing was handed off.
|
|
6192
|
+
*/
|
|
6193
|
+
serverTasksOn(handedOff = false) {
|
|
6194
|
+
const option = this.accountReporting.zyfaiServerTasks;
|
|
6195
|
+
if (handedOff) return option === true || typeof option === "function";
|
|
6196
|
+
return zyfaiServerTasksEnabled(option);
|
|
6197
|
+
}
|
|
6198
|
+
/** True when this browser may send the operation's Zyfai withdrawal leg
|
|
6199
|
+
* itself: it never handed it off, or the server proved it has no task. */
|
|
6200
|
+
mayWithdrawLocally(operationId) {
|
|
6201
|
+
const current = this.serverLegStates.get(operationId);
|
|
6202
|
+
return current === void 0 || current === "cleared";
|
|
6203
|
+
}
|
|
6204
|
+
/** Moves the leg's server ownership forward: absent -> requested ->
|
|
6205
|
+
* accepted. "accepted" is sticky, and "cleared" is final (such a leg is
|
|
6206
|
+
* never handed off again). Clearing goes through `clearServerLeg`. */
|
|
6207
|
+
setServerLegState(operationId, next) {
|
|
6208
|
+
const current = this.serverLegStates.get(operationId);
|
|
6209
|
+
if (current === "accepted" || current === "cleared" || current === next) return;
|
|
6210
|
+
this.serverLegStates.set(operationId, next);
|
|
6211
|
+
while (this.serverLegStates.size > SERVER_LEG_STATES_LIMIT)
|
|
6212
|
+
this.serverLegStates.delete(this.serverLegStates.keys().next().value);
|
|
6213
|
+
}
|
|
6214
|
+
/** Clears the leg on a proof that the server has no task: a no-task decline
|
|
6215
|
+
* after a request, or nothing ever requested (T8-R4b). Never from accepted.
|
|
6216
|
+
* Returns whether the leg is cleared; the caller then emits
|
|
6217
|
+
* `serverTask: false` and may send locally (T10-I1, T9-I5). */
|
|
6218
|
+
clearServerLeg(operationId, proof) {
|
|
6219
|
+
const current = this.serverLegStates.get(operationId);
|
|
6220
|
+
if (current === "cleared") return true;
|
|
6221
|
+
if (proof === "no-task-decline" ? current !== "requested" : current !== void 0) return false;
|
|
6222
|
+
this.serverLegStates.set(operationId, "cleared");
|
|
6223
|
+
while (this.serverLegStates.size > SERVER_LEG_STATES_LIMIT)
|
|
6224
|
+
this.serverLegStates.delete(this.serverLegStates.keys().next().value);
|
|
6225
|
+
return true;
|
|
6226
|
+
}
|
|
6227
|
+
/** T9-I5: the server may hold this leg's task, so this browser never sends
|
|
6228
|
+
* it. The leg is reported failed with `serverTask: "unknown"` (never
|
|
6229
|
+
* cleared; a re-run hands it off again), and `error`, or an unresolved-leg
|
|
6230
|
+
* error, is thrown. */
|
|
6231
|
+
serverLegUnresolved(onProgress, base7, error) {
|
|
6232
|
+
notifyFunding(onProgress, {
|
|
6233
|
+
...base7,
|
|
6234
|
+
transfer: "failed",
|
|
6235
|
+
delivery: "failed",
|
|
6236
|
+
withdrawalRequest: { attempted: false },
|
|
6237
|
+
serverTask: "unknown"
|
|
6238
|
+
});
|
|
6239
|
+
throw error ?? new OwneyError(
|
|
6240
|
+
"WITHDRAW_FAILED",
|
|
6241
|
+
"The server may still send this Zyfai withdrawal, so this browser does not send it.",
|
|
6242
|
+
{ reason: "ZYFAI_SERVER_LEG_UNRESOLVED", serverTask: true, rejectedBeforeSubmission: true },
|
|
6243
|
+
this.id
|
|
6244
|
+
);
|
|
6245
|
+
}
|
|
6246
|
+
/** The current execution client's session. Never logged or returned. */
|
|
6247
|
+
executionSession() {
|
|
6248
|
+
const token = this.sdk.httpClient?.authToken;
|
|
6249
|
+
return typeof token === "string" && token ? token : void 0;
|
|
6250
|
+
}
|
|
6251
|
+
/** The owner's Zyfai wallet for a handoff (read-only), or undefined. */
|
|
6252
|
+
async handoffAgentWallet(owner, chainId) {
|
|
6253
|
+
if (!ADDRESS_PATTERN.test(owner)) return void 0;
|
|
6254
|
+
try {
|
|
6255
|
+
const wallet = await this.readSmartWallet(owner, chainId);
|
|
6256
|
+
return typeof wallet === "string" && ADDRESS_PATTERN.test(wallet) ? wallet : void 0;
|
|
6257
|
+
} catch {
|
|
6258
|
+
return void 0;
|
|
6259
|
+
}
|
|
6260
|
+
}
|
|
6261
|
+
/**
|
|
6262
|
+
* Part 4: hands this withdrawal to our server, or nudges the task a plan-time
|
|
6263
|
+
* handoff created (`planned`). The server is the one sender from then on:
|
|
6264
|
+
* this never calls Zyfai's withdraw route, and never sends again after an
|
|
6265
|
+
* ambiguous answer. Returns undefined only when nothing was handed off, or
|
|
6266
|
+
* when the server proved no task exists (a decline in
|
|
6267
|
+
* `ZYFAI_HANDOFF_NO_TASK_CODES`, rulings P4-11, T7-R1, T8-C1 and F-I1(b);
|
|
6268
|
+
* after a lost answer only the server's own declines, F-I2(i)); today's
|
|
6269
|
+
* local path then runs, after the handoff's mark was cleared (T8-C2).
|
|
6270
|
+
*/
|
|
6271
|
+
async withdrawThroughServer(state, chainId, token, requestedAmount, onProgress, onBeforeSubmission, assertOperation, context) {
|
|
6272
|
+
const owner = state.walletAddress;
|
|
6273
|
+
const operationId = context.operationId;
|
|
6274
|
+
const plan = context.planned ? this.plannedHandoffs.get(operationId) : void 0;
|
|
6275
|
+
if (context.planned) this.plannedHandoffs.delete(operationId);
|
|
6276
|
+
if (context.planned && this.serverLegStates.get(operationId) === void 0)
|
|
6277
|
+
this.setServerLegState(operationId, "accepted");
|
|
6278
|
+
const planLost = context.planned && this.serverLegStates.get(operationId) === "requested";
|
|
6279
|
+
const serverOwned = () => context.planned || this.serverLegStates.get(operationId) === "accepted";
|
|
6280
|
+
const amount = plan?.amount === "MAX" ? void 0 : plan && /^[1-9][0-9]*$/.test(plan.amount) ? plan.amount : requestedAmount;
|
|
6281
|
+
const route = amount ? { attempted: true, method: "POST", path: "/users/partial-withdraw/v2" } : { attempted: true, method: "GET", path: "/users/withdraw/v2" };
|
|
6282
|
+
const base7 = {
|
|
6283
|
+
agentId: this.id,
|
|
6284
|
+
chainId,
|
|
6285
|
+
token,
|
|
6286
|
+
walletAddress: owner,
|
|
6287
|
+
intendedAmount: amount ?? "MAX",
|
|
6288
|
+
registration: "not-applicable"
|
|
6289
|
+
};
|
|
6290
|
+
let head;
|
|
6291
|
+
const searchHead = () => head ??= settleWithin(this.readPayoutSearchHead(chainId), PAYOUT_SEARCH_HEAD_LIMIT_MS);
|
|
6292
|
+
const accept = async (serverTask, agentWallet2, txHash, providerWithdrawalId) => {
|
|
6293
|
+
const withdrawalRequest = { ...route, ...providerWithdrawalId ? { providerWithdrawalId } : {} };
|
|
6294
|
+
let evidence;
|
|
6295
|
+
if (txHash) {
|
|
6296
|
+
evidence = { ...base7, txHash, transfer: "pending", delivery: "pending", withdrawalRequest, serverTask };
|
|
6297
|
+
} else {
|
|
6298
|
+
const acceptedAt = Date.now();
|
|
6299
|
+
const fromBlock = await searchHead();
|
|
6300
|
+
evidence = {
|
|
6301
|
+
...base7,
|
|
6302
|
+
transfer: "pending",
|
|
6303
|
+
delivery: "pending",
|
|
6304
|
+
...agentWallet2 ? { agentWallet: agentWallet2 } : {},
|
|
6305
|
+
withdrawalRequest: { ...withdrawalRequest, acceptedAt },
|
|
6306
|
+
...fromBlock !== void 0 ? { payoutSearch: { fromBlock: fromBlock.toString() } } : {},
|
|
6307
|
+
serverTask
|
|
6308
|
+
};
|
|
6309
|
+
}
|
|
6310
|
+
notifyFunding(onProgress, evidence);
|
|
6311
|
+
this.ledgerReads.clear();
|
|
6312
|
+
return { ...mapWithdraw({
|
|
6313
|
+
success: true,
|
|
6314
|
+
message: "Withdrawal handed to the server",
|
|
6315
|
+
txHash,
|
|
6316
|
+
type: amount ? "partial" : "full",
|
|
6317
|
+
amount: amount || "all"
|
|
6318
|
+
}), evidence };
|
|
6319
|
+
};
|
|
6320
|
+
try {
|
|
6321
|
+
await this.ensureConnected(state, chainId);
|
|
6322
|
+
} catch (error) {
|
|
6323
|
+
if (serverOwned()) return accept("unknown", void 0);
|
|
6324
|
+
if (!this.mayWithdrawLocally(operationId)) this.serverLegUnresolved(onProgress, base7, error);
|
|
6325
|
+
notifyFunding(onProgress, { ...base7, transfer: "failed", delivery: "failed" });
|
|
6326
|
+
throw error;
|
|
6327
|
+
}
|
|
6328
|
+
const generation = this.connectionGeneration;
|
|
6329
|
+
const internals = this.sdk;
|
|
6330
|
+
const session = this.executionSession();
|
|
6331
|
+
if (session && !context.planned && this.serverLegStates.get(operationId) === void 0 && !zyfaiSessionOutlivesTask(session, "zyfai_withdrawal")) return void 0;
|
|
6332
|
+
const agentWallet = session ? await this.handoffAgentWallet(owner, chainId) : void 0;
|
|
6333
|
+
if (!session || !agentWallet) {
|
|
6334
|
+
if (serverOwned()) return accept("unknown", agentWallet);
|
|
6335
|
+
if (!this.mayWithdrawLocally(operationId)) this.serverLegUnresolved(onProgress, base7);
|
|
6336
|
+
return void 0;
|
|
6337
|
+
}
|
|
6338
|
+
const assertSubmission = () => {
|
|
6339
|
+
const account = internals.walletClient?.account?.address;
|
|
6340
|
+
if (generation !== this.connectionGeneration || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId || state.chainId !== null && state.chainId !== chainId || account && account.toLowerCase() !== owner.toLowerCase())
|
|
6341
|
+
throw new OwneyError(
|
|
6342
|
+
"NOT_CONNECTED",
|
|
6343
|
+
"Wallet changed before Zyfai withdrawal submission.",
|
|
6344
|
+
{ phase: "prepare", rejectedBeforeSubmission: true },
|
|
6345
|
+
this.id
|
|
6346
|
+
);
|
|
6347
|
+
assertOperation?.();
|
|
6348
|
+
};
|
|
6349
|
+
try {
|
|
6350
|
+
if (!context.planned) await this.ensureWithdrawalSessionKey(owner, chainId);
|
|
6351
|
+
assertSubmission();
|
|
6352
|
+
const fromBlock = await searchHead();
|
|
6353
|
+
await onBeforeSubmission?.({
|
|
6354
|
+
...base7,
|
|
6355
|
+
transfer: "pending",
|
|
6356
|
+
delivery: "pending",
|
|
6357
|
+
withdrawalRequest: { ...route },
|
|
6358
|
+
agentWallet,
|
|
6359
|
+
...fromBlock !== void 0 ? { payoutSearch: { fromBlock: fromBlock.toString() } } : {},
|
|
6360
|
+
serverTask: true
|
|
6361
|
+
});
|
|
6362
|
+
assertSubmission();
|
|
6363
|
+
} catch (error) {
|
|
6364
|
+
if (serverOwned()) return accept("unknown", agentWallet);
|
|
6365
|
+
if (!this.clearServerLeg(operationId, "never-requested")) this.serverLegUnresolved(onProgress, base7, error);
|
|
6366
|
+
notifyFunding(onProgress, {
|
|
6367
|
+
...base7,
|
|
6368
|
+
transfer: "failed",
|
|
6369
|
+
delivery: "failed",
|
|
6370
|
+
withdrawalRequest: { attempted: false },
|
|
6371
|
+
serverTask: false
|
|
6372
|
+
});
|
|
6373
|
+
throw error;
|
|
6374
|
+
}
|
|
6375
|
+
const cfg = {
|
|
6376
|
+
apiKey: this.accountReporting.owneyApiKey,
|
|
6377
|
+
baseUrl: this.accountReporting.routingApiBaseUrl,
|
|
6378
|
+
timeoutMs: WITHDRAW_HANDOFF_TIMEOUT_MS,
|
|
6379
|
+
zyfaiServerTasks: true
|
|
6380
|
+
};
|
|
6381
|
+
const body = {
|
|
6382
|
+
kind: "zyfai_withdrawal",
|
|
6383
|
+
operationId: context.operationId,
|
|
6384
|
+
owner,
|
|
6385
|
+
chainId,
|
|
6386
|
+
asset: token,
|
|
6387
|
+
amount: amount ?? "MAX",
|
|
6388
|
+
agentWallet,
|
|
6389
|
+
...plan?.after.length ? { after: plan.after } : {},
|
|
6390
|
+
...context.planned ? { runNow: true } : {}
|
|
6391
|
+
};
|
|
6392
|
+
const requestedBefore = this.serverLegStates.get(operationId) === "requested";
|
|
6393
|
+
let afterUnknown = planLost;
|
|
6394
|
+
this.setServerLegState(operationId, "requested");
|
|
6395
|
+
let answer = await handOffZyfaiTask(cfg, body, session);
|
|
6396
|
+
if (answer.type === "unknown" && !context.planned) {
|
|
6397
|
+
afterUnknown = true;
|
|
6398
|
+
answer = await handOffZyfaiTask(cfg, body, session);
|
|
6399
|
+
}
|
|
6400
|
+
if (answer.type === "declined" && ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code) && !provesNoServerTask(answer.code, requestedBefore || afterUnknown)) answer = { type: "unknown" };
|
|
6401
|
+
if (answer.type === "accepted" || answer.type === "declined" && answer.code === "LEG_ALREADY_SENT")
|
|
6402
|
+
this.setServerLegState(operationId, "accepted");
|
|
6403
|
+
if (answer.type === "declined") {
|
|
6404
|
+
if (ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code) && this.clearServerLeg(operationId, "no-task-decline")) {
|
|
6405
|
+
const cleared = { ...base7, transfer: "pending", delivery: "pending", serverTask: false };
|
|
6406
|
+
notifyFunding(onProgress, cleared);
|
|
6407
|
+
try {
|
|
6408
|
+
await onBeforeSubmission?.({ ...cleared, withdrawalRequest: { attempted: false } });
|
|
6409
|
+
} catch (error) {
|
|
6410
|
+
notifyFunding(onProgress, { ...cleared, transfer: "failed", delivery: "failed", withdrawalRequest: { attempted: false } });
|
|
6411
|
+
throw error;
|
|
6412
|
+
}
|
|
6413
|
+
return void 0;
|
|
6414
|
+
}
|
|
6415
|
+
if (this.serverLegStates.get(operationId) === "accepted" || afterUnknown) return accept("unknown", agentWallet);
|
|
6416
|
+
return this.serverWithdrawalNotSent(answer.code, state, chainId, token, session, onProgress, base7, route, "unknown");
|
|
6417
|
+
}
|
|
6418
|
+
if (answer.type === "unknown") return accept("unknown", agentWallet);
|
|
6419
|
+
if (answer.state === "failed" && answer.code && ZYFAI_WITHDRAWAL_NOT_SENT_CODES.has(answer.code))
|
|
6420
|
+
return this.serverWithdrawalNotSent(answer.code, state, chainId, token, session, onProgress, base7, route, true);
|
|
6421
|
+
if (answer.state === "failed" || answer.state === "expired") return accept("unknown", agentWallet);
|
|
6422
|
+
return accept(true, agentWallet, answer.result.txHash, answer.result.providerWithdrawalId);
|
|
6423
|
+
}
|
|
6424
|
+
/** A server answer proving the withdrawal was never sent: today's matching
|
|
6425
|
+
* error, with evidence transfer and delivery `failed`. "Already in progress"
|
|
6426
|
+
* keeps the hold: failure `withdrawal-in-progress`, never rejectedBeforeSubmission.
|
|
6427
|
+
* `serverTask`: true for a task that ended unsent (the server owns the leg's
|
|
6428
|
+
* close), "unknown" for a declined handoff whose code does not prove no task
|
|
6429
|
+
* (never false: such a leg is never cleared, ruling T9-I3). */
|
|
6430
|
+
serverWithdrawalNotSent(code, state, chainId, token, session, onProgress, base7, route, serverTask) {
|
|
6431
|
+
let failure;
|
|
6432
|
+
let error;
|
|
6433
|
+
const sessionUnavailable = () => new OwneyError(
|
|
6434
|
+
"AGENT_AUTH_FAILED",
|
|
6435
|
+
"Zyfai has no active withdrawal session. Its withdrawal was not submitted.",
|
|
6436
|
+
{ reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token, serverTask: true, rejectedBeforeSubmission: true },
|
|
6437
|
+
this.id
|
|
6438
|
+
);
|
|
6439
|
+
switch (code) {
|
|
6440
|
+
case "NEEDS_USER_SESSION":
|
|
6441
|
+
failure = "unauthorized";
|
|
6442
|
+
error = sessionUnavailable();
|
|
6443
|
+
this.dropWithdrawalSession(state, chainId, session);
|
|
6444
|
+
break;
|
|
6445
|
+
case "NEEDS_USER_SESSION_KEY":
|
|
6446
|
+
failure = "session-key-missing";
|
|
6447
|
+
error = sessionUnavailable();
|
|
6448
|
+
this.rearmWithdrawalSessionKey(state.walletAddress, chainId);
|
|
6449
|
+
break;
|
|
6450
|
+
case "WITHDRAW_INSUFFICIENT_BALANCE":
|
|
6451
|
+
failure = "insufficient-balance";
|
|
6452
|
+
error = new OwneyError(
|
|
6453
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
6454
|
+
"No funds remain in Zyfai for this asset.",
|
|
6455
|
+
{ available: "0", chainId, token, rejectedBeforeSubmission: true },
|
|
6456
|
+
this.id
|
|
6457
|
+
);
|
|
6458
|
+
break;
|
|
6459
|
+
case "WITHDRAWAL_IN_PROGRESS":
|
|
6460
|
+
case "ZYFAI_REDEMPTION_IN_FLIGHT":
|
|
6461
|
+
failure = "withdrawal-in-progress";
|
|
6462
|
+
error = new OwneyError(
|
|
6463
|
+
"WITHDRAW_IN_PROGRESS",
|
|
6464
|
+
"A Zyfai withdrawal is already in progress.",
|
|
6465
|
+
{ reason: code, chainId, token, serverTask: true },
|
|
6466
|
+
this.id
|
|
6467
|
+
);
|
|
6468
|
+
break;
|
|
6469
|
+
default:
|
|
6470
|
+
failure = code === "ZYFAI_FORBIDDEN" ? "forbidden" : "other";
|
|
6471
|
+
error = new OwneyError(
|
|
6472
|
+
"WITHDRAW_FAILED",
|
|
6473
|
+
"The server did not send the Zyfai withdrawal.",
|
|
6474
|
+
{ reason: code, serverTask: true, rejectedBeforeSubmission: true },
|
|
6475
|
+
this.id
|
|
6476
|
+
);
|
|
6477
|
+
}
|
|
6478
|
+
notifyFunding(onProgress, {
|
|
6479
|
+
...base7,
|
|
6480
|
+
transfer: "failed",
|
|
6481
|
+
delivery: "failed",
|
|
6482
|
+
serverTask,
|
|
6483
|
+
withdrawalRequest: { ...route, failure }
|
|
6484
|
+
});
|
|
6485
|
+
throw error;
|
|
6486
|
+
}
|
|
6487
|
+
/** Zyfai has no session key for this wallet: for a legacy (not pre-deployed)
|
|
6488
|
+
* wallet, forget the login's "active" flag and this session's attempt, so the
|
|
6489
|
+
* next `ensureWithdrawalSessionKey` prompts once. Sends and prompts nothing. */
|
|
6490
|
+
rearmWithdrawalSessionKey(owner, chainId) {
|
|
6491
|
+
const internals = this.sdk;
|
|
6492
|
+
if (internals.isPredeployed !== false || this.connectedAddress?.toLowerCase() !== owner.toLowerCase() || this.connectedChainId !== chainId) return;
|
|
6493
|
+
internals.hasActiveSessionKey = false;
|
|
6494
|
+
this.sessionKeyAttempts.delete(`${owner.toLowerCase()}:${chainId}`);
|
|
6495
|
+
}
|
|
6496
|
+
/** The routing API's handoff config. Only called with the opt-in and the key. */
|
|
6497
|
+
registrationHandoffConfig(timeoutMs) {
|
|
6498
|
+
return {
|
|
6499
|
+
apiKey: this.accountReporting.owneyApiKey,
|
|
6500
|
+
baseUrl: this.accountReporting.routingApiBaseUrl,
|
|
6501
|
+
timeoutMs,
|
|
6502
|
+
zyfaiServerTasks: true
|
|
6503
|
+
};
|
|
6504
|
+
}
|
|
6505
|
+
/**
|
|
6506
|
+
* Part 4: hands a deposit's registration to our server before its transfer.
|
|
6507
|
+
* Undefined when nothing was handed off, or when the server proved it has no
|
|
6508
|
+
* task (a decline in `ZYFAI_HANDOFF_NO_TASK_CODES`, rulings P4-11, T7-R1 and
|
|
6509
|
+
* T8-F2; for a retried create only the server's own declines, F-I2(i)):
|
|
6510
|
+
* today's local registration then runs. Any other answer means the
|
|
6511
|
+
* server holds the registration, or may hold it (an unknown answer, or
|
|
6512
|
+
* another decline), so the browser never registers it except by the
|
|
6513
|
+
* not-sent fallback. Never throws; never logs or returns the session.
|
|
6514
|
+
*/
|
|
6515
|
+
async handOffDepositRegistration(owner, chainId, asset, amount, agentWallet, operationId) {
|
|
6516
|
+
const session = this.executionSession();
|
|
6517
|
+
if (!session || !ADDRESS_PATTERN.test(owner) || !ADDRESS_PATTERN.test(agentWallet) || !REGISTRATION_AMOUNT.test(amount) || asset !== "USDC" && asset !== "WETH") return void 0;
|
|
6518
|
+
const retried = this.requestedRegistrations.has(operationId);
|
|
6519
|
+
if (!retried) {
|
|
6520
|
+
if (!zyfaiSessionOutlivesTask(session, "zyfai_deposit_registration")) return void 0;
|
|
6521
|
+
this.requestedRegistrations.add(operationId);
|
|
6522
|
+
while (this.requestedRegistrations.size > SERVER_LEG_STATES_LIMIT)
|
|
6523
|
+
this.requestedRegistrations.delete(this.requestedRegistrations.values().next().value);
|
|
6524
|
+
}
|
|
6525
|
+
const answer = await handOffZyfaiTask(
|
|
6526
|
+
this.registrationHandoffConfig(REGISTRATION_HANDOFF_TIMEOUT_MS),
|
|
6527
|
+
{ kind: "zyfai_deposit_registration", operationId, owner, chainId, asset, amount, agentWallet },
|
|
6528
|
+
session
|
|
6529
|
+
);
|
|
6530
|
+
if (answer.type === "declined" && provesNoServerTask(answer.code, retried)) return void 0;
|
|
6531
|
+
if (answer.type === "accepted") return { serverTask: true, serverTaskOperationId: operationId };
|
|
6532
|
+
const lost = answer.type === "unknown" || ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code);
|
|
6533
|
+
return { serverTask: "unknown", serverTaskOperationId: operationId, serverTaskCreate: lost ? "lost" : "declined" };
|
|
6534
|
+
}
|
|
6535
|
+
/**
|
|
6536
|
+
* Nudges the server's registration task of this deposit evidence (with
|
|
6537
|
+
* `serverTask`) and reads what its answer proves. Without the opt-in (read
|
|
6538
|
+
* now, unless this session sent the registration's create, F-I2(iii)), the
|
|
6539
|
+
* key, a session or the task's identity no nudge is sent and the outcome is
|
|
6540
|
+
* unknown. Never throws; never registers locally.
|
|
6541
|
+
*/
|
|
6542
|
+
async nudgeDepositRegistration(evidence, session) {
|
|
6543
|
+
const { owneyApiKey } = this.accountReporting;
|
|
6544
|
+
const operationId = evidence.serverTaskOperationId;
|
|
6545
|
+
const agentWallet = evidence.agentWallet;
|
|
6546
|
+
const chainId = evidence.chainId;
|
|
6547
|
+
if (typeof operationId !== "string" || !operationId || !this.serverTasksOn(this.requestedRegistrations.has(operationId)) || !owneyApiKey || !session || typeof agentWallet !== "string" || !ADDRESS_PATTERN.test(agentWallet) || !ADDRESS_PATTERN.test(evidence.walletAddress) || !REGISTRATION_AMOUNT.test(evidence.intendedAmount) || evidence.token !== "USDC" && evidence.token !== "WETH" || chainId !== 1 && chainId !== 8453 && chainId !== 42161)
|
|
6548
|
+
return { kind: "unknown" };
|
|
6549
|
+
const answer = await handOffZyfaiTask(
|
|
6550
|
+
this.registrationHandoffConfig(REGISTRATION_NUDGE_TIMEOUT_MS),
|
|
6551
|
+
{
|
|
6552
|
+
kind: "zyfai_deposit_registration",
|
|
6553
|
+
operationId,
|
|
6554
|
+
owner: evidence.walletAddress,
|
|
6555
|
+
chainId,
|
|
6556
|
+
asset: evidence.token,
|
|
6557
|
+
amount: evidence.intendedAmount,
|
|
6558
|
+
agentWallet,
|
|
6559
|
+
runNow: true
|
|
6560
|
+
},
|
|
6561
|
+
session
|
|
6562
|
+
);
|
|
6563
|
+
return registrationOutcome(answer, evidence.serverTask === true || evidence.serverTaskCreate !== "lost");
|
|
6564
|
+
}
|
|
6565
|
+
/** Registration recovery never signs, transfers, activates, or refreshes authentication.
|
|
6566
|
+
* For a deposit the server registers (`serverTask`), it nudges the server's
|
|
6567
|
+
* task instead: still pending gives a transient error, and an unknown or
|
|
6568
|
+
* expired outcome the status-409 error. It registers locally only when the
|
|
6569
|
+
* server proved it never sent the registration (ruling A4). */
|
|
5501
6570
|
async retryDepositRegistration(state, evidence) {
|
|
5502
6571
|
const executionSdk = this.sdk;
|
|
5503
6572
|
const internals = executionSdk;
|
|
@@ -5508,11 +6577,49 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5508
6577
|
throw new OwneyError("AGENT_API_ERROR", "Deposit transfer must be verified before registration recovery.");
|
|
5509
6578
|
}
|
|
5510
6579
|
const cid = isValidChainId(evidence.chainId);
|
|
6580
|
+
let cleared = {};
|
|
6581
|
+
if (evidence.serverTask === true || evidence.serverTask === "unknown") {
|
|
6582
|
+
const outcome = await this.nudgeDepositRegistration(evidence, internals.httpClient.authToken ?? void 0);
|
|
6583
|
+
if (outcome.kind === "confirmed") {
|
|
6584
|
+
this.pendingRegistrations.get(`${evidence.walletAddress.toLowerCase()}:${cid}`)?.delete(evidence.txHash.toLowerCase());
|
|
6585
|
+
this.ledgerReads.clear();
|
|
6586
|
+
return { ...evidence, registration: "confirmed", providerDepositId: outcome.providerDepositId, serverTask: true };
|
|
6587
|
+
}
|
|
6588
|
+
if (outcome.kind === "pending")
|
|
6589
|
+
throw new OwneyError("AGENT_API_ERROR", "Zyfai registration by the server is temporarily unavailable.");
|
|
6590
|
+
if (outcome.kind === "unknown") throw registrationOutcomeUnknown(outcome.code);
|
|
6591
|
+
cleared = { serverTask: false };
|
|
6592
|
+
}
|
|
5511
6593
|
const tokenAddress = evidence.token === "WETH" ? WETH_ADDRESS_BY_CHAIN[cid] : void 0;
|
|
5512
|
-
await executionSdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
|
|
6594
|
+
const logged = await executionSdk.logDeposit(cid, evidence.txHash, evidence.intendedAmount, tokenAddress);
|
|
6595
|
+
const providerDepositId = depositIdFromLog(logged);
|
|
5513
6596
|
this.pendingRegistrations.get(`${evidence.walletAddress.toLowerCase()}:${cid}`)?.delete(evidence.txHash.toLowerCase());
|
|
5514
6597
|
this.ledgerReads.clear();
|
|
5515
|
-
return { ...evidence, registration: "confirmed" };
|
|
6598
|
+
return { ...evidence, ...cleared, registration: "confirmed", ...providerDepositId ? { providerDepositId } : {} };
|
|
6599
|
+
}
|
|
6600
|
+
/** Reads a deposit's placement status by its id. Never signs, prompts or authenticates:
|
|
6601
|
+
* it uses only a session the vendor client already holds, and any failure is `null`. */
|
|
6602
|
+
async getDepositPlacement(state, depositId) {
|
|
6603
|
+
const internals = this.sdk;
|
|
6604
|
+
const client = internals.httpClient;
|
|
6605
|
+
if (!client?.authToken || typeof client.get !== "function" || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase()) return null;
|
|
6606
|
+
try {
|
|
6607
|
+
return mapDepositPlacement(await client.get(`/users/deposits/${encodeURIComponent(depositId)}/v2`));
|
|
6608
|
+
} catch {
|
|
6609
|
+
return null;
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6612
|
+
/** Reads a withdrawal's lifecycle by its id. Never signs, prompts or authenticates:
|
|
6613
|
+
* it uses only a session the vendor client already holds, and any failure is `null`. */
|
|
6614
|
+
async getWithdrawalLifecycle(state, withdrawalId) {
|
|
6615
|
+
const internals = this.sdk;
|
|
6616
|
+
const client = internals.httpClient;
|
|
6617
|
+
if (!client?.authToken || typeof client.get !== "function" || this.connectedAddress?.toLowerCase() !== state.walletAddress.toLowerCase()) return null;
|
|
6618
|
+
try {
|
|
6619
|
+
return mapWithdrawalLifecycle(await client.get(`/users/withdrawals/${encodeURIComponent(withdrawalId)}`));
|
|
6620
|
+
} catch {
|
|
6621
|
+
return null;
|
|
6622
|
+
}
|
|
5516
6623
|
}
|
|
5517
6624
|
// --- IAgent: Portfolio reads ---
|
|
5518
6625
|
async getFundingWallet(state, chainId) {
|
|
@@ -5742,16 +6849,16 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
5742
6849
|
}
|
|
5743
6850
|
const now = Date.now();
|
|
5744
6851
|
const pending2 = [...this.pendingRegistrations.get(scope)?.values() ?? []].map((entry) => entry.at);
|
|
5745
|
-
const
|
|
6852
|
+
const active = zyfaiHoldActive({
|
|
5746
6853
|
movements,
|
|
5747
6854
|
pendingRegistrations: pending2,
|
|
5748
6855
|
lastCheckTimestamp: raw2?.data?.lastCheckTimestamp,
|
|
5749
6856
|
lastLogDate: raw2?.data?.lastLogDate?.[String(chainId)],
|
|
5750
6857
|
now
|
|
5751
6858
|
});
|
|
5752
|
-
if (
|
|
6859
|
+
if (active) this.holdActiveScopes.add(scope);
|
|
5753
6860
|
else this.holdActiveScopes.delete(scope);
|
|
5754
|
-
if (!
|
|
6861
|
+
if (!active) {
|
|
5755
6862
|
this.lastGoodEarnings.set(key2, raw2);
|
|
5756
6863
|
return { value: raw2, active: false };
|
|
5757
6864
|
}
|
|
@@ -6719,7 +7826,7 @@ function actionEntry(action) {
|
|
|
6719
7826
|
rebalanceLog: []
|
|
6720
7827
|
};
|
|
6721
7828
|
}
|
|
6722
|
-
var
|
|
7829
|
+
var TX_HASH3 = /^0x[a-fA-F0-9]{64}$/;
|
|
6723
7830
|
function allocationLegs(value, context, requireAmount) {
|
|
6724
7831
|
if (!Array.isArray(value)) return void 0;
|
|
6725
7832
|
const legs = [];
|
|
@@ -6748,7 +7855,7 @@ function allocationEntry(action, context) {
|
|
|
6748
7855
|
if (action.actionType.toLowerCase() !== "autoseek") return void 0;
|
|
6749
7856
|
const hash2 = action.details?.depositTransactionHash;
|
|
6750
7857
|
const withdrawHash = action.details?.withdrawTransactionHash;
|
|
6751
|
-
if (typeof hash2 !== "string" || !
|
|
7858
|
+
if (typeof hash2 !== "string" || !TX_HASH3.test(hash2)) return null;
|
|
6752
7859
|
const deposits = allocationLegs(action.details?.deposits, context, true);
|
|
6753
7860
|
const withdrawals = action.details?.withdrawals == null ? [] : allocationLegs(action.details.withdrawals, context, false);
|
|
6754
7861
|
if (!deposits?.length || !withdrawals) return null;
|
|
@@ -6761,7 +7868,7 @@ function allocationEntry(action, context) {
|
|
|
6761
7868
|
}));
|
|
6762
7869
|
const amountRaw = deposits.reduce((total, leg) => total + leg.rawAmount, 0n);
|
|
6763
7870
|
const rebalance = withdrawals.length > 0;
|
|
6764
|
-
const hashes = rebalance && typeof withdrawHash === "string" &&
|
|
7871
|
+
const hashes = rebalance && typeof withdrawHash === "string" && TX_HASH3.test(withdrawHash) && withdrawHash.toLowerCase() !== hash2.toLowerCase() ? [hash2, withdrawHash] : [hash2];
|
|
6765
7872
|
const source = withdrawals.length === 1 ? withdrawals[0] : {
|
|
6766
7873
|
protocol: [...new Set(withdrawals.map((leg) => leg.protocol))].join(" + "),
|
|
6767
7874
|
pool: withdrawals.map((leg) => leg.pool).filter(Boolean).join(" + ") || void 0
|
|
@@ -7145,6 +8252,7 @@ function operationHash(operation) {
|
|
|
7145
8252
|
}
|
|
7146
8253
|
});
|
|
7147
8254
|
}
|
|
8255
|
+
var YIELDSEEKER_WITHDRAW_SERVER_SUBMIT = 1;
|
|
7148
8256
|
function sponsorError(message, safeToFallback, details = {}) {
|
|
7149
8257
|
return new OwneyError(
|
|
7150
8258
|
"SPONSOR_REQUEST_FAILED",
|
|
@@ -7167,6 +8275,22 @@ function assertIntentFresh(expiresAt) {
|
|
|
7167
8275
|
{ failureCode: "SPONSOR_INTENT_EXPIRED" }
|
|
7168
8276
|
);
|
|
7169
8277
|
}
|
|
8278
|
+
var PAYMASTER_EXPIRY_MARGIN_SECONDS = 30;
|
|
8279
|
+
function paymasterValidUntil(paymasterAndData) {
|
|
8280
|
+
if (paymasterAndData.length < 2 + 64) return void 0;
|
|
8281
|
+
const validUntil = Number.parseInt(paymasterAndData.slice(42, 54), 16);
|
|
8282
|
+
const now = Date.now() / 1e3;
|
|
8283
|
+
return validUntil > now - 86400 && validUntil < now + 86400 ? validUntil : void 0;
|
|
8284
|
+
}
|
|
8285
|
+
function assertPaymasterFresh(paymasterAndData) {
|
|
8286
|
+
const validUntil = paymasterValidUntil(paymasterAndData);
|
|
8287
|
+
if (validUntil !== void 0 && Date.now() / 1e3 + PAYMASTER_EXPIRY_MARGIN_SECONDS >= validUntil)
|
|
8288
|
+
throw sponsorError(
|
|
8289
|
+
"The withdrawal sponsorship expired while waiting for the signature. Please try again.",
|
|
8290
|
+
true,
|
|
8291
|
+
{ failureCode: "SPONSOR_INTENT_EXPIRED" }
|
|
8292
|
+
);
|
|
8293
|
+
}
|
|
7170
8294
|
function providerReason(value) {
|
|
7171
8295
|
if (typeof value !== "string") return void 0;
|
|
7172
8296
|
return value.replace(/https?:\/\/\S+/gi, "[url]").replace(/0x[0-9a-fA-F]{40,}/g, "[hex]").replace(/[A-Za-z0-9_+=\/-]{48,}/g, "[token]").slice(0, 300);
|
|
@@ -7248,6 +8372,12 @@ async function checkpoint(callback, pending2) {
|
|
|
7248
8372
|
);
|
|
7249
8373
|
}
|
|
7250
8374
|
}
|
|
8375
|
+
function reportAccepted(onAccepted, accepted) {
|
|
8376
|
+
try {
|
|
8377
|
+
onAccepted?.(accepted);
|
|
8378
|
+
} catch {
|
|
8379
|
+
}
|
|
8380
|
+
}
|
|
7251
8381
|
function finalPaymasterFields(value) {
|
|
7252
8382
|
const result = {};
|
|
7253
8383
|
for (const key2 of [
|
|
@@ -7326,15 +8456,57 @@ async function requestIntent(input) {
|
|
|
7326
8456
|
url.searchParams.set("intent", intent.intent);
|
|
7327
8457
|
return { intent, rpcUrl: url.toString() };
|
|
7328
8458
|
}
|
|
8459
|
+
var RPC_TIMEOUT_MS = 15e3;
|
|
8460
|
+
var SERVER_SUBMIT_SEND_TIMEOUT_MS = 25e3;
|
|
8461
|
+
function postRpc(url, method, params, timeoutMs) {
|
|
8462
|
+
return fetch(url, {
|
|
8463
|
+
method: "POST",
|
|
8464
|
+
headers: { "content-type": "application/json" },
|
|
8465
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
8466
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
8467
|
+
});
|
|
8468
|
+
}
|
|
8469
|
+
function rpcRejection(status, payload, method, phase) {
|
|
8470
|
+
const rejection = payload?.error;
|
|
8471
|
+
const diagnostics = rpcDiagnostics(rejection?.data);
|
|
8472
|
+
const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
|
|
8473
|
+
const details = {
|
|
8474
|
+
statusCode: status,
|
|
8475
|
+
rpcCode: rejection?.code,
|
|
8476
|
+
rpcMethod: method,
|
|
8477
|
+
phase,
|
|
8478
|
+
providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
|
|
8479
|
+
failureCode: "RPC_REJECTED",
|
|
8480
|
+
...diagnostics
|
|
8481
|
+
};
|
|
8482
|
+
if (phase === "prepare" && executionReverted)
|
|
8483
|
+
return new OwneyError(
|
|
8484
|
+
"WITHDRAW_FAILED",
|
|
8485
|
+
"Yieldseeker withdrawal simulation reverted before submission.",
|
|
8486
|
+
{
|
|
8487
|
+
...details,
|
|
8488
|
+
failureCode: "SPONSOR_EXECUTION_REVERTED",
|
|
8489
|
+
safeToFallback: false,
|
|
8490
|
+
rejectedBeforeSubmission: true
|
|
8491
|
+
},
|
|
8492
|
+
"yieldseeker"
|
|
8493
|
+
);
|
|
8494
|
+
const definitiveRejection = phase === "send" && !executionReverted && status < 500 && !!rejection && (rejection.data?.rejectedBeforeSubmission === true || [-32501, -32502, -32503, -32504, -32505, -32507].includes(rejection.code ?? 0) || // AA3x: the paymaster failed validation (e.g. AA32 expired), so the
|
|
8495
|
+
// bundler never accepted it. AA2x stays ambiguous (AA25 can mean it ran).
|
|
8496
|
+
/\bAA3\d\b/.test(rejection.message ?? "") || /maxFeePerGas.*(?:too low|lower than|less than|must be at least)/i.test(rejection.message ?? ""));
|
|
8497
|
+
return sponsorError(
|
|
8498
|
+
phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
|
|
8499
|
+
phase === "prepare" || definitiveRejection,
|
|
8500
|
+
{
|
|
8501
|
+
...details,
|
|
8502
|
+
...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
|
|
8503
|
+
}
|
|
8504
|
+
);
|
|
8505
|
+
}
|
|
7329
8506
|
async function rpc(url, method, params, phase) {
|
|
7330
8507
|
let response;
|
|
7331
8508
|
try {
|
|
7332
|
-
response = await
|
|
7333
|
-
method: "POST",
|
|
7334
|
-
headers: { "content-type": "application/json" },
|
|
7335
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
7336
|
-
signal: AbortSignal.timeout(15e3)
|
|
7337
|
-
});
|
|
8509
|
+
response = await postRpc(url, method, params, RPC_TIMEOUT_MS);
|
|
7338
8510
|
} catch {
|
|
7339
8511
|
throw sponsorError(
|
|
7340
8512
|
"Withdrawal sponsorship request status is unknown.",
|
|
@@ -7348,43 +8520,37 @@ async function rpc(url, method, params, phase) {
|
|
|
7348
8520
|
);
|
|
7349
8521
|
}
|
|
7350
8522
|
const payload = await response.json().catch(() => null);
|
|
7351
|
-
if (!response.ok || payload?.error || payload?.result === void 0)
|
|
7352
|
-
|
|
7353
|
-
const diagnostics = rpcDiagnostics(rejection?.data);
|
|
7354
|
-
const executionReverted = rejection?.code === -32521 || diagnostics.failureCode === "SPONSOR_EXECUTION_REVERTED";
|
|
7355
|
-
const details = {
|
|
7356
|
-
statusCode: response.status,
|
|
7357
|
-
rpcCode: rejection?.code,
|
|
7358
|
-
rpcMethod: method,
|
|
7359
|
-
phase,
|
|
7360
|
-
providerReason: providerReason(rejection?.data?.providerReason) ?? providerReason(rejection?.message),
|
|
7361
|
-
failureCode: "RPC_REJECTED",
|
|
7362
|
-
...diagnostics
|
|
7363
|
-
};
|
|
7364
|
-
if (phase === "prepare" && executionReverted)
|
|
7365
|
-
throw new OwneyError(
|
|
7366
|
-
"WITHDRAW_FAILED",
|
|
7367
|
-
"Yieldseeker withdrawal simulation reverted before submission.",
|
|
7368
|
-
{
|
|
7369
|
-
...details,
|
|
7370
|
-
failureCode: "SPONSOR_EXECUTION_REVERTED",
|
|
7371
|
-
safeToFallback: false,
|
|
7372
|
-
rejectedBeforeSubmission: true
|
|
7373
|
-
},
|
|
7374
|
-
"yieldseeker"
|
|
7375
|
-
);
|
|
7376
|
-
const definitiveRejection = phase === "send" && !executionReverted && response.status < 500 && !!rejection && (rejection.data?.rejectedBeforeSubmission === true || [-32501, -32502, -32503, -32504, -32505, -32507].includes(rejection.code ?? 0) || /maxFeePerGas.*(?:too low|lower than|less than|must be at least)/i.test(rejection.message ?? ""));
|
|
7377
|
-
throw sponsorError(
|
|
7378
|
-
phase !== "prepare" && !definitiveRejection ? "Sponsored withdrawal submission status is unknown." : "Withdrawal sponsorship was declined before submission.",
|
|
7379
|
-
phase === "prepare" || definitiveRejection,
|
|
7380
|
-
{
|
|
7381
|
-
...details,
|
|
7382
|
-
...phase === "prepare" || definitiveRejection ? { rejectedBeforeSubmission: true } : {}
|
|
7383
|
-
}
|
|
7384
|
-
);
|
|
7385
|
-
}
|
|
8523
|
+
if (!response.ok || payload?.error || payload?.result === void 0)
|
|
8524
|
+
throw rpcRejection(response.status, payload, method, phase);
|
|
7386
8525
|
return payload.result;
|
|
7387
8526
|
}
|
|
8527
|
+
async function sendSignedUserOperation(rpcUrl, operation, operationId) {
|
|
8528
|
+
const method = "eth_sendUserOperation";
|
|
8529
|
+
const params = [operation, ENTRY_POINT_V06];
|
|
8530
|
+
if (!operationId) return { type: "accepted", hash: await rpc(rpcUrl, method, params, "send"), serverSubmit: false };
|
|
8531
|
+
const url = new URL(rpcUrl);
|
|
8532
|
+
url.searchParams.set("operationId", operationId);
|
|
8533
|
+
url.searchParams.set("serverSubmit", "1");
|
|
8534
|
+
let response;
|
|
8535
|
+
try {
|
|
8536
|
+
response = await postRpc(url.toString(), method, params, SERVER_SUBMIT_SEND_TIMEOUT_MS);
|
|
8537
|
+
} catch {
|
|
8538
|
+
return { type: "pending", serverSubmit: "unknown" };
|
|
8539
|
+
}
|
|
8540
|
+
const payload = await response.json().catch(() => null);
|
|
8541
|
+
if (!payload) return { type: "pending", serverSubmit: "unknown" };
|
|
8542
|
+
if (response.ok && !payload.error && payload.result !== void 0)
|
|
8543
|
+
return {
|
|
8544
|
+
type: "accepted",
|
|
8545
|
+
hash: payload.result,
|
|
8546
|
+
serverSubmit: payload.owney?.serverSubmit === true && payload.owney.state === "accepted"
|
|
8547
|
+
};
|
|
8548
|
+
const rejection = rpcRejection(response.status, payload, method, "send");
|
|
8549
|
+
if (rejection.details?.rejectedBeforeSubmission === true) throw rejection;
|
|
8550
|
+
if (payload.error?.data?.serverSubmit === true) return { type: "pending", serverSubmit: true };
|
|
8551
|
+
if (response.status >= 500) return { type: "pending", serverSubmit: "unknown" };
|
|
8552
|
+
throw rejection;
|
|
8553
|
+
}
|
|
7388
8554
|
async function waitForTransaction(client, txHash) {
|
|
7389
8555
|
const receipt = await client.waitForTransactionReceipt({ hash: txHash, confirmations: 1 });
|
|
7390
8556
|
if (receipt.status !== "success")
|
|
@@ -7466,6 +8632,7 @@ async function submitEoa(input) {
|
|
|
7466
8632
|
operation = { ...operation, signature };
|
|
7467
8633
|
await assertOwnerSession(input.state, input.owner);
|
|
7468
8634
|
assertIntentFresh(input.expiresAt);
|
|
8635
|
+
assertPaymasterFresh(operation.paymasterAndData);
|
|
7469
8636
|
const userOperation = {
|
|
7470
8637
|
sender: operation.sender,
|
|
7471
8638
|
nonce: operation.nonce,
|
|
@@ -7482,18 +8649,19 @@ async function submitEoa(input) {
|
|
|
7482
8649
|
input.assertCurrent?.();
|
|
7483
8650
|
await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
|
|
7484
8651
|
input.assertCurrent?.();
|
|
7485
|
-
const
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
);
|
|
8652
|
+
const sent = await sendSignedUserOperation(input.rpcUrl, operation, input.operationId);
|
|
8653
|
+
const serverSubmit = sent.type === "pending" ? sent.serverSubmit : sent.serverSubmit ? true : void 0;
|
|
8654
|
+
if (serverSubmit) savePending(input.onPending, { userOpHash, serverSubmit });
|
|
8655
|
+
if (sent.type === "accepted") {
|
|
8656
|
+
const submittedHash = sent.hash;
|
|
8657
|
+
if (!isHash(submittedHash) || submittedHash.toLowerCase() !== userOpHash.toLowerCase())
|
|
8658
|
+
throw sponsorError(
|
|
8659
|
+
"Bundler returned a different UserOperation hash.",
|
|
8660
|
+
false,
|
|
8661
|
+
{ userOpHash, submittedHash, phase: "send", failureCode: "BUNDLER_HASH_MISMATCH" }
|
|
8662
|
+
);
|
|
8663
|
+
reportAccepted(input.onAccepted, { userOpHash });
|
|
8664
|
+
}
|
|
7497
8665
|
const deadline = Date.now() + OBSERVATION_WINDOW_MS;
|
|
7498
8666
|
let lastReadError;
|
|
7499
8667
|
for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
|
|
@@ -7526,6 +8694,7 @@ async function submitEoa(input) {
|
|
|
7526
8694
|
userOpHash,
|
|
7527
8695
|
phase: "observe",
|
|
7528
8696
|
failureCode: "RECEIPT_UNRESOLVED",
|
|
8697
|
+
...serverSubmit ? { serverSubmit } : {},
|
|
7529
8698
|
...lastReadError instanceof OwneyError ? { lastObservationError: lastReadError.details } : {}
|
|
7530
8699
|
}
|
|
7531
8700
|
);
|
|
@@ -7586,6 +8755,7 @@ async function submitSmart(input) {
|
|
|
7586
8755
|
{ callsId: requestedId, phase: "send", failureCode: "WALLET_SEND_UNRESOLVED" }
|
|
7587
8756
|
);
|
|
7588
8757
|
savePending(input.onPending, { callsId });
|
|
8758
|
+
reportAccepted(input.onAccepted, { callsId });
|
|
7589
8759
|
const deadline = Date.now() + OBSERVATION_WINDOW_MS;
|
|
7590
8760
|
for (let poll = 0; poll < 40 && Date.now() < deadline; poll++) {
|
|
7591
8761
|
let status;
|
|
@@ -7879,6 +9049,9 @@ var YieldseekerAgent = class {
|
|
|
7879
9049
|
endpointReadFailures = /* @__PURE__ */ new Map();
|
|
7880
9050
|
reconcileUntil = /* @__PURE__ */ new Map();
|
|
7881
9051
|
allocationRefresh = /* @__PURE__ */ new Set();
|
|
9052
|
+
/** Contexts whose next permitted snapshot read is forced once: a funding
|
|
9053
|
+
* invalidation with a tx hash (receipt recovery, a re-armed refresh). */
|
|
9054
|
+
forceNextSnapshot = /* @__PURE__ */ new Set();
|
|
7882
9055
|
activityRefreshUntil = /* @__PURE__ */ new Map();
|
|
7883
9056
|
// null means a confirmed movement has no reliable baseline. Do not guess
|
|
7884
9057
|
// settlement; keep slow reconciliation after the fast window expires.
|
|
@@ -8285,6 +9458,7 @@ var YieldseekerAgent = class {
|
|
|
8285
9458
|
this.endpointReadFailures.clear();
|
|
8286
9459
|
this.reconcileUntil.clear();
|
|
8287
9460
|
this.allocationRefresh.clear();
|
|
9461
|
+
this.forceNextSnapshot.clear();
|
|
8288
9462
|
this.activityRefreshUntil.clear();
|
|
8289
9463
|
this.snapshotMovements.clear();
|
|
8290
9464
|
this.movementEpochs.clear();
|
|
@@ -8398,7 +9572,7 @@ var YieldseekerAgent = class {
|
|
|
8398
9572
|
notifyFunding(onProgress, evidence);
|
|
8399
9573
|
return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
|
|
8400
9574
|
}
|
|
8401
|
-
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation) {
|
|
9575
|
+
async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation, submission) {
|
|
8402
9576
|
let ownerTransferAttempted = false;
|
|
8403
9577
|
let evidence = {
|
|
8404
9578
|
agentId: this.id,
|
|
@@ -8411,12 +9585,23 @@ var YieldseekerAgent = class {
|
|
|
8411
9585
|
delivery: "pending"
|
|
8412
9586
|
};
|
|
8413
9587
|
try {
|
|
8414
|
-
return await this.withdrawInternal(
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
9588
|
+
return await this.withdrawInternal(
|
|
9589
|
+
state,
|
|
9590
|
+
chainId,
|
|
9591
|
+
asset,
|
|
9592
|
+
amount,
|
|
9593
|
+
(next) => {
|
|
9594
|
+
evidence = { ...evidence, ...next };
|
|
9595
|
+
notifyFunding(onProgress, evidence);
|
|
9596
|
+
},
|
|
9597
|
+
() => {
|
|
9598
|
+
ownerTransferAttempted = true;
|
|
9599
|
+
},
|
|
9600
|
+
gasMode,
|
|
9601
|
+
onBeforeSubmission,
|
|
9602
|
+
assertOperation,
|
|
9603
|
+
submission?.operationId
|
|
9604
|
+
);
|
|
8420
9605
|
} catch (error) {
|
|
8421
9606
|
if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") {
|
|
8422
9607
|
this.withdrawalVersionCache.clear();
|
|
@@ -8432,7 +9617,7 @@ var YieldseekerAgent = class {
|
|
|
8432
9617
|
throw error;
|
|
8433
9618
|
}
|
|
8434
9619
|
}
|
|
8435
|
-
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission, assertOperation) {
|
|
9620
|
+
async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission, assertOperation, operationId) {
|
|
8436
9621
|
this.assertChain(chainId);
|
|
8437
9622
|
this.assertAsset(asset);
|
|
8438
9623
|
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
@@ -8564,13 +9749,13 @@ var YieldseekerAgent = class {
|
|
|
8564
9749
|
const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
|
|
8565
9750
|
const verifiedPositions = [];
|
|
8566
9751
|
if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
|
|
8567
|
-
let
|
|
9752
|
+
let active = true;
|
|
8568
9753
|
const checks = this.readVaultShares(values.map((position2) => position2.vaultOption), agentWallet, blockNumber).catch(() => []);
|
|
8569
9754
|
const reads = values.map(async (position2, index) => {
|
|
8570
9755
|
const address = (0, import_viem17.getAddress)(position2.vaultOption.address);
|
|
8571
9756
|
try {
|
|
8572
9757
|
const group = (await checks).slice(index * 2, index * 2 + 2);
|
|
8573
|
-
if (!
|
|
9758
|
+
if (!active || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
|
|
8574
9759
|
this.assertSession(generation, chainId, asset);
|
|
8575
9760
|
const [underlying, shares] = group.map((value) => value.result);
|
|
8576
9761
|
if (typeof underlying !== "string" || !(0, import_viem17.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
|
|
@@ -8583,7 +9768,7 @@ var YieldseekerAgent = class {
|
|
|
8583
9768
|
shares,
|
|
8584
9769
|
blockNumber
|
|
8585
9770
|
);
|
|
8586
|
-
if (!
|
|
9771
|
+
if (!active || value === void 0) return null;
|
|
8587
9772
|
this.assertSession(generation, chainId, asset);
|
|
8588
9773
|
verified.withdrawableAssetsRaw = value.toString();
|
|
8589
9774
|
verifiedPositions.push(verified);
|
|
@@ -8600,7 +9785,7 @@ var YieldseekerAgent = class {
|
|
|
8600
9785
|
args: [shares],
|
|
8601
9786
|
blockNumber
|
|
8602
9787
|
});
|
|
8603
|
-
if (!
|
|
9788
|
+
if (!active || typeof value !== "bigint" || value < 0n) return null;
|
|
8604
9789
|
this.assertSession(generation, chainId, asset);
|
|
8605
9790
|
if (value > 0n && !positiveValue) {
|
|
8606
9791
|
positiveValue = true;
|
|
@@ -8628,19 +9813,20 @@ var YieldseekerAgent = class {
|
|
|
8628
9813
|
}
|
|
8629
9814
|
});
|
|
8630
9815
|
const result = await boundedVerification(Promise.all(reads), timeoutMs);
|
|
8631
|
-
|
|
9816
|
+
active = false;
|
|
8632
9817
|
this.assertSession(generation, chainId, asset);
|
|
8633
9818
|
return {
|
|
8634
9819
|
verified: result !== void 0 && result.every((position2) => position2 !== null),
|
|
8635
9820
|
positions: [...verifiedPositions]
|
|
8636
9821
|
};
|
|
8637
9822
|
};
|
|
8638
|
-
const refreshPositions = async () => {
|
|
9823
|
+
const refreshPositions = async (promptless = false) => {
|
|
8639
9824
|
this.assertSession(generation, chainId, asset);
|
|
8640
9825
|
const response = await this.walletRequest(
|
|
8641
9826
|
state,
|
|
8642
9827
|
chainId,
|
|
8643
|
-
this.agentPath(context, "vault-positions")
|
|
9828
|
+
this.agentPath(context, "vault-positions"),
|
|
9829
|
+
{ promptless }
|
|
8644
9830
|
);
|
|
8645
9831
|
this.assertSession(generation, chainId, asset);
|
|
8646
9832
|
if (!Array.isArray(response?.vaultPositions)) throw this.invalidResponse("vault positions");
|
|
@@ -8871,6 +10057,8 @@ var YieldseekerAgent = class {
|
|
|
8871
10057
|
agentWallet: (0, import_viem17.getAddress)(context.wallet.walletAddress),
|
|
8872
10058
|
asset: metadata.address,
|
|
8873
10059
|
callData: data,
|
|
10060
|
+
// The EOA send opts in to the server-owned send with it.
|
|
10061
|
+
operationId,
|
|
8874
10062
|
onPending: (pending2) => {
|
|
8875
10063
|
submissionEvidence = { ...submissionEvidence, ...pending2 };
|
|
8876
10064
|
notifyFunding(onProgress, submissionEvidence);
|
|
@@ -8879,6 +10067,11 @@ var YieldseekerAgent = class {
|
|
|
8879
10067
|
assertOperation?.();
|
|
8880
10068
|
await onBeforeSubmission?.({ ...base7, ...pending2 });
|
|
8881
10069
|
onOwnerTransferAttempted?.();
|
|
10070
|
+
},
|
|
10071
|
+
// The owner's signed operation was accepted: another agent may start.
|
|
10072
|
+
onAccepted: (accepted) => {
|
|
10073
|
+
submissionEvidence = { ...submissionEvidence, ...accepted, ownerAuthorization: "accepted" };
|
|
10074
|
+
notifyFunding(onProgress, submissionEvidence);
|
|
8882
10075
|
}
|
|
8883
10076
|
});
|
|
8884
10077
|
txHash = submitted.txHash;
|
|
@@ -8895,7 +10088,10 @@ var YieldseekerAgent = class {
|
|
|
8895
10088
|
value: "0",
|
|
8896
10089
|
chainId
|
|
8897
10090
|
},
|
|
8898
|
-
(hash2) =>
|
|
10091
|
+
(hash2) => {
|
|
10092
|
+
submissionEvidence = { ...submissionEvidence, txHash: hash2, ownerAuthorization: "accepted" };
|
|
10093
|
+
notifyFunding(onProgress, submissionEvidence);
|
|
10094
|
+
},
|
|
8899
10095
|
(value) => {
|
|
8900
10096
|
receipt = value;
|
|
8901
10097
|
},
|
|
@@ -8926,7 +10122,9 @@ var YieldseekerAgent = class {
|
|
|
8926
10122
|
const verificationDeadline = Date.now() + YIELDSEEKER_MAX_POSITION_VERIFICATION_MS;
|
|
8927
10123
|
const knownAtPayout = new Set(knownPositions.keys());
|
|
8928
10124
|
const [catalogVerified, knownProof] = await Promise.all([
|
|
8929
|
-
|
|
10125
|
+
// Never a sign-in prompt here: a refused or missing cached sign-in
|
|
10126
|
+
// leaves the catalog unverified, the existing unverified-MAX outcome.
|
|
10127
|
+
boundedVerification(refreshPositions(true).then((values) => {
|
|
8930
10128
|
rememberPositions(values);
|
|
8931
10129
|
return true;
|
|
8932
10130
|
}).catch(() => false)),
|
|
@@ -8971,7 +10169,8 @@ var YieldseekerAgent = class {
|
|
|
8971
10169
|
);
|
|
8972
10170
|
const confirmedAmount = evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) ? BigInt(evidence.confirmedAmount) : void 0;
|
|
8973
10171
|
const feeIncrease = ownerFunds.fees - initialFunds.fees;
|
|
8974
|
-
const
|
|
10172
|
+
const feeShortfall = requested - payout;
|
|
10173
|
+
const feeAdjustment = amount !== void 0 && receipt && initialFunds.blockNumber !== void 0 && latestUnwindBlock !== void 0 && initialFunds.blockNumber <= latestUnwindBlock && receipt.blockNumber >= latestUnwindBlock && evidence.transfer === "confirmed" && evidence.delivery === "confirmed" && confirmedAmount === payout && feeIncrease > 0n && feeIncrease <= maxFee && feeShortfall > 0n && feeShortfall <= feeIncrease ? { grossAmount: requested.toString(), feeAmount: feeShortfall.toString() } : void 0;
|
|
8975
10174
|
return {
|
|
8976
10175
|
txHash,
|
|
8977
10176
|
type: amount === void 0 && !completion ? "full" : "partial",
|
|
@@ -9033,14 +10232,14 @@ var YieldseekerAgent = class {
|
|
|
9033
10232
|
}
|
|
9034
10233
|
/** Recheck MAX without re-unwinding, paying out, or requesting a signature. */
|
|
9035
10234
|
async verifyWithdrawalCompletion(state, evidence) {
|
|
9036
|
-
const
|
|
10235
|
+
const unknown2 = (reason2) => ({ status: "unavailable", reason: reason2 });
|
|
9037
10236
|
const proof = evidence.maxCompletion;
|
|
9038
|
-
if (evidence.agentId !== this.id || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.chainId !== 8453 || !proof || !evidence.agentWallet || !(0, import_viem17.isAddress)(evidence.agentWallet) || !evidence.blockNumber || !/^\d+$/.test(evidence.blockNumber) || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || !this.isTransactionHash(evidence.txHash)) return
|
|
10237
|
+
if (evidence.agentId !== this.id || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.chainId !== 8453 || !proof || !evidence.agentWallet || !(0, import_viem17.isAddress)(evidence.agentWallet) || !evidence.blockNumber || !/^\d+$/.test(evidence.blockNumber) || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || !this.isTransactionHash(evidence.txHash)) return unknown2("PAYOUT_PROOF_UNAVAILABLE");
|
|
9039
10238
|
this.assertAsset(evidence.token);
|
|
9040
10239
|
const asset = evidence.token;
|
|
9041
10240
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
9042
10241
|
const signature = this.auth.peekToken(state, evidence.chainId);
|
|
9043
|
-
if (!signature) return
|
|
10242
|
+
if (!signature) return unknown2("AUTHENTICATION_REQUIRED");
|
|
9044
10243
|
let reason = "CATALOG_UNAVAILABLE";
|
|
9045
10244
|
try {
|
|
9046
10245
|
return await bounded(async () => {
|
|
@@ -9048,7 +10247,7 @@ var YieldseekerAgent = class {
|
|
|
9048
10247
|
`/users/${encodeURIComponent(proof.userId)}/agents/${encodeURIComponent(proof.agentId)}/vault-positions`,
|
|
9049
10248
|
{ signature }
|
|
9050
10249
|
);
|
|
9051
|
-
if (!Array.isArray(response?.vaultPositions)) return
|
|
10250
|
+
if (!Array.isArray(response?.vaultPositions)) return unknown2("CATALOG_INVALID");
|
|
9052
10251
|
const positions = this.validateWithdrawalPositions(response.vaultPositions, evidence.chainId, asset);
|
|
9053
10252
|
const vaults = new Map(proof.vaults.map((address) => [address.toLowerCase(), { address }]));
|
|
9054
10253
|
positions.forEach((p) => vaults.set(p.vaultOption.address.toLowerCase(), p.vaultOption));
|
|
@@ -9056,14 +10255,14 @@ var YieldseekerAgent = class {
|
|
|
9056
10255
|
if (needsInventory || [...vaults.values()].some((option) => !option.provider)) {
|
|
9057
10256
|
const catalog = await this.loadVaultOptions(asset).catch(() => []);
|
|
9058
10257
|
const eligible = catalog.filter((o) => o.chainId === evidence.chainId && o.assetAddress?.toLowerCase() === metadata.address.toLowerCase() && (0, import_viem17.isAddress)(o.address));
|
|
9059
|
-
if (needsInventory && !eligible.length) return
|
|
10258
|
+
if (needsInventory && !eligible.length) return unknown2("INVENTORY_INCOMPLETE");
|
|
9060
10259
|
for (const option of eligible) {
|
|
9061
10260
|
const key2 = option.address.toLowerCase();
|
|
9062
10261
|
if (needsInventory || vaults.has(key2)) vaults.set(key2, option);
|
|
9063
10262
|
}
|
|
9064
10263
|
}
|
|
9065
10264
|
if (vaults.size > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT || [...vaults.keys()].some((a) => !(0, import_viem17.isAddress)(a)))
|
|
9066
|
-
return
|
|
10265
|
+
return unknown2("INVENTORY_INVALID");
|
|
9067
10266
|
reason = "VAULT_PROOF_UNAVAILABLE";
|
|
9068
10267
|
const blockNumber = BigInt(evidence.blockNumber);
|
|
9069
10268
|
const options = [...vaults.values()];
|
|
@@ -9094,7 +10293,7 @@ var YieldseekerAgent = class {
|
|
|
9094
10293
|
};
|
|
9095
10294
|
}, 8e3, () => new Error("MAX proof read timed out"));
|
|
9096
10295
|
} catch {
|
|
9097
|
-
return
|
|
10296
|
+
return unknown2(reason);
|
|
9098
10297
|
}
|
|
9099
10298
|
}
|
|
9100
10299
|
async readVaultShares(options, wallet, blockNumber) {
|
|
@@ -9175,12 +10374,12 @@ var YieldseekerAgent = class {
|
|
|
9175
10374
|
{ asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
|
|
9176
10375
|
this.id
|
|
9177
10376
|
);
|
|
9178
|
-
let
|
|
10377
|
+
let active = true;
|
|
9179
10378
|
let knownProof;
|
|
9180
10379
|
const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
|
|
9181
10380
|
const assertReadCurrent = () => {
|
|
9182
10381
|
this.assertSession(generation, chainId, context.asset);
|
|
9183
|
-
if (!
|
|
10382
|
+
if (!active || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
|
|
9184
10383
|
};
|
|
9185
10384
|
const read2 = async () => {
|
|
9186
10385
|
const client = this.getReceiptClient();
|
|
@@ -9369,7 +10568,7 @@ var YieldseekerAgent = class {
|
|
|
9369
10568
|
if (error instanceof OwneyError) throw error;
|
|
9370
10569
|
throw unavailable();
|
|
9371
10570
|
} finally {
|
|
9372
|
-
|
|
10571
|
+
active = false;
|
|
9373
10572
|
if (timer) clearTimeout(timer);
|
|
9374
10573
|
}
|
|
9375
10574
|
}
|
|
@@ -9454,6 +10653,7 @@ var YieldseekerAgent = class {
|
|
|
9454
10653
|
this.assertAsset(asset);
|
|
9455
10654
|
const until = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_SNAPSHOT_WINDOW_MS);
|
|
9456
10655
|
const activityUntil = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
10656
|
+
const hash2 = typeof txHash === "string" && /^0x[0-9a-fA-F]{64}$/.test(txHash);
|
|
9457
10657
|
this.invalidateSlot(state, chainId, asset, (key2) => {
|
|
9458
10658
|
const failure = this.snapshotFailures.get(key2);
|
|
9459
10659
|
this.advancePortfolioVersion(key2);
|
|
@@ -9465,6 +10665,7 @@ var YieldseekerAgent = class {
|
|
|
9465
10665
|
}
|
|
9466
10666
|
this.reconcileUntil.set(key2, Math.max(this.reconcileUntil.get(key2) ?? 0, until));
|
|
9467
10667
|
this.activityRefreshUntil.set(key2, Math.max(this.activityRefreshUntil.get(key2) ?? 0, activityUntil));
|
|
10668
|
+
if (hash2) this.forceNextSnapshot.add(key2);
|
|
9468
10669
|
});
|
|
9469
10670
|
}
|
|
9470
10671
|
endFundingBalanceRefresh(state, chainId, asset, read2) {
|
|
@@ -10300,7 +11501,7 @@ var YieldseekerAgent = class {
|
|
|
10300
11501
|
return this.walletRequest(
|
|
10301
11502
|
state,
|
|
10302
11503
|
chainId,
|
|
10303
|
-
|
|
11504
|
+
this.snapshotPath(context, forceRefresh)
|
|
10304
11505
|
).then((response) => {
|
|
10305
11506
|
if (!response?.agentSnapshot) {
|
|
10306
11507
|
throw this.invalidResponse("agent snapshot");
|
|
@@ -10330,6 +11531,16 @@ var YieldseekerAgent = class {
|
|
|
10330
11531
|
throw error;
|
|
10331
11532
|
});
|
|
10332
11533
|
}
|
|
11534
|
+
snapshotPath(context, forceRefresh) {
|
|
11535
|
+
return `${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`;
|
|
11536
|
+
}
|
|
11537
|
+
/** True while `walletRequest` would refuse a read of `path`: this user's
|
|
11538
|
+
* rate-limit cooldown or that endpoint's outage cooldown. */
|
|
11539
|
+
readCoolingDown(state, chainId, path) {
|
|
11540
|
+
const userKey = this.userKey(state, chainId);
|
|
11541
|
+
const now = Date.now();
|
|
11542
|
+
return (this.standardReadFailures.get(userKey)?.retryAt ?? 0) > now || (this.endpointReadFailures.get(`${userKey}:${path}`)?.retryAt ?? 0) > now;
|
|
11543
|
+
}
|
|
10333
11544
|
portfolioSnapshot(state, chainId, context, contextKey) {
|
|
10334
11545
|
const key2 = `snapshot:${contextKey}`;
|
|
10335
11546
|
const cached = this.readCache.get(key2);
|
|
@@ -10348,6 +11559,7 @@ var YieldseekerAgent = class {
|
|
|
10348
11559
|
const settling = inWindow && !this.deferredToLedger(contextKey);
|
|
10349
11560
|
if (inWindow) this.settlementChecks.set(contextKey, (this.settlementChecks.get(contextKey) ?? 0) + 1);
|
|
10350
11561
|
const ready = this.readyForces.delete(contextKey) && this.noteReadyForce(contextKey);
|
|
11562
|
+
const movementForce = this.forceNextSnapshot.has(contextKey) && !this.readCoolingDown(state, chainId, this.snapshotPath(context, true)) && this.forceNextSnapshot.delete(contextKey);
|
|
10351
11563
|
return this.requestPortfolioSnapshot(
|
|
10352
11564
|
state,
|
|
10353
11565
|
chainId,
|
|
@@ -10355,7 +11567,7 @@ var YieldseekerAgent = class {
|
|
|
10355
11567
|
contextKey,
|
|
10356
11568
|
version,
|
|
10357
11569
|
generation,
|
|
10358
|
-
allocationChanged || settling || ready
|
|
11570
|
+
allocationChanged || settling || ready || movementForce
|
|
10359
11571
|
);
|
|
10360
11572
|
})
|
|
10361
11573
|
};
|
|
@@ -10805,7 +12017,7 @@ var YieldseekerAgent = class {
|
|
|
10805
12017
|
}
|
|
10806
12018
|
const ttl = this.portfolioCacheMs(contextKey);
|
|
10807
12019
|
this.settlementChecks.set(contextKey, 1);
|
|
10808
|
-
const force =
|
|
12020
|
+
const force = true;
|
|
10809
12021
|
await this.cachedRead(
|
|
10810
12022
|
`snapshot:${contextKey}`,
|
|
10811
12023
|
ttl,
|
|
@@ -10860,13 +12072,22 @@ var YieldseekerAgent = class {
|
|
|
10860
12072
|
options.assertCurrent?.();
|
|
10861
12073
|
return this.api.request(path, { method: options.method, body: options.body, signature: signature2 });
|
|
10862
12074
|
};
|
|
10863
|
-
|
|
12075
|
+
const cached = options.promptless ? this.auth.peekToken(state, chainId) : void 0;
|
|
12076
|
+
if (options.promptless && !cached)
|
|
12077
|
+
throw new OwneyError(
|
|
12078
|
+
"AGENT_AUTH_FAILED",
|
|
12079
|
+
"No Yieldseeker sign-in is available without a prompt.",
|
|
12080
|
+
{ reason: "YIELDSEEKER_SIGN_IN_REQUIRED" },
|
|
12081
|
+
this.id
|
|
12082
|
+
);
|
|
12083
|
+
let signature = cached ?? await this.auth.getToken(state, chainId);
|
|
10864
12084
|
try {
|
|
10865
12085
|
return await request2(signature);
|
|
10866
12086
|
} catch (error) {
|
|
10867
12087
|
if (!(error instanceof YieldseekerApiError)) throw error;
|
|
10868
12088
|
if (error.providerCode === "NO_USER") throw error;
|
|
10869
12089
|
if (!error.isAuthenticationError) throw error;
|
|
12090
|
+
if (options.promptless) throw error;
|
|
10870
12091
|
signature = await this.auth.refreshToken(state, chainId, signature);
|
|
10871
12092
|
try {
|
|
10872
12093
|
return await request2(signature);
|
|
@@ -11098,6 +12319,16 @@ init_errors();
|
|
|
11098
12319
|
|
|
11099
12320
|
// src/lib/swap/swap-api.ts
|
|
11100
12321
|
init_errors();
|
|
12322
|
+
var FUSION_SECRET_SERVER_PUBLISH = 1;
|
|
12323
|
+
var FUSION_SECRET_HANDOFF_TIMEOUT_MS = 5e3;
|
|
12324
|
+
function handoffFailure(error) {
|
|
12325
|
+
const statusCode2 = error instanceof OwneyError && typeof error.details?.statusCode === "number" ? error.details.statusCode : void 0;
|
|
12326
|
+
return new OwneyError(
|
|
12327
|
+
error instanceof OwneyError ? error.code : "SWAP_REQUEST_FAILED",
|
|
12328
|
+
"The swap secret handoff did not complete.",
|
|
12329
|
+
statusCode2 === void 0 ? {} : { statusCode: statusCode2 }
|
|
12330
|
+
);
|
|
12331
|
+
}
|
|
11101
12332
|
async function request(baseUrl, apiKey, path, init) {
|
|
11102
12333
|
const url = `${baseUrl}/api/v1/swap${path}`;
|
|
11103
12334
|
const controller = new AbortController();
|
|
@@ -11110,7 +12341,8 @@ async function request(baseUrl, apiKey, path, init) {
|
|
|
11110
12341
|
"Content-Type": "application/json",
|
|
11111
12342
|
"x-owney-api-key": apiKey
|
|
11112
12343
|
},
|
|
11113
|
-
...init ? { body: JSON.stringify(init.body) } : {}
|
|
12344
|
+
...init ? { body: JSON.stringify(init.body) } : {},
|
|
12345
|
+
...init?.redirect ? { redirect: init.redirect } : {}
|
|
11114
12346
|
});
|
|
11115
12347
|
if (!res.ok) {
|
|
11116
12348
|
const text = await res.text().catch(() => "");
|
|
@@ -11143,7 +12375,7 @@ async function request(baseUrl, apiKey, path, init) {
|
|
|
11143
12375
|
);
|
|
11144
12376
|
}
|
|
11145
12377
|
return json.data;
|
|
11146
|
-
}, 2e4, () => new OwneyError(
|
|
12378
|
+
}, init?.timeoutMs ?? 2e4, () => new OwneyError(
|
|
11147
12379
|
"SWAP_REQUEST_FAILED",
|
|
11148
12380
|
"Swap request timed out. Its outcome may still need confirmation.",
|
|
11149
12381
|
{ requestTimedOut: true, phase: path === "/order" ? "submit" : "read" }
|
|
@@ -11227,8 +12459,9 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
11227
12459
|
/**
|
|
11228
12460
|
* Builds a Fusion+ order server-side and returns EIP-712 typed data.
|
|
11229
12461
|
*
|
|
11230
|
-
* Only HASHES go over the wire. The preimages
|
|
11231
|
-
* see
|
|
12462
|
+
* Only HASHES go over the wire. The preimages leave the browser only
|
|
12463
|
+
* through `handoffSecrets`, and only with the caller's opt-in — see
|
|
12464
|
+
* swap.secrets.
|
|
11232
12465
|
*/
|
|
11233
12466
|
buildOrder: (params) => request(baseUrl, apiKey, "/order/build", {
|
|
11234
12467
|
method: "POST",
|
|
@@ -11255,6 +12488,23 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
11255
12488
|
method: "POST",
|
|
11256
12489
|
body: { orderHash, secret }
|
|
11257
12490
|
}),
|
|
12491
|
+
/**
|
|
12492
|
+
* Hands a signed Fusion+ order's secrets to our routing API, which checks
|
|
12493
|
+
* them against the order and publishes each one only once 1inch reports
|
|
12494
|
+
* its destination escrow ready (part 5). The browser keeps publishing too;
|
|
12495
|
+
* a double publish is a no-op upstream.
|
|
12496
|
+
*
|
|
12497
|
+
* Only our own API: a redirect is refused, since the body carries the
|
|
12498
|
+
* preimages. Bounded to 5 s.
|
|
12499
|
+
*/
|
|
12500
|
+
handoffSecrets: (body) => request(baseUrl, apiKey, "/order/secrets", {
|
|
12501
|
+
method: "POST",
|
|
12502
|
+
body,
|
|
12503
|
+
redirect: "error",
|
|
12504
|
+
timeoutMs: FUSION_SECRET_HANDOFF_TIMEOUT_MS
|
|
12505
|
+
}).catch((error) => {
|
|
12506
|
+
throw handoffFailure(error);
|
|
12507
|
+
}),
|
|
11258
12508
|
/**
|
|
11259
12509
|
* @param chainId present only for a SAME-CHAIN order. That product keys its
|
|
11260
12510
|
* orders per chain and the Fusion+ endpoint does not know them, so asking
|
|
@@ -11433,14 +12683,14 @@ init_errors();
|
|
|
11433
12683
|
|
|
11434
12684
|
// src/lib/swap/swap.arrival.ts
|
|
11435
12685
|
init_errors();
|
|
11436
|
-
var
|
|
12686
|
+
var DEFAULT_TIMEOUT_MS2 = 18e4;
|
|
11437
12687
|
var DEFAULT_POLL_MS = 4e3;
|
|
11438
12688
|
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
11439
12689
|
async function awaitBalanceIncrease(options) {
|
|
11440
12690
|
const {
|
|
11441
12691
|
readBalance,
|
|
11442
12692
|
baseline,
|
|
11443
|
-
timeoutMs =
|
|
12693
|
+
timeoutMs = DEFAULT_TIMEOUT_MS2,
|
|
11444
12694
|
pollMs = DEFAULT_POLL_MS
|
|
11445
12695
|
} = options;
|
|
11446
12696
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -11537,14 +12787,14 @@ var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
|
|
|
11537
12787
|
var DEFAULT_POLL_MS2 = 5e3;
|
|
11538
12788
|
var MAX_BACKOFF_MS = 3e4;
|
|
11539
12789
|
var backoffFor = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
|
|
11540
|
-
var
|
|
12790
|
+
var DEFAULT_TIMEOUT_MS3 = 15 * 60 * 1e3;
|
|
11541
12791
|
async function runFusionOrderInternal(deps, options) {
|
|
11542
12792
|
const {
|
|
11543
12793
|
orderHash,
|
|
11544
12794
|
secrets,
|
|
11545
12795
|
onStage,
|
|
11546
12796
|
pollIntervalMs = DEFAULT_POLL_MS2,
|
|
11547
|
-
timeoutMs =
|
|
12797
|
+
timeoutMs = DEFAULT_TIMEOUT_MS3
|
|
11548
12798
|
} = options;
|
|
11549
12799
|
const deadline = deps.now() + timeoutMs;
|
|
11550
12800
|
let partial = options.hasPartialFill === true;
|
|
@@ -11625,7 +12875,7 @@ async function runFusionOrderInternal(deps, options) {
|
|
|
11625
12875
|
}
|
|
11626
12876
|
}
|
|
11627
12877
|
function runFusionOrder(deps, options) {
|
|
11628
|
-
return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options));
|
|
12878
|
+
return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options), { wait: options.waitForWatcher === true });
|
|
11629
12879
|
}
|
|
11630
12880
|
|
|
11631
12881
|
// src/lib/swap/swap.same-chain-runner.ts
|
|
@@ -11633,7 +12883,7 @@ init_errors();
|
|
|
11633
12883
|
var DEFAULT_POLL_MS3 = 5e3;
|
|
11634
12884
|
var MAX_BACKOFF_MS2 = 3e4;
|
|
11635
12885
|
var backoffFor2 = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS2);
|
|
11636
|
-
var
|
|
12886
|
+
var DEFAULT_TIMEOUT_MS4 = 3 * 60 * 1e3;
|
|
11637
12887
|
function explain(status) {
|
|
11638
12888
|
switch (status) {
|
|
11639
12889
|
case "expired":
|
|
@@ -11657,7 +12907,7 @@ async function runSameChainOrderInternal(deps, options) {
|
|
|
11657
12907
|
orderHash,
|
|
11658
12908
|
onStage,
|
|
11659
12909
|
pollIntervalMs = DEFAULT_POLL_MS3,
|
|
11660
|
-
timeoutMs =
|
|
12910
|
+
timeoutMs = DEFAULT_TIMEOUT_MS4
|
|
11661
12911
|
} = options;
|
|
11662
12912
|
const deadline = deps.now() + timeoutMs;
|
|
11663
12913
|
let partial = options.hasPartialFill === true;
|
|
@@ -11710,7 +12960,7 @@ async function runSameChainOrderInternal(deps, options) {
|
|
|
11710
12960
|
}
|
|
11711
12961
|
}
|
|
11712
12962
|
function runSameChainOrder(deps, options) {
|
|
11713
|
-
return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options));
|
|
12963
|
+
return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options), { wait: options.waitForWatcher === true });
|
|
11714
12964
|
}
|
|
11715
12965
|
|
|
11716
12966
|
// src/lib/swap/swap.secret-store.ts
|
|
@@ -11735,6 +12985,21 @@ function saveOrder(order) {
|
|
|
11735
12985
|
return false;
|
|
11736
12986
|
}
|
|
11737
12987
|
}
|
|
12988
|
+
function loadOrder(orderHash) {
|
|
12989
|
+
const store = storage5();
|
|
12990
|
+
if (!store) return null;
|
|
12991
|
+
try {
|
|
12992
|
+
const raw2 = store.getItem(keyFor2(orderHash));
|
|
12993
|
+
if (!raw2) return null;
|
|
12994
|
+
const parsed = JSON.parse(raw2);
|
|
12995
|
+
if (!Array.isArray(parsed.secrets) || parsed.secrets.length === 0) {
|
|
12996
|
+
return null;
|
|
12997
|
+
}
|
|
12998
|
+
return parsed;
|
|
12999
|
+
} catch {
|
|
13000
|
+
return null;
|
|
13001
|
+
}
|
|
13002
|
+
}
|
|
11738
13003
|
function clearOrder(orderHash) {
|
|
11739
13004
|
const store = storage5();
|
|
11740
13005
|
if (!store) return;
|
|
@@ -11813,10 +13078,10 @@ async function affordableAmount(deps, quoted, reserve = 0n) {
|
|
|
11813
13078
|
}
|
|
11814
13079
|
async function executeSwap(deps, options) {
|
|
11815
13080
|
const { quote, walletAddress, onStage } = options;
|
|
11816
|
-
let
|
|
13081
|
+
let active = true;
|
|
11817
13082
|
const original = deps;
|
|
11818
13083
|
const assertActive = () => {
|
|
11819
|
-
if (!
|
|
13084
|
+
if (!active) throw new OwneyError("SWAP_REQUEST_FAILED", "This swap attempt is no longer active.");
|
|
11820
13085
|
};
|
|
11821
13086
|
const call = async (work, write = false) => {
|
|
11822
13087
|
assertActive();
|
|
@@ -11877,7 +13142,7 @@ async function executeSwap(deps, options) {
|
|
|
11877
13142
|
}
|
|
11878
13143
|
return { received: arrived.toString(), ...result };
|
|
11879
13144
|
} finally {
|
|
11880
|
-
|
|
13145
|
+
active = false;
|
|
11881
13146
|
}
|
|
11882
13147
|
}
|
|
11883
13148
|
async function runClassic(deps, options) {
|
|
@@ -11996,6 +13261,7 @@ async function runSameChainFusion(deps, options, walletAddress) {
|
|
|
11996
13261
|
}
|
|
11997
13262
|
await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
|
|
11998
13263
|
orderHash: built.orderHash,
|
|
13264
|
+
waitForWatcher: true,
|
|
11999
13265
|
...onStage ? { onStage } : {}
|
|
12000
13266
|
});
|
|
12001
13267
|
return { orderHash: built.orderHash, spent: amount.toString() };
|
|
@@ -12064,67 +13330,118 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
12064
13330
|
...direction ? { direction } : {},
|
|
12065
13331
|
...permit ? { permit } : {}
|
|
12066
13332
|
});
|
|
12067
|
-
const
|
|
12068
|
-
orderHash: built.orderHash,
|
|
12069
|
-
walletAddress,
|
|
12070
|
-
secrets,
|
|
12071
|
-
srcChainId: quote.src.chainId,
|
|
12072
|
-
srcSymbol: quote.src.symbol,
|
|
12073
|
-
dstChainId: quote.dst.chainId,
|
|
12074
|
-
dstSymbol: quote.dst.symbol,
|
|
12075
|
-
amount: amount.toString(),
|
|
12076
|
-
createdAt: Date.now()
|
|
12077
|
-
});
|
|
12078
|
-
debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
|
|
12079
|
-
let signature;
|
|
13333
|
+
const release = claimOrder(built.orderHash);
|
|
12080
13334
|
try {
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
13335
|
+
const stored = {
|
|
13336
|
+
orderHash: built.orderHash,
|
|
13337
|
+
walletAddress,
|
|
13338
|
+
secrets,
|
|
13339
|
+
srcChainId: quote.src.chainId,
|
|
13340
|
+
srcSymbol: quote.src.symbol,
|
|
13341
|
+
dstChainId: quote.dst.chainId,
|
|
13342
|
+
dstSymbol: quote.dst.symbol,
|
|
13343
|
+
amount: amount.toString(),
|
|
13344
|
+
createdAt: Date.now(),
|
|
13345
|
+
...options.pendingDeposit ? { pendingDeposit: withinOrderMinimum(options.pendingDeposit, built.dstAmountMin) } : {}
|
|
13346
|
+
};
|
|
13347
|
+
const durable = saveOrder(stored);
|
|
13348
|
+
debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
|
|
13349
|
+
let signature;
|
|
13350
|
+
try {
|
|
13351
|
+
onStage?.("signing");
|
|
13352
|
+
await deps.ensureChain(quote.src.chainId);
|
|
13353
|
+
signature = await awaitWalletPrompt(() => deps.signTypedData(built.typedData), "sign the swap");
|
|
13354
|
+
options.assertCurrent?.();
|
|
13355
|
+
options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
|
|
13356
|
+
} catch (error) {
|
|
13357
|
+
clearOrder(built.orderHash);
|
|
13358
|
+
throw error;
|
|
13359
|
+
}
|
|
13360
|
+
if (options.secretHandoff && deps.api.handoffSecrets) {
|
|
13361
|
+
if (await tryHandoff(deps, options.secretHandoff, built, quote, secretHashes, secrets))
|
|
13362
|
+
saveOrder({ ...stored, serverSecrets: true });
|
|
13363
|
+
}
|
|
13364
|
+
debugLog("owney-sdk", "swap: signed, submitting to relayer");
|
|
13365
|
+
try {
|
|
13366
|
+
await deps.api.submitOrder({
|
|
13367
|
+
srcChainId: quote.src.chainId,
|
|
13368
|
+
// The ORDER STRUCT, not the typed-data envelope we just signed. Sending
|
|
13369
|
+
// the envelope here gets a bare 500 from the relayer.
|
|
13370
|
+
order: built.order,
|
|
13371
|
+
signature,
|
|
13372
|
+
quoteId: built.quoteId,
|
|
13373
|
+
// Single-fill orders must NOT carry secretHashes — the relayer rejects
|
|
13374
|
+
// them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
|
|
13375
|
+
// order's hashlock, so repeating it here is redundant, and only a
|
|
13376
|
+
// multi-fill order (a Merkle tree of hashes) needs them listed.
|
|
13377
|
+
...secretHashes.length > 1 ? { secretHashes } : {},
|
|
13378
|
+
...built.extension ? { extension: built.extension } : {}
|
|
13379
|
+
});
|
|
13380
|
+
} catch (error) {
|
|
13381
|
+
throw submissionUnknown(error, built.orderHash);
|
|
13382
|
+
}
|
|
13383
|
+
debugLog("owney-sdk", "swap: order submitted, polling escrows");
|
|
13384
|
+
try {
|
|
13385
|
+
options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
|
|
13386
|
+
} catch {
|
|
13387
|
+
}
|
|
13388
|
+
try {
|
|
13389
|
+
await runFusionOrder(deps.runner, {
|
|
13390
|
+
orderHash: built.orderHash,
|
|
13391
|
+
// This run owns the order: wait out a watcher another tab started.
|
|
13392
|
+
waitForWatcher: true,
|
|
13393
|
+
secrets,
|
|
13394
|
+
...onStage ? { onStage } : {}
|
|
13395
|
+
});
|
|
13396
|
+
} catch (error) {
|
|
13397
|
+
if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
|
|
13398
|
+
clearOrder(built.orderHash);
|
|
13399
|
+
}
|
|
13400
|
+
throw error;
|
|
13401
|
+
}
|
|
12087
13402
|
clearOrder(built.orderHash);
|
|
12088
|
-
|
|
12089
|
-
}
|
|
12090
|
-
|
|
13403
|
+
return { orderHash: built.orderHash, spent: amount.toString() };
|
|
13404
|
+
} finally {
|
|
13405
|
+
release();
|
|
13406
|
+
}
|
|
13407
|
+
}
|
|
13408
|
+
var HANDOFF_DECLINE_CODES = /* @__PURE__ */ new Set([
|
|
13409
|
+
"FUNDING_TASK_KIND_DISABLED",
|
|
13410
|
+
"SECRETS_INVALID",
|
|
13411
|
+
"SECRET_HASH_MISMATCH",
|
|
13412
|
+
"ORDER_UNREADABLE",
|
|
13413
|
+
"ORDER_HASH_MISMATCH",
|
|
13414
|
+
"HASHLOCK_MISMATCH",
|
|
13415
|
+
"MAKER_MISMATCH",
|
|
13416
|
+
"CHAIN_MISMATCH",
|
|
13417
|
+
"OPERATION_INVALID",
|
|
13418
|
+
"ORDER_CONFLICT"
|
|
13419
|
+
]);
|
|
13420
|
+
async function tryHandoff(deps, handoff, built, quote, secretHashes, secrets) {
|
|
12091
13421
|
try {
|
|
12092
|
-
await deps.api.
|
|
13422
|
+
const answer = await bounded(() => deps.api.handoffSecrets({
|
|
13423
|
+
operation: handoff.operation,
|
|
13424
|
+
orderHash: built.orderHash,
|
|
12093
13425
|
srcChainId: quote.src.chainId,
|
|
12094
|
-
|
|
12095
|
-
// the envelope here gets a bare 500 from the relayer.
|
|
13426
|
+
dstChainId: quote.dst.chainId,
|
|
12096
13427
|
order: built.order,
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
...built.extension ? { extension: built.extension } : {}
|
|
12105
|
-
});
|
|
12106
|
-
} catch (error) {
|
|
12107
|
-
throw submissionUnknown(error, built.orderHash);
|
|
12108
|
-
}
|
|
12109
|
-
debugLog("owney-sdk", "swap: order submitted, polling escrows");
|
|
12110
|
-
try {
|
|
12111
|
-
options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
|
|
12112
|
-
} catch {
|
|
12113
|
-
}
|
|
12114
|
-
try {
|
|
12115
|
-
await runFusionOrder(deps.runner, {
|
|
12116
|
-
orderHash: built.orderHash,
|
|
12117
|
-
secrets,
|
|
12118
|
-
...onStage ? { onStage } : {}
|
|
12119
|
-
});
|
|
12120
|
-
} catch (error) {
|
|
12121
|
-
if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
|
|
12122
|
-
clearOrder(built.orderHash);
|
|
13428
|
+
extension: built.extension,
|
|
13429
|
+
secretHashes,
|
|
13430
|
+
fusionSecrets: secrets
|
|
13431
|
+
}), FUSION_SECRET_HANDOFF_TIMEOUT_MS, () => new Error("secret handoff timed out"));
|
|
13432
|
+
if (answer?.accepted === true) {
|
|
13433
|
+
debugLog("owney-sdk", "swap: secrets handed to the server", { orderHash: built.orderHash });
|
|
13434
|
+
return true;
|
|
12123
13435
|
}
|
|
12124
|
-
|
|
13436
|
+
const code = typeof answer?.code === "string" && HANDOFF_DECLINE_CODES.has(answer.code) ? answer.code : "OTHER";
|
|
13437
|
+
debugLog("owney-sdk", "swap: secret handoff declined, the browser publishes", { code });
|
|
13438
|
+
} catch {
|
|
13439
|
+
debugLog("owney-sdk", "swap: secret handoff unavailable, the browser publishes");
|
|
12125
13440
|
}
|
|
12126
|
-
|
|
12127
|
-
|
|
13441
|
+
return false;
|
|
13442
|
+
}
|
|
13443
|
+
function withinOrderMinimum(deposit, orderMinimum) {
|
|
13444
|
+
return typeof orderMinimum === "string" && /^\d+$/.test(orderMinimum) && /^\d+$/.test(deposit.received) && BigInt(orderMinimum) < BigInt(deposit.received) ? { ...deposit, received: orderMinimum } : deposit;
|
|
12128
13445
|
}
|
|
12129
13446
|
function submissionUnknown(error, orderHash) {
|
|
12130
13447
|
return new OwneyError(
|
|
@@ -12148,7 +13465,7 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
12148
13465
|
const payouts = new Map(Object.entries(options.results).map(([id, result]) => [id, { result, evidence: result.evidence }]));
|
|
12149
13466
|
const timeoutMs = Math.max(0, options.timeoutMs ?? 18e4);
|
|
12150
13467
|
const deadline = options.deadline ?? Date.now() + timeoutMs;
|
|
12151
|
-
let
|
|
13468
|
+
let active = true;
|
|
12152
13469
|
let timer;
|
|
12153
13470
|
const incomplete = (reason, agentId) => new OwneyError(
|
|
12154
13471
|
"WITHDRAW_SWAP_INCOMPLETE",
|
|
@@ -12166,7 +13483,7 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
12166
13483
|
);
|
|
12167
13484
|
const assertActive = () => {
|
|
12168
13485
|
options.assertCurrent();
|
|
12169
|
-
if (!
|
|
13486
|
+
if (!active || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
|
|
12170
13487
|
};
|
|
12171
13488
|
const pause = async () => {
|
|
12172
13489
|
assertActive();
|
|
@@ -12343,7 +13660,7 @@ async function awaitWithdrawalPayouts(options) {
|
|
|
12343
13660
|
timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), Math.max(0, deadline - Date.now()));
|
|
12344
13661
|
})]);
|
|
12345
13662
|
} finally {
|
|
12346
|
-
|
|
13663
|
+
active = false;
|
|
12347
13664
|
if (timer) clearTimeout(timer);
|
|
12348
13665
|
}
|
|
12349
13666
|
}
|
|
@@ -12912,21 +14229,42 @@ init_errors();
|
|
|
12912
14229
|
var import_viem22 = require("viem");
|
|
12913
14230
|
init_errors();
|
|
12914
14231
|
init_token_batch_family();
|
|
14232
|
+
var SPONSORED_DEPOSIT_SERVER_BROADCAST = 1;
|
|
12915
14233
|
async function executeTokenBatch(input, initial) {
|
|
12916
14234
|
let body = initial, family, ended = false;
|
|
12917
14235
|
const deadlineAt = Date.now() + 9e4, controller = new AbortController();
|
|
12918
14236
|
let timer;
|
|
12919
|
-
const
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
12929
|
-
|
|
14237
|
+
const serverSent = /* @__PURE__ */ new Set();
|
|
14238
|
+
let accepted;
|
|
14239
|
+
let maybeAccepted = !!input.resumeSaved, alreadySending = false;
|
|
14240
|
+
const verifiedFailures = /* @__PURE__ */ new WeakSet();
|
|
14241
|
+
const pending2 = () => {
|
|
14242
|
+
const deposit = body.serializedTransaction ? (0, import_viem22.keccak256)(body.serializedTransaction) : accepted;
|
|
14243
|
+
const unknown2 = !deposit && maybeAccepted;
|
|
14244
|
+
const deadline = /^\d{1,15}$/.test(body.deadline) ? Number(body.deadline) : void 0;
|
|
14245
|
+
return new OwneyError(
|
|
14246
|
+
"DEPOSIT_IN_PROGRESS",
|
|
14247
|
+
"Deposit is awaiting confirmation. Its transaction is still being checked.",
|
|
14248
|
+
{
|
|
14249
|
+
sponsorPending: true,
|
|
14250
|
+
phase: deposit || unknown2 ? "deposit" : "approval",
|
|
14251
|
+
txHash: deposit ?? (body.serializedPermitTransaction && !unknown2 ? (0, import_viem22.keccak256)(body.serializedPermitTransaction) : void 0),
|
|
14252
|
+
notSubmitted: false,
|
|
14253
|
+
safeToFallback: false,
|
|
14254
|
+
// Only while the current deposit is the one the server will send.
|
|
14255
|
+
...deposit && serverSent.has(deposit) ? { serverBroadcast: true } : unknown2 ? { serverBroadcast: "unknown" } : {},
|
|
14256
|
+
...unknown2 && alreadySending ? { code: "DEPOSIT_ALREADY_SENDING" } : {},
|
|
14257
|
+
// The Permit2 deadline (seconds): after it no deposit of this
|
|
14258
|
+
// authorization can execute, which bounds an unknown outcome. Never
|
|
14259
|
+
// after a 409 DEPOSIT_ALREADY_SENDING: this refused authorization is
|
|
14260
|
+
// not the deposit being sent.
|
|
14261
|
+
...unknown2 && !alreadySending && deadline !== void 0 ? { authorizationDeadline: deadline } : {},
|
|
14262
|
+
// Its Permit2 nonce: with the deadline, the chain shows whether any
|
|
14263
|
+
// deposit of this authorization executed.
|
|
14264
|
+
...unknown2 && !alreadySending || deposit && serverSent.has(deposit) ? { permit2Nonce: body.nonce } : {}
|
|
14265
|
+
}
|
|
14266
|
+
);
|
|
14267
|
+
};
|
|
12930
14268
|
const assertCurrent = () => {
|
|
12931
14269
|
if (ended || Date.now() >= deadlineAt) throw pending2();
|
|
12932
14270
|
input.assertCurrent?.();
|
|
@@ -12951,20 +14289,52 @@ async function executeTokenBatch(input, initial) {
|
|
|
12951
14289
|
}
|
|
12952
14290
|
return operationId ? { ...value, operationId } : value;
|
|
12953
14291
|
};
|
|
12954
|
-
const
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
14292
|
+
const withServerBroadcast = (value) => {
|
|
14293
|
+
const request2 = withOperationId(value);
|
|
14294
|
+
return request2.operationId ? { ...request2, serverBroadcast: true } : request2;
|
|
14295
|
+
};
|
|
14296
|
+
const post2 = async () => {
|
|
14297
|
+
const request2 = withServerBroadcast(body);
|
|
14298
|
+
const flaggedPrepare = request2.serverBroadcast === true && !request2.serializedTransaction && !(request2.erc2612Permit && !request2.serializedPermitTransaction);
|
|
14299
|
+
let result;
|
|
14300
|
+
try {
|
|
14301
|
+
result = await Promise.race([
|
|
14302
|
+
postSponsorBatchTransfer({
|
|
14303
|
+
apiKey: input.apiKey,
|
|
14304
|
+
baseUrl: input.baseUrl,
|
|
14305
|
+
body: request2,
|
|
14306
|
+
signal: controller.signal
|
|
14307
|
+
}),
|
|
14308
|
+
timeout
|
|
14309
|
+
]);
|
|
14310
|
+
} catch (error) {
|
|
14311
|
+
const details = error instanceof OwneyError ? error.details : void 0;
|
|
14312
|
+
const sending = details?.code === "DEPOSIT_ALREADY_SENDING";
|
|
14313
|
+
if (flaggedPrepare && (ended || !(error instanceof OwneyError) || details?.outcomeUnknown === true || sending)) {
|
|
14314
|
+
maybeAccepted = true;
|
|
14315
|
+
alreadySending = sending;
|
|
14316
|
+
}
|
|
14317
|
+
throw error;
|
|
14318
|
+
}
|
|
14319
|
+
maybeAccepted = false;
|
|
14320
|
+
alreadySending = false;
|
|
14321
|
+
if (result.serverBroadcast === true && result.serializedTransaction && !body.serializedTransaction) {
|
|
14322
|
+
serverSent.add(result.txHash);
|
|
14323
|
+
accepted = result.txHash;
|
|
14324
|
+
try {
|
|
14325
|
+
input.onSubmitted?.(result.txHash, reference(result.txHash), true);
|
|
14326
|
+
} catch {
|
|
14327
|
+
}
|
|
14328
|
+
}
|
|
14329
|
+
assertCurrent();
|
|
14330
|
+
return result;
|
|
14331
|
+
};
|
|
12962
14332
|
const replacements = { approval: 0, deposit: 0 };
|
|
12963
14333
|
const unavailable = /* @__PURE__ */ new Set();
|
|
12964
14334
|
const observedApprovals = /* @__PURE__ */ new Set();
|
|
12965
14335
|
const checkpointed = /* @__PURE__ */ new Set();
|
|
12966
14336
|
const accounted = /* @__PURE__ */ new Set();
|
|
12967
|
-
const remember = async (phase, bytes, hash2, predecessorHash) => {
|
|
14337
|
+
const remember = async (phase, bytes, hash2, predecessorHash, submissionAttempted = false) => {
|
|
12968
14338
|
assertCurrent();
|
|
12969
14339
|
if ((0, import_viem22.keccak256)(bytes) !== hash2) throw pending2();
|
|
12970
14340
|
const tx = (0, import_viem22.parseTransaction)(bytes);
|
|
@@ -12983,7 +14353,7 @@ async function executeTokenBatch(input, initial) {
|
|
|
12983
14353
|
nonce: String(tx.nonce),
|
|
12984
14354
|
predecessorHash,
|
|
12985
14355
|
preparedAt: Date.now(),
|
|
12986
|
-
submissionAttempted
|
|
14356
|
+
submissionAttempted
|
|
12987
14357
|
},
|
|
12988
14358
|
assertCurrent
|
|
12989
14359
|
)
|
|
@@ -12997,13 +14367,18 @@ async function executeTokenBatch(input, initial) {
|
|
|
12997
14367
|
const markSubmission = async (phase) => {
|
|
12998
14368
|
const hash2 = phase === "deposit" ? family?.current.deposit : family?.current.approval;
|
|
12999
14369
|
if (!family || !hash2) throw pending2();
|
|
14370
|
+
const flag = serverSent.has(hash2) ? [true] : [];
|
|
13000
14371
|
if (phase === "deposit" && !checkpointed.has(hash2)) {
|
|
13001
14372
|
try {
|
|
13002
14373
|
await bounded2(
|
|
13003
|
-
Promise.resolve(
|
|
14374
|
+
Promise.resolve(
|
|
14375
|
+
input.onBeforeSubmission?.(hash2, reference(hash2), ...flag)
|
|
14376
|
+
)
|
|
13004
14377
|
);
|
|
14378
|
+
checkpointed.add(hash2);
|
|
13005
14379
|
} catch (error) {
|
|
13006
|
-
if (
|
|
14380
|
+
if (serverSent.has(hash2)) {
|
|
14381
|
+
} else if (error instanceof OwneyError && error.code === "DEPOSIT_CHECKPOINT_FAILED")
|
|
13007
14382
|
throw new OwneyError(error.code, error.message, {
|
|
13008
14383
|
...error.details,
|
|
13009
14384
|
notSubmitted: !family.attempts.some(
|
|
@@ -13011,9 +14386,8 @@ async function executeTokenBatch(input, initial) {
|
|
|
13011
14386
|
),
|
|
13012
14387
|
safeToFallback: false
|
|
13013
14388
|
});
|
|
13014
|
-
throw error;
|
|
14389
|
+
else throw error;
|
|
13015
14390
|
}
|
|
13016
|
-
checkpointed.add(hash2);
|
|
13017
14391
|
}
|
|
13018
14392
|
assertCurrent();
|
|
13019
14393
|
const attempt = family.attempts.find((a) => a.hash === hash2);
|
|
@@ -13028,7 +14402,7 @@ async function executeTokenBatch(input, initial) {
|
|
|
13028
14402
|
assertCurrent();
|
|
13029
14403
|
if (phase === "deposit")
|
|
13030
14404
|
try {
|
|
13031
|
-
input.onSubmitted?.(hash2, reference(hash2));
|
|
14405
|
+
input.onSubmitted?.(hash2, reference(hash2), ...flag);
|
|
13032
14406
|
} catch {
|
|
13033
14407
|
}
|
|
13034
14408
|
assertCurrent();
|
|
@@ -13064,12 +14438,15 @@ async function executeTokenBatch(input, initial) {
|
|
|
13064
14438
|
}
|
|
13065
14439
|
return observation.txHash;
|
|
13066
14440
|
}
|
|
13067
|
-
if (observation.status === "failed" || observation.status === "expired")
|
|
13068
|
-
|
|
14441
|
+
if (observation.status === "failed" || observation.status === "expired") {
|
|
14442
|
+
const failure = new OwneyError(
|
|
13069
14443
|
"SPONSOR_REQUEST_FAILED",
|
|
13070
14444
|
observation.status === "expired" ? "The deposit authorization expired without transferring funds. Start a new deposit." : "Sponsored deposit transactions reverted. Please retry.",
|
|
13071
14445
|
{ notSubmitted: true, safeToFallback: false }
|
|
13072
14446
|
);
|
|
14447
|
+
verifiedFailures.add(failure);
|
|
14448
|
+
throw failure;
|
|
14449
|
+
}
|
|
13073
14450
|
};
|
|
13074
14451
|
try {
|
|
13075
14452
|
assertCurrent();
|
|
@@ -13085,6 +14462,14 @@ async function executeTokenBatch(input, initial) {
|
|
|
13085
14462
|
tokenBatchKey(input),
|
|
13086
14463
|
JSON.stringify(tokenBatchStoredBody(body))
|
|
13087
14464
|
);
|
|
14465
|
+
if (/^\d{1,78}$/.test(body.nonce) && /^\d{1,15}$/.test(body.deadline))
|
|
14466
|
+
try {
|
|
14467
|
+
input.onAuthorization?.({
|
|
14468
|
+
permit2Nonce: body.nonce,
|
|
14469
|
+
authorizationDeadline: Number(body.deadline)
|
|
14470
|
+
});
|
|
14471
|
+
} catch {
|
|
14472
|
+
}
|
|
13088
14473
|
if (family) {
|
|
13089
14474
|
const winner = await observe();
|
|
13090
14475
|
if (winner) return winner;
|
|
@@ -13108,6 +14493,20 @@ async function executeTokenBatch(input, initial) {
|
|
|
13108
14493
|
result = await post2();
|
|
13109
14494
|
} catch (error) {
|
|
13110
14495
|
assertCurrent();
|
|
14496
|
+
if (maybeAccepted && alreadySending) {
|
|
14497
|
+
if (family)
|
|
14498
|
+
finishTokenBatchFamily(input, family, {
|
|
14499
|
+
kind: "failed",
|
|
14500
|
+
blockNumber: "0"
|
|
14501
|
+
});
|
|
14502
|
+
else
|
|
14503
|
+
tokenBatchClear(
|
|
14504
|
+
tokenBatchKey(input),
|
|
14505
|
+
JSON.stringify(tokenBatchStoredBody(body))
|
|
14506
|
+
);
|
|
14507
|
+
throw pending2();
|
|
14508
|
+
}
|
|
14509
|
+
if (maybeAccepted && !family) throw pending2();
|
|
13111
14510
|
if (!family) {
|
|
13112
14511
|
tokenBatchClear(
|
|
13113
14512
|
tokenBatchKey(input),
|
|
@@ -13130,7 +14529,9 @@ async function executeTokenBatch(input, initial) {
|
|
|
13130
14529
|
await remember(
|
|
13131
14530
|
"deposit",
|
|
13132
14531
|
result.serializedTransaction,
|
|
13133
|
-
result.txHash
|
|
14532
|
+
result.txHash,
|
|
14533
|
+
void 0,
|
|
14534
|
+
serverSent.has(result.txHash)
|
|
13134
14535
|
);
|
|
13135
14536
|
needsPost = true;
|
|
13136
14537
|
continue;
|
|
@@ -13206,6 +14607,10 @@ async function executeTokenBatch(input, initial) {
|
|
|
13206
14607
|
)
|
|
13207
14608
|
);
|
|
13208
14609
|
}
|
|
14610
|
+
} catch (error) {
|
|
14611
|
+
if ((serverSent.size || maybeAccepted) && !(error && typeof error === "object" && verifiedFailures.has(error)))
|
|
14612
|
+
throw pending2();
|
|
14613
|
+
throw error;
|
|
13209
14614
|
} finally {
|
|
13210
14615
|
ended = true;
|
|
13211
14616
|
if (timer) clearTimeout(timer);
|
|
@@ -13232,14 +14637,14 @@ async function releaseExpiredTokenBatch(i) {
|
|
|
13232
14637
|
let timer;
|
|
13233
14638
|
let expired = false;
|
|
13234
14639
|
const check = async () => {
|
|
13235
|
-
let
|
|
13236
|
-
if (
|
|
13237
|
-
const current = JSON.parse(
|
|
14640
|
+
let active = read(key2);
|
|
14641
|
+
if (active && i.expectedTxHash) {
|
|
14642
|
+
const current = JSON.parse(active);
|
|
13238
14643
|
if (!current.serializedTransaction || (0, import_viem23.keccak256)(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
|
|
13239
|
-
|
|
14644
|
+
active = void 0;
|
|
13240
14645
|
}
|
|
13241
14646
|
const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
|
|
13242
|
-
const saved =
|
|
14647
|
+
const saved = active ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
|
|
13243
14648
|
if (!saved) return;
|
|
13244
14649
|
const body = JSON.parse(saved);
|
|
13245
14650
|
if (!body.serializedTransaction || body.chainId !== i.chainId || !(0, import_viem23.isAddressEqual)(body.from, i.owner) || !(0, import_viem23.isAddressEqual)(body.token, i.token) || !body.transfers?.length) return;
|
|
@@ -13263,8 +14668,8 @@ async function releaseExpiredTokenBatch(i) {
|
|
|
13263
14668
|
args: [i.owner, nonce >> 8n],
|
|
13264
14669
|
blockNumber: block.number
|
|
13265
14670
|
});
|
|
13266
|
-
if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n ||
|
|
13267
|
-
if (
|
|
14671
|
+
if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active && read(key2) !== active) return;
|
|
14672
|
+
if (active) {
|
|
13268
14673
|
const terminalKey = `${key2}:expired:${txHash.toLowerCase()}`;
|
|
13269
14674
|
expiredRecords.set(terminalKey, saved);
|
|
13270
14675
|
let persisted = typeof window === "undefined";
|
|
@@ -13301,15 +14706,15 @@ function clear(key2, body) {
|
|
|
13301
14706
|
function sponsorTokenBatch(i) {
|
|
13302
14707
|
const key2 = pendingKey(i);
|
|
13303
14708
|
const plan = planOf(i.transfers);
|
|
13304
|
-
const
|
|
13305
|
-
if (
|
|
13306
|
-
if (
|
|
14709
|
+
const active = inflight.get(key2);
|
|
14710
|
+
if (active) {
|
|
14711
|
+
if (active.plan !== plan)
|
|
13307
14712
|
return Promise.reject(
|
|
13308
14713
|
new Error(
|
|
13309
14714
|
"A token deposit is already in progress. Wait for its result before depositing again."
|
|
13310
14715
|
)
|
|
13311
14716
|
);
|
|
13312
|
-
return
|
|
14717
|
+
return active.promise;
|
|
13313
14718
|
}
|
|
13314
14719
|
const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
|
|
13315
14720
|
inflight.set(key2, { plan, promise });
|
|
@@ -13321,8 +14726,15 @@ async function execute(i, key2, plan) {
|
|
|
13321
14726
|
) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
|
|
13322
14727
|
throw new Error("Invalid token deposit shares.");
|
|
13323
14728
|
const send = (body) => executeTokenBatch(i, body);
|
|
14729
|
+
if (!i.resumeTxHash) await dropExpiredHashlessTokenBatch(i, i.pub);
|
|
13324
14730
|
const family = await loadTokenBatchFamily(i, i.resumeTxHash);
|
|
13325
14731
|
const saved = family ? JSON.stringify(family.body) : read(key2);
|
|
14732
|
+
if (!saved && i.resumeSaved)
|
|
14733
|
+
throw new OwneyError(
|
|
14734
|
+
"DEPOSIT_IN_PROGRESS",
|
|
14735
|
+
"The saved deposit is no longer available. Check its status before depositing again.",
|
|
14736
|
+
{ notSubmitted: false, safeToFallback: false }
|
|
14737
|
+
);
|
|
13326
14738
|
if (saved) {
|
|
13327
14739
|
const previous = JSON.parse(saved);
|
|
13328
14740
|
if (previous.chainId !== i.chainId || !(0, import_viem23.isAddressEqual)(previous.from, i.owner) || !(0, import_viem23.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
|
|
@@ -13432,10 +14844,13 @@ function makeSponsoredTokenCallback(deps) {
|
|
|
13432
14844
|
pub,
|
|
13433
14845
|
wallet,
|
|
13434
14846
|
onApproved: deps.onApproved,
|
|
13435
|
-
|
|
13436
|
-
|
|
14847
|
+
// Rest arguments keep the callbacks' arity: the flag appears only when set.
|
|
14848
|
+
onSubmitted: (hash2, ...rest) => deps.onSubmitted?.(hash2, transfers, ...rest),
|
|
14849
|
+
onBeforeSubmission: (hash2, ...rest) => deps.onBeforeSubmission?.(hash2, transfers, ...rest),
|
|
13437
14850
|
assertCurrent: deps.assertCurrent,
|
|
13438
14851
|
resumeTxHash: deps.resumeTxHash,
|
|
14852
|
+
...deps.resumeSaved ? { resumeSaved: true } : {},
|
|
14853
|
+
...deps.onAuthorization ? { onAuthorization: deps.onAuthorization } : {},
|
|
13439
14854
|
operationId: deps.operationId
|
|
13440
14855
|
});
|
|
13441
14856
|
};
|
|
@@ -13744,6 +15159,33 @@ function sponsoredTokensFor(asset) {
|
|
|
13744
15159
|
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
13745
15160
|
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
13746
15161
|
}
|
|
15162
|
+
function serverBroadcastOf(error) {
|
|
15163
|
+
const flag = (value) => {
|
|
15164
|
+
const details = value?.details;
|
|
15165
|
+
return details?.serverBroadcast === true || details?.serverBroadcast === "unknown" ? details.serverBroadcast : void 0;
|
|
15166
|
+
};
|
|
15167
|
+
return flag(error) ?? flag(error?.details?.cause);
|
|
15168
|
+
}
|
|
15169
|
+
function authorizationDeadlineOf(error) {
|
|
15170
|
+
const read2 = (value) => {
|
|
15171
|
+
const deadline = value?.details?.authorizationDeadline;
|
|
15172
|
+
return typeof deadline === "number" && Number.isSafeInteger(deadline) && deadline > 0 ? deadline : void 0;
|
|
15173
|
+
};
|
|
15174
|
+
return read2(error) ?? read2(error?.details?.cause);
|
|
15175
|
+
}
|
|
15176
|
+
function alreadySendingOf(error) {
|
|
15177
|
+
const code = (value) => value?.details?.code;
|
|
15178
|
+
return code(error) === "DEPOSIT_ALREADY_SENDING" || code(error?.details?.cause) === "DEPOSIT_ALREADY_SENDING";
|
|
15179
|
+
}
|
|
15180
|
+
function permit2NonceOf(error) {
|
|
15181
|
+
const read2 = (value) => {
|
|
15182
|
+
const nonce = value?.details?.permit2Nonce;
|
|
15183
|
+
return typeof nonce === "string" && /^\d{1,78}$/.test(nonce) ? nonce : void 0;
|
|
15184
|
+
};
|
|
15185
|
+
return read2(error) ?? read2(error?.details?.cause);
|
|
15186
|
+
}
|
|
15187
|
+
var serverMayOwn = (leg) => leg !== void 0 && leg.settled !== null;
|
|
15188
|
+
var SERVER_HANDOFF_WAIT_MS = 1e4;
|
|
13747
15189
|
var OwneySDK = class {
|
|
13748
15190
|
agents = /* @__PURE__ */ new Map();
|
|
13749
15191
|
activeAgents = /* @__PURE__ */ new Set();
|
|
@@ -13772,13 +15214,21 @@ var OwneySDK = class {
|
|
|
13772
15214
|
ledger;
|
|
13773
15215
|
reservationMode;
|
|
13774
15216
|
reservationTransport;
|
|
15217
|
+
/** The caller's Zyfai server tasks opt-in: `true`, a function read each
|
|
15218
|
+
* time a handoff would start (F-I2(iii)), or undefined (off). */
|
|
15219
|
+
zyfaiServerTasks;
|
|
15220
|
+
/** The caller opted in to the Fusion+ secret handoff (`fusionSecretHandoff: true`). */
|
|
15221
|
+
fusionSecretHandoff;
|
|
13775
15222
|
cachedSponsoredCallback = null;
|
|
13776
15223
|
cachedWethSponsoredCallback = null;
|
|
13777
15224
|
paymasterServiceUrl;
|
|
13778
15225
|
cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
|
|
13779
15226
|
agentReads = new AgentReads();
|
|
13780
15227
|
depositFamilyReads = /* @__PURE__ */ new Map();
|
|
13781
|
-
|
|
15228
|
+
/** Movements (owner, chain, agent, token, tx hash) whose balance refresh this
|
|
15229
|
+
* session started: true when it armed the refresh, false when its deadline
|
|
15230
|
+
* had passed (it only dropped the cache and forced one snapshot read). */
|
|
15231
|
+
recoveredFundingReads = /* @__PURE__ */ new Map();
|
|
13782
15232
|
fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
|
|
13783
15233
|
activationChecks = /* @__PURE__ */ new Map();
|
|
13784
15234
|
// Protect the SDK entry point as well as the mini-app hook. This owns only
|
|
@@ -13816,6 +15266,8 @@ var OwneySDK = class {
|
|
|
13816
15266
|
});
|
|
13817
15267
|
this.reservationMode = config.withdrawalReservation === "fail-closed" || config.withdrawalReservation === "off" ? config.withdrawalReservation : "fail-open";
|
|
13818
15268
|
this.reservationTransport = { apiKey: config.apiKey, baseUrl: config.routingApiBaseUrl, appName: config.appName };
|
|
15269
|
+
this.zyfaiServerTasks = config.zyfaiServerTasks === true || typeof config.zyfaiServerTasks === "function" ? config.zyfaiServerTasks : void 0;
|
|
15270
|
+
this.fusionSecretHandoff = config.fusionSecretHandoff === true;
|
|
13819
15271
|
}
|
|
13820
15272
|
/** Finishes a funding-ledger operation on success and adds its reference to
|
|
13821
15273
|
* the result. The result object is copied, never mutated or replaced.
|
|
@@ -13881,16 +15333,34 @@ var OwneySDK = class {
|
|
|
13881
15333
|
return void 0;
|
|
13882
15334
|
}
|
|
13883
15335
|
}
|
|
15336
|
+
/**
|
|
15337
|
+
* Part 5: the swap's ledger identity, for the executor's Fusion+ secret
|
|
15338
|
+
* handoff. Only with the `fusionSecretHandoff` opt-in and an open operation;
|
|
15339
|
+
* otherwise undefined, and the executor sends nothing. Owner and token are
|
|
15340
|
+
* lowercased, as the ledger reports them.
|
|
15341
|
+
*/
|
|
15342
|
+
secretHandoffFor(op, input) {
|
|
15343
|
+
if (!this.fusionSecretHandoff || !op || !input || input.kind !== "swap_deposit" && input.kind !== "withdraw_swap")
|
|
15344
|
+
return void 0;
|
|
15345
|
+
return { operation: {
|
|
15346
|
+
clientOperationId: op.operationId,
|
|
15347
|
+
kind: input.kind,
|
|
15348
|
+
owner: input.owner.toLowerCase(),
|
|
15349
|
+
chainId: input.chainId,
|
|
15350
|
+
token: input.token.toLowerCase()
|
|
15351
|
+
} };
|
|
15352
|
+
}
|
|
13884
15353
|
closeSwapLedger(op) {
|
|
13885
15354
|
if (op && this.openSwapLedgers.get(op.operationId) === op) this.openSwapLedgers.delete(op.operationId);
|
|
13886
15355
|
}
|
|
13887
|
-
/** Reports one agent's withdrawal evidence as its leg
|
|
13888
|
-
|
|
15356
|
+
/** Reports one agent's withdrawal evidence as its leg (with `errorCode`, when
|
|
15357
|
+
* given). Never throws. */
|
|
15358
|
+
ledgerWithdrawalLeg(op, evidence, errorCode) {
|
|
13889
15359
|
if (!op) return;
|
|
13890
15360
|
try {
|
|
13891
15361
|
const leg = ledgerLegFromEvidence(evidence, "withdraw");
|
|
13892
15362
|
const rail = withdrawalLedgerRail(evidence);
|
|
13893
|
-
if (leg) op.leg(rail ? { ...leg, rail } : leg);
|
|
15363
|
+
if (leg) op.leg({ ...rail ? { ...leg, rail } : leg, ...errorCode ? { errorCode } : {} });
|
|
13894
15364
|
if (leg?.status === "confirmed") this.ledgerProviderSamples(op, evidence, "withdraw");
|
|
13895
15365
|
} catch {
|
|
13896
15366
|
}
|
|
@@ -14125,8 +15595,8 @@ var OwneySDK = class {
|
|
|
14125
15595
|
return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
|
|
14126
15596
|
}
|
|
14127
15597
|
/** Builds the default USDC batch callback for the connected wallet. */
|
|
14128
|
-
getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId) {
|
|
14129
|
-
if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && this.cachedSponsoredCallback)
|
|
15598
|
+
getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId, resumeSaved, onAuthorization) {
|
|
15599
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization && this.cachedSponsoredCallback)
|
|
14130
15600
|
return this.cachedSponsoredCallback;
|
|
14131
15601
|
const provider = this.requireConnectedProvider();
|
|
14132
15602
|
const connection = this.requireState();
|
|
@@ -14140,6 +15610,8 @@ var OwneySDK = class {
|
|
|
14140
15610
|
onSubmitted,
|
|
14141
15611
|
onBeforeSubmission,
|
|
14142
15612
|
resumeTxHash,
|
|
15613
|
+
...resumeSaved ? { resumeSaved: true } : {},
|
|
15614
|
+
...onAuthorization ? { onAuthorization } : {},
|
|
14143
15615
|
operationId,
|
|
14144
15616
|
assertCurrent: () => {
|
|
14145
15617
|
assertSubmissionCurrent?.();
|
|
@@ -14160,7 +15632,7 @@ var OwneySDK = class {
|
|
|
14160
15632
|
transport: (0, import_viem25.custom)(provider)
|
|
14161
15633
|
})
|
|
14162
15634
|
});
|
|
14163
|
-
if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId) this.cachedSponsoredCallback = callback;
|
|
15635
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization) this.cachedSponsoredCallback = callback;
|
|
14164
15636
|
return callback;
|
|
14165
15637
|
}
|
|
14166
15638
|
/** Builds the wallet-native sponsored calls callback for compatible paymasters. */
|
|
@@ -14189,8 +15661,8 @@ var OwneySDK = class {
|
|
|
14189
15661
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
14190
15662
|
* single-use batch authorization instead of an EIP-3009 authorization.
|
|
14191
15663
|
*/
|
|
14192
|
-
getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId) {
|
|
14193
|
-
if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && this.cachedWethSponsoredCallback)
|
|
15664
|
+
getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent, operationId, resumeSaved, onAuthorization) {
|
|
15665
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization && this.cachedWethSponsoredCallback)
|
|
14194
15666
|
return this.cachedWethSponsoredCallback;
|
|
14195
15667
|
const provider = this.requireConnectedProvider();
|
|
14196
15668
|
const connection = this.requireState();
|
|
@@ -14204,6 +15676,8 @@ var OwneySDK = class {
|
|
|
14204
15676
|
onSubmitted,
|
|
14205
15677
|
onBeforeSubmission,
|
|
14206
15678
|
resumeTxHash,
|
|
15679
|
+
...resumeSaved ? { resumeSaved: true } : {},
|
|
15680
|
+
...onAuthorization ? { onAuthorization } : {},
|
|
14207
15681
|
operationId,
|
|
14208
15682
|
assertCurrent: () => {
|
|
14209
15683
|
assertSubmissionCurrent?.();
|
|
@@ -14224,7 +15698,7 @@ var OwneySDK = class {
|
|
|
14224
15698
|
transport: (0, import_viem25.custom)(provider)
|
|
14225
15699
|
})
|
|
14226
15700
|
});
|
|
14227
|
-
if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId) this.cachedWethSponsoredCallback = callback;
|
|
15701
|
+
if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && !operationId && !resumeSaved && !onAuthorization) this.cachedWethSponsoredCallback = callback;
|
|
14228
15702
|
return callback;
|
|
14229
15703
|
}
|
|
14230
15704
|
getAgent(agentId) {
|
|
@@ -14238,12 +15712,12 @@ var OwneySDK = class {
|
|
|
14238
15712
|
return [...this.getActiveAgents().keys()].sort();
|
|
14239
15713
|
}
|
|
14240
15714
|
getActiveAgents() {
|
|
14241
|
-
const
|
|
15715
|
+
const active = /* @__PURE__ */ new Map();
|
|
14242
15716
|
for (const id of this.activeAgents) {
|
|
14243
15717
|
const agent = this.agents.get(id);
|
|
14244
|
-
if (agent)
|
|
15718
|
+
if (agent) active.set(id, agent);
|
|
14245
15719
|
}
|
|
14246
|
-
return
|
|
15720
|
+
return active;
|
|
14247
15721
|
}
|
|
14248
15722
|
/**
|
|
14249
15723
|
* Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
|
|
@@ -14342,7 +15816,13 @@ var OwneySDK = class {
|
|
|
14342
15816
|
this.routingApiBaseUrl
|
|
14343
15817
|
) : this.rpcUrls,
|
|
14344
15818
|
this.referralSource,
|
|
14345
|
-
{
|
|
15819
|
+
{
|
|
15820
|
+
owneyApiKey: this.apiKey,
|
|
15821
|
+
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
15822
|
+
// Only an explicit opt-in lets the agent hand the Zyfai session to
|
|
15823
|
+
// Owney; a function is passed as it is and read per handoff.
|
|
15824
|
+
...this.zyfaiServerTasks !== void 0 ? { zyfaiServerTasks: this.zyfaiServerTasks } : {}
|
|
15825
|
+
}
|
|
14346
15826
|
);
|
|
14347
15827
|
}
|
|
14348
15828
|
if (agentId === "yieldseeker") {
|
|
@@ -14564,10 +16044,21 @@ var OwneySDK = class {
|
|
|
14564
16044
|
} catch {
|
|
14565
16045
|
}
|
|
14566
16046
|
};
|
|
14567
|
-
|
|
16047
|
+
let permit;
|
|
16048
|
+
const onAuthorization = (value) => {
|
|
16049
|
+
permit = value;
|
|
16050
|
+
};
|
|
16051
|
+
const permitFields = () => permit ? { permit2Nonce: permit.permit2Nonce, authorizationDeadline: permit.authorizationDeadline } : {};
|
|
16052
|
+
const onSubmitted = (hash2, destinations, depositAuthorization, serverBroadcast) => {
|
|
14568
16053
|
for (const destination of destinations) {
|
|
14569
16054
|
const evidence = prepared.get(destination.to.toLowerCase());
|
|
14570
|
-
if (evidence) notifyFunding(onProgress, {
|
|
16055
|
+
if (evidence) notifyFunding(onProgress, {
|
|
16056
|
+
...evidence,
|
|
16057
|
+
txHash: hash2,
|
|
16058
|
+
...depositAuthorization ? { depositAuthorization } : {},
|
|
16059
|
+
...serverBroadcast ? { serverBroadcast, ...permitFields() } : {},
|
|
16060
|
+
transfer: "pending"
|
|
16061
|
+
});
|
|
14571
16062
|
}
|
|
14572
16063
|
};
|
|
14573
16064
|
const onCallsProgress = (callsId, destinations, transfer) => {
|
|
@@ -14576,6 +16067,7 @@ var OwneySDK = class {
|
|
|
14576
16067
|
if (evidence) notifyFunding(onProgress, { ...evidence, callsId, transfer });
|
|
14577
16068
|
}
|
|
14578
16069
|
};
|
|
16070
|
+
const serverTaskContext = (agent) => this.zyfaiServerTasks !== void 0 && agent.supportsServerTasks === true && ledgerOp ? { operationId: ledgerOp.operationId } : void 0;
|
|
14579
16071
|
const ledgerDone = (result) => this.ledgerSucceeded(ledgerOp, result);
|
|
14580
16072
|
const ledgerFailed = (error) => this.ledgerFailed(ledgerOp, error);
|
|
14581
16073
|
if (BigInt(amount) === 0n) {
|
|
@@ -14611,7 +16103,7 @@ var OwneySDK = class {
|
|
|
14611
16103
|
});
|
|
14612
16104
|
await checkpoint2(evidence);
|
|
14613
16105
|
} : void 0;
|
|
14614
|
-
const checkpointHash = checkpointDestinations ? (hash2, destinations, depositAuthorization) => checkpointDestinations(destinations, { txHash: hash2, depositAuthorization }) : void 0;
|
|
16106
|
+
const checkpointHash = checkpointDestinations ? (hash2, destinations, depositAuthorization, serverBroadcast) => checkpointDestinations(destinations, { txHash: hash2, depositAuthorization, ...serverBroadcast ? { serverBroadcast, ...permitFields() } : {} }) : void 0;
|
|
14615
16107
|
const checkpointCalls = checkpointDestinations ? (id, destinations) => checkpointDestinations(destinations, { callsId: id }) : void 0;
|
|
14616
16108
|
const checkpointPaid = checkpoint2 ? (evidence) => checkpoint2([evidence]) : void 0;
|
|
14617
16109
|
const customCallback = depositCallback && checkpointDestinations ? makeVerificationAwareDepositCallback(async (to, cid, value, verification) => {
|
|
@@ -14619,9 +16111,10 @@ var OwneySDK = class {
|
|
|
14619
16111
|
await checkpointDestinations([{ to, amount: value }], {});
|
|
14620
16112
|
return depositCallback(to, cid, value);
|
|
14621
16113
|
}) : depositCallback;
|
|
14622
|
-
if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
|
|
16114
|
+
if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)) || options.resumeSavedBatch && (options.resumeTxHash || depositCallback || this.paymasterServiceUrl))
|
|
14623
16115
|
throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
|
|
14624
|
-
const effectiveCallback = customCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress, checkpointCalls) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash, options.assertSubmissionCurrent, ledgerOperationId) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash, options.assertSubmissionCurrent, ledgerOperationId));
|
|
16116
|
+
const effectiveCallback = customCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress, checkpointCalls) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash, options.assertSubmissionCurrent, ledgerOperationId, options.resumeSavedBatch, onAuthorization) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash, options.assertSubmissionCurrent, ledgerOperationId, options.resumeSavedBatch, onAuthorization));
|
|
16117
|
+
let resumedDeadline;
|
|
14625
16118
|
try {
|
|
14626
16119
|
const ledgerToken = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
|
|
14627
16120
|
ledgerOp = this.startLedger({
|
|
@@ -14636,16 +16129,19 @@ var OwneySDK = class {
|
|
|
14636
16129
|
} catch {
|
|
14637
16130
|
}
|
|
14638
16131
|
try {
|
|
14639
|
-
if (options.resumeTxHash) {
|
|
16132
|
+
if (options.resumeTxHash || options.resumeSavedBatch) {
|
|
14640
16133
|
const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
|
|
14641
|
-
const
|
|
16134
|
+
const scope = token && {
|
|
14642
16135
|
apiKey: this.apiKey,
|
|
14643
16136
|
baseUrl: this.routingApiBaseUrl,
|
|
14644
16137
|
owner: state.walletAddress,
|
|
14645
16138
|
chainId,
|
|
14646
16139
|
token: token.address
|
|
14647
|
-
}
|
|
14648
|
-
const
|
|
16140
|
+
};
|
|
16141
|
+
const family = scope && (options.resumeTxHash ? await loadTokenBatchFamily(scope, options.resumeTxHash) : { body: await savedTokenBatchBody(scope, this.getPaidRpcClient(chainId)) });
|
|
16142
|
+
if (options.resumeSavedBatch && family?.body && /^\d{1,15}$/.test(family.body.deadline))
|
|
16143
|
+
resumedDeadline = Number(family.body.deadline);
|
|
16144
|
+
const pending2 = family?.body && { transfers: family.body.transfers };
|
|
14649
16145
|
const invalid2 = () => new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit does not match this request. Check its status before depositing again.");
|
|
14650
16146
|
if (!pending2 || pending2.transfers.some((transfer) => !/^\d+$/.test(transfer.amount) || BigInt(transfer.amount) <= 0n) || pending2.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n) !== BigInt(amount)) throw invalid2();
|
|
14651
16147
|
const candidates = agentId ? [this.getAgent(agentId)] : this.getEligibleAgents(chainId, asset);
|
|
@@ -14665,12 +16161,10 @@ var OwneySDK = class {
|
|
|
14665
16161
|
return {
|
|
14666
16162
|
id: agent.id,
|
|
14667
16163
|
amount: transfer.amount,
|
|
14668
|
-
run: (callback) => withFailureReporting(
|
|
14669
|
-
|
|
14670
|
-
agent.
|
|
14671
|
-
|
|
14672
|
-
this.routingApiBaseUrl
|
|
14673
|
-
)
|
|
16164
|
+
run: (callback) => withFailureReporting(this.apiKey, agent.id, () => {
|
|
16165
|
+
const context = serverTaskContext(agent);
|
|
16166
|
+
return context ? agent.deposit(state, chainId, transfer.amount, asset, callback, onProgress, void 0, context) : agent.deposit(state, chainId, transfer.amount, asset, callback, onProgress);
|
|
16167
|
+
}, this.routingApiBaseUrl)
|
|
14674
16168
|
};
|
|
14675
16169
|
});
|
|
14676
16170
|
const batchTransfer2 = getDepositBatchTransfer(effectiveCallback);
|
|
@@ -14709,7 +16203,8 @@ var OwneySDK = class {
|
|
|
14709
16203
|
onProgress,
|
|
14710
16204
|
() => paidFallbackAgents.add(agent.id),
|
|
14711
16205
|
!!options.resumeTxHash,
|
|
14712
|
-
checkpointPaid
|
|
16206
|
+
checkpointPaid,
|
|
16207
|
+
serverTaskContext(agent)
|
|
14713
16208
|
));
|
|
14714
16209
|
}
|
|
14715
16210
|
const totalAmount = BigInt(amount);
|
|
@@ -14771,7 +16266,10 @@ var OwneySDK = class {
|
|
|
14771
16266
|
run: (callback) => withFailureReporting(
|
|
14772
16267
|
this.apiKey,
|
|
14773
16268
|
agent.id,
|
|
14774
|
-
() =>
|
|
16269
|
+
() => {
|
|
16270
|
+
const context = serverTaskContext(agent);
|
|
16271
|
+
return context ? agent.deposit(state, chainId, amount2, asset, callback, onProgress, void 0, context) : agent.deposit(state, chainId, amount2, asset, callback, onProgress);
|
|
16272
|
+
},
|
|
14775
16273
|
this.routingApiBaseUrl
|
|
14776
16274
|
)
|
|
14777
16275
|
})),
|
|
@@ -14812,7 +16310,8 @@ var OwneySDK = class {
|
|
|
14812
16310
|
onProgress,
|
|
14813
16311
|
() => paidFallbackAgents.add(agent.id),
|
|
14814
16312
|
!!options.resumeTxHash,
|
|
14815
|
-
checkpointPaid
|
|
16313
|
+
checkpointPaid,
|
|
16314
|
+
serverTaskContext(agent)
|
|
14816
16315
|
);
|
|
14817
16316
|
} catch (error) {
|
|
14818
16317
|
if (Object.keys(agentResults).length === 0) throw error;
|
|
@@ -14832,7 +16331,22 @@ var OwneySDK = class {
|
|
|
14832
16331
|
}
|
|
14833
16332
|
return ledgerDone({ agentResults });
|
|
14834
16333
|
} catch (error) {
|
|
14835
|
-
if (
|
|
16334
|
+
if (options.resumeSavedBatch && !serverBroadcastOf(error))
|
|
16335
|
+
error = new OwneyError(
|
|
16336
|
+
"DEPOSIT_IN_PROGRESS",
|
|
16337
|
+
"Deposit is awaiting confirmation. Its transaction is still being checked.",
|
|
16338
|
+
{
|
|
16339
|
+
sponsorPending: true,
|
|
16340
|
+
phase: "deposit",
|
|
16341
|
+
notSubmitted: false,
|
|
16342
|
+
safeToFallback: false,
|
|
16343
|
+
serverBroadcast: "unknown",
|
|
16344
|
+
...resumedDeadline !== void 0 ? { authorizationDeadline: resumedDeadline } : {},
|
|
16345
|
+
cause: error
|
|
16346
|
+
}
|
|
16347
|
+
);
|
|
16348
|
+
const serverMaySend = serverBroadcastOf(error);
|
|
16349
|
+
if (checkpointFailure && !serverMaySend && !(error instanceof OwneyError && error.details?.notSubmitted === false)) {
|
|
14836
16350
|
error = error instanceof OwneyError && error.code === "DEPOSIT_PARTIAL_FAILURE" ? new OwneyError(
|
|
14837
16351
|
error.code,
|
|
14838
16352
|
"Some deposits completed before another agent could be submitted.",
|
|
@@ -14850,6 +16364,19 @@ var OwneySDK = class {
|
|
|
14850
16364
|
const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
|
|
14851
16365
|
if (!rejected && paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
|
|
14852
16366
|
if (!rejected && (evidence?.txHash || evidence?.userOpHash || evidence?.callsId)) continue;
|
|
16367
|
+
if (serverMaySend) {
|
|
16368
|
+
const refused = alreadySendingOf(error);
|
|
16369
|
+
const deadline = refused ? void 0 : authorizationDeadlineOf(error) ?? permit?.authorizationDeadline;
|
|
16370
|
+
const nonce = refused ? void 0 : permit2NonceOf(error) ?? permit?.permit2Nonce;
|
|
16371
|
+
if (evidence) notifyFunding(onProgress, {
|
|
16372
|
+
...evidence,
|
|
16373
|
+
serverBroadcast: evidence.serverBroadcast ?? "unknown",
|
|
16374
|
+
...deadline !== void 0 && !evidence.authorizationDeadline ? { authorizationDeadline: deadline } : {},
|
|
16375
|
+
...nonce !== void 0 && !evidence.permit2Nonce ? { permit2Nonce: nonce } : {},
|
|
16376
|
+
transfer: "pending"
|
|
16377
|
+
});
|
|
16378
|
+
continue;
|
|
16379
|
+
}
|
|
14853
16380
|
notifyFunding(onProgress, { ...evidence ?? {
|
|
14854
16381
|
agentId: id,
|
|
14855
16382
|
chainId,
|
|
@@ -14896,11 +16423,11 @@ var OwneySDK = class {
|
|
|
14896
16423
|
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
14897
16424
|
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
14898
16425
|
*/
|
|
14899
|
-
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false, onBeforeSubmission) {
|
|
16426
|
+
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false, onBeforeSubmission, serverTaskContext) {
|
|
14900
16427
|
const attempt = (callback) => withFailureReporting(
|
|
14901
16428
|
this.apiKey,
|
|
14902
16429
|
agent.id,
|
|
14903
|
-
() => onBeforeSubmission ? agent.deposit(state, chainId, amount, asset, callback, onProgress, onBeforeSubmission) : onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
|
|
16430
|
+
() => serverTaskContext ? agent.deposit(state, chainId, amount, asset, callback, onProgress, onBeforeSubmission, serverTaskContext) : onBeforeSubmission ? agent.deposit(state, chainId, amount, asset, callback, onProgress, onBeforeSubmission) : onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
|
|
14904
16431
|
this.routingApiBaseUrl
|
|
14905
16432
|
);
|
|
14906
16433
|
let approvalAttempted = false;
|
|
@@ -15197,11 +16724,36 @@ var OwneySDK = class {
|
|
|
15197
16724
|
const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${evidence.agentId}:${evidence.token}:${evidence.txHash}`;
|
|
15198
16725
|
if (options.refreshBalance !== false && (verified.transfer === "confirmed" || verified.transfer === "failed" && verified.confirmedAmount === "0") && !this.recoveredFundingReads.has(key2)) {
|
|
15199
16726
|
agent.invalidateFundingRead?.(state, chainId, evidence.token, options.balanceRefreshUntil, evidence.txHash);
|
|
15200
|
-
|
|
15201
|
-
|
|
16727
|
+
const until = options.balanceRefreshUntil;
|
|
16728
|
+
this.noteFundingRefresh(key2, until === void 0 || until > Date.now());
|
|
15202
16729
|
}
|
|
15203
16730
|
return verified;
|
|
15204
16731
|
}
|
|
16732
|
+
noteFundingRefresh(key2, armed) {
|
|
16733
|
+
this.recoveredFundingReads.delete(key2);
|
|
16734
|
+
this.recoveredFundingReads.set(key2, armed);
|
|
16735
|
+
if (this.recoveredFundingReads.size > 256) this.recoveredFundingReads.delete(this.recoveredFundingReads.keys().next().value);
|
|
16736
|
+
}
|
|
16737
|
+
/**
|
|
16738
|
+
* Re-arms an agent's balance refresh after a confirmed deposit or withdrawal
|
|
16739
|
+
* the caller already verified (e.g. after a page reload, which loses the
|
|
16740
|
+
* refresh the SDK started in memory). Same refresh as receipt recovery
|
|
16741
|
+
* (`inspectFunding`), without a receipt read, and shared with it: once per
|
|
16742
|
+
* movement (`txHash`) in this SDK session, unless that recovery came after
|
|
16743
|
+
* its own deadline and so armed nothing. `until` bounds it (the agent caps
|
|
16744
|
+
* it further). Returns false when the agent has no such refresh or `until`
|
|
16745
|
+
* has passed; throws when not connected or on another chain. Never prompts.
|
|
16746
|
+
*/
|
|
16747
|
+
refreshAgentBalanceAfterMovement(input) {
|
|
16748
|
+
const { state, chainId } = this.captureReadScope(input.chainId);
|
|
16749
|
+
const agent = this.getAgent(input.agentId);
|
|
16750
|
+
if (typeof agent.invalidateFundingRead !== "function" || !Number.isFinite(input.until) || input.until <= Date.now()) return false;
|
|
16751
|
+
const key2 = input.txHash ? `${state.walletAddress.toLowerCase()}:${chainId}:${input.agentId}:${input.token}:${input.txHash}` : void 0;
|
|
16752
|
+
if (key2 && this.recoveredFundingReads.get(key2) === true) return true;
|
|
16753
|
+
agent.invalidateFundingRead(state, chainId, input.token, input.until, input.txHash);
|
|
16754
|
+
if (key2) this.noteFundingRefresh(key2, true);
|
|
16755
|
+
return true;
|
|
16756
|
+
}
|
|
15205
16757
|
/**
|
|
15206
16758
|
* Lists on-chain payout candidates for a withdrawal the provider accepted
|
|
15207
16759
|
* without a hash: ERC-20 `Transfer` logs on the evidence's token that reach
|
|
@@ -15323,22 +16875,25 @@ var OwneySDK = class {
|
|
|
15323
16875
|
try {
|
|
15324
16876
|
if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
|
|
15325
16877
|
const first = evidences[0];
|
|
15326
|
-
|
|
16878
|
+
const saved = !first.txHash && evidences.every((e) => !e.txHash && (e.serverBroadcast === "unknown" || e.serverBroadcast === true) && typeof e.authorizationDeadline === "number");
|
|
16879
|
+
if (!first.txHash && !saved) return false;
|
|
15327
16880
|
const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
|
|
15328
16881
|
const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
|
|
15329
16882
|
if (!token) return false;
|
|
15330
|
-
const
|
|
16883
|
+
const scope = {
|
|
15331
16884
|
apiKey: this.apiKey,
|
|
15332
16885
|
baseUrl: this.routingApiBaseUrl,
|
|
15333
16886
|
owner: state.walletAddress,
|
|
15334
16887
|
chainId,
|
|
15335
16888
|
token: token.address
|
|
15336
|
-
}
|
|
15337
|
-
const
|
|
15338
|
-
|
|
16889
|
+
};
|
|
16890
|
+
const family = saved ? void 0 : await loadTokenBatchFamily(scope, first.txHash);
|
|
16891
|
+
const savedBody = saved ? await savedTokenBatchBody(scope, this.getPaidRpcClient(chainId)) : void 0;
|
|
16892
|
+
const transfers = saved ? savedBody?.transfers : family?.body.transfers;
|
|
16893
|
+
if (!family && !savedBody || !transfers || transfers.length !== evidences.length || transfers.reduce((n, t) => n + BigInt(t.amount), 0n).toString() !== amount) return false;
|
|
15339
16894
|
const destinations = /* @__PURE__ */ new Set();
|
|
15340
16895
|
for (const e of evidences) {
|
|
15341
|
-
if (e.chainId !== chainId || e.token !== first.token || e.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || e.callsId || e.userOpHash || !family.attempts.some((a) => a.phase === "deposit" && a.hash.toLowerCase() === e.txHash?.toLowerCase()) || e.depositAuthorization && e.depositAuthorization.authorizationId !== family.authorizationId) return false;
|
|
16896
|
+
if (e.chainId !== chainId || e.token !== first.token || e.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || e.callsId || e.userOpHash || (family ? !family.attempts.some((a) => a.phase === "deposit" && a.hash.toLowerCase() === e.txHash?.toLowerCase()) || e.depositAuthorization && e.depositAuthorization.authorizationId !== family.authorizationId : String(e.authorizationDeadline) !== savedBody.deadline)) return false;
|
|
15342
16897
|
const agent = this.getAgent(e.agentId);
|
|
15343
16898
|
const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, e.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
|
|
15344
16899
|
assertCurrent();
|
|
@@ -15351,6 +16906,62 @@ var OwneySDK = class {
|
|
|
15351
16906
|
return false;
|
|
15352
16907
|
}
|
|
15353
16908
|
}
|
|
16909
|
+
/** Whether a Permit2 deposit authorization can still execute and whether it
|
|
16910
|
+
* did: `expired` when the finalized block is more than 300 s past
|
|
16911
|
+
* `deadline` (chain time), `used` when the owner's Permit2 nonce bit is set
|
|
16912
|
+
* at that block. Null when anything cannot be read. Never throws. */
|
|
16913
|
+
async checkDepositAuthorization(input) {
|
|
16914
|
+
try {
|
|
16915
|
+
const { chainId, owner, permit2Nonce, deadline } = input;
|
|
16916
|
+
if (!SupportedAssets.some((asset) => asset.chainId === chainId) || !(0, import_viem25.isAddress)(owner) || typeof permit2Nonce !== "string" || !/^\d{1,78}$/.test(permit2Nonce) || !Number.isSafeInteger(deadline) || deadline <= 0) return null;
|
|
16917
|
+
const nonce = BigInt(permit2Nonce);
|
|
16918
|
+
if (nonce >= 1n << 256n) return null;
|
|
16919
|
+
const pub = this.getPaidRpcClient(chainId);
|
|
16920
|
+
const block = await pub.getBlock({ blockTag: "finalized" });
|
|
16921
|
+
if (typeof block?.timestamp !== "bigint" || typeof block.number !== "bigint") return null;
|
|
16922
|
+
const bitmap = await pub.readContract({
|
|
16923
|
+
address: BATCH_PERMIT2_ADDRESS,
|
|
16924
|
+
abi: PERMIT2_BATCH_ABI,
|
|
16925
|
+
functionName: "nonceBitmap",
|
|
16926
|
+
args: [owner, nonce >> 8n],
|
|
16927
|
+
blockNumber: block.number
|
|
16928
|
+
});
|
|
16929
|
+
if (typeof bitmap !== "bigint" || bitmap < 0n) return null;
|
|
16930
|
+
return { expired: block.timestamp > BigInt(deadline) + 300n, used: (bitmap & 1n << (nonce & 255n)) !== 0n };
|
|
16931
|
+
} catch {
|
|
16932
|
+
return null;
|
|
16933
|
+
}
|
|
16934
|
+
}
|
|
16935
|
+
/** Drops the saved signed body of a hashless deposit the server may have
|
|
16936
|
+
* been sending (`serverBroadcast` 'unknown'/true, no hash) once the app has
|
|
16937
|
+
* settled it another way: by the ledger's hash or its expired deadline.
|
|
16938
|
+
* Only the body with this Permit2 deadline and these exact shares is
|
|
16939
|
+
* dropped, so it never resumes or blocks a different deposit. Never throws. */
|
|
16940
|
+
async discardSavedDepositBatch(evidences) {
|
|
16941
|
+
try {
|
|
16942
|
+
if (this.paymasterServiceUrl || !evidences.length) return false;
|
|
16943
|
+
const first = evidences[0];
|
|
16944
|
+
const deadline = first.authorizationDeadline;
|
|
16945
|
+
if (typeof deadline !== "number" || !Number.isSafeInteger(deadline) || deadline <= 0) return false;
|
|
16946
|
+
const { state, chainId } = this.captureReadScope(first.chainId);
|
|
16947
|
+
const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
|
|
16948
|
+
if (!token) return false;
|
|
16949
|
+
if (evidences.some((e) => e.chainId !== chainId || e.token !== first.token || e.authorizationDeadline !== deadline || e.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || !e.agentWallet || e.serverBroadcast !== "unknown" && e.serverBroadcast !== true)) return false;
|
|
16950
|
+
return await discardHashlessTokenBatch(
|
|
16951
|
+
{
|
|
16952
|
+
apiKey: this.apiKey,
|
|
16953
|
+
baseUrl: this.routingApiBaseUrl,
|
|
16954
|
+
owner: state.walletAddress,
|
|
16955
|
+
chainId,
|
|
16956
|
+
token: token.address
|
|
16957
|
+
},
|
|
16958
|
+
String(deadline),
|
|
16959
|
+
(body) => evidences.every((e) => body.transfers.some((t) => t.to.toLowerCase() === e.agentWallet.toLowerCase() && t.amount === e.intendedAmount))
|
|
16960
|
+
);
|
|
16961
|
+
} catch {
|
|
16962
|
+
return false;
|
|
16963
|
+
}
|
|
16964
|
+
}
|
|
15354
16965
|
/** Validate signed journal membership, never just a caller-supplied family ID. */
|
|
15355
16966
|
async validateDepositReplacement(previous, next) {
|
|
15356
16967
|
try {
|
|
@@ -15443,6 +17054,22 @@ var OwneySDK = class {
|
|
|
15443
17054
|
if (verified.agentId !== "zyfai") return verified;
|
|
15444
17055
|
return this.getAgent("zyfai").retryDepositRegistration(state, verified);
|
|
15445
17056
|
}
|
|
17057
|
+
/** A Zyfai withdrawal's lifecycle by the id recorded on its evidence. Never signs,
|
|
17058
|
+
* prompts or authenticates; `null` when unavailable or not this wallet's Zyfai withdrawal. */
|
|
17059
|
+
async getZyfaiWithdrawal(evidence) {
|
|
17060
|
+
const state = this.state;
|
|
17061
|
+
const id = evidence.withdrawalRequest?.providerWithdrawalId;
|
|
17062
|
+
if (evidence.agentId !== "zyfai" || !id || !state || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return null;
|
|
17063
|
+
return this.getAgent("zyfai").getWithdrawalLifecycle(state, id);
|
|
17064
|
+
}
|
|
17065
|
+
/** A Zyfai deposit's placement status by the id recorded on its evidence. Never signs,
|
|
17066
|
+
* prompts or authenticates; `null` when unavailable or not this wallet's Zyfai deposit. */
|
|
17067
|
+
async getZyfaiDeposit(evidence) {
|
|
17068
|
+
const state = this.state;
|
|
17069
|
+
const id = evidence.providerDepositId;
|
|
17070
|
+
if (evidence.agentId !== "zyfai" || !id || !state || state.walletAddress.toLowerCase() !== evidence.walletAddress.toLowerCase()) return null;
|
|
17071
|
+
return this.getAgent("zyfai").getDepositPlacement(state, id);
|
|
17072
|
+
}
|
|
15446
17073
|
// --- Fund operations ---
|
|
15447
17074
|
/** On-chain version and ownership status for this owner's Yieldseeker agent. */
|
|
15448
17075
|
async getYieldseekerWalletStatus(asset) {
|
|
@@ -16025,6 +17652,17 @@ var OwneySDK = class {
|
|
|
16025
17652
|
onStage: (stage) => options.onStage(order.orderHash, stage)
|
|
16026
17653
|
} : {}
|
|
16027
17654
|
});
|
|
17655
|
+
const deposit = order.pendingDeposit;
|
|
17656
|
+
if (deposit && typeof deposit.received === "string" && typeof deposit.symbol === "string" && typeof deposit.chainId === "number" && loadOrder(order.orderHash))
|
|
17657
|
+
savePendingDeposit({
|
|
17658
|
+
id: `${deposit.chainId}-${deposit.symbol}-${order.orderHash}`,
|
|
17659
|
+
received: deposit.received,
|
|
17660
|
+
symbol: deposit.symbol,
|
|
17661
|
+
chainId: deposit.chainId,
|
|
17662
|
+
createdAt: Date.now(),
|
|
17663
|
+
...typeof deposit.agentId === "string" ? { agentId: deposit.agentId } : {},
|
|
17664
|
+
...typeof deposit.operationId === "string" ? { operationId: deposit.operationId } : {}
|
|
17665
|
+
});
|
|
16028
17666
|
clearOrder(order.orderHash);
|
|
16029
17667
|
updateWithdrawSwap(order.orderHash, { status: "landed" });
|
|
16030
17668
|
return { orderHash: order.orderHash, status: "completed" };
|
|
@@ -16195,14 +17833,16 @@ var OwneySDK = class {
|
|
|
16195
17833
|
options.to.symbol
|
|
16196
17834
|
);
|
|
16197
17835
|
}
|
|
16198
|
-
|
|
17836
|
+
let ledgerInput;
|
|
17837
|
+
const ledgerOp = this.openSwapLedger(() => ledgerInput = {
|
|
16199
17838
|
operationId: options.operationId,
|
|
16200
17839
|
kind: "swap_deposit",
|
|
16201
17840
|
owner: state.walletAddress,
|
|
16202
17841
|
chainId: options.to.chainId,
|
|
16203
17842
|
token: SupportedAssets.find((value) => value.chainId === options.to.chainId && value.symbol === options.to.symbol)?.address ?? "",
|
|
16204
17843
|
asset: options.to.symbol.toLowerCase()
|
|
16205
|
-
})
|
|
17844
|
+
});
|
|
17845
|
+
const secretHandoff = this.secretHandoffFor(ledgerOp, ledgerInput);
|
|
16206
17846
|
try {
|
|
16207
17847
|
const api = this.swapApi();
|
|
16208
17848
|
const quoted = await api.quote({
|
|
@@ -16230,6 +17870,17 @@ var OwneySDK = class {
|
|
|
16230
17870
|
...options.assertCurrent ? { assertCurrent: options.assertCurrent } : {},
|
|
16231
17871
|
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
16232
17872
|
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
|
|
17873
|
+
// Stored with a cross-chain order, so a resume that finishes the swap
|
|
17874
|
+
// can still lead to the deposit (ROUT-625). The guaranteed minimum,
|
|
17875
|
+
// never the quoted amount.
|
|
17876
|
+
pendingDeposit: {
|
|
17877
|
+
received: quote.dstAmountMin,
|
|
17878
|
+
symbol: options.to.symbol,
|
|
17879
|
+
chainId: quote.dst.chainId,
|
|
17880
|
+
...options.agentId ? { agentId: options.agentId } : {},
|
|
17881
|
+
...ledgerOp ? { operationId: ledgerOp.operationId } : {}
|
|
17882
|
+
},
|
|
17883
|
+
...secretHandoff ? { secretHandoff } : {},
|
|
16233
17884
|
...ledgerOp ? { onOrderSubmitted: (orderHash) => {
|
|
16234
17885
|
swapIds.orderHash = orderHash;
|
|
16235
17886
|
this.ledgerSwap(ledgerOp, quote, { status: "submitted", orderHash });
|
|
@@ -16249,6 +17900,7 @@ var OwneySDK = class {
|
|
|
16249
17900
|
receivedDstAmountRaw: swap.received
|
|
16250
17901
|
});
|
|
16251
17902
|
options.onSwapProgress?.("depositing");
|
|
17903
|
+
const backgroundDepositId = swap.orderHash ? `${quote.dst.chainId}-${options.to.symbol}-${swap.orderHash}` : void 0;
|
|
16252
17904
|
try {
|
|
16253
17905
|
options.assertCurrent?.();
|
|
16254
17906
|
await this.ensureSwapChain(quote.dst.chainId);
|
|
@@ -16278,10 +17930,13 @@ var OwneySDK = class {
|
|
|
16278
17930
|
...options.assertCurrent ? { assertSubmissionCurrent: options.assertCurrent } : {},
|
|
16279
17931
|
...ledgerOp ? { operationId: ledgerOp.operationId } : {}
|
|
16280
17932
|
});
|
|
17933
|
+
if (backgroundDepositId) clearPendingDeposit(backgroundDepositId);
|
|
16281
17934
|
return this.ledgerSucceeded(ledgerOp, { swap, deposit, deposited: amount });
|
|
16282
17935
|
} catch (error) {
|
|
16283
17936
|
const pending2 = {
|
|
16284
|
-
|
|
17937
|
+
// Keyed by the order when there is one, so it replaces any record a
|
|
17938
|
+
// background resume wrote for the same order rather than doubling it.
|
|
17939
|
+
id: backgroundDepositId ?? `${quote.dst.chainId}-${options.to.symbol}-${Date.now()}`,
|
|
16285
17940
|
received: swap.received,
|
|
16286
17941
|
symbol: options.to.symbol,
|
|
16287
17942
|
chainId: quote.dst.chainId,
|
|
@@ -16437,7 +18092,7 @@ var OwneySDK = class {
|
|
|
16437
18092
|
const state = this.requireState();
|
|
16438
18093
|
const activeChainId = this.requireChainId();
|
|
16439
18094
|
const revision = this.connectionRevision;
|
|
16440
|
-
let
|
|
18095
|
+
let active = true;
|
|
16441
18096
|
let record2;
|
|
16442
18097
|
let ledgerOp;
|
|
16443
18098
|
let hadDurableRecord = false;
|
|
@@ -16445,7 +18100,7 @@ var OwneySDK = class {
|
|
|
16445
18100
|
let confirmationDeadline = Infinity;
|
|
16446
18101
|
const assertCurrent = () => {
|
|
16447
18102
|
options.assertCurrent?.();
|
|
16448
|
-
if (!
|
|
18103
|
+
if (!active) throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal attempt is no longer active.", { swapSubmitted: false });
|
|
16449
18104
|
if (this.state !== state || this.connectionRevision !== revision || state.chainId !== activeChainId)
|
|
16450
18105
|
throw new OwneyError("NOT_CONNECTED", "Wallet changed during withdrawal and swap.");
|
|
16451
18106
|
};
|
|
@@ -16490,9 +18145,10 @@ var OwneySDK = class {
|
|
|
16490
18145
|
const saved = options.recoveryId ? listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === options.recoveryId) : void 0;
|
|
16491
18146
|
if (options.recoveryId && (!saved?.recovery?.results || saved.recovery.phase !== "ready" || saved.walletAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || saved.srcChainId !== activeChainId || saved.srcSymbol !== asset.symbol || saved.dstChainId !== options.to.chainId || saved.dstSymbol !== options.to.symbol))
|
|
16492
18147
|
throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal is not ready to continue.");
|
|
18148
|
+
let ledgerInput;
|
|
16493
18149
|
ledgerOp = this.openSwapLedger(() => {
|
|
16494
18150
|
const requestedRaw = saved ? saved.recovery.requested : options.amount === void 0 ? void 0 : (0, import_viem25.parseUnits)(options.amount, asset.decimals).toString();
|
|
16495
|
-
return {
|
|
18151
|
+
return ledgerInput = {
|
|
16496
18152
|
operationId: saved?.operationId ?? options.operationId,
|
|
16497
18153
|
kind: "withdraw_swap",
|
|
16498
18154
|
owner: state.walletAddress,
|
|
@@ -16503,6 +18159,7 @@ var OwneySDK = class {
|
|
|
16503
18159
|
...requestedRaw === void 0 ? {} : { requestedAmountRaw: requestedRaw }
|
|
16504
18160
|
};
|
|
16505
18161
|
});
|
|
18162
|
+
const secretHandoff = this.secretHandoffFor(ledgerOp, ledgerInput);
|
|
16506
18163
|
const prepareTimeout = () => new OwneyError(
|
|
16507
18164
|
"WITHDRAW_SWAP_INCOMPLETE",
|
|
16508
18165
|
"Could not read the source wallet. No new withdrawal was submitted.",
|
|
@@ -16562,7 +18219,7 @@ var OwneySDK = class {
|
|
|
16562
18219
|
evidence.set(next.agentId, next);
|
|
16563
18220
|
if (record2?.recovery) {
|
|
16564
18221
|
record2.recovery.evidence[next.agentId] = next;
|
|
16565
|
-
if (
|
|
18222
|
+
if (active) saveWithdrawSwap(record2);
|
|
16566
18223
|
else {
|
|
16567
18224
|
const latest = listWithdrawSwaps(Date.now(), true).find((row) => row.recovery?.attemptId === record2.recovery.attemptId);
|
|
16568
18225
|
if (latest?.recovery) updateWithdrawSwap(latest.orderHash, { recovery: {
|
|
@@ -16678,6 +18335,7 @@ var OwneySDK = class {
|
|
|
16678
18335
|
quote,
|
|
16679
18336
|
walletAddress: state.walletAddress,
|
|
16680
18337
|
direction: "withdraw",
|
|
18338
|
+
...secretHandoff ? { secretHandoff } : {},
|
|
16681
18339
|
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
16682
18340
|
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
|
|
16683
18341
|
onOrderPrepared: (orderHash, details) => {
|
|
@@ -16766,7 +18424,7 @@ var OwneySDK = class {
|
|
|
16766
18424
|
this.ledgerFailed(ledgerOp, error);
|
|
16767
18425
|
throw error;
|
|
16768
18426
|
} finally {
|
|
16769
|
-
|
|
18427
|
+
active = false;
|
|
16770
18428
|
releaseSubmission();
|
|
16771
18429
|
this.closeSwapLedger(ledgerOp);
|
|
16772
18430
|
}
|
|
@@ -16814,11 +18472,13 @@ var OwneySDK = class {
|
|
|
16814
18472
|
});
|
|
16815
18473
|
} catch {
|
|
16816
18474
|
}
|
|
16817
|
-
const ledgerLeg = (evidence) => this.ledgerWithdrawalLeg(ledgerOp, evidence);
|
|
18475
|
+
const ledgerLeg = (evidence, errorCode) => this.ledgerWithdrawalLeg(ledgerOp, evidence, errorCode);
|
|
16818
18476
|
let hold;
|
|
16819
18477
|
let reservationRejected = false;
|
|
16820
18478
|
let innerStarted = false;
|
|
16821
18479
|
const reservationLegs = /* @__PURE__ */ new Map();
|
|
18480
|
+
const plannedLegs = /* @__PURE__ */ new Map();
|
|
18481
|
+
const serverLegs = /* @__PURE__ */ new Map();
|
|
16822
18482
|
const reservation = {
|
|
16823
18483
|
dispatching: (id) => hold?.dispatch(id),
|
|
16824
18484
|
settled: (id, leg) => {
|
|
@@ -16828,11 +18488,22 @@ var OwneySDK = class {
|
|
|
16828
18488
|
const settleHold = () => {
|
|
16829
18489
|
if (!hold) return;
|
|
16830
18490
|
hold.stopHeartbeat();
|
|
16831
|
-
|
|
18491
|
+
const serverOwned = new Set(ids.filter((id) => !attempted.has(id) && serverMayOwn(serverLegs.get(id))));
|
|
18492
|
+
this.settleWithdrawalReservation(hold, ids, attempted, reservationLegs, serverOwned);
|
|
16832
18493
|
};
|
|
16833
18494
|
const runAgents = () => {
|
|
16834
18495
|
innerStarted = true;
|
|
16835
|
-
const inner = this.withdrawFromAgents(
|
|
18496
|
+
const inner = this.withdrawFromAgents(
|
|
18497
|
+
options,
|
|
18498
|
+
state,
|
|
18499
|
+
chainId,
|
|
18500
|
+
attempted,
|
|
18501
|
+
ledgerLeg,
|
|
18502
|
+
reservation,
|
|
18503
|
+
plannedLegs,
|
|
18504
|
+
(timeoutMs) => ledgerOp?.settle?.(timeoutMs),
|
|
18505
|
+
serverLegs
|
|
18506
|
+
);
|
|
16836
18507
|
void inner.then(settleHold, settleHold);
|
|
16837
18508
|
return inner;
|
|
16838
18509
|
};
|
|
@@ -16856,7 +18527,9 @@ var OwneySDK = class {
|
|
|
16856
18527
|
} catch (error) {
|
|
16857
18528
|
if (!reservationRejected) {
|
|
16858
18529
|
for (const id of ids) if (!attempted.has(id)) {
|
|
16859
|
-
|
|
18530
|
+
if (serverMayOwn(serverLegs.get(id))) continue;
|
|
18531
|
+
const planned = plannedLegs.get(id);
|
|
18532
|
+
notifyFunding(options.onProgress, planned ? { ...planned, transfer: "failed", delivery: "failed" } : {
|
|
16860
18533
|
agentId: id,
|
|
16861
18534
|
chainId,
|
|
16862
18535
|
token: options.asset,
|
|
@@ -16978,12 +18651,13 @@ var OwneySDK = class {
|
|
|
16978
18651
|
* - a throw without submission evidence: `not_submitted`;
|
|
16979
18652
|
* - Zyfai "withdrawal already in progress": kept, dispatch `provider_in_progress`.
|
|
16980
18653
|
*/
|
|
16981
|
-
settleWithdrawalReservation(hold, ids, attempted, legs) {
|
|
18654
|
+
settleWithdrawalReservation(hold, ids, attempted, legs, keep) {
|
|
16982
18655
|
try {
|
|
16983
18656
|
if ([...attempted].some((id) => !legs.has(id))) return;
|
|
16984
18657
|
const releases = /* @__PURE__ */ new Map();
|
|
16985
18658
|
for (const id of new Set(ids)) {
|
|
16986
18659
|
if (!RESERVATION_AGENTS.includes(id)) continue;
|
|
18660
|
+
if (keep?.has(id)) continue;
|
|
16987
18661
|
const leg = legs.get(id);
|
|
16988
18662
|
const outcome = attempted.has(id) && leg ? reservationLegOutcome(leg) : "not_submitted";
|
|
16989
18663
|
if (outcome === "keep") continue;
|
|
@@ -17014,6 +18688,31 @@ var OwneySDK = class {
|
|
|
17014
18688
|
} catch {
|
|
17015
18689
|
}
|
|
17016
18690
|
}
|
|
18691
|
+
/**
|
|
18692
|
+
* Reports a withdrawal step the app closed as never sent (a reload before its
|
|
18693
|
+
* request was saved) as a failed ledger leg with `NOT_SUBMITTED`, so other
|
|
18694
|
+
* devices and later recovery stop treating it as in flight. Refuses any step
|
|
18695
|
+
* with a hash or a saved request. Never touches the hold; never throws.
|
|
18696
|
+
*/
|
|
18697
|
+
async reportWithdrawalNotSubmitted(operationId, evidence) {
|
|
18698
|
+
try {
|
|
18699
|
+
if (typeof operationId !== "string" || !operationId || !evidence || evidence.transfer !== "failed" || evidence.txHash || evidence.userOpHash || evidence.callsId || evidence.withdrawalRequest?.attempted === true) return;
|
|
18700
|
+
const leg = ledgerLegFromEvidence(evidence, "withdraw");
|
|
18701
|
+
if (leg?.status !== "failed") return;
|
|
18702
|
+
const token = SupportedAssets.find((value) => value.chainId === evidence.chainId && value.symbol === evidence.token);
|
|
18703
|
+
const op = this.ledger.resume({
|
|
18704
|
+
operationId,
|
|
18705
|
+
kind: "withdraw",
|
|
18706
|
+
owner: evidence.walletAddress,
|
|
18707
|
+
chainId: evidence.chainId,
|
|
18708
|
+
token: token?.address ?? "",
|
|
18709
|
+
asset: String(evidence.token).toLowerCase()
|
|
18710
|
+
});
|
|
18711
|
+
op.leg({ ...leg, errorCode: "NOT_SUBMITTED" });
|
|
18712
|
+
op.finish({});
|
|
18713
|
+
} catch {
|
|
18714
|
+
}
|
|
18715
|
+
}
|
|
17017
18716
|
/**
|
|
17018
18717
|
* Reports a withdrawal leg that was proven after its call ended (a hashless
|
|
17019
18718
|
* Zyfai payout found on chain or in history) to the funding ledger, then
|
|
@@ -17063,7 +18762,36 @@ var OwneySDK = class {
|
|
|
17063
18762
|
} catch {
|
|
17064
18763
|
}
|
|
17065
18764
|
}
|
|
17066
|
-
|
|
18765
|
+
/**
|
|
18766
|
+
* Reports a deposit leg that the app's recovery verified on chain after its
|
|
18767
|
+
* call ended to the funding ledger. Only evidence whose transfer is confirmed
|
|
18768
|
+
* with a transaction hash is reported; anything else is ignored. The ledger
|
|
18769
|
+
* PATCHes the existing operation (creating it only on a 404) and the server
|
|
18770
|
+
* re-verifies the hash. Deposits hold no reservation, so nothing is released.
|
|
18771
|
+
* Never throws.
|
|
18772
|
+
*/
|
|
18773
|
+
async reportDepositRecovery(operationId, evidence) {
|
|
18774
|
+
try {
|
|
18775
|
+
if (typeof operationId !== "string" || !operationId || !evidence || evidence.transfer !== "confirmed") return;
|
|
18776
|
+
const leg = ledgerLegFromEvidence(evidence, "deposit");
|
|
18777
|
+
if (leg?.status !== "confirmed" || !leg.txHash) return;
|
|
18778
|
+
const rail = depositLedgerRail(evidence);
|
|
18779
|
+
const token = SupportedAssets.find((value) => value.chainId === evidence.chainId && value.symbol === evidence.token);
|
|
18780
|
+
const op = this.ledger.resume({
|
|
18781
|
+
operationId,
|
|
18782
|
+
kind: "deposit",
|
|
18783
|
+
owner: evidence.walletAddress,
|
|
18784
|
+
chainId: evidence.chainId,
|
|
18785
|
+
token: token?.address ?? "",
|
|
18786
|
+
asset: String(evidence.token).toLowerCase()
|
|
18787
|
+
});
|
|
18788
|
+
op.leg(rail ? { ...leg, rail } : leg);
|
|
18789
|
+
op.finish({});
|
|
18790
|
+
this.ledgerProviderSamples(op, evidence, "deposit");
|
|
18791
|
+
} catch {
|
|
18792
|
+
}
|
|
18793
|
+
}
|
|
18794
|
+
async withdrawFromAgents(options, state, chainId, attempted, ledgerLeg, reservation, plannedLegs, settleLedger, serverLegs = /* @__PURE__ */ new Map()) {
|
|
17067
18795
|
const { asset, amount, agentId, onAgentResult } = options;
|
|
17068
18796
|
const notifyAgentResult = (id, result) => {
|
|
17069
18797
|
try {
|
|
@@ -17109,7 +18837,22 @@ var OwneySDK = class {
|
|
|
17109
18837
|
{ asset, amount, agentErrors: agentErrors2, agentFailures, pendingSubmissions: pending2 }
|
|
17110
18838
|
);
|
|
17111
18839
|
};
|
|
17112
|
-
const
|
|
18840
|
+
const awaitServerHandoffs = async () => {
|
|
18841
|
+
const pending2 = [...serverLegs.values()].filter((leg) => leg.settled === void 0).map((leg) => leg.answer);
|
|
18842
|
+
if (!pending2.length) return;
|
|
18843
|
+
let timer;
|
|
18844
|
+
await Promise.race([
|
|
18845
|
+
Promise.all(pending2),
|
|
18846
|
+
new Promise((resolve) => {
|
|
18847
|
+
timer = setTimeout(resolve, SERVER_HANDOFF_WAIT_MS);
|
|
18848
|
+
})
|
|
18849
|
+
]);
|
|
18850
|
+
clearTimeout(timer);
|
|
18851
|
+
};
|
|
18852
|
+
const submit = async (agent, requested2, onAccepted) => {
|
|
18853
|
+
const plannedTask = serverLegs.get(agent.id);
|
|
18854
|
+
const serverTask = plannedTask ? await plannedTask.answer : void 0;
|
|
18855
|
+
if (serverTask) await settleLedger?.(3e3);
|
|
17113
18856
|
if (this.state !== state || this.state.chainId !== chainId)
|
|
17114
18857
|
throw new OwneyError("NOT_CONNECTED", "Wallet connection changed before withdrawal submission.");
|
|
17115
18858
|
attempted.add(agent.id);
|
|
@@ -17131,6 +18874,7 @@ var OwneySDK = class {
|
|
|
17131
18874
|
submissionEvidence.set(agent.id, evidence);
|
|
17132
18875
|
notifyFunding(options.onProgress, evidence);
|
|
17133
18876
|
ledgerLeg?.(evidence);
|
|
18877
|
+
if (onAccepted && evidence.ownerAuthorization === "accepted" && evidence.transfer !== "failed" && (evidence.userOpHash || evidence.callsId || evidence.txHash)) onAccepted();
|
|
17134
18878
|
};
|
|
17135
18879
|
progress(evidence);
|
|
17136
18880
|
adapterReported = false;
|
|
@@ -17145,14 +18889,26 @@ var OwneySDK = class {
|
|
|
17145
18889
|
reservation?.dispatching(agent.id);
|
|
17146
18890
|
} catch {
|
|
17147
18891
|
}
|
|
17148
|
-
const
|
|
18892
|
+
const context = typeof options.operationId === "string" ? { operationId: options.operationId, ...serverTask ? { planned: true } : {} } : void 0;
|
|
18893
|
+
const onBeforeSubmission = options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0;
|
|
18894
|
+
const result = context ? await agent.withdraw(
|
|
18895
|
+
state,
|
|
18896
|
+
chainId,
|
|
18897
|
+
token,
|
|
18898
|
+
requested2,
|
|
18899
|
+
progress,
|
|
18900
|
+
options.gasMode ?? "auto",
|
|
18901
|
+
onBeforeSubmission,
|
|
18902
|
+
options.assertCurrent,
|
|
18903
|
+
context
|
|
18904
|
+
) : options.onBeforeSubmission || options.assertCurrent ? await agent.withdraw(
|
|
17149
18905
|
state,
|
|
17150
18906
|
chainId,
|
|
17151
18907
|
token,
|
|
17152
18908
|
requested2,
|
|
17153
18909
|
progress,
|
|
17154
18910
|
options.gasMode ?? "auto",
|
|
17155
|
-
|
|
18911
|
+
onBeforeSubmission,
|
|
17156
18912
|
options.assertCurrent
|
|
17157
18913
|
) : await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
|
|
17158
18914
|
settled2({
|
|
@@ -17163,6 +18919,10 @@ var OwneySDK = class {
|
|
|
17163
18919
|
} catch (error) {
|
|
17164
18920
|
if (!adapterReported && !evidence.txHash && !evidence.userOpHash && !evidence.callsId && (this.isUserRejectedWithdrawal(error) || /insufficient (balance|funds)/i.test(error instanceof Error ? error.message : "")))
|
|
17165
18921
|
progress({ ...evidence, transfer: "failed", delivery: "failed" });
|
|
18922
|
+
else if (error instanceof OwneyError && error.details?.rejectedBeforeSubmission === true && evidence.transfer !== "confirmed")
|
|
18923
|
+
progress({ ...evidence, transfer: "failed", delivery: "failed" });
|
|
18924
|
+
if (serverLegs.size && requested2 === void 0 && agent.withdrawalRequiresWalletApproval && error instanceof OwneyError && error.code === "WITHDRAW_INSUFFICIENT_BALANCE" && error.details?.available === "0" && evidence.transfer === "failed" && !evidence.txHash && !evidence.userOpHash && !evidence.callsId)
|
|
18925
|
+
ledgerLeg?.(evidence, "WITHDRAW_INSUFFICIENT_BALANCE");
|
|
17166
18926
|
settled2({ settled: "threw", evidence });
|
|
17167
18927
|
throw error;
|
|
17168
18928
|
}
|
|
@@ -17188,40 +18948,151 @@ var OwneySDK = class {
|
|
|
17188
18948
|
notifyAgentResult(agent.id, result);
|
|
17189
18949
|
return result;
|
|
17190
18950
|
}
|
|
18951
|
+
const dispatch = async (agent, requested2) => {
|
|
18952
|
+
let accept;
|
|
18953
|
+
const accepted = new Promise((resolve) => {
|
|
18954
|
+
accept = () => resolve(true);
|
|
18955
|
+
});
|
|
18956
|
+
const outcome = submit(agent, requested2, agent.withdrawalRequiresWalletApproval ? accept : void 0);
|
|
18957
|
+
const handedOver = await Promise.race([accepted, outcome.then(() => false, () => false)]);
|
|
18958
|
+
return { outcome, handedOver };
|
|
18959
|
+
};
|
|
18960
|
+
const prepareLaterAgents = async (agents) => {
|
|
18961
|
+
if (!agents.some((agent) => agent.withdrawalRequiresWalletApproval)) return;
|
|
18962
|
+
for (const agent of agents) {
|
|
18963
|
+
if (agent.withdrawalRequiresWalletApproval || typeof agent.prepareWithdrawal !== "function") continue;
|
|
18964
|
+
try {
|
|
18965
|
+
await agent.prepareWithdrawal(state, chainId, token);
|
|
18966
|
+
} catch (error) {
|
|
18967
|
+
if (this.isUserRejectedWithdrawal(error)) throw error;
|
|
18968
|
+
console.warn(`withdraw preparation failed for agent "${agent.id}":`, error);
|
|
18969
|
+
}
|
|
18970
|
+
}
|
|
18971
|
+
};
|
|
18972
|
+
const recordPlan = (legs) => {
|
|
18973
|
+
if (legs.length < 2) return;
|
|
18974
|
+
const handOffs = [];
|
|
18975
|
+
for (const [index, [agent, intendedAmount]] of legs.entries()) {
|
|
18976
|
+
const planned = {
|
|
18977
|
+
agentId: agent.id,
|
|
18978
|
+
chainId,
|
|
18979
|
+
token,
|
|
18980
|
+
walletAddress: state.walletAddress,
|
|
18981
|
+
intendedAmount,
|
|
18982
|
+
transfer: "pending",
|
|
18983
|
+
registration: "not-applicable",
|
|
18984
|
+
delivery: "pending",
|
|
18985
|
+
planned: true
|
|
18986
|
+
};
|
|
18987
|
+
plannedLegs?.set(planned.agentId, planned);
|
|
18988
|
+
notifyFunding(options.onProgress, planned);
|
|
18989
|
+
const after = legs.slice(0, index).filter(([earlier]) => earlier.withdrawalRequiresWalletApproval).map(([earlier]) => `agent:${earlier.id}`);
|
|
18990
|
+
if (agent.supportsServerTasks === true && typeof agent.handOffPlannedWithdrawal === "function" && after.length && zyfaiServerTasksEnabled(this.zyfaiServerTasks)) handOffs.push([agent, planned, after]);
|
|
18991
|
+
}
|
|
18992
|
+
const operationId = options.operationId;
|
|
18993
|
+
if (typeof operationId !== "string" || !operationId) return;
|
|
18994
|
+
for (const [agent, planned, after] of handOffs) {
|
|
18995
|
+
const leg = { answer: Promise.resolve(void 0), share: planned.intendedAmount };
|
|
18996
|
+
leg.answer = (async () => {
|
|
18997
|
+
let serverTask;
|
|
18998
|
+
let noTask = false;
|
|
18999
|
+
try {
|
|
19000
|
+
const answer = await agent.handOffPlannedWithdrawal(
|
|
19001
|
+
state,
|
|
19002
|
+
chainId,
|
|
19003
|
+
token,
|
|
19004
|
+
planned.intendedAmount,
|
|
19005
|
+
{
|
|
19006
|
+
operationId,
|
|
19007
|
+
after,
|
|
19008
|
+
// T10-I1: saved before the request leaves, as the dispatch
|
|
19009
|
+
// handoff's checkpoint is, so a reload while the server
|
|
19010
|
+
// answers finds a leg the server may own.
|
|
19011
|
+
beforeRequest: () => notifyFunding(options.onProgress, { ...planned, serverTask: "unknown" })
|
|
19012
|
+
}
|
|
19013
|
+
);
|
|
19014
|
+
serverTask = answer.type === "accepted" ? true : answer.type === "unknown" || answer.code === "LEG_ALREADY_SENT" ? "unknown" : void 0;
|
|
19015
|
+
noTask = answer.type === "declined" && ZYFAI_HANDOFF_NO_TASK_CODES.has(answer.code);
|
|
19016
|
+
} catch {
|
|
19017
|
+
serverTask = "unknown";
|
|
19018
|
+
}
|
|
19019
|
+
leg.settled = serverTask ?? null;
|
|
19020
|
+
if (serverTask) {
|
|
19021
|
+
const marked = { ...planned, serverTask };
|
|
19022
|
+
plannedLegs?.set(planned.agentId, marked);
|
|
19023
|
+
notifyFunding(options.onProgress, marked);
|
|
19024
|
+
} else if (noTask) {
|
|
19025
|
+
notifyFunding(options.onProgress, { ...planned, serverTask: false });
|
|
19026
|
+
}
|
|
19027
|
+
return serverTask;
|
|
19028
|
+
})();
|
|
19029
|
+
serverLegs.set(planned.agentId, leg);
|
|
19030
|
+
}
|
|
19031
|
+
};
|
|
17191
19032
|
const eligibleAgents = this.getEligibleAgents(chainId, asset).filter((agent) => !options.excludeAgentIds?.includes(agent.id));
|
|
17192
19033
|
const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
|
|
17193
19034
|
if (!amount) {
|
|
17194
19035
|
const results2 = {};
|
|
17195
19036
|
const agentErrors2 = {};
|
|
17196
19037
|
const incompleteWithdrawals = {};
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
results2[agent.id] = await submit(agent);
|
|
17200
|
-
notifyAgentResult(agent.id, results2[agent.id]);
|
|
17201
|
-
} catch (err) {
|
|
19038
|
+
const failed2 = (agent, err, handedOver) => {
|
|
19039
|
+
if (!handedOver) {
|
|
17202
19040
|
if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
|
|
17203
19041
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17209
|
-
|
|
17210
|
-
|
|
17211
|
-
|
|
19042
|
+
}
|
|
19043
|
+
if (err instanceof OwneyError && err.code === "WITHDRAW_INSUFFICIENT_BALANCE" && err.details?.available === "0" && submissionEvidence.get(agent.id)?.transfer === "failed") return;
|
|
19044
|
+
if (agent.id === "yieldseeker" && err instanceof OwneyError && err.code === "WITHDRAW_PARTIAL_FAILURE" && err.details?.reason === "YIELDSEEKER_MAX_INCOMPLETE") {
|
|
19045
|
+
incompleteWithdrawals[agent.id] = { positionsStatus: err.details.positionsStatus };
|
|
19046
|
+
const evidence = submissionEvidence.get(agent.id);
|
|
19047
|
+
if (evidence?.agentId === agent.id && evidence.chainId === chainId && evidence.token === token && evidence.walletAddress.toLowerCase() === state.walletAddress.toLowerCase() && evidence.intendedAmount === "MAX" && evidence.transfer === "confirmed" && evidence.delivery === "confirmed" && evidence.txHash && /^0x[\da-f]{64}$/i.test(evidence.txHash) && evidence.confirmedAmount && /^\d+$/.test(evidence.confirmedAmount) && BigInt(evidence.confirmedAmount) > 0n) {
|
|
19048
|
+
results2[agent.id] = { txHash: evidence.txHash, type: "partial", amount: evidence.confirmedAmount, evidence };
|
|
19049
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
19050
|
+
}
|
|
19051
|
+
}
|
|
19052
|
+
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
19053
|
+
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
19054
|
+
recordAgentFailure(agent.id, err);
|
|
19055
|
+
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
19056
|
+
void reportAgentFailure(
|
|
19057
|
+
this.apiKey,
|
|
19058
|
+
agent.id,
|
|
19059
|
+
code,
|
|
19060
|
+
this.routingApiBaseUrl
|
|
19061
|
+
);
|
|
19062
|
+
};
|
|
19063
|
+
const handedOverLegs2 = [];
|
|
19064
|
+
const settleHandedOver2 = async () => {
|
|
19065
|
+
for (const { agent, outcome } of handedOverLegs2.splice(0)) {
|
|
19066
|
+
try {
|
|
19067
|
+
results2[agent.id] = await outcome;
|
|
19068
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
19069
|
+
} catch (err) {
|
|
19070
|
+
failed2(agent, err, true);
|
|
19071
|
+
}
|
|
19072
|
+
}
|
|
19073
|
+
};
|
|
19074
|
+
await prepareLaterAgents(withdrawalAgents);
|
|
19075
|
+
recordPlan(withdrawalAgents.map((agent) => [agent, "MAX"]));
|
|
19076
|
+
try {
|
|
19077
|
+
for (const agent of withdrawalAgents) {
|
|
19078
|
+
const leg = await dispatch(agent);
|
|
19079
|
+
if (leg.handedOver) {
|
|
19080
|
+
handedOverLegs2.push({ agent, outcome: leg.outcome });
|
|
19081
|
+
continue;
|
|
19082
|
+
}
|
|
19083
|
+
try {
|
|
19084
|
+
results2[agent.id] = await leg.outcome;
|
|
19085
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
19086
|
+
} catch (err) {
|
|
19087
|
+
failed2(agent, err, false);
|
|
17212
19088
|
}
|
|
17213
|
-
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
17214
|
-
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
17215
|
-
recordAgentFailure(agent.id, err);
|
|
17216
|
-
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
17217
|
-
void reportAgentFailure(
|
|
17218
|
-
this.apiKey,
|
|
17219
|
-
agent.id,
|
|
17220
|
-
code,
|
|
17221
|
-
this.routingApiBaseUrl
|
|
17222
|
-
);
|
|
17223
19089
|
}
|
|
19090
|
+
} catch (error) {
|
|
19091
|
+
if (serverLegs.size) await awaitServerHandoffs();
|
|
19092
|
+
await settleHandedOver2();
|
|
19093
|
+
throw error;
|
|
17224
19094
|
}
|
|
19095
|
+
await settleHandedOver2();
|
|
17225
19096
|
if (Object.keys(results2).length === 0) {
|
|
17226
19097
|
throwUnconfirmed(agentErrors2);
|
|
17227
19098
|
if (Object.keys(agentErrors2).length === 0)
|
|
@@ -17366,32 +19237,65 @@ var OwneySDK = class {
|
|
|
17366
19237
|
const agentErrors = {
|
|
17367
19238
|
...aggregated.agentErrors ?? {}
|
|
17368
19239
|
};
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
assertPlan();
|
|
17372
|
-
if (p.planned <= 0n) continue;
|
|
17373
|
-
try {
|
|
17374
|
-
results[p.agent.id] = await submit(p.agent, p.planned.toString());
|
|
17375
|
-
notifyAgentResult(p.agent.id, results[p.agent.id]);
|
|
17376
|
-
} catch (err) {
|
|
19240
|
+
const failed = (p, index, err, handedOver) => {
|
|
19241
|
+
if (!handedOver) {
|
|
17377
19242
|
if (err instanceof OwneyError && err.code === "SPONSOR_REQUEST_FAILED" && err.details?.safeToFallback === true) throw err;
|
|
17378
19243
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
17379
|
-
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
17380
|
-
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
17381
|
-
recordAgentFailure(p.agent.id, err);
|
|
17382
|
-
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
17383
|
-
void reportAgentFailure(
|
|
17384
|
-
this.apiKey,
|
|
17385
|
-
p.agent.id,
|
|
17386
|
-
code,
|
|
17387
|
-
this.routingApiBaseUrl
|
|
17388
|
-
);
|
|
17389
|
-
const failedAmount = p.planned;
|
|
17390
|
-
p.planned = 0n;
|
|
17391
|
-
if (submissionEvidence.get(p.agent.id)?.transfer === "failed")
|
|
17392
|
-
redistributeShare(orderedPlans, i, failedAmount);
|
|
17393
19244
|
}
|
|
19245
|
+
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
19246
|
+
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
19247
|
+
recordAgentFailure(p.agent.id, err);
|
|
19248
|
+
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
19249
|
+
void reportAgentFailure(
|
|
19250
|
+
this.apiKey,
|
|
19251
|
+
p.agent.id,
|
|
19252
|
+
code,
|
|
19253
|
+
this.routingApiBaseUrl
|
|
19254
|
+
);
|
|
19255
|
+
const failedAmount = p.planned;
|
|
19256
|
+
p.planned = 0n;
|
|
19257
|
+
if (!handedOver && submissionEvidence.get(p.agent.id)?.transfer === "failed")
|
|
19258
|
+
redistributeShare(orderedPlans, index, failedAmount);
|
|
19259
|
+
};
|
|
19260
|
+
const handedOverLegs = [];
|
|
19261
|
+
const settleHandedOver = async () => {
|
|
19262
|
+
for (const { plan, index, outcome } of handedOverLegs.splice(0)) {
|
|
19263
|
+
try {
|
|
19264
|
+
results[plan.agent.id] = await outcome;
|
|
19265
|
+
notifyAgentResult(plan.agent.id, results[plan.agent.id]);
|
|
19266
|
+
} catch (err) {
|
|
19267
|
+
failed(plan, index, err, true);
|
|
19268
|
+
}
|
|
19269
|
+
}
|
|
19270
|
+
};
|
|
19271
|
+
const dispatchedPlans = orderedPlans.filter((plan) => plan.planned > 0n);
|
|
19272
|
+
await prepareLaterAgents(dispatchedPlans.map((plan) => plan.agent));
|
|
19273
|
+
recordPlan(dispatchedPlans.map((plan) => [plan.agent, plan.planned.toString()]));
|
|
19274
|
+
try {
|
|
19275
|
+
for (let i = 0; i < orderedPlans.length; i++) {
|
|
19276
|
+
const p = orderedPlans[i];
|
|
19277
|
+
assertPlan();
|
|
19278
|
+
if (p.planned <= 0n) continue;
|
|
19279
|
+
const serverLeg = serverLegs.get(p.agent.id);
|
|
19280
|
+
if (serverLeg && await serverLeg.answer && /^[1-9][0-9]*$/.test(serverLeg.share)) p.planned = BigInt(serverLeg.share);
|
|
19281
|
+
const leg = await dispatch(p.agent, p.planned.toString());
|
|
19282
|
+
if (leg.handedOver) {
|
|
19283
|
+
handedOverLegs.push({ plan: p, index: i, outcome: leg.outcome });
|
|
19284
|
+
continue;
|
|
19285
|
+
}
|
|
19286
|
+
try {
|
|
19287
|
+
results[p.agent.id] = await leg.outcome;
|
|
19288
|
+
notifyAgentResult(p.agent.id, results[p.agent.id]);
|
|
19289
|
+
} catch (err) {
|
|
19290
|
+
failed(p, i, err, false);
|
|
19291
|
+
}
|
|
19292
|
+
}
|
|
19293
|
+
} catch (error) {
|
|
19294
|
+
if (serverLegs.size) await awaitServerHandoffs();
|
|
19295
|
+
await settleHandedOver();
|
|
19296
|
+
throw error;
|
|
17394
19297
|
}
|
|
19298
|
+
await settleHandedOver();
|
|
17395
19299
|
if (Object.keys(results).length === 0) {
|
|
17396
19300
|
throwUnconfirmed(agentErrors);
|
|
17397
19301
|
throw new OwneyError(
|
|
@@ -18331,12 +20235,20 @@ function createOwneySIWX(config) {
|
|
|
18331
20235
|
0 && (module.exports = {
|
|
18332
20236
|
AgentChainIncompatibleError,
|
|
18333
20237
|
AgentNotFoundError,
|
|
20238
|
+
FUNDING_DEPOSIT_RECOVERY_REPORT,
|
|
20239
|
+
FUSION_SECRET_SERVER_PUBLISH,
|
|
18334
20240
|
InvalidHistoryCursorError,
|
|
18335
20241
|
NotConnectedError,
|
|
18336
20242
|
OwneyError,
|
|
18337
20243
|
OwneySDK,
|
|
18338
20244
|
SDK_VERSION,
|
|
20245
|
+
SPONSORED_DEPOSIT_SERVER_BROADCAST,
|
|
20246
|
+
YIELDSEEKER_WITHDRAW_SERVER_SUBMIT,
|
|
18339
20247
|
YieldseekerAgent,
|
|
20248
|
+
ZYFAI_REGISTRATION_NOT_SENT_CODES,
|
|
20249
|
+
ZYFAI_SERVER_TASKS,
|
|
20250
|
+
ZYFAI_WITHDRAWAL_NOT_SENT_CODES,
|
|
20251
|
+
ZYFAI_WITHDRAW_DISPATCH_CHECKPOINT,
|
|
18340
20252
|
clearWithdrawSwap,
|
|
18341
20253
|
createOwneySIWX,
|
|
18342
20254
|
listPendingDeposits,
|