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