@owney/sdk 0.7.27-beta.2 → 0.7.27-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,213 +1,28 @@
1
- // src/errors.ts
2
- var OwneyError = class extends Error {
3
- code;
4
- details;
5
- agentId;
6
- constructor(code, message, details, agentId) {
7
- const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
8
- super(`${prefix} ${message}`);
9
- this.name = "OwneyError";
10
- this.code = code;
11
- this.details = details;
12
- this.agentId = agentId;
13
- }
14
- };
15
- var AgentNotFoundError = class extends OwneyError {
16
- constructor(agentId, available) {
17
- super(
18
- "AGENT_NOT_FOUND",
19
- `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
20
- { agentId, available },
21
- agentId
22
- );
23
- this.name = "AgentNotFoundError";
24
- }
25
- };
26
- var NotConnectedError = class extends OwneyError {
27
- constructor() {
28
- super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
29
- this.name = "NotConnectedError";
30
- }
31
- };
32
- var AgentChainIncompatibleError = class extends OwneyError {
33
- incompatibleAgents;
34
- connectedChainId;
35
- constructor(incompatibleAgents, connectedChainId) {
36
- const details = incompatibleAgents.map(
37
- ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
38
- ).join("; ");
39
- super(
40
- "AGENT_CHAIN_INCOMPATIBLE",
41
- `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
42
- { incompatibleAgents, connectedChainId }
43
- );
44
- this.name = "AgentChainIncompatibleError";
45
- this.incompatibleAgents = incompatibleAgents;
46
- this.connectedChainId = connectedChainId;
47
- }
48
- };
49
-
50
- // src/lib/swap/swap.observation.ts
51
- var active = /* @__PURE__ */ new Set();
52
- var isOrderObserved = (key2) => active.has(key2);
53
- async function observeOrder(key2, run) {
54
- const busy = () => new OwneyError(
55
- "SWAP_REQUEST_FAILED",
56
- "This order is already being observed.",
57
- { orderHash: key2, watchActive: true, settlementUnconfirmed: true }
58
- );
59
- if (active.has(key2)) throw busy();
60
- active.add(key2);
61
- try {
62
- if (typeof navigator !== "undefined" && navigator.locks)
63
- return await navigator.locks.request(`owney.swap.observe:${key2}`, { ifAvailable: true }, (lock) => {
64
- if (!lock) throw busy();
65
- return run();
66
- });
67
- return await run();
68
- } finally {
69
- active.delete(key2);
70
- }
71
- }
72
-
73
- // src/lib/swap/swap.deadline.ts
74
- async function bounded(work, timeoutMs, error) {
75
- if (timeoutMs <= 0) throw error();
76
- let timer;
77
- try {
78
- return await Promise.race([
79
- Promise.resolve().then(work),
80
- new Promise((_, reject) => {
81
- timer = setTimeout(() => reject(error()), timeoutMs);
82
- })
83
- ]);
84
- } finally {
85
- clearTimeout(timer);
86
- }
87
- }
88
- async function mapConcurrent(items, run, concurrency = 3) {
89
- const results = new Array(items.length);
90
- let next = 0;
91
- await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, async () => {
92
- for (; ; ) {
93
- const index = next++;
94
- if (index >= items.length) return;
95
- results[index] = await run(items[index]);
96
- }
97
- }));
98
- return results;
99
- }
100
-
101
- // src/lib/permit2-batch.ts
102
- import { parseAbi, hashStruct } from "viem";
103
- var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
104
- var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
105
- var ARBITRUM_USDC_ADDRESS = "0xaf88d065e77c8cc2239327c5edb3a432268e5831";
106
- function supportsSponsoredErc2612(chainId, token) {
107
- const expected = chainId === 8453 ? BASE_USDC_ADDRESS : chainId === 42161 ? ARBITRUM_USDC_ADDRESS : void 0;
108
- return expected !== void 0 && token.toLowerCase() === expected.toLowerCase();
109
- }
110
- var ERC2612_PERMIT_TYPES = {
111
- Permit: [
112
- { name: "owner", type: "address" },
113
- { name: "spender", type: "address" },
114
- { name: "value", type: "uint256" },
115
- { name: "nonce", type: "uint256" },
116
- { name: "deadline", type: "uint256" }
117
- ]
118
- };
119
- var ERC2612_READ_ABI = parseAbi([
120
- "function name() view returns (string)",
121
- "function version() view returns (string)",
122
- "function nonces(address owner) view returns (uint256)"
123
- ]);
124
- function erc2612TypedData(input) {
125
- return {
126
- domain: {
127
- name: input.permit.tokenName,
128
- version: input.permit.tokenVersion,
129
- chainId: input.chainId,
130
- verifyingContract: input.token
131
- },
132
- types: ERC2612_PERMIT_TYPES,
133
- primaryType: "Permit",
134
- message: {
135
- owner: input.owner,
136
- spender: BATCH_PERMIT2_ADDRESS,
137
- value: BigInt(input.permit.value),
138
- nonce: BigInt(input.permit.nonce),
139
- deadline: BigInt(input.permit.deadline)
140
- }
141
- };
142
- }
143
- var PERMIT_BATCH_TYPES = {
144
- PermitBatchWitnessTransferFrom: [
145
- { name: "permitted", type: "TokenPermissions[]" },
146
- { name: "spender", type: "address" },
147
- { name: "nonce", type: "uint256" },
148
- { name: "deadline", type: "uint256" },
149
- { name: "witness", type: "Deposit" }
150
- ],
151
- Deposit: [{ name: "recipients", type: "address[]" }],
152
- TokenPermissions: [
153
- { name: "token", type: "address" },
154
- { name: "amount", type: "uint256" }
155
- ]
156
- };
157
- var PERMIT2_BATCH_ABI = parseAbi([
158
- "struct TokenPermissions { address token; uint256 amount; }",
159
- "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
160
- "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
161
- "function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
162
- "function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
163
- ]);
164
- function batchPermit(b) {
165
- return {
166
- permitted: b.transfers.map((t) => ({
167
- token: b.token,
168
- amount: BigInt(t.amount)
169
- })),
170
- nonce: BigInt(b.nonce),
171
- deadline: BigInt(b.deadline)
172
- };
173
- }
174
- function batchTypedData(b, spender) {
175
- return {
176
- domain: {
177
- name: "Permit2",
178
- chainId: b.chainId,
179
- verifyingContract: BATCH_PERMIT2_ADDRESS
180
- },
181
- types: PERMIT_BATCH_TYPES,
182
- primaryType: "PermitBatchWitnessTransferFrom",
183
- message: {
184
- ...batchPermit(b),
185
- spender,
186
- witness: { recipients: b.transfers.map((t) => t.to) }
187
- }
188
- };
189
- }
190
- var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
191
- function batchWitness(b) {
192
- return hashStruct({
193
- types: PERMIT_BATCH_TYPES,
194
- primaryType: "Deposit",
195
- data: { recipients: b.transfers.map((t) => t.to) }
196
- });
197
- }
198
- function batchArgs(b) {
199
- return [
200
- batchPermit(b),
201
- b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
202
- b.from,
203
- batchWitness(b),
204
- BATCH_WITNESS_TYPE,
205
- b.signature
206
- ];
207
- }
1
+ import {
2
+ AgentChainIncompatibleError,
3
+ AgentNotFoundError,
4
+ BATCH_PERMIT2_ADDRESS,
5
+ ERC2612_READ_ABI,
6
+ NotConnectedError,
7
+ OwneyError,
8
+ PERMIT2_BATCH_ABI,
9
+ batchArgs,
10
+ batchTypedData,
11
+ erc2612TypedData,
12
+ finishTokenBatchFamily,
13
+ loadTokenBatchFamily,
14
+ saveTokenBatchAttempt,
15
+ supportsSponsoredErc2612,
16
+ tokenBatchClear,
17
+ tokenBatchKey,
18
+ tokenBatchRead,
19
+ tokenBatchStoredBody,
20
+ tokenBatchWrite,
21
+ validateTokenBatchAttempt
22
+ } from "./chunk-LUQ4H72X.js";
208
23
 
209
24
  // src/lib/funding-evidence.ts
210
- import { decodeEventLog, decodeFunctionData, erc20Abi, parseAbi as parseAbi2 } from "viem";
25
+ import { decodeEventLog, decodeFunctionData, erc20Abi, parseAbi } from "viem";
211
26
  import { entryPoint06Abi, entryPoint07Abi, getUserOperationHash, toUserOperation } from "viem/account-abstraction";
212
27
  function notifyFunding(callback, evidence) {
213
28
  try {
@@ -216,9 +31,9 @@ function notifyFunding(callback, evidence) {
216
31
  } catch {
217
32
  }
218
33
  }
219
- var fullWithdrawalAbi = parseAbi2(["function withdrawAllAssetToUser(address recipient, address asset)"]);
220
- var withdrawalEventAbi = parseAbi2(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
221
- var accountExecutionAbi = parseAbi2([
34
+ var fullWithdrawalAbi = parseAbi(["function withdrawAllAssetToUser(address recipient, address asset)"]);
35
+ var withdrawalEventAbi = parseAbi(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
36
+ var accountExecutionAbi = parseAbi([
222
37
  "function execute(address target,uint256 value,bytes data)",
223
38
  "function executeBatch((address target,uint256 value,bytes data)[] calls)"
224
39
  ]);
@@ -384,6 +199,213 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction, optio
384
199
  };
385
200
  }
386
201
 
202
+ // src/lib/token-batch-observer.ts
203
+ var cursors = /* @__PURE__ */ new Map();
204
+ async function observeTokenBatchFamily(input) {
205
+ let expired = false, timer;
206
+ const assertCurrent = () => {
207
+ if (expired || Date.now() >= input.deadlineAt)
208
+ throw Error("observation ended");
209
+ input.assertCurrent();
210
+ };
211
+ const run = async () => {
212
+ assertCurrent();
213
+ const { scope, pub } = input;
214
+ const family = await loadTokenBatchFamily(
215
+ scope,
216
+ input.family.attempts[0].hash
217
+ );
218
+ assertCurrent();
219
+ if (!family || family.authorizationId !== input.family.authorizationId)
220
+ return { status: "unresolved" };
221
+ const attempts = family.attempts.filter((a) => a.phase === "deposit");
222
+ const key2 = `${tokenBatchKey(scope)}:${family.authorizationId}`;
223
+ const start = (cursors.get(key2) ?? 0) % Math.max(1, attempts.length);
224
+ const ordered = [...attempts.slice(start), ...attempts.slice(0, start)];
225
+ const reverted = [];
226
+ for (let index = 0; index < ordered.length; index += 3) {
227
+ assertCurrent();
228
+ const group = ordered.slice(index, index + 3);
229
+ cursors.set(key2, (start + index + group.length) % attempts.length);
230
+ const observations = await Promise.all(
231
+ group.map(async (attempt) => {
232
+ try {
233
+ await validateTokenBatchAttempt(
234
+ scope,
235
+ family.body,
236
+ attempt,
237
+ family.relayer
238
+ );
239
+ assertCurrent();
240
+ const receipt = await pub.getTransactionReceipt({
241
+ hash: attempt.hash
242
+ });
243
+ assertCurrent();
244
+ if (receipt.transactionHash?.toLowerCase() !== attempt.hash.toLowerCase() || typeof receipt.blockNumber !== "bigint" || !receipt.blockHash)
245
+ return;
246
+ if (receipt.status === "reverted")
247
+ return { kind: "reverted", receipt };
248
+ if (receipt.status !== "success" || receipt.from?.toLowerCase() !== family.relayer.toLowerCase() || receipt.to?.toLowerCase() !== BATCH_PERMIT2_ADDRESS.toLowerCase())
249
+ return;
250
+ const exact = family.body.transfers.every(
251
+ (t) => inspectFundingReceipt(
252
+ receipt,
253
+ {
254
+ agentId: "yieldseeker",
255
+ chainId: family.body.chainId,
256
+ token: "USDC",
257
+ walletAddress: family.body.from,
258
+ agentWallet: t.to,
259
+ intendedAmount: t.amount,
260
+ txHash: attempt.hash,
261
+ transfer: "pending",
262
+ registration: "pending",
263
+ delivery: "not-applicable"
264
+ },
265
+ family.body.token,
266
+ "deposit",
267
+ { requireAmountMatch: true }
268
+ ).transfer === "confirmed"
269
+ );
270
+ return exact ? { kind: "confirmed", receipt } : void 0;
271
+ } catch {
272
+ return;
273
+ }
274
+ })
275
+ );
276
+ assertCurrent();
277
+ const winner = observations.find((o) => o?.kind === "confirmed");
278
+ if (winner) {
279
+ const latest2 = await loadTokenBatchFamily(
280
+ scope,
281
+ winner.receipt.transactionHash
282
+ );
283
+ assertCurrent();
284
+ if (!latest2) return { status: "unresolved" };
285
+ finishTokenBatchFamily(scope, latest2, {
286
+ kind: "confirmed",
287
+ txHash: winner.receipt.transactionHash,
288
+ blockNumber: winner.receipt.blockNumber.toString()
289
+ });
290
+ return {
291
+ status: "confirmed",
292
+ txHash: winner.receipt.transactionHash,
293
+ receipt: winner.receipt
294
+ };
295
+ }
296
+ for (const o of observations)
297
+ if (o?.kind === "reverted") reverted.push(o.receipt);
298
+ }
299
+ const latest = await loadTokenBatchFamily(scope, family.attempts[0].hash);
300
+ assertCurrent();
301
+ if (!latest) return { status: "unresolved" };
302
+ if (attempts.length > 0 && reverted.length === latest.attempts.filter((a) => a.phase === "deposit").length) {
303
+ const block2 = reverted.reduce(
304
+ (n, r) => r.blockNumber > n ? r.blockNumber : n,
305
+ 0n
306
+ );
307
+ finishTokenBatchFamily(scope, latest, {
308
+ kind: "failed",
309
+ blockNumber: block2.toString()
310
+ });
311
+ return { status: "failed", receipts: reverted };
312
+ }
313
+ const block = await pub.getBlock({ blockTag: "finalized" });
314
+ assertCurrent();
315
+ if (typeof block.number !== "bigint" || !block.hash || typeof block.timestamp !== "bigint" || block.timestamp <= BigInt(family.body.deadline))
316
+ return { status: "unresolved" };
317
+ const nonce = BigInt(family.body.nonce);
318
+ const bitmap = await pub.readContract({
319
+ address: BATCH_PERMIT2_ADDRESS,
320
+ abi: PERMIT2_BATCH_ABI,
321
+ functionName: "nonceBitmap",
322
+ args: [family.body.from, nonce >> 8n],
323
+ blockNumber: block.number
324
+ });
325
+ assertCurrent();
326
+ if (typeof bitmap !== "bigint" || bitmap < 0n || bitmap >= 1n << 256n || (bitmap & 1n << (nonce & 255n)) !== 0n)
327
+ return { status: "unresolved" };
328
+ if ((await pub.getBlock({ blockNumber: block.number })).hash !== block.hash)
329
+ return { status: "unresolved" };
330
+ assertCurrent();
331
+ finishTokenBatchFamily(scope, latest, {
332
+ kind: "expired",
333
+ blockNumber: block.number.toString()
334
+ });
335
+ return { status: "expired", blockNumber: block.number };
336
+ };
337
+ try {
338
+ return await Promise.race([
339
+ run(),
340
+ new Promise((resolve) => {
341
+ timer = setTimeout(
342
+ () => {
343
+ expired = true;
344
+ resolve({ status: "unresolved" });
345
+ },
346
+ Math.max(0, input.deadlineAt - Date.now())
347
+ );
348
+ })
349
+ ]);
350
+ } catch {
351
+ return { status: "unresolved" };
352
+ } finally {
353
+ expired = true;
354
+ if (timer) clearTimeout(timer);
355
+ }
356
+ }
357
+
358
+ // src/lib/swap/swap.observation.ts
359
+ var active = /* @__PURE__ */ new Set();
360
+ var isOrderObserved = (key2) => active.has(key2);
361
+ async function observeOrder(key2, run) {
362
+ const busy = () => new OwneyError(
363
+ "SWAP_REQUEST_FAILED",
364
+ "This order is already being observed.",
365
+ { orderHash: key2, watchActive: true, settlementUnconfirmed: true }
366
+ );
367
+ if (active.has(key2)) throw busy();
368
+ active.add(key2);
369
+ try {
370
+ if (typeof navigator !== "undefined" && navigator.locks)
371
+ return await navigator.locks.request(`owney.swap.observe:${key2}`, { ifAvailable: true }, (lock) => {
372
+ if (!lock) throw busy();
373
+ return run();
374
+ });
375
+ return await run();
376
+ } finally {
377
+ active.delete(key2);
378
+ }
379
+ }
380
+
381
+ // src/lib/swap/swap.deadline.ts
382
+ async function bounded(work, timeoutMs, error) {
383
+ if (timeoutMs <= 0) throw error();
384
+ let timer;
385
+ try {
386
+ return await Promise.race([
387
+ Promise.resolve().then(work),
388
+ new Promise((_, reject) => {
389
+ timer = setTimeout(() => reject(error()), timeoutMs);
390
+ })
391
+ ]);
392
+ } finally {
393
+ clearTimeout(timer);
394
+ }
395
+ }
396
+ async function mapConcurrent(items, run, concurrency = 3) {
397
+ const results = new Array(items.length);
398
+ let next = 0;
399
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, async () => {
400
+ for (; ; ) {
401
+ const index = next++;
402
+ if (index >= items.length) return;
403
+ results[index] = await run(items[index]);
404
+ }
405
+ }));
406
+ return results;
407
+ }
408
+
387
409
  // src/lib/helpers/funding-amount.ts
388
410
  function isFundingAmount(value, allowZero = false) {
389
411
  if (typeof value !== "string" || !/^\d{1,78}$/.test(value)) return false;
@@ -498,6 +520,7 @@ async function postSponsorBatchTransfer(input) {
498
520
  `${input.baseUrl ?? ROUTING_API_BASE_URL}/api/v1/sponsor/permit2-batch`,
499
521
  {
500
522
  method: "POST",
523
+ ...input.signal ? { signal: input.signal } : {},
501
524
  headers: {
502
525
  "content-type": "application/json",
503
526
  "x-owney-api-key": input.apiKey
@@ -526,6 +549,45 @@ async function postSponsorBatchTransfer(input) {
526
549
  }
527
550
  return parsed.data;
528
551
  }
552
+ async function postSponsorBatchRecovery(input) {
553
+ const failure = () => new OwneyError("SPONSOR_REQUEST_FAILED", "Deposit recovery is unavailable. The existing transaction remains tracked.", { safeToFallback: false });
554
+ try {
555
+ input.signal?.throwIfAborted();
556
+ const response = await fetch(`${input.baseUrl ?? ROUTING_API_BASE_URL}/api/v1/sponsor/permit2-batch/recover`, { method: "POST", headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey }, body: JSON.stringify(input.body), signal: input.signal });
557
+ input.signal?.throwIfAborted();
558
+ if (response.status === 404) return;
559
+ const payload = await response.json();
560
+ input.signal?.throwIfAborted();
561
+ if (!response.ok || payload?.success !== true) throw failure();
562
+ const value = payload.data;
563
+ const { isAddress: isAddress5, keccak256: keccak2564, parseTransaction: parseTransaction3, recoverTransactionAddress } = await import("viem");
564
+ const { validateTokenBatchAttempt: validateTokenBatchAttempt2 } = await import("./token-batch-family-VW6ZJLK5.js");
565
+ const phase = input.body.phase;
566
+ const original = phase === "approval" ? input.body.serializedPermitTransaction : input.body.serializedTransaction;
567
+ if (!original) throw failure();
568
+ const hash2 = keccak2564(original), relayer = await recoverTransactionAddress({ serializedTransaction: original });
569
+ const validHash = (v) => typeof v === "string" && /^0x[\da-f]{64}$/i.test(v);
570
+ if (!value || value.protocol !== 1 || value.phase !== phase || !validHash(value.txHash) || !isAddress5(value.relayer) || value.relayer.toLowerCase() !== relayer.toLowerCase()) throw failure();
571
+ if (value.status !== "prepared" && value.txHash.toLowerCase() !== hash2.toLowerCase()) throw failure();
572
+ if (value.status === "unresolved") {
573
+ if (!["nonce-not-consumed", "authorization-used", "authorization-expired", "state-unavailable", "preparation-rejected", "relayer-changed"].includes(value.reason)) throw failure();
574
+ } else {
575
+ if (!("blockNumber" in value) || typeof value.blockNumber !== "string" || !/^\d{1,78}$/.test(value.blockNumber) || BigInt(value.blockNumber) >= 1n << 256n || !validHash(value.blockHash)) throw failure();
576
+ if (value.status === "prepared") {
577
+ if (value.predecessorHash?.toLowerCase() !== hash2.toLowerCase()) throw failure();
578
+ const tx = parseTransaction3(value.serializedTransaction), old = parseTransaction3(original);
579
+ if (!Number.isSafeInteger(tx.nonce) || tx.nonce <= old.nonce) throw failure();
580
+ await validateTokenBatchAttempt2({ apiKey: input.apiKey, baseUrl: input.baseUrl, chainId: input.body.chainId, owner: input.body.from, token: input.body.token }, input.body, { phase, hash: value.txHash, serializedTransaction: value.serializedTransaction, nonce: String(tx.nonce), predecessorHash: hash2, preparedAt: Date.now(), submissionAttempted: false }, relayer);
581
+ } else if (value.status === "included") {
582
+ if (value.outcome !== "success" && value.outcome !== "reverted") throw failure();
583
+ } else if (value.status !== "approval-satisfied" || phase !== "approval") throw failure();
584
+ }
585
+ input.signal?.throwIfAborted();
586
+ return value;
587
+ } catch {
588
+ throw failure();
589
+ }
590
+ }
529
591
 
530
592
  // src/lib/permit2.ts
531
593
  import { bytesToHex as bytesToHex2 } from "viem";
@@ -1065,7 +1127,7 @@ async function submitZyfaiDeposit({ sdk, state, chain: chain2, amount, asset, sm
1065
1127
 
1066
1128
  // src/agents/zyfai/zyfai.agent.ts
1067
1129
  import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
1068
- import { erc20Abi as erc20Abi3, formatUnits as formatUnits2, isAddress as isAddress2, parseAbi as parseAbi3, parseUnits } from "viem";
1130
+ import { erc20Abi as erc20Abi3, formatUnits as formatUnits2, isAddress as isAddress2, parseAbi as parseAbi2, parseUnits } from "viem";
1069
1131
  import { base, arbitrum, mainnet } from "viem/chains";
1070
1132
 
1071
1133
  // src/lib/debug.ts
@@ -2023,7 +2085,7 @@ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
2023
2085
  }
2024
2086
 
2025
2087
  // src/agents/zyfai/zyfai.agent.ts
2026
- var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi3([
2088
+ var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi2([
2027
2089
  "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
2028
2090
  ]);
2029
2091
  var WETH_ADDRESS_BY_CHAIN = {
@@ -2093,6 +2155,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2093
2155
  connectedAddress = null;
2094
2156
  connectedChainId = null;
2095
2157
  smartWalletCache = /* @__PURE__ */ new Map();
2158
+ smartWalletReads = /* @__PURE__ */ new Map();
2096
2159
  /**
2097
2160
  * `${eoa}:${chainId}` keys we have already tried createSessionKey for. Guards
2098
2161
  * against re-running it on every activation/deposit when Zyfai's
@@ -2110,6 +2173,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
2110
2173
  fundingHistoryCache = /* @__PURE__ */ new Map();
2111
2174
  earningsReads = /* @__PURE__ */ new Map();
2112
2175
  earningsRefreshes = /* @__PURE__ */ new Map();
2176
+ // Recalculation is wallet-wide across assets/chains. Bound attempts locally,
2177
+ // including failures; a negative result must not trigger a POST every poll.
2178
+ earningsCalculationAttempts = /* @__PURE__ */ new Map();
2113
2179
  earningsSnapshot = null;
2114
2180
  earningsGeneration = 0;
2115
2181
  balanceSnapshots = /* @__PURE__ */ new WeakMap();
@@ -2285,6 +2351,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
2285
2351
  this.sdkHasConnected = true;
2286
2352
  const executionSdk = this.sdk;
2287
2353
  const generation = ++this.connectionGeneration;
2354
+ this.smartWalletCache.clear();
2355
+ this.smartWalletReads.clear();
2288
2356
  this.connectedAddress = null;
2289
2357
  this.connectedChainId = null;
2290
2358
  const pending = (async () => {
@@ -2396,18 +2464,29 @@ var ZyfaiAgent = class _ZyfaiAgent {
2396
2464
  await this.connectAuth(state, chainId);
2397
2465
  const validChainId = isValidChainId(chainId);
2398
2466
  const ownerAddress = this.getAddress();
2399
- const cacheKey = `${ownerAddress}_${validChainId}`;
2400
- let smartWallet = this.smartWalletCache.get(cacheKey);
2401
- if (!smartWallet) {
2402
- const { address } = await this.sdk.getSmartWalletAddress(
2403
- ownerAddress,
2404
- validChainId
2405
- );
2406
- smartWallet = address;
2407
- this.smartWalletCache.set(cacheKey, smartWallet);
2408
- }
2467
+ const smartWallet = await this.readSmartWallet(ownerAddress, validChainId);
2409
2468
  return { smartWallet, chainId: validChainId };
2410
2469
  }
2470
+ /** Read-only resolution shared with recovery; never enters authentication. */
2471
+ readSmartWallet(owner, chainId) {
2472
+ const key2 = `${owner.toLowerCase()}_${chainId}`;
2473
+ const cached = this.smartWalletCache.get(key2);
2474
+ if (cached) return Promise.resolve(cached);
2475
+ const existing = this.smartWalletReads.get(key2);
2476
+ if (existing) return existing;
2477
+ const generation = this.connectionGeneration;
2478
+ const executionSdk = this.sdk;
2479
+ const pending = Promise.resolve().then(() => executionSdk.getSmartWalletAddress(owner, chainId)).then(({ address }) => {
2480
+ if (generation !== this.connectionGeneration || executionSdk !== this.sdk)
2481
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while resolving Zyfai.", void 0, this.id);
2482
+ this.smartWalletCache.set(key2, address);
2483
+ return address;
2484
+ }).finally(() => {
2485
+ if (this.smartWalletReads.get(key2) === pending) this.smartWalletReads.delete(key2);
2486
+ });
2487
+ this.smartWalletReads.set(key2, pending);
2488
+ return pending;
2489
+ }
2411
2490
  // --- IAgent: Connection lifecycle ---
2412
2491
  async disconnect() {
2413
2492
  const executionSdk = this.sdk;
@@ -2415,6 +2494,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2415
2494
  this.earningsGeneration++;
2416
2495
  this.earningsReads.clear();
2417
2496
  this.earningsRefreshes.clear();
2497
+ this.earningsCalculationAttempts.clear();
2418
2498
  this.earningsSnapshot = null;
2419
2499
  if (this.connectedAddress && this.connectedChainId !== null) {
2420
2500
  clearSession(this.connectedAddress, this.connectedChainId);
@@ -2424,6 +2504,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2424
2504
  this.connectAuthPromise = null;
2425
2505
  this.connectAuthScope = null;
2426
2506
  this.smartWalletCache.clear();
2507
+ this.smartWalletReads.clear();
2427
2508
  this.sdk = this.createSdk();
2428
2509
  this.sdkHasConnected = false;
2429
2510
  await executionSdk.disconnectAccount();
@@ -3269,7 +3350,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
3269
3350
  }
3270
3351
  // --- IAgent: Portfolio reads ---
3271
3352
  async getFundingWallet(state, chainId) {
3272
- return (await this.sdk.getSmartWalletAddress(state.walletAddress, isValidChainId(chainId))).address;
3353
+ return this.readSmartWallet(state.walletAddress, isValidChainId(chainId));
3273
3354
  }
3274
3355
  /** A positive deployed Safe balance is a conservative planning floor. Zero
3275
3356
  * requires both the mapped Safe's exact idle balance and a fresh complete
@@ -3384,8 +3465,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
3384
3465
  const existing = this.earningsReads.get(key2);
3385
3466
  if (existing) return existing;
3386
3467
  const generation = this.earningsGeneration;
3387
- const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw2) => {
3388
- if (generation === this.earningsGeneration) {
3468
+ const executionSdk = this.sdk;
3469
+ const pending = Promise.resolve().then(() => {
3470
+ if (generation !== this.earningsGeneration || executionSdk !== this.sdk)
3471
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while reading Zyfai earnings.", void 0, this.id);
3472
+ return executionSdk.getOnchainEarnings(smartWallet);
3473
+ }).then((raw2) => {
3474
+ if (generation === this.earningsGeneration && executionSdk === this.sdk) {
3389
3475
  this.earningsSnapshot = { key: key2, raw: raw2, at: Date.now() };
3390
3476
  }
3391
3477
  return raw2;
@@ -3405,20 +3491,37 @@ var ZyfaiAgent = class _ZyfaiAgent {
3405
3491
  return mapEarnings(raw2, smartWallet);
3406
3492
  }
3407
3493
  async refreshEarnings(state, chainId) {
3494
+ const generation = this.earningsGeneration;
3495
+ await this.connectAuth(state, chainId);
3496
+ const executionSdk = this.sdk;
3497
+ const connectionGeneration = this.connectionGeneration;
3498
+ const assertCurrent = () => {
3499
+ if (generation !== this.earningsGeneration || connectionGeneration !== this.connectionGeneration || executionSdk !== this.sdk)
3500
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed while refreshing Zyfai earnings.", void 0, this.id);
3501
+ };
3502
+ assertCurrent();
3408
3503
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
3504
+ assertCurrent();
3409
3505
  const key2 = this.earningsKey(state, chainId, smartWallet);
3410
3506
  const existing = this.earningsRefreshes.get(key2);
3411
3507
  if (existing) return existing;
3412
- const generation = this.earningsGeneration;
3413
3508
  const pending = (async () => {
3414
3509
  const recent = this.earningsSnapshot;
3415
3510
  const current = recent?.key === key2 && Date.now() - recent.at < 5e3 ? recent.raw : await this.readEarnings(key2, smartWallet);
3416
3511
  const lastCheck = current.data.lastCheckTimestamp ? Date.parse(current.data.lastCheckTimestamp) : Number.NaN;
3417
- const isFresh = Number.isFinite(lastCheck) && Date.now() - lastCheck < EARNINGS_REFRESH_COOLDOWN_MS;
3418
- const earnings = isFresh ? current : await this.sdk.calculateOnchainEarnings(smartWallet);
3419
- if (!isFresh && generation === this.earningsGeneration) {
3420
- this.earningsSnapshot = { key: key2, raw: earnings, at: Date.now() };
3421
- }
3512
+ const mapped = mapEarnings(current, smartWallet);
3513
+ assertCurrent();
3514
+ const now = Date.now();
3515
+ const isFresh = Number.isFinite(lastCheck) && lastCheck <= now && now - lastCheck < EARNINGS_REFRESH_COOLDOWN_MS;
3516
+ const negative = mapped.lifetimeEarnings < 0 || mapped.tokens.some((token) => Number(token.amount) < 0);
3517
+ const calculationKey = JSON.stringify([state.walletAddress?.toLowerCase(), smartWallet.toLowerCase()]);
3518
+ const attemptedAt = this.earningsCalculationAttempts.get(calculationKey);
3519
+ const canCalculate = attemptedAt === void 0 || now - attemptedAt >= EARNINGS_REFRESH_COOLDOWN_MS;
3520
+ if (isFresh && !negative || !canCalculate) return mapped;
3521
+ this.earningsCalculationAttempts.set(calculationKey, now);
3522
+ const earnings = await executionSdk.calculateOnchainEarnings(smartWallet);
3523
+ assertCurrent();
3524
+ this.earningsSnapshot = { key: key2, raw: earnings, at: Date.now() };
3422
3525
  return mapEarnings(earnings, smartWallet);
3423
3526
  })().finally(() => {
3424
3527
  if (this.earningsRefreshes.get(key2) === pending)
@@ -3576,10 +3679,10 @@ import {
3576
3679
  custom as custom4,
3577
3680
  decodeEventLog as decodeEventLog2,
3578
3681
  encodeEventTopics,
3579
- encodeFunctionData as encodeFunctionData2,
3682
+ encodeFunctionData as encodeFunctionData3,
3580
3683
  erc20Abi as erc20Abi5,
3581
- erc4626Abi as erc4626Abi2,
3582
- getAddress as getAddress5,
3684
+ erc4626Abi as erc4626Abi3,
3685
+ getAddress as getAddress6,
3583
3686
  isAddress as isAddress4,
3584
3687
  parseUnits as parseUnits2
3585
3688
  } from "viem";
@@ -4656,13 +4759,13 @@ async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber)
4656
4759
  import {
4657
4760
  getAddress as getAddress3,
4658
4761
  isAddressEqual,
4659
- parseAbi as parseAbi4
4762
+ parseAbi as parseAbi3
4660
4763
  } from "viem";
4661
4764
  var YIELDSEEKER_FACTORY = "0x9c7410a0faCAC60850C46eE5b58B518daec95130";
4662
4765
  var YIELDSEEKER_V1_IMPLEMENTATION = "0x32403455357589523435dB18b414057D7cE05051";
4663
4766
  var YIELDSEEKER_V2_IMPLEMENTATION = "0x613Db9c924ADB7A7A90c11b02Cce20c913E3Fe50";
4664
4767
  var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
4665
- var walletAbi = parseAbi4([
4768
+ var walletAbi = parseAbi3([
4666
4769
  "function owner() view returns (address)",
4667
4770
  "function ownerAgentIndex() view returns (uint256)",
4668
4771
  "function baseAsset() view returns (address)",
@@ -4670,7 +4773,7 @@ var walletAbi = parseAbi4([
4670
4773
  "function upgradeToLatest()",
4671
4774
  "event Upgraded(address indexed implementation)"
4672
4775
  ]);
4673
- var factoryAbi = parseAbi4([
4776
+ var factoryAbi = parseAbi3([
4674
4777
  "function userWallets(address owner, uint256 index) view returns (address)",
4675
4778
  "function agentWalletImplementation() view returns (address)"
4676
4779
  ]);
@@ -5285,6 +5388,57 @@ async function submitYieldseekerSponsoredUpgrade(input) {
5285
5388
  return submitSmart({ ...input, owner, rpcUrl, expiresAt: intent.expiresAt });
5286
5389
  }
5287
5390
 
5391
+ // src/agents/yieldseeker/yieldseeker.vault-reads.ts
5392
+ import { decodeFunctionResult, encodeFunctionData as encodeFunctionData2, erc4626Abi as erc4626Abi2, getAddress as getAddress5, parseAbi as parseAbi4 } from "viem";
5393
+ var nativeAbi = parseAbi4([
5394
+ "function underlying() view returns (address)",
5395
+ "function baseToken() view returns (address)",
5396
+ "function UNDERLYING_ASSET_ADDRESS() view returns (address)",
5397
+ "function balanceOfUnderlying(address owner) returns (uint256)"
5398
+ ]);
5399
+ function nativeYieldseekerProtocol(option) {
5400
+ switch (option.provider?.toLowerCase()) {
5401
+ case "moonwell":
5402
+ return "moonwell";
5403
+ case "compound":
5404
+ return "compound";
5405
+ case "aave":
5406
+ return "aave";
5407
+ default:
5408
+ return void 0;
5409
+ }
5410
+ }
5411
+ function yieldseekerUnderlyingCall(option) {
5412
+ const address = getAddress5(option.address);
5413
+ switch (nativeYieldseekerProtocol(option)) {
5414
+ case "moonwell":
5415
+ return { address, abi: nativeAbi, functionName: "underlying" };
5416
+ case "compound":
5417
+ return { address, abi: nativeAbi, functionName: "baseToken" };
5418
+ case "aave":
5419
+ return { address, abi: nativeAbi, functionName: "UNDERLYING_ASSET_ADDRESS" };
5420
+ default:
5421
+ return { address, abi: erc4626Abi2, functionName: "asset" };
5422
+ }
5423
+ }
5424
+ async function readNativeYieldseekerAssets(client, option, wallet, balance, blockNumber) {
5425
+ if (nativeYieldseekerProtocol(option) !== "moonwell") {
5426
+ return balance;
5427
+ }
5428
+ try {
5429
+ const { data } = await client.call({
5430
+ to: getAddress5(option.address),
5431
+ blockNumber,
5432
+ data: encodeFunctionData2({ abi: nativeAbi, functionName: "balanceOfUnderlying", args: [getAddress5(wallet)] })
5433
+ });
5434
+ if (!data) return void 0;
5435
+ const assets = decodeFunctionResult({ abi: nativeAbi, functionName: "balanceOfUnderlying", data });
5436
+ return assets > 0n ? assets : void 0;
5437
+ } catch {
5438
+ return void 0;
5439
+ }
5440
+ }
5441
+
5288
5442
  // src/agents/yieldseeker/yieldseeker.agent.ts
5289
5443
  var OWNEY_AGENT_NAME = "owney";
5290
5444
  var OWNEY_AGENT_RULE_PRESET = "explorative";
@@ -5294,7 +5448,8 @@ var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
5294
5448
  var YIELDSEEKER_VAULT_OPTIONS_CACHE_MS = 6e4;
5295
5449
  var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
5296
5450
  var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
5297
- var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
5451
+ var YIELDSEEKER_SETTLEMENT_BACKOFF_MS = [5e3, 1e4, 2e4, 4e4, 6e4];
5452
+ var YIELDSEEKER_SETTLEMENT_SNAPSHOT_WINDOW_MS = 10 * 6e4;
5298
5453
  var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
5299
5454
  var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
5300
5455
  var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
@@ -5381,6 +5536,7 @@ var YieldseekerAgent = class {
5381
5536
  receiptClient;
5382
5537
  // A client preparation hint only; the routing API still checks live identity.
5383
5538
  withdrawalVersionCache = /* @__PURE__ */ new Map();
5539
+ pendingWalletStatus = /* @__PURE__ */ new Map();
5384
5540
  pendingUpgrades = /* @__PURE__ */ new Map();
5385
5541
  transactionExecutor;
5386
5542
  unwindReceiptWaiter;
@@ -5397,6 +5553,7 @@ var YieldseekerAgent = class {
5397
5553
  liquidityVersions = /* @__PURE__ */ new Map();
5398
5554
  snapshotFailures = /* @__PURE__ */ new Map();
5399
5555
  standardReadFailures = /* @__PURE__ */ new Map();
5556
+ endpointReadFailures = /* @__PURE__ */ new Map();
5400
5557
  reconcileUntil = /* @__PURE__ */ new Map();
5401
5558
  allocationRefresh = /* @__PURE__ */ new Set();
5402
5559
  activityRefreshUntil = /* @__PURE__ */ new Map();
@@ -5404,6 +5561,8 @@ var YieldseekerAgent = class {
5404
5561
  // settlement; keep slow reconciliation after the fast window expires.
5405
5562
  snapshotMovements = /* @__PURE__ */ new Map();
5406
5563
  movementEpochs = /* @__PURE__ */ new Map();
5564
+ // Forced checks issued by the current movement's settlement schedule.
5565
+ settlementChecks = /* @__PURE__ */ new Map();
5407
5566
  vaultOptions = /* @__PURE__ */ new Map();
5408
5567
  pendingVaultOptions = /* @__PURE__ */ new Map();
5409
5568
  constructor(owneyApiKey, options = {}) {
@@ -5427,19 +5586,31 @@ var YieldseekerAgent = class {
5427
5586
  async getWalletStatus(state, chainId, asset, useCachedV2 = false) {
5428
5587
  this.assertChain(chainId);
5429
5588
  this.assertAsset(asset);
5589
+ const generation = this.readGeneration;
5430
5590
  const context = await this.findAgent(state, chainId, asset);
5591
+ this.assertSession(generation, chainId, asset);
5431
5592
  if (!context) return null;
5432
5593
  const key2 = `${chainId}:${state.walletAddress.toLowerCase()}:${context.wallet.walletAddress.toLowerCase()}:${asset}`;
5433
5594
  const cached = this.withdrawalVersionCache.get(key2);
5434
5595
  if (useCachedV2 && cached && Date.now() - cached.at < 15 * 6e4) return { ...cached.status };
5435
- const status = await readYieldseekerWalletStatus(
5596
+ const existing = this.pendingWalletStatus.get(key2);
5597
+ if (existing) return { ...await existing };
5598
+ const pending = readYieldseekerWalletStatus(
5436
5599
  this.getReceiptClient(),
5437
- getAddress5(context.wallet.walletAddress),
5438
- getAddress5(state.walletAddress),
5600
+ getAddress6(context.wallet.walletAddress),
5601
+ getAddress6(state.walletAddress),
5439
5602
  YIELDSEEKER_ASSET_METADATA[asset].address
5440
- );
5441
- this.rememberWalletStatus(key2, status);
5442
- return status;
5603
+ ).then((status) => {
5604
+ this.assertSession(generation, chainId, asset);
5605
+ if (this.pendingWalletStatus.get(key2) !== pending)
5606
+ throw new OwneyError("AGENT_API_ERROR", "Yieldseeker wallet verification changed. Please retry.", void 0, this.id);
5607
+ this.rememberWalletStatus(key2, status);
5608
+ return status;
5609
+ }).finally(() => {
5610
+ if (this.pendingWalletStatus.get(key2) === pending) this.pendingWalletStatus.delete(key2);
5611
+ });
5612
+ this.pendingWalletStatus.set(key2, pending);
5613
+ return { ...await pending };
5443
5614
  }
5444
5615
  rememberWalletStatus(key2, status) {
5445
5616
  if (status.verified && status.version === "v2") {
@@ -5465,6 +5636,7 @@ var YieldseekerAgent = class {
5465
5636
  const pendingKey2 = `owney.yieldseeker.upgrade:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
5466
5637
  const confirmUpgrade = async (hash2, receipt2) => {
5467
5638
  this.withdrawalVersionCache.delete(versionKey);
5639
+ this.pendingWalletStatus.delete(versionKey);
5468
5640
  const upgradeTopic = encodeEventTopics({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded" })[0];
5469
5641
  const lastUpgrade = receipt2?.logs.filter((log) => log.address.toLowerCase() === before.agentWallet.toLowerCase() && log.topics[0] === upgradeTopic).at(-1);
5470
5642
  const expectedUpgrade = (() => {
@@ -5608,7 +5780,7 @@ var YieldseekerAgent = class {
5608
5780
  { version: before.version },
5609
5781
  this.id
5610
5782
  );
5611
- const data = encodeFunctionData2({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
5783
+ const data = encodeFunctionData3({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
5612
5784
  let txHash;
5613
5785
  let receipt;
5614
5786
  if (gasMode === "sponsored") {
@@ -5651,7 +5823,7 @@ var YieldseekerAgent = class {
5651
5823
  let submittedHash;
5652
5824
  try {
5653
5825
  txHash = await this.submitTransaction(state, chainId, {
5654
- from: getAddress5(state.walletAddress),
5826
+ from: getAddress6(state.walletAddress),
5655
5827
  to: before.agentWallet,
5656
5828
  data,
5657
5829
  value: "0",
@@ -5685,6 +5857,7 @@ var YieldseekerAgent = class {
5685
5857
  }
5686
5858
  async disconnect() {
5687
5859
  this.withdrawalVersionCache.clear();
5860
+ this.pendingWalletStatus.clear();
5688
5861
  this.readGeneration += 1;
5689
5862
  this.auth.clear();
5690
5863
  for (const key2 of this.users.keys()) {
@@ -5702,11 +5875,13 @@ var YieldseekerAgent = class {
5702
5875
  this.liquidityVersions.clear();
5703
5876
  this.snapshotFailures.clear();
5704
5877
  this.standardReadFailures.clear();
5878
+ this.endpointReadFailures.clear();
5705
5879
  this.reconcileUntil.clear();
5706
5880
  this.allocationRefresh.clear();
5707
5881
  this.activityRefreshUntil.clear();
5708
5882
  this.snapshotMovements.clear();
5709
5883
  this.movementEpochs.clear();
5884
+ this.settlementChecks.clear();
5710
5885
  }
5711
5886
  async activateAgent(state, chainId, asset) {
5712
5887
  this.assertChain(chainId);
@@ -5759,12 +5934,12 @@ var YieldseekerAgent = class {
5759
5934
  } else {
5760
5935
  try {
5761
5936
  txHash = await this.submitTransaction(state, chainId, {
5762
- from: getAddress5(state.walletAddress),
5937
+ from: getAddress6(state.walletAddress),
5763
5938
  to: YIELDSEEKER_ASSET_METADATA[asset].address,
5764
- data: encodeFunctionData2({
5939
+ data: encodeFunctionData3({
5765
5940
  abi: erc20Abi5,
5766
5941
  functionName: "transfer",
5767
- args: [getAddress5(context.wallet.walletAddress), BigInt(amount)]
5942
+ args: [getAddress6(context.wallet.walletAddress), BigInt(amount)]
5768
5943
  }),
5769
5944
  value: "0",
5770
5945
  chainId
@@ -5827,7 +6002,10 @@ var YieldseekerAgent = class {
5827
6002
  ownerTransferAttempted = true;
5828
6003
  }, gasMode, onBeforeSubmission, assertOperation);
5829
6004
  } catch (error) {
5830
- if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
6005
+ if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") {
6006
+ this.withdrawalVersionCache.clear();
6007
+ this.pendingWalletStatus.clear();
6008
+ }
5831
6009
  if (!ownerTransferAttempted || error?.code === 4001 || error instanceof OwneyError && (error.code === "AGENT_TRANSACTION_REVERTED" || error.details?.ownerTransferNotExecuted === true || error.code === "SPONSOR_REQUEST_FAILED" && (error.details?.safeToFallback === true || error.details?.executionFailed === true)))
5832
6010
  notifyFunding(onProgress, {
5833
6011
  ...evidence,
@@ -5910,7 +6088,7 @@ var YieldseekerAgent = class {
5910
6088
  );
5911
6089
  const positionsRead = this.readWithdrawalPositions(state, chainId, context);
5912
6090
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
5913
- const agentWallet = getAddress5(context.wallet.walletAddress);
6091
+ const agentWallet = getAddress6(context.wallet.walletAddress);
5914
6092
  const readOwnerFunds = async (blockNumber) => {
5915
6093
  const feeTracker = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5916
6094
  address: agentWallet,
@@ -5930,7 +6108,7 @@ var YieldseekerAgent = class {
5930
6108
  blockNumber
5931
6109
  }), chainId, "eth_call", this.id),
5932
6110
  withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5933
- address: getAddress5(feeTracker),
6111
+ address: getAddress6(feeTracker),
5934
6112
  abi: YIELDSEEKER_FEE_TRACKER_ABI,
5935
6113
  functionName: "getFeesOwed",
5936
6114
  args: [agentWallet],
@@ -5972,7 +6150,7 @@ var YieldseekerAgent = class {
5972
6150
  let active2 = true;
5973
6151
  const checks = this.readVaultShares(values.map((position2) => position2.vaultOption), agentWallet, blockNumber).catch(() => []);
5974
6152
  const reads = values.map(async (position2, index) => {
5975
- const address = getAddress5(position2.vaultOption.address);
6153
+ const address = getAddress6(position2.vaultOption.address);
5976
6154
  try {
5977
6155
  const group = (await checks).slice(index * 2, index * 2 + 2);
5978
6156
  if (!active2 || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
@@ -5980,13 +6158,27 @@ var YieldseekerAgent = class {
5980
6158
  const [underlying, shares] = group.map((value) => value.result);
5981
6159
  if (typeof underlying !== "string" || !isAddress4(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
5982
6160
  const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: "0" };
6161
+ if (shares > 0n && nativeYieldseekerProtocol(position2.vaultOption)) {
6162
+ const value = await readNativeYieldseekerAssets(
6163
+ this.getReceiptClient(),
6164
+ position2.vaultOption,
6165
+ agentWallet,
6166
+ shares,
6167
+ blockNumber
6168
+ );
6169
+ if (!active2 || value === void 0) return null;
6170
+ this.assertSession(generation, chainId, asset);
6171
+ verified.withdrawableAssetsRaw = value.toString();
6172
+ verifiedPositions.push(verified);
6173
+ return verified;
6174
+ }
5983
6175
  if (shares > 0n) {
5984
6176
  let positiveValue = false;
5985
6177
  const valuations = await Promise.all(["convertToAssets", "previewRedeem"].map(async (functionName) => {
5986
6178
  try {
5987
6179
  const value = await this.getReceiptClient().readContract({
5988
6180
  address,
5989
- abi: erc4626Abi2,
6181
+ abi: erc4626Abi3,
5990
6182
  functionName,
5991
6183
  args: [shares],
5992
6184
  blockNumber
@@ -6227,7 +6419,7 @@ var YieldseekerAgent = class {
6227
6419
  delta: maxSettlementBasis.delta - payout,
6228
6420
  expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
6229
6421
  } : this.snapshotMovement(state, chainId, context, -payout);
6230
- const account = getAddress5(state.walletAddress);
6422
+ const account = getAddress6(state.walletAddress);
6231
6423
  let receipt;
6232
6424
  const base7 = { agentId: this.id, chainId, token: asset, walletAddress: state.walletAddress, agentWallet: context.wallet.walletAddress, intendedAmount: amount ?? "MAX", transfer: "pending", registration: "not-applicable", delivery: "pending" };
6233
6425
  if (amount === void 0) base7.maxCompletion = {
@@ -6238,11 +6430,11 @@ var YieldseekerAgent = class {
6238
6430
  };
6239
6431
  if (base7.maxCompletion) notifyFunding(onProgress, base7);
6240
6432
  let submissionEvidence = base7;
6241
- const data = amount === void 0 ? encodeFunctionData2({
6433
+ const data = amount === void 0 ? encodeFunctionData3({
6242
6434
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
6243
6435
  functionName: "withdrawAllAssetToUser",
6244
6436
  args: [account, metadata.address]
6245
- }) : encodeFunctionData2({
6437
+ }) : encodeFunctionData3({
6246
6438
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
6247
6439
  functionName: "withdrawAssetToUser",
6248
6440
  args: [account, metadata.address, payout]
@@ -6259,7 +6451,7 @@ var YieldseekerAgent = class {
6259
6451
  refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
6260
6452
  userId: context.user.userId,
6261
6453
  agentId: context.agent.agentId,
6262
- agentWallet: getAddress5(context.wallet.walletAddress),
6454
+ agentWallet: getAddress6(context.wallet.walletAddress),
6263
6455
  asset: metadata.address,
6264
6456
  callData: data,
6265
6457
  onPending: (pending) => {
@@ -6281,7 +6473,7 @@ var YieldseekerAgent = class {
6281
6473
  chainId,
6282
6474
  {
6283
6475
  from: account,
6284
- to: getAddress5(context.wallet.walletAddress),
6476
+ to: getAddress6(context.wallet.walletAddress),
6285
6477
  data,
6286
6478
  value: "0",
6287
6479
  chainId
@@ -6426,28 +6618,35 @@ var YieldseekerAgent = class {
6426
6618
  );
6427
6619
  if (!Array.isArray(response?.vaultPositions)) return unknown("CATALOG_INVALID");
6428
6620
  const positions = this.validateWithdrawalPositions(response.vaultPositions, evidence.chainId, asset);
6429
- const vaults = new Set([...proof.vaults, ...positions.map((p) => p.vaultOption.address)].map((a) => a.toLowerCase()));
6430
- if (!proof.inventoryComplete) {
6431
- const catalog = await this.loadVaultOptions(asset);
6621
+ const vaults = new Map(proof.vaults.map((address) => [address.toLowerCase(), { address }]));
6622
+ positions.forEach((p) => vaults.set(p.vaultOption.address.toLowerCase(), p.vaultOption));
6623
+ const needsInventory = !proof.inventoryComplete;
6624
+ if (needsInventory || [...vaults.values()].some((option) => !option.provider)) {
6625
+ const catalog = await this.loadVaultOptions(asset).catch(() => []);
6432
6626
  const eligible = catalog.filter((o) => o.chainId === evidence.chainId && o.assetAddress?.toLowerCase() === metadata.address.toLowerCase() && isAddress4(o.address));
6433
- if (!eligible.length) return unknown("INVENTORY_INCOMPLETE");
6434
- eligible.forEach((o) => vaults.add(o.address.toLowerCase()));
6627
+ if (needsInventory && !eligible.length) return unknown("INVENTORY_INCOMPLETE");
6628
+ for (const option of eligible) {
6629
+ const key2 = option.address.toLowerCase();
6630
+ if (needsInventory || vaults.has(key2)) vaults.set(key2, option);
6631
+ }
6435
6632
  }
6436
- if (vaults.size > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT || [...vaults].some((a) => !isAddress4(a)))
6633
+ if (vaults.size > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT || [...vaults.keys()].some((a) => !isAddress4(a)))
6437
6634
  return unknown("INVENTORY_INVALID");
6438
6635
  reason = "VAULT_PROOF_UNAVAILABLE";
6439
6636
  const blockNumber = BigInt(evidence.blockNumber);
6440
- const addresses = [...vaults];
6441
- const shares = await this.readVaultShares(addresses.map((address) => ({ address })), evidence.agentWallet, blockNumber);
6637
+ const options = [...vaults.values()];
6638
+ const addresses = options.map((option) => option.address);
6639
+ const shares = await this.readVaultShares(options, evidence.agentWallet, blockNumber);
6442
6640
  const checks = await Promise.all(addresses.map(async (address, i) => {
6443
6641
  const group = shares.slice(i * 2, i * 2 + 2);
6444
6642
  if (group.length !== 2 || group.some((r) => r.status !== "success")) return "unavailable";
6445
6643
  const [underlying, value] = group.map((r) => r.result);
6446
6644
  if (typeof underlying !== "string" || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof value !== "bigint" || value < 0n) return "unavailable";
6447
6645
  if (value === 0n) return "complete";
6646
+ if (nativeYieldseekerProtocol(options[i])) return "remaining";
6448
6647
  const amounts = await Promise.allSettled(["convertToAssets", "previewRedeem"].map((functionName) => this.getReceiptClient().readContract({
6449
- address: getAddress5(address),
6450
- abi: erc4626Abi2,
6648
+ address: getAddress6(address),
6649
+ abi: erc4626Abi3,
6451
6650
  functionName,
6452
6651
  args: [value],
6453
6652
  blockNumber
@@ -6467,15 +6666,15 @@ var YieldseekerAgent = class {
6467
6666
  }
6468
6667
  }
6469
6668
  async readVaultShares(options, wallet, blockNumber) {
6470
- const owner = getAddress5(wallet);
6669
+ const owner = getAddress6(wallet);
6471
6670
  const batches = Array.from({ length: Math.ceil(options.length / YIELDSEEKER_VAULTS_PER_RPC_BATCH) }, (_, index) => options.slice(index * YIELDSEEKER_VAULTS_PER_RPC_BATCH, (index + 1) * YIELDSEEKER_VAULTS_PER_RPC_BATCH));
6472
6671
  const results = await Promise.all(batches.map((batch) => this.getReceiptClient().multicall({
6473
6672
  blockNumber,
6474
6673
  allowFailure: true,
6475
6674
  contracts: batch.flatMap((option) => {
6476
- const address = getAddress5(option.address);
6675
+ const address = getAddress6(option.address);
6477
6676
  return [
6478
- { address, abi: erc4626Abi2, functionName: "asset" },
6677
+ yieldseekerUnderlyingCall(option),
6479
6678
  { address, abi: erc20Abi5, functionName: "balanceOf", args: [owner] }
6480
6679
  ];
6481
6680
  })
@@ -6554,7 +6753,7 @@ var YieldseekerAgent = class {
6554
6753
  const read2 = async () => {
6555
6754
  const client = this.getReceiptClient();
6556
6755
  const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
6557
- const wallet = getAddress5(context.wallet.walletAddress);
6756
+ const wallet = getAddress6(context.wallet.walletAddress);
6558
6757
  assertReadCurrent();
6559
6758
  if (typeof blockNumber !== "bigint") throw unavailable();
6560
6759
  const indexedPositions = this.validateWithdrawalPositions(positions ?? [], chainId, context.asset);
@@ -6582,7 +6781,7 @@ var YieldseekerAgent = class {
6582
6781
  if (typeof idle !== "bigint" || idle < 0n || !isAddress4(feeTracker) || /^0x0{40}$/i.test(feeTracker)) throw unavailable();
6583
6782
  assertReadCurrent();
6584
6783
  const fees = await client.readContract({
6585
- address: getAddress5(feeTracker),
6784
+ address: getAddress6(feeTracker),
6586
6785
  abi: YIELDSEEKER_FEE_TRACKER_ABI,
6587
6786
  functionName: "getFeesOwed",
6588
6787
  args: [wallet],
@@ -6648,11 +6847,21 @@ var YieldseekerAgent = class {
6648
6847
  verified.push(position2);
6649
6848
  knownProof = proof(false);
6650
6849
  if (shares === 0n) return true;
6850
+ if (nativeYieldseekerProtocol(option)) {
6851
+ const value2 = await readNativeYieldseekerAssets(client, option, wallet, shares, blockNumber);
6852
+ assertReadCurrent();
6853
+ if (value2 === void 0) return false;
6854
+ position2.withdrawableAssetsRaw = value2.toString();
6855
+ position2.withdrawableAssetsBase = value2.toString();
6856
+ deployed += value2;
6857
+ knownProof = proof(false);
6858
+ return true;
6859
+ }
6651
6860
  let preview;
6652
6861
  try {
6653
6862
  const value2 = await client.readContract({
6654
- address: getAddress5(option.address),
6655
- abi: erc4626Abi2,
6863
+ address: getAddress6(option.address),
6864
+ abi: erc4626Abi3,
6656
6865
  functionName: "previewRedeem",
6657
6866
  args: [shares],
6658
6867
  blockNumber
@@ -6665,8 +6874,8 @@ var YieldseekerAgent = class {
6665
6874
  if (value === void 0 || value === 0n) {
6666
6875
  try {
6667
6876
  const converted = await client.readContract({
6668
- address: getAddress5(option.address),
6669
- abi: erc4626Abi2,
6877
+ address: getAddress6(option.address),
6878
+ abi: erc4626Abi3,
6670
6879
  functionName: "convertToAssets",
6671
6880
  args: [shares],
6672
6881
  blockNumber
@@ -6757,7 +6966,7 @@ var YieldseekerAgent = class {
6757
6966
  const key2 = this.contextKey(state, evidence.chainId, evidence.token);
6758
6967
  const generation = this.readGeneration, version = this.portfolioVersion(key2);
6759
6968
  if (read2.readVersion !== version) return void 0;
6760
- const wallet = getAddress5(context.wallet.walletAddress);
6969
+ const wallet = getAddress6(context.wallet.walletAddress);
6761
6970
  const asset = YIELDSEEKER_ASSET_METADATA[evidence.token];
6762
6971
  const client = this.getReceiptClient();
6763
6972
  const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
@@ -6798,16 +7007,18 @@ var YieldseekerAgent = class {
6798
7007
  if (failure && failure.retryAt > Date.now()) this.snapshotFailures.set(key2, failure);
6799
7008
  this.allocationRefresh.add(key2);
6800
7009
  }
6801
- invalidateFundingRead(state, chainId, asset, refreshUntil = Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS) {
7010
+ invalidateFundingRead(state, chainId, asset, refreshUntil = Date.now() + YIELDSEEKER_SETTLEMENT_SNAPSHOT_WINDOW_MS) {
6802
7011
  this.assertAsset(asset);
6803
7012
  const key2 = this.contextKey(state, chainId, asset);
6804
7013
  const failure = this.snapshotFailures.get(key2);
6805
7014
  this.advancePortfolioVersion(key2);
6806
7015
  if (failure && failure.retryAt > Date.now()) this.snapshotFailures.set(key2, failure);
6807
7016
  if (!this.snapshotMovements.has(key2)) this.snapshotMovements.set(key2, null);
6808
- const until = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
7017
+ const until = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_SNAPSHOT_WINDOW_MS);
7018
+ const activityUntil = Math.min(refreshUntil, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
7019
+ if (until > Date.now()) this.beginSettlement(key2, false);
6809
7020
  this.reconcileUntil.set(key2, Math.max(this.reconcileUntil.get(key2) ?? 0, until));
6810
- this.activityRefreshUntil.set(key2, Math.max(this.activityRefreshUntil.get(key2) ?? 0, until));
7021
+ this.activityRefreshUntil.set(key2, Math.max(this.activityRefreshUntil.get(key2) ?? 0, activityUntil));
6811
7022
  }
6812
7023
  endFundingBalanceRefresh(state, chainId, asset, read2) {
6813
7024
  this.assertChain(chainId);
@@ -6838,7 +7049,7 @@ var YieldseekerAgent = class {
6838
7049
  address: YIELDSEEKER_ASSET_METADATA[asset].address,
6839
7050
  abi: erc20Abi5,
6840
7051
  functionName: "balanceOf",
6841
- args: [getAddress5(context.wallet.walletAddress)]
7052
+ args: [getAddress6(context.wallet.walletAddress)]
6842
7053
  }), chainId, "eth_call", this.id);
6843
7054
  this.assertSession(generation, chainId, asset);
6844
7055
  if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
@@ -7044,7 +7255,7 @@ var YieldseekerAgent = class {
7044
7255
  this.users.set(key2, persisted);
7045
7256
  return persisted;
7046
7257
  }
7047
- const walletAddress = getAddress5(state.walletAddress);
7258
+ const walletAddress = getAddress6(state.walletAddress);
7048
7259
  const generation = this.readGeneration;
7049
7260
  let user = null;
7050
7261
  try {
@@ -7217,8 +7428,21 @@ var YieldseekerAgent = class {
7217
7428
  portfolioVersion(contextKey) {
7218
7429
  return this.portfolioVersions.get(contextKey) ?? 0;
7219
7430
  }
7431
+ /** Snapshot cache lifetime: the settlement backoff while a movement is being
7432
+ * checked, otherwise the routine non-forced cadence. */
7220
7433
  portfolioCacheMs(contextKey) {
7221
- return (this.reconcileUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_CACHE_MS : YIELDSEEKER_PORTFOLIO_CACHE_MS;
7434
+ if ((this.reconcileUntil.get(contextKey) ?? 0) <= Date.now()) return YIELDSEEKER_PORTFOLIO_CACHE_MS;
7435
+ const checks = this.settlementChecks.get(contextKey) ?? 0;
7436
+ return YIELDSEEKER_SETTLEMENT_BACKOFF_MS[Math.min(checks, YIELDSEEKER_SETTLEMENT_BACKOFF_MS.length - 1)];
7437
+ }
7438
+ /** A pending movement inside its ten-minute window forces snapshots. */
7439
+ settling(contextKey) {
7440
+ return this.snapshotMovements.has(contextKey) && (this.reconcileUntil.get(contextKey) ?? 0) > Date.now();
7441
+ }
7442
+ /** Start a new forced-check schedule, or keep the active one. */
7443
+ beginSettlement(contextKey, restart) {
7444
+ if (!restart && (this.reconcileUntil.get(contextKey) ?? 0) > Date.now()) return;
7445
+ this.settlementChecks.set(contextKey, 0);
7222
7446
  }
7223
7447
  snapshotMovement(state, chainId, context, delta) {
7224
7448
  const key2 = this.contextKey(state, chainId, context.asset);
@@ -7244,13 +7468,15 @@ var YieldseekerAgent = class {
7244
7468
  expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
7245
7469
  };
7246
7470
  }
7247
- advancePortfolioVersion(contextKey, { snapshotOnly = false } = {}) {
7471
+ advancePortfolioVersion(contextKey, { snapshotOnly = false, keepPositions = false } = {}) {
7248
7472
  const version = this.portfolioVersion(contextKey) + 1;
7249
7473
  this.portfolioVersions.set(contextKey, version);
7250
7474
  if (!snapshotOnly) this.liquidityVersions.set(contextKey, version);
7251
7475
  this.snapshotFailures.delete(contextKey);
7252
- for (const kind of ["snapshot", "positions", "historic", "actions"]) {
7476
+ const kinds = !snapshotOnly ? ["snapshot", "positions", "historic", "actions"] : keepPositions ? ["snapshot"] : ["snapshot", "positions"];
7477
+ for (const kind of kinds) {
7253
7478
  const key2 = `${kind}:${contextKey}`;
7479
+ if (snapshotOnly && kind === "positions" && this.pendingReads.has(key2)) continue;
7254
7480
  this.readCache.delete(key2);
7255
7481
  this.pendingReads.delete(key2);
7256
7482
  }
@@ -7295,7 +7521,7 @@ var YieldseekerAgent = class {
7295
7521
  if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
7296
7522
  const failure = this.snapshotFailures.get(contextKey);
7297
7523
  if (failure && failure.retryAt > Date.now()) throw failure.error;
7298
- this.advancePortfolioVersion(contextKey, { snapshotOnly: true });
7524
+ this.advancePortfolioVersion(contextKey, { snapshotOnly: true, keepPositions: this.settling(contextKey) });
7299
7525
  }
7300
7526
  const version = this.portfolioVersion(contextKey);
7301
7527
  const generation = this.readGeneration;
@@ -7303,6 +7529,8 @@ var YieldseekerAgent = class {
7303
7529
  version,
7304
7530
  read: this.cachedRead(key2, this.portfolioCacheMs(contextKey), () => {
7305
7531
  const allocationChanged = this.allocationRefresh.delete(contextKey);
7532
+ const settling = this.settling(contextKey);
7533
+ if (settling) this.settlementChecks.set(contextKey, (this.settlementChecks.get(contextKey) ?? 0) + 1);
7306
7534
  return this.requestPortfolioSnapshot(
7307
7535
  state,
7308
7536
  chainId,
@@ -7310,7 +7538,7 @@ var YieldseekerAgent = class {
7310
7538
  contextKey,
7311
7539
  version,
7312
7540
  generation,
7313
- allocationChanged || this.snapshotMovements.has(contextKey) && (this.reconcileUntil.get(contextKey) ?? 0) > Date.now()
7541
+ allocationChanged || settling
7314
7542
  );
7315
7543
  })
7316
7544
  };
@@ -7380,7 +7608,7 @@ var YieldseekerAgent = class {
7380
7608
  const [positions, historic, actions] = await Promise.all([
7381
7609
  options.includePositions === false ? Promise.resolve({ vaultPositions: [] }) : this.cachedRead(
7382
7610
  `positions:${contextKey}`,
7383
- this.portfolioCacheMs(contextKey),
7611
+ YIELDSEEKER_PORTFOLIO_CACHE_MS,
7384
7612
  async () => {
7385
7613
  const response = await this.walletRequest(
7386
7614
  state,
@@ -7436,7 +7664,8 @@ var YieldseekerAgent = class {
7436
7664
  }
7437
7665
  async refreshSnapshotAfterMovement(state, chainId, context, movement, settlement) {
7438
7666
  const contextKey = this.contextKey(state, chainId, context.asset);
7439
- this.reconcileUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
7667
+ this.beginSettlement(contextKey, true);
7668
+ this.reconcileUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_SNAPSHOT_WINDOW_MS);
7440
7669
  this.activityRefreshUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
7441
7670
  const epoch = this.movementEpochs.get(contextKey) ?? 0;
7442
7671
  this.snapshotMovements.set(contextKey, settlement?.epoch === epoch ? settlement : null);
@@ -7444,9 +7673,11 @@ var YieldseekerAgent = class {
7444
7673
  const version = this.advancePortfolioVersion(contextKey);
7445
7674
  const generation = this.readGeneration;
7446
7675
  try {
7676
+ const ttl = this.portfolioCacheMs(contextKey);
7677
+ this.settlementChecks.set(contextKey, 1);
7447
7678
  await this.cachedRead(
7448
7679
  `snapshot:${contextKey}`,
7449
- YIELDSEEKER_SETTLEMENT_CACHE_MS,
7680
+ ttl,
7450
7681
  () => this.requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, true)
7451
7682
  );
7452
7683
  } catch (error) {
@@ -7462,10 +7693,14 @@ var YieldseekerAgent = class {
7462
7693
  async walletRequest(state, chainId, path, options = {}) {
7463
7694
  const read2 = (options.method ?? "GET") === "GET";
7464
7695
  const userKey = this.userKey(state, chainId);
7696
+ const endpointKey = `${userKey}:${path}`;
7465
7697
  if (read2) {
7466
7698
  const failure = this.standardReadFailures.get(userKey);
7467
7699
  if (failure && failure.retryAt > Date.now()) throw failure.error;
7468
7700
  if (failure) this.standardReadFailures.delete(userKey);
7701
+ const endpointFailure = this.endpointReadFailures.get(endpointKey);
7702
+ if (endpointFailure && endpointFailure.retryAt > Date.now()) throw endpointFailure.error;
7703
+ if (endpointFailure) this.endpointReadFailures.delete(endpointKey);
7469
7704
  }
7470
7705
  const generation = this.readGeneration;
7471
7706
  try {
@@ -7479,6 +7714,12 @@ var YieldseekerAgent = class {
7479
7714
  error: mapped
7480
7715
  });
7481
7716
  }
7717
+ if (read2 && this.readGeneration === generation && error instanceof YieldseekerApiError && (error.status === 0 || error.status === 408 || error.status >= 500 && error.status < 600)) {
7718
+ this.endpointReadFailures.set(endpointKey, {
7719
+ retryAt: Date.now() + YIELDSEEKER_READ_FAILURE_COOLDOWN_MS,
7720
+ error: mapped
7721
+ });
7722
+ }
7482
7723
  throw mapped;
7483
7724
  }
7484
7725
  }
@@ -7536,7 +7777,7 @@ var YieldseekerAgent = class {
7536
7777
  return hash3;
7537
7778
  }
7538
7779
  this.assertTransaction(transaction, state, chainId);
7539
- const account = getAddress5(state.walletAddress);
7780
+ const account = getAddress6(state.walletAddress);
7540
7781
  const walletClient = createWalletClient4({
7541
7782
  account,
7542
7783
  chain: base4,
@@ -7557,7 +7798,7 @@ var YieldseekerAgent = class {
7557
7798
  const hash2 = await walletClient.sendTransaction({
7558
7799
  account,
7559
7800
  chain: base4,
7560
- to: getAddress5(transaction.to),
7801
+ to: getAddress6(transaction.to),
7561
7802
  data: transaction.data,
7562
7803
  value: BigInt(transaction.value)
7563
7804
  });
@@ -7661,7 +7902,7 @@ var YieldseekerAgent = class {
7661
7902
  return receipt;
7662
7903
  }
7663
7904
  assertTransaction(transaction, state, chainId) {
7664
- if (!transaction || typeof transaction.from !== "string" || !isAddress4(transaction.from) || typeof transaction.to !== "string" || !isAddress4(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || getAddress5(transaction.from) !== getAddress5(state.walletAddress)) {
7905
+ if (!transaction || typeof transaction.from !== "string" || !isAddress4(transaction.from) || typeof transaction.to !== "string" || !isAddress4(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || getAddress6(transaction.from) !== getAddress6(state.walletAddress)) {
7665
7906
  throw this.invalidResponse("transaction");
7666
7907
  }
7667
7908
  }
@@ -8878,7 +9119,7 @@ async function awaitWithdrawalPayouts(options) {
8878
9119
  const candidates = /* @__PURE__ */ new Map();
8879
9120
  const toDate = (/* @__PURE__ */ new Date()).toISOString();
8880
9121
  const fromDate = new Date(options.submittedAt - 5e3).toISOString();
8881
- const cursors = /* @__PURE__ */ new Set();
9122
+ const cursors2 = /* @__PURE__ */ new Set();
8882
9123
  let cursor;
8883
9124
  for (let pageNumber = 0; pageNumber < 10; pageNumber++) {
8884
9125
  assertActive();
@@ -8904,8 +9145,8 @@ async function awaitWithdrawalPayouts(options) {
8904
9145
  const confirmed = inspected.flatMap((value) => value.status === "fulfilled" && value.value.transfer === "confirmed" && value.value.delivery === "confirmed" && (rawAmount(value.value.blockNumber) ?? 0n) > options.baselineBlock ? [value.value] : []);
8905
9146
  return confirmed.length === 1 ? confirmed[0] : void 0;
8906
9147
  }
8907
- if (!page.nextCursor || cursors.has(page.nextCursor)) return;
8908
- cursors.add(page.nextCursor);
9148
+ if (!page.nextCursor || cursors2.has(page.nextCursor)) return;
9149
+ cursors2.add(page.nextCursor);
8909
9150
  cursor = page.nextCursor;
8910
9151
  }
8911
9152
  };
@@ -9572,7 +9813,7 @@ function aggregateDailyEarnings(series, tokenSymbol) {
9572
9813
  }
9573
9814
  return [...byAsset.entries()].map(([asset, components]) => {
9574
9815
  const dates = [...new Set(components.flatMap((points2) => points2.map((point) => point.date)))].sort();
9575
- const cursors = components.map((points2) => ({
9816
+ const cursors2 = components.map((points2) => ({
9576
9817
  index: 0,
9577
9818
  // Providers can have different lifetime/window offsets and start dates.
9578
9819
  // Remove each opening reading before combining their earned amounts.
@@ -9582,7 +9823,7 @@ function aggregateDailyEarnings(series, tokenSymbol) {
9582
9823
  const points = dates.map((date) => {
9583
9824
  let amount = 0;
9584
9825
  components.forEach((component, index) => {
9585
- const cursor = cursors[index];
9826
+ const cursor = cursors2[index];
9586
9827
  while (cursor.index < component.length && component[cursor.index].date <= date) {
9587
9828
  cursor.current = component[cursor.index++].amount;
9588
9829
  }
@@ -9604,16 +9845,312 @@ import {
9604
9845
  } from "viem";
9605
9846
  import { base as base6, arbitrum as arbitrum3, mainnet as mainnet3 } from "viem/chains";
9606
9847
 
9848
+ // src/lib/token-batch-execution.ts
9849
+ import {
9850
+ keccak256 as keccak2562,
9851
+ parseTransaction
9852
+ } from "viem";
9853
+ async function executeTokenBatch(input, initial) {
9854
+ let body = initial, family, ended = false;
9855
+ const deadlineAt = Date.now() + 9e4, controller = new AbortController();
9856
+ let timer;
9857
+ const pending = () => new OwneyError(
9858
+ "DEPOSIT_IN_PROGRESS",
9859
+ "Deposit is awaiting confirmation. Its transaction is still being checked.",
9860
+ {
9861
+ sponsorPending: true,
9862
+ phase: body.serializedTransaction ? "deposit" : "approval",
9863
+ txHash: body.serializedTransaction ? keccak2562(body.serializedTransaction) : body.serializedPermitTransaction ? keccak2562(body.serializedPermitTransaction) : void 0,
9864
+ notSubmitted: false,
9865
+ safeToFallback: false
9866
+ }
9867
+ );
9868
+ const assertCurrent = () => {
9869
+ if (ended || Date.now() >= deadlineAt) throw pending();
9870
+ input.assertCurrent?.();
9871
+ };
9872
+ const timeout = new Promise((_, reject) => {
9873
+ timer = setTimeout(() => {
9874
+ ended = true;
9875
+ controller.abort();
9876
+ reject(pending());
9877
+ }, 9e4);
9878
+ });
9879
+ const bounded2 = async (work) => {
9880
+ const value = await Promise.race([work, timeout]);
9881
+ assertCurrent();
9882
+ return value;
9883
+ };
9884
+ const post = () => bounded2(
9885
+ postSponsorBatchTransfer({
9886
+ apiKey: input.apiKey,
9887
+ baseUrl: input.baseUrl,
9888
+ body,
9889
+ signal: controller.signal
9890
+ })
9891
+ );
9892
+ const replacements = { approval: 0, deposit: 0 };
9893
+ const unavailable = /* @__PURE__ */ new Set();
9894
+ const observedApprovals = /* @__PURE__ */ new Set();
9895
+ const checkpointed = /* @__PURE__ */ new Set();
9896
+ const accounted = /* @__PURE__ */ new Set();
9897
+ const remember = async (phase, bytes, hash2, predecessorHash) => {
9898
+ assertCurrent();
9899
+ if (keccak2562(bytes) !== hash2) throw pending();
9900
+ const tx = parseTransaction(bytes);
9901
+ const next = {
9902
+ ...body,
9903
+ [phase === "deposit" ? "serializedTransaction" : "serializedPermitTransaction"]: bytes
9904
+ };
9905
+ family = await bounded2(
9906
+ saveTokenBatchAttempt(
9907
+ input,
9908
+ next,
9909
+ {
9910
+ phase,
9911
+ hash: hash2,
9912
+ serializedTransaction: bytes,
9913
+ nonce: String(tx.nonce),
9914
+ predecessorHash,
9915
+ preparedAt: Date.now(),
9916
+ submissionAttempted: false
9917
+ },
9918
+ assertCurrent
9919
+ )
9920
+ );
9921
+ body = { ...family.body, transfers: initial.transfers };
9922
+ };
9923
+ const reference = (hash2) => family ? {
9924
+ authorizationId: family.authorizationId,
9925
+ predecessorTxHash: family.attempts.find((a) => a.hash === hash2)?.predecessorHash
9926
+ } : void 0;
9927
+ const markSubmission = async (phase) => {
9928
+ const hash2 = phase === "deposit" ? family?.current.deposit : family?.current.approval;
9929
+ if (!family || !hash2) throw pending();
9930
+ if (phase === "deposit" && !checkpointed.has(hash2)) {
9931
+ try {
9932
+ await bounded2(
9933
+ Promise.resolve(input.onBeforeSubmission?.(hash2, reference(hash2)))
9934
+ );
9935
+ } catch (error) {
9936
+ if (error instanceof OwneyError && error.code === "DEPOSIT_CHECKPOINT_FAILED")
9937
+ throw new OwneyError(error.code, error.message, {
9938
+ ...error.details,
9939
+ notSubmitted: !family.attempts.some(
9940
+ (a) => a.phase === "deposit" && a.submissionAttempted
9941
+ ),
9942
+ safeToFallback: false
9943
+ });
9944
+ throw error;
9945
+ }
9946
+ checkpointed.add(hash2);
9947
+ }
9948
+ assertCurrent();
9949
+ const attempt = family.attempts.find((a) => a.hash === hash2);
9950
+ family = await bounded2(
9951
+ saveTokenBatchAttempt(
9952
+ input,
9953
+ body,
9954
+ { ...attempt, submissionAttempted: true },
9955
+ assertCurrent
9956
+ )
9957
+ );
9958
+ assertCurrent();
9959
+ if (phase === "deposit")
9960
+ try {
9961
+ input.onSubmitted?.(hash2, reference(hash2));
9962
+ } catch {
9963
+ }
9964
+ assertCurrent();
9965
+ };
9966
+ const observe = async () => {
9967
+ if (!family) return;
9968
+ const observation = await bounded2(
9969
+ observeTokenBatchFamily({
9970
+ scope: input,
9971
+ family,
9972
+ pub: input.pub,
9973
+ deadlineAt,
9974
+ assertCurrent
9975
+ })
9976
+ );
9977
+ if (observation.status === "confirmed") {
9978
+ const winning = family.attempts.find(
9979
+ (a) => a.hash === observation.txHash
9980
+ );
9981
+ if (!accounted.has(observation.txHash)) {
9982
+ const accounting = new AbortController();
9983
+ const accountingTimer = setTimeout(() => accounting.abort(), 8e3);
9984
+ void postSponsorBatchTransfer({
9985
+ apiKey: input.apiKey,
9986
+ baseUrl: input.baseUrl,
9987
+ body: {
9988
+ ...body,
9989
+ serializedTransaction: winning.serializedTransaction
9990
+ },
9991
+ signal: accounting.signal
9992
+ }).catch(() => {
9993
+ }).finally(() => clearTimeout(accountingTimer));
9994
+ }
9995
+ return observation.txHash;
9996
+ }
9997
+ if (observation.status === "failed" || observation.status === "expired")
9998
+ throw new OwneyError(
9999
+ "SPONSOR_REQUEST_FAILED",
10000
+ observation.status === "expired" ? "The deposit authorization expired without transferring funds. Start a new deposit." : "Sponsored deposit transactions reverted. Please retry.",
10001
+ { notSubmitted: true, safeToFallback: false }
10002
+ );
10003
+ };
10004
+ try {
10005
+ assertCurrent();
10006
+ family = await bounded2(
10007
+ loadTokenBatchFamily(
10008
+ input,
10009
+ initial.serializedTransaction ? keccak2562(initial.serializedTransaction) : initial.serializedPermitTransaction ? keccak2562(initial.serializedPermitTransaction) : void 0
10010
+ )
10011
+ );
10012
+ if (family) body = { ...family.body, transfers: initial.transfers };
10013
+ else
10014
+ tokenBatchWrite(
10015
+ tokenBatchKey(input),
10016
+ JSON.stringify(tokenBatchStoredBody(body))
10017
+ );
10018
+ if (family) {
10019
+ const winner = await observe();
10020
+ if (winner) return winner;
10021
+ }
10022
+ if (family)
10023
+ for (const attempt of family.attempts)
10024
+ family = await bounded2(
10025
+ saveTokenBatchAttempt(input, body, attempt, assertCurrent)
10026
+ );
10027
+ let needsPost = true;
10028
+ while (true) {
10029
+ assertCurrent();
10030
+ const winner = await observe();
10031
+ if (winner) return winner;
10032
+ if (needsPost) {
10033
+ needsPost = false;
10034
+ const signedPhase = body.serializedTransaction ? "deposit" : body.serializedPermitTransaction ? "approval" : void 0;
10035
+ if (signedPhase) await markSubmission(signedPhase);
10036
+ let result;
10037
+ try {
10038
+ result = await post();
10039
+ } catch (error) {
10040
+ assertCurrent();
10041
+ if (!family) {
10042
+ tokenBatchClear(
10043
+ tokenBatchKey(input),
10044
+ JSON.stringify(tokenBatchStoredBody(body))
10045
+ );
10046
+ throw error;
10047
+ }
10048
+ }
10049
+ if (result?.serializedPermitTransaction && !body.serializedPermitTransaction) {
10050
+ if (!result.permitTxHash) throw pending();
10051
+ await remember(
10052
+ "approval",
10053
+ result.serializedPermitTransaction,
10054
+ result.permitTxHash
10055
+ );
10056
+ needsPost = true;
10057
+ continue;
10058
+ }
10059
+ if (result?.serializedTransaction && !body.serializedTransaction) {
10060
+ await remember(
10061
+ "deposit",
10062
+ result.serializedTransaction,
10063
+ result.txHash
10064
+ );
10065
+ needsPost = true;
10066
+ continue;
10067
+ }
10068
+ if (result && !result.status && !result.serializedTransaction && !result.serializedPermitTransaction && family?.current.deposit === result.txHash)
10069
+ accounted.add(result.txHash);
10070
+ const winner2 = await observe();
10071
+ if (winner2) return winner2;
10072
+ }
10073
+ const phase = body.serializedTransaction ? "deposit" : "approval";
10074
+ const current = family?.current[phase];
10075
+ if (!current) throw pending();
10076
+ if (phase === "approval" && !observedApprovals.has(current)) {
10077
+ const receipt = await bounded2(
10078
+ input.pub.getTransactionReceipt({ hash: current }).catch(() => void 0)
10079
+ );
10080
+ if (receipt?.status === "success" && receipt.transactionHash?.toLowerCase() === current.toLowerCase() && receipt.from?.toLowerCase() === family.relayer.toLowerCase() && receipt.to?.toLowerCase() === body.token.toLowerCase()) {
10081
+ observedApprovals.add(current);
10082
+ needsPost = true;
10083
+ continue;
10084
+ }
10085
+ }
10086
+ if (replacements[phase] < 2 && !unavailable.has(phase)) {
10087
+ let recovered;
10088
+ try {
10089
+ recovered = await bounded2(
10090
+ postSponsorBatchRecovery({
10091
+ apiKey: input.apiKey,
10092
+ baseUrl: input.baseUrl,
10093
+ body: { ...body, phase },
10094
+ signal: controller.signal
10095
+ })
10096
+ );
10097
+ } catch {
10098
+ assertCurrent();
10099
+ }
10100
+ assertCurrent();
10101
+ if (!recovered) unavailable.add(phase);
10102
+ else if (recovered.status === "prepared") {
10103
+ if (recovered.predecessorHash !== current) throw pending();
10104
+ await remember(
10105
+ phase,
10106
+ recovered.serializedTransaction,
10107
+ recovered.txHash,
10108
+ current
10109
+ );
10110
+ replacements[phase]++;
10111
+ needsPost = true;
10112
+ continue;
10113
+ } else if (phase === "approval" && (recovered.status === "approval-satisfied" || recovered.status === "included" && recovered.outcome === "success")) {
10114
+ needsPost = true;
10115
+ } else if (recovered.status === "included" && recovered.outcome === "reverted") {
10116
+ if (phase === "approval" && !family.attempts.some((a) => a.phase === "deposit") && family.attempts.filter((a) => a.phase === "approval").length === 1) {
10117
+ finishTokenBatchFamily(input, family, {
10118
+ kind: "failed",
10119
+ blockNumber: recovered.blockNumber
10120
+ });
10121
+ throw new OwneyError(
10122
+ "SPONSOR_REQUEST_FAILED",
10123
+ "Sponsored approval reverted. Please retry.",
10124
+ { notSubmitted: true, safeToFallback: false }
10125
+ );
10126
+ }
10127
+ unavailable.add(phase);
10128
+ }
10129
+ }
10130
+ await bounded2(
10131
+ new Promise(
10132
+ (resolve) => setTimeout(
10133
+ resolve,
10134
+ Math.min(2e3, Math.max(0, deadlineAt - Date.now()))
10135
+ )
10136
+ )
10137
+ );
10138
+ }
10139
+ } finally {
10140
+ ended = true;
10141
+ if (timer) clearTimeout(timer);
10142
+ controller.abort();
10143
+ }
10144
+ }
10145
+
9607
10146
  // src/lib/sponsored-token-batch.ts
9608
10147
  import {
9609
10148
  isAddressEqual as isAddressEqual2,
9610
- encodeFunctionData as encodeFunctionData3,
9611
- keccak256 as keccak2562,
9612
- parseTransaction,
10149
+ encodeFunctionData as encodeFunctionData4,
10150
+ keccak256 as keccak2563,
10151
+ parseTransaction as parseTransaction2,
9613
10152
  toBytes
9614
10153
  } from "viem";
9615
- var memory = /* @__PURE__ */ new Map();
9616
- var settledRecords = /* @__PURE__ */ new Map();
9617
10154
  var expiredRecords = /* @__PURE__ */ new Map();
9618
10155
  var inflight = /* @__PURE__ */ new Map();
9619
10156
  var planOf = (transfers) => JSON.stringify(
@@ -9622,18 +10159,7 @@ var planOf = (transfers) => JSON.stringify(
9622
10159
  amount: BigInt(t.amount).toString()
9623
10160
  }))
9624
10161
  );
9625
- var pendingKey = (i) => `owney.token-batch.v1:${keccak2562(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
9626
- function pendingTokenBatch(i) {
9627
- try {
9628
- const saved = read(pendingKey(i));
9629
- if (!saved) return;
9630
- const body = JSON.parse(saved);
9631
- if (!body.serializedTransaction || body.chainId !== i.chainId || !isAddressEqual2(body.from, i.owner) || !isAddressEqual2(body.token, i.token) || !body.transfers?.length) return;
9632
- return { txHash: keccak2562(body.serializedTransaction), transfers: body.transfers.map(({ to, amount }) => ({ to, amount })) };
9633
- } catch {
9634
- return;
9635
- }
9636
- }
10162
+ var pendingKey = (i) => `owney.token-batch.v1:${keccak2563(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
9637
10163
  async function releaseExpiredTokenBatch(i) {
9638
10164
  const key2 = pendingKey(i);
9639
10165
  let timer;
@@ -9642,7 +10168,7 @@ async function releaseExpiredTokenBatch(i) {
9642
10168
  let active2 = read(key2);
9643
10169
  if (active2 && i.expectedTxHash) {
9644
10170
  const current = JSON.parse(active2);
9645
- if (!current.serializedTransaction || keccak2562(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
10171
+ if (!current.serializedTransaction || keccak2563(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
9646
10172
  active2 = void 0;
9647
10173
  }
9648
10174
  const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
@@ -9650,10 +10176,10 @@ async function releaseExpiredTokenBatch(i) {
9650
10176
  if (!saved) return;
9651
10177
  const body = JSON.parse(saved);
9652
10178
  if (!body.serializedTransaction || body.chainId !== i.chainId || !isAddressEqual2(body.from, i.owner) || !isAddressEqual2(body.token, i.token) || !body.transfers?.length) return;
9653
- const txHash = keccak2562(body.serializedTransaction);
10179
+ const txHash = keccak2563(body.serializedTransaction);
9654
10180
  if (i.expectedTxHash && txHash.toLowerCase() !== i.expectedTxHash.toLowerCase()) return;
9655
- const transaction = parseTransaction(body.serializedTransaction);
9656
- if (transaction.chainId !== i.chainId || !transaction.to || !isAddressEqual2(transaction.to, BATCH_PERMIT2_ADDRESS) || (transaction.value ?? 0n) !== 0n || transaction.data !== encodeFunctionData3({
10181
+ const transaction = parseTransaction2(body.serializedTransaction);
10182
+ if (transaction.chainId !== i.chainId || !transaction.to || !isAddressEqual2(transaction.to, BATCH_PERMIT2_ADDRESS) || (transaction.value ?? 0n) !== 0n || transaction.data !== encodeFunctionData4({
9657
10183
  abi: PERMIT2_BATCH_ABI,
9658
10184
  functionName: "permitWitnessTransferFrom",
9659
10185
  args: batchArgs(body)
@@ -9682,8 +10208,7 @@ async function releaseExpiredTokenBatch(i) {
9682
10208
  }
9683
10209
  } catch {
9684
10210
  }
9685
- if (!persisted) settledRecords.set(key2, saved);
9686
- else clear(key2, body);
10211
+ if (persisted) clear(key2, body);
9687
10212
  }
9688
10213
  return { txHash, transfers: body.transfers.map(({ to, amount }) => ({ to, amount })), blockNumber: block.number };
9689
10214
  };
@@ -9701,37 +10226,17 @@ async function releaseExpiredTokenBatch(i) {
9701
10226
  if (timer) clearTimeout(timer);
9702
10227
  }
9703
10228
  }
9704
- function read(key2) {
9705
- const value = typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
9706
- return value && settledRecords.get(key2) === value ? void 0 : value;
9707
- }
9708
- function storedBody(body) {
9709
- return JSON.stringify({
9710
- ...body,
9711
- transfers: body.transfers.map(({ to, amount }) => ({ to, amount }))
9712
- });
9713
- }
9714
- function save(key2, body) {
9715
- const value = storedBody(body);
9716
- if (typeof window === "undefined") memory.set(key2, value);
9717
- else window.localStorage.setItem(key2, value);
9718
- settledRecords.delete(key2);
9719
- }
10229
+ var read = tokenBatchRead;
10230
+ var storedBody = (body) => JSON.stringify(tokenBatchStoredBody(body));
9720
10231
  function clear(key2, body) {
9721
- try {
9722
- if (typeof window === "undefined") memory.delete(key2);
9723
- else window.localStorage.removeItem(key2);
9724
- settledRecords.delete(key2);
9725
- } catch {
9726
- settledRecords.set(key2, storedBody(body));
9727
- }
10232
+ tokenBatchClear(key2, storedBody(body));
9728
10233
  }
9729
10234
  function sponsorTokenBatch(i) {
9730
10235
  const key2 = pendingKey(i);
9731
10236
  const plan = planOf(i.transfers);
9732
10237
  const active2 = inflight.get(key2);
9733
10238
  if (active2) {
9734
- if (active2.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
10239
+ if (active2.plan !== plan)
9735
10240
  return Promise.reject(
9736
10241
  new Error(
9737
10242
  "A token deposit is already in progress. Wait for its result before depositing again."
@@ -9748,136 +10253,13 @@ async function execute(i, key2, plan) {
9748
10253
  (t) => BigInt(t.amount) <= 0n || BigInt(t.amount) >= 1n << 256n
9749
10254
  ) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
9750
10255
  throw new Error("Invalid token deposit shares.");
9751
- const send = async (initial) => {
9752
- let body = initial;
9753
- save(key2, body);
9754
- const rejectedPreparation = (error) => !body.serializedTransaction && error instanceof OwneyError && typeof error.details?.statusCode === "number" && error.details.statusCode >= 400 && error.details.statusCode < 500 && ![408, 429].includes(error.details.statusCode);
9755
- try {
9756
- const post = () => postSponsorBatchTransfer({ apiKey: i.apiKey, baseUrl: i.baseUrl, body });
9757
- const pendingError = (phase, txHash) => new OwneyError(
9758
- "DEPOSIT_IN_PROGRESS",
9759
- phase === "approval" ? "Deposit approval is awaiting confirmation. Retry the same amount to continue it." : "Deposit is awaiting confirmation. Its transaction is still being checked.",
9760
- { sponsorPending: true, phase, txHash, safeToFallback: false }
9761
- );
9762
- const assertReceipt = (receipt, phase) => {
9763
- if (receipt.status !== "success") throw new OwneyError(
9764
- "SPONSOR_REQUEST_FAILED",
9765
- `Sponsored ${phase} transaction reverted. Please retry.`,
9766
- { notSubmitted: true, safeToFallback: false }
9767
- );
9768
- };
9769
- const waitForConfirmation = async (phase, hash3) => {
9770
- let receipt;
9771
- try {
9772
- receipt = await i.pub.waitForTransactionReceipt({ hash: hash3, confirmations: 1, timeout: 9e4, checkReplacement: false });
9773
- } catch {
9774
- receipt = await i.pub.getTransactionReceipt({ hash: hash3 }).catch(() => void 0);
9775
- }
9776
- if (!receipt) throw pendingError(phase, hash3);
9777
- assertReceipt(receipt, phase);
9778
- };
9779
- const postSigned = async (phase, hash3) => {
9780
- try {
9781
- return await post();
9782
- } catch (error) {
9783
- if (rejectedPreparation(error) || error instanceof OwneyError && error.details?.notSubmitted === true) throw error;
9784
- await waitForConfirmation(phase, hash3);
9785
- return void 0;
9786
- }
9787
- };
9788
- if (body.erc2612Permit && !body.serializedPermitTransaction) {
9789
- const preparedPermit = await postSponsorBatchTransfer({
9790
- apiKey: i.apiKey,
9791
- baseUrl: i.baseUrl,
9792
- body
9793
- });
9794
- if (!preparedPermit.serializedPermitTransaction || !preparedPermit.permitTxHash || keccak2562(preparedPermit.serializedPermitTransaction) !== preparedPermit.permitTxHash)
9795
- throw new Error(
9796
- "Sponsorship API did not return a valid prepared permit transaction."
9797
- );
9798
- body = {
9799
- ...body,
9800
- serializedPermitTransaction: preparedPermit.serializedPermitTransaction
9801
- };
9802
- save(key2, body);
9803
- }
9804
- if (!body.serializedTransaction) {
9805
- const permitHash = body.serializedPermitTransaction ? keccak2562(body.serializedPermitTransaction) : void 0;
9806
- let prepared = permitHash ? await postSigned("approval", permitHash) : await post();
9807
- if (prepared?.status === "pending") {
9808
- if (!permitHash || prepared.phase !== "approval" || prepared.txHash !== permitHash)
9809
- throw new Error("Sponsorship approval does not match the pending transaction.");
9810
- await waitForConfirmation("approval", permitHash);
9811
- prepared = void 0;
9812
- }
9813
- if (!prepared) prepared = await post();
9814
- if (prepared.status === "pending" && permitHash && prepared.phase === "approval" && prepared.txHash === permitHash)
9815
- throw pendingError("approval", permitHash);
9816
- if (!prepared.serializedTransaction || keccak2562(prepared.serializedTransaction) !== prepared.txHash)
9817
- throw new Error(
9818
- "Sponsorship API did not return a valid prepared transaction."
9819
- );
9820
- body = {
9821
- ...body,
9822
- serializedTransaction: prepared.serializedTransaction
9823
- };
9824
- save(key2, body);
9825
- }
9826
- await i.onBeforeSubmission?.(keccak2562(body.serializedTransaction));
9827
- try {
9828
- i.onSubmitted?.(keccak2562(body.serializedTransaction));
9829
- } catch {
9830
- }
9831
- const hash2 = keccak2562(body.serializedTransaction);
9832
- const existing = await i.pub.getTransactionReceipt({ hash: hash2 }).catch(() => void 0);
9833
- if (existing) {
9834
- assertReceipt(existing, "deposit");
9835
- void post().catch(() => void 0);
9836
- clear(key2, body);
9837
- return hash2;
9838
- }
9839
- let result = await postSigned("deposit", hash2);
9840
- if (result?.status === "pending" && result.phase === "approval" && body.serializedPermitTransaction && result.txHash === keccak2562(body.serializedPermitTransaction)) {
9841
- await waitForConfirmation("approval", result.txHash);
9842
- result = await postSigned("deposit", hash2);
9843
- if (result?.status === "pending" && result.phase === "approval" && result.txHash === keccak2562(body.serializedPermitTransaction)) throw pendingError("approval", result.txHash);
9844
- }
9845
- if (result) {
9846
- if (result.txHash !== hash2 || result.status === "pending" && result.phase !== "deposit")
9847
- throw new Error("Sponsorship receipt does not match the pending transaction.");
9848
- if (result.status === "pending") await waitForConfirmation("deposit", hash2);
9849
- }
9850
- if (!result || result.status === "pending") void post().catch(() => void 0);
9851
- clear(key2, body);
9852
- return hash2;
9853
- } catch (error) {
9854
- if (initial.serializedTransaction && error instanceof OwneyError && error.code === "DEPOSIT_CHECKPOINT_FAILED") {
9855
- throw new OwneyError(
9856
- error.code,
9857
- "Could not save recovery of the existing deposit.",
9858
- { ...error.details, notSubmitted: false, safeToFallback: false }
9859
- );
9860
- }
9861
- if (!body.serializedTransaction && !body.serializedPermitTransaction || rejectedPreparation(error) || error instanceof OwneyError && error.details?.notSubmitted === true)
9862
- clear(key2, body);
9863
- throw error;
9864
- }
9865
- };
9866
- const saved = read(key2);
9867
- if (i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
9868
- throw new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit changed. Check its status before depositing again.");
10256
+ const send = (body) => executeTokenBatch(i, body);
10257
+ const family = await loadTokenBatchFamily(i, i.resumeTxHash);
10258
+ const saved = family ? JSON.stringify(family.body) : read(key2);
9869
10259
  if (saved) {
9870
10260
  const previous = JSON.parse(saved);
9871
10261
  if (previous.chainId !== i.chainId || !isAddressEqual2(previous.from, i.owner) || !isAddressEqual2(previous.token, i.token) || planOf(previous.transfers) !== plan)
9872
- throw new Error(
9873
- "Retry the previous token deposit and agent split first to reconcile its status."
9874
- );
9875
- if (await releaseExpiredTokenBatch({ ...i, expectedTxHash: previous.serializedTransaction ? keccak2562(previous.serializedTransaction) : void 0 }))
9876
- throw new OwneyError(
9877
- "SPONSOR_REQUEST_FAILED",
9878
- "The deposit authorization expired without transferring funds. Start a new deposit.",
9879
- { notSubmitted: true, safeToFallback: false }
9880
- );
10262
+ throw new OwneyError("DEPOSIT_IN_PROGRESS", "Retry the previous token deposit and agent split first to reconcile its status.");
9881
10263
  i.onApproved?.();
9882
10264
  return send({ ...previous, transfers: i.transfers });
9883
10265
  }
@@ -9983,8 +10365,9 @@ function makeSponsoredTokenCallback(deps) {
9983
10365
  pub,
9984
10366
  wallet,
9985
10367
  onApproved: deps.onApproved,
9986
- onSubmitted: (hash2) => deps.onSubmitted?.(hash2, transfers),
9987
- onBeforeSubmission: (hash2) => deps.onBeforeSubmission?.(hash2, transfers),
10368
+ onSubmitted: (hash2, reference) => deps.onSubmitted?.(hash2, transfers, reference),
10369
+ onBeforeSubmission: (hash2, reference) => deps.onBeforeSubmission?.(hash2, transfers, reference),
10370
+ assertCurrent: deps.assertCurrent,
9988
10371
  resumeTxHash: deps.resumeTxHash
9989
10372
  });
9990
10373
  };
@@ -10071,7 +10454,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
10071
10454
  }
10072
10455
 
10073
10456
  // src/lib/sponsored-calls-deposit.ts
10074
- import { encodeFunctionData as encodeFunctionData4, erc20Abi as erc20Abi6, toHex as toHex3 } from "viem";
10457
+ import { encodeFunctionData as encodeFunctionData5, erc20Abi as erc20Abi6, toHex as toHex3 } from "viem";
10075
10458
  var DEFAULT_POLL_INTERVAL_MS = 1500;
10076
10459
  var DEFAULT_MAX_POLLS = 30;
10077
10460
  async function paymasterSupported(provider, owner, chainId) {
@@ -10116,7 +10499,7 @@ function makeSponsoredCallsCallback(deps) {
10116
10499
  const calls = transfers.map((transfer) => ({
10117
10500
  to: token,
10118
10501
  value: "0x0",
10119
- data: encodeFunctionData4({
10502
+ data: encodeFunctionData5({
10120
10503
  abi: erc20Abi6,
10121
10504
  functionName: "transfer",
10122
10505
  args: [transfer.to, BigInt(transfer.amount)]
@@ -10321,6 +10704,7 @@ var OwneySDK = class {
10321
10704
  paymasterServiceUrl;
10322
10705
  cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
10323
10706
  agentReads = new AgentReads();
10707
+ depositFamilyReads = /* @__PURE__ */ new Map();
10324
10708
  recoveredFundingReads = /* @__PURE__ */ new Set();
10325
10709
  fundingBalanceChecks = /* @__PURE__ */ new WeakMap();
10326
10710
  activationChecks = /* @__PURE__ */ new Map();
@@ -10456,11 +10840,13 @@ var OwneySDK = class {
10456
10840
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
10457
10841
  }
10458
10842
  /** Builds the default USDC batch callback for the connected wallet. */
10459
- getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission) {
10460
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && this.cachedSponsoredCallback)
10843
+ getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent) {
10844
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && this.cachedSponsoredCallback)
10461
10845
  return this.cachedSponsoredCallback;
10462
10846
  const provider = this.requireConnectedProvider();
10463
- const owner = this.state.walletAddress;
10847
+ const connection = this.requireState();
10848
+ const owner = connection.walletAddress;
10849
+ const connectedChain = connection.chainId;
10464
10850
  const callback = makeSponsoredTokenCallback({
10465
10851
  apiKey: this.apiKey,
10466
10852
  baseUrl: this.routingApiBaseUrl,
@@ -10469,6 +10855,10 @@ var OwneySDK = class {
10469
10855
  onSubmitted,
10470
10856
  onBeforeSubmission,
10471
10857
  resumeTxHash,
10858
+ assertCurrent: () => {
10859
+ assertSubmissionCurrent?.();
10860
+ if (!this.state || this.state !== connection || this.state.chainId !== connectedChain || this.state.provider !== provider || this.state.walletAddress.toLowerCase() !== owner.toLowerCase()) throw new OwneyError("NOT_CONNECTED", "Wallet changed before deposit submission.");
10861
+ },
10472
10862
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
10473
10863
  // Casts work around viem's chain-narrowed Client vs the generic
10474
10864
  // PublicClient/WalletClient param types — structurally identical at
@@ -10484,7 +10874,7 @@ var OwneySDK = class {
10484
10874
  transport: custom5(provider)
10485
10875
  })
10486
10876
  });
10487
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission) this.cachedSponsoredCallback = callback;
10877
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent) this.cachedSponsoredCallback = callback;
10488
10878
  return callback;
10489
10879
  }
10490
10880
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
@@ -10513,11 +10903,13 @@ var OwneySDK = class {
10513
10903
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
10514
10904
  * single-use batch authorization instead of an EIP-3009 authorization.
10515
10905
  */
10516
- getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission) {
10517
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && this.cachedWethSponsoredCallback)
10906
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission, assertSubmissionCurrent) {
10907
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent && this.cachedWethSponsoredCallback)
10518
10908
  return this.cachedWethSponsoredCallback;
10519
10909
  const provider = this.requireConnectedProvider();
10520
- const owner = this.state.walletAddress;
10910
+ const connection = this.requireState();
10911
+ const owner = connection.walletAddress;
10912
+ const connectedChain = connection.chainId;
10521
10913
  const callback = makeSponsoredTokenCallback({
10522
10914
  apiKey: this.apiKey,
10523
10915
  baseUrl: this.routingApiBaseUrl,
@@ -10526,6 +10918,10 @@ var OwneySDK = class {
10526
10918
  onSubmitted,
10527
10919
  onBeforeSubmission,
10528
10920
  resumeTxHash,
10921
+ assertCurrent: () => {
10922
+ assertSubmissionCurrent?.();
10923
+ if (!this.state || this.state !== connection || this.state.chainId !== connectedChain || this.state.provider !== provider || this.state.walletAddress.toLowerCase() !== owner.toLowerCase()) throw new OwneyError("NOT_CONNECTED", "Wallet changed before deposit submission.");
10924
+ },
10529
10925
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
10530
10926
  // Casts work around viem's chain-narrowed Client vs the generic
10531
10927
  // PublicClient/WalletClient param types — structurally identical at
@@ -10541,7 +10937,7 @@ var OwneySDK = class {
10541
10937
  transport: custom5(provider)
10542
10938
  })
10543
10939
  });
10544
- if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission) this.cachedWethSponsoredCallback = callback;
10940
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && !assertSubmissionCurrent) this.cachedWethSponsoredCallback = callback;
10545
10941
  return callback;
10546
10942
  }
10547
10943
  getAgent(agentId) {
@@ -10865,10 +11261,10 @@ var OwneySDK = class {
10865
11261
  }
10866
11262
  notifyFunding(options.onProgress, evidence);
10867
11263
  } : void 0;
10868
- const onSubmitted = onProgress ? (hash2, destinations) => {
11264
+ const onSubmitted = onProgress ? (hash2, destinations, depositAuthorization) => {
10869
11265
  for (const destination of destinations) {
10870
11266
  const evidence = prepared.get(destination.to.toLowerCase());
10871
- if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash2, transfer: "pending" });
11267
+ if (evidence) notifyFunding(onProgress, { ...evidence, txHash: hash2, ...depositAuthorization ? { depositAuthorization } : {}, transfer: "pending" });
10872
11268
  }
10873
11269
  } : void 0;
10874
11270
  const onCallsProgress = onProgress ? (callsId, destinations, transfer) => {
@@ -10910,7 +11306,7 @@ var OwneySDK = class {
10910
11306
  });
10911
11307
  await checkpoint2(evidence);
10912
11308
  } : void 0;
10913
- const checkpointHash = checkpointDestinations ? (hash2, destinations) => checkpointDestinations(destinations, { txHash: hash2 }) : void 0;
11309
+ const checkpointHash = checkpointDestinations ? (hash2, destinations, depositAuthorization) => checkpointDestinations(destinations, { txHash: hash2, depositAuthorization }) : void 0;
10914
11310
  const checkpointCalls = checkpointDestinations ? (id, destinations) => checkpointDestinations(destinations, { callsId: id }) : void 0;
10915
11311
  const checkpointPaid = checkpoint2 ? (evidence) => checkpoint2([evidence]) : void 0;
10916
11312
  const customCallback = depositCallback && checkpointDestinations ? makeVerificationAwareDepositCallback(async (to, cid, value, verification) => {
@@ -10920,19 +11316,20 @@ var OwneySDK = class {
10920
11316
  }) : depositCallback;
10921
11317
  if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
10922
11318
  throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
10923
- const effectiveCallback = customCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress, checkpointCalls) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash));
11319
+ const effectiveCallback = customCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress, checkpointCalls) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash, options.assertSubmissionCurrent) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash, options.assertSubmissionCurrent));
10924
11320
  try {
10925
11321
  if (options.resumeTxHash) {
10926
11322
  const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
10927
- const pending = token && pendingTokenBatch({
11323
+ const family = token && await loadTokenBatchFamily({
10928
11324
  apiKey: this.apiKey,
10929
11325
  baseUrl: this.routingApiBaseUrl,
10930
11326
  owner: state.walletAddress,
10931
11327
  chainId,
10932
11328
  token: token.address
10933
- });
11329
+ }, options.resumeTxHash);
11330
+ const pending = family && { transfers: family.body.transfers };
10934
11331
  const invalid2 = () => new OwneyError("DEPOSIT_IN_PROGRESS", "The saved deposit does not match this request. Check its status before depositing again.");
10935
- if (!pending || pending.txHash.toLowerCase() !== options.resumeTxHash.toLowerCase() || pending.transfers.some((transfer) => !/^\d+$/.test(transfer.amount) || BigInt(transfer.amount) <= 0n) || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n) !== BigInt(amount)) throw invalid2();
11332
+ if (!pending || pending.transfers.some((transfer) => !/^\d+$/.test(transfer.amount) || BigInt(transfer.amount) <= 0n) || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n) !== BigInt(amount)) throw invalid2();
10936
11333
  const candidates = agentId ? [this.getAgent(agentId)] : this.getEligibleAgents(chainId, asset);
10937
11334
  const wallets = await Promise.all(candidates.map(async (agent) => {
10938
11335
  this.validateAssetSupport(agent, chainId, asset);
@@ -11391,8 +11788,46 @@ var OwneySDK = class {
11391
11788
  if (!token) throw new OwneyError("CHAIN_UNSUPPORTED", "Unsupported funding asset.");
11392
11789
  const rpc2 = this.getPaidRpcClient(chainId);
11393
11790
  let receipt;
11791
+ if (direction === "deposit" && !evidence.callsId && !evidence.userOpHash) {
11792
+ const scope = {
11793
+ apiKey: this.apiKey,
11794
+ baseUrl: this.routingApiBaseUrl,
11795
+ owner: state.walletAddress,
11796
+ chainId,
11797
+ token: token.address
11798
+ };
11799
+ const family = await loadTokenBatchFamily(scope, evidence.txHash, !evidence.depositAuthorization);
11800
+ assertCurrent();
11801
+ if (family) {
11802
+ if (evidence.depositAuthorization && evidence.depositAuthorization.authorizationId !== family.authorizationId || !family.body.transfers.some((t) => t.to.toLowerCase() === agentWallet.toLowerCase() && t.amount === evidence.intendedAmount))
11803
+ throw new OwneyError("DEPOSIT_IN_PROGRESS", "Deposit evidence does not match its signed authorization.");
11804
+ const familyKey = `${tokenBatchKey(scope)}:${family.authorizationId}`;
11805
+ let reading = this.depositFamilyReads.get(familyKey);
11806
+ if (!reading) {
11807
+ reading = observeTokenBatchFamily({ scope, family, pub: rpc2, deadlineAt: Date.now() + 8e3, assertCurrent }).finally(() => this.depositFamilyReads.delete(familyKey));
11808
+ this.depositFamilyReads.set(familyKey, reading);
11809
+ }
11810
+ const result = await reading;
11811
+ assertCurrent();
11812
+ evidence = { ...evidence, depositAuthorization: { authorizationId: family.authorizationId } };
11813
+ if (result.status === "confirmed") {
11814
+ receipt = result.receipt;
11815
+ evidence = { ...evidence, txHash: result.txHash };
11816
+ } else if (result.status === "expired" || result.status === "failed")
11817
+ return {
11818
+ ...evidence,
11819
+ agentWallet,
11820
+ transfer: "failed",
11821
+ delivery: "not-applicable",
11822
+ registration: "not-applicable",
11823
+ confirmedAmount: "0",
11824
+ blockNumber: (result.status === "expired" ? result.blockNumber : result.receipts.reduce((n, r) => r.blockNumber > n ? r.blockNumber : n, 0n)).toString()
11825
+ };
11826
+ else return { ...evidence, agentWallet, transfer: "unknown", delivery: "not-applicable" };
11827
+ }
11828
+ }
11394
11829
  try {
11395
- receipt = await rpc2.getTransactionReceipt({ hash: evidence.txHash });
11830
+ receipt ??= await rpc2.getTransactionReceipt({ hash: evidence.txHash });
11396
11831
  } catch (error) {
11397
11832
  if (direction === "deposit") {
11398
11833
  const expired = await releaseExpiredTokenBatch({
@@ -11512,29 +11947,65 @@ var OwneySDK = class {
11512
11947
  /** Whether a foreground deposit can resume the same signed batch, never a
11513
11948
  * fresh authorization. The batch rail independently rechecks the exact plan. */
11514
11949
  async canResumePendingDeposit(evidences, amount) {
11515
- if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
11516
- const first = evidences[0];
11517
- const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
11518
- const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
11519
- if (!token) return false;
11520
- const pending = pendingTokenBatch({
11521
- apiKey: this.apiKey,
11522
- baseUrl: this.routingApiBaseUrl,
11523
- owner: state.walletAddress,
11524
- chainId,
11525
- token: token.address
11526
- });
11527
- if (!pending || pending.transfers.length !== evidences.length || pending.transfers.reduce((sum, transfer) => sum + BigInt(transfer.amount), 0n).toString() !== amount) return false;
11528
- const destinations = /* @__PURE__ */ new Set();
11529
- for (const evidence of evidences) {
11530
- if (evidence.chainId !== chainId || evidence.token !== first.token || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.callsId || evidence.userOpHash || evidence.txHash?.toLowerCase() !== pending.txHash.toLowerCase()) return false;
11531
- const agent = this.getAgent(evidence.agentId);
11532
- const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, evidence.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
11533
- if (!wallet || destinations.has(wallet.toLowerCase()) || !pending.transfers.some((transfer) => transfer.to.toLowerCase() === wallet.toLowerCase() && transfer.amount === evidence.intendedAmount)) return false;
11534
- destinations.add(wallet.toLowerCase());
11950
+ try {
11951
+ if (this.paymasterServiceUrl || !evidences.length || !/^\d+$/.test(amount)) return false;
11952
+ const first = evidences[0];
11953
+ if (!first.txHash) return false;
11954
+ const { state, chainId, assertCurrent } = this.captureReadScope(first.chainId);
11955
+ const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === first.token);
11956
+ if (!token) return false;
11957
+ const family = await loadTokenBatchFamily({
11958
+ apiKey: this.apiKey,
11959
+ baseUrl: this.routingApiBaseUrl,
11960
+ owner: state.walletAddress,
11961
+ chainId,
11962
+ token: token.address
11963
+ }, first.txHash);
11964
+ const transfers = family?.body.transfers;
11965
+ if (!family || !transfers || transfers.length !== evidences.length || transfers.reduce((n, t) => n + BigInt(t.amount), 0n).toString() !== amount) return false;
11966
+ const destinations = /* @__PURE__ */ new Set();
11967
+ for (const e of evidences) {
11968
+ 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;
11969
+ const agent = this.getAgent(e.agentId);
11970
+ const wallet = agent.getFundingWallet ? await agent.getFundingWallet(state, chainId, e.token) : (await agent.getUserProfile(state, chainId)).smartWallet;
11971
+ assertCurrent();
11972
+ if (!wallet || destinations.has(wallet.toLowerCase()) || e.agentWallet && e.agentWallet.toLowerCase() !== wallet.toLowerCase() || !transfers.some((t) => t.to.toLowerCase() === wallet.toLowerCase() && t.amount === e.intendedAmount)) return false;
11973
+ destinations.add(wallet.toLowerCase());
11974
+ }
11975
+ assertCurrent();
11976
+ return true;
11977
+ } catch {
11978
+ return false;
11979
+ }
11980
+ }
11981
+ /** Validate signed journal membership, never just a caller-supplied family ID. */
11982
+ async validateDepositReplacement(previous, next) {
11983
+ try {
11984
+ if (!previous.length || previous.length !== next.length || previous.some((e) => e.transfer === "confirmed" || !/^\d+$/.test(e.intendedAmount))) return;
11985
+ const amount = previous.reduce((sum, e) => sum + BigInt(e.intendedAmount), 0n).toString();
11986
+ if (!await this.canResumePendingDeposit(previous, amount) || !await this.canResumePendingDeposit(next, amount)) return;
11987
+ const { state, chainId, assertCurrent } = this.captureReadScope(previous[0].chainId);
11988
+ const token = SupportedAssets.find((t) => t.chainId === chainId && t.symbol === previous[0].token);
11989
+ const family = await loadTokenBatchFamily({
11990
+ apiKey: this.apiKey,
11991
+ baseUrl: this.routingApiBaseUrl,
11992
+ owner: state.walletAddress,
11993
+ chainId,
11994
+ token: token.address
11995
+ }, previous[0].txHash);
11996
+ assertCurrent();
11997
+ if (!family || family.terminal) return;
11998
+ const predecessorTxHash = previous[0].txHash, replacementTxHash = next[0].txHash;
11999
+ const replacement = family.attempts.find((a) => a.phase === "deposit" && a.hash.toLowerCase() === replacementTxHash.toLowerCase());
12000
+ if (!replacement || replacement.predecessorHash?.toLowerCase() !== predecessorTxHash.toLowerCase()) return;
12001
+ for (const p of previous) {
12002
+ const matches = next.filter((n2) => n2.agentId === p.agentId), n = matches[0];
12003
+ if (matches.length !== 1 || p.txHash?.toLowerCase() !== predecessorTxHash.toLowerCase() || n.txHash?.toLowerCase() !== replacementTxHash.toLowerCase() || p.intendedAmount !== n.intendedAmount || p.agentWallet?.toLowerCase() !== n.agentWallet?.toLowerCase() || n.depositAuthorization?.authorizationId !== family.authorizationId || n.depositAuthorization.predecessorTxHash?.toLowerCase() !== predecessorTxHash.toLowerCase()) return;
12004
+ }
12005
+ return { authorizationId: family.authorizationId, predecessorTxHash, replacementTxHash };
12006
+ } catch {
12007
+ return;
11535
12008
  }
11536
- assertCurrent();
11537
- return true;
11538
12009
  }
11539
12010
  /** Optional post-payout display proof; receipt verification and the provider
11540
12011
  * adapter must agree before attaching it to this exact balance object. */
@@ -11686,7 +12157,7 @@ var OwneySDK = class {
11686
12157
  * quote's source token. A quote naming ETH would have all of them reading
11687
12158
  * the wrong asset.
11688
12159
  */
11689
- async wrapNativeSource(quote, walletAddress, onStage, direction) {
12160
+ async wrapNativeSource(quote, walletAddress, onStage, direction, assertCurrent) {
11690
12161
  const wrapper = requireWrapper(quote);
11691
12162
  const chainId = quote.src.chainId;
11692
12163
  const chain2 = swapChain(chainId);
@@ -11698,7 +12169,10 @@ var OwneySDK = class {
11698
12169
  const wallet = createWalletClient5({
11699
12170
  account: walletAddress,
11700
12171
  chain: chain2,
11701
- transport: custom5(provider)
12172
+ transport: custom5({ request: (args) => {
12173
+ assertCurrent?.();
12174
+ return provider.request(args);
12175
+ } })
11702
12176
  });
11703
12177
  onStage?.("wrapping");
11704
12178
  const { wrapped } = await wrapNative(
@@ -11719,6 +12193,7 @@ var OwneySDK = class {
11719
12193
  return fees.maxFeePerGas ?? publicClient.getGasPrice();
11720
12194
  },
11721
12195
  sendTransaction: async (tx, onSubmitted) => {
12196
+ assertCurrent?.();
11722
12197
  const hash2 = await wallet.sendTransaction({
11723
12198
  to: tx.to,
11724
12199
  data: tx.data,
@@ -12006,13 +12481,19 @@ var OwneySDK = class {
12006
12481
  * Native Base/Arbitrum USDC uses sponsored ERC-2612; configured paymaster
12007
12482
  * wallet calls also cover approval without spending the received tokens.
12008
12483
  */
12009
- async settleSwapDepositAmount(chainId, token, symbol2, received) {
12484
+ async settleSwapDepositAmount(chainId, token, symbol2, received, assertCurrent) {
12010
12485
  if (symbol2 !== "WETH" && symbol2 !== "USDC" || this.paymasterServiceUrl || supportsSponsoredErc2612(chainId, token)) {
12011
12486
  return received.toString();
12012
12487
  }
12013
12488
  const allowance = await this.readTokenPermit2Allowance(chainId, token);
12489
+ assertCurrent?.();
12014
12490
  if (allowance < received) {
12015
- await this.approvePermit2(symbol2, received, chainId);
12491
+ await this.approvePermit2(
12492
+ symbol2,
12493
+ received,
12494
+ chainId,
12495
+ ...assertCurrent ? [assertCurrent] : []
12496
+ );
12016
12497
  }
12017
12498
  const balance = await this.readTokenBalance(chainId, token);
12018
12499
  return (balance < received ? balance : received).toString();
@@ -12043,7 +12524,7 @@ var OwneySDK = class {
12043
12524
  * so the deposit half of `swapAndDeposit` can be exercised on its own.
12044
12525
  */
12045
12526
  runSwap(quote, options) {
12046
- return executeSwap(this.buildSwapDeps(quote), { quote, ...options });
12527
+ return executeSwap(this.buildSwapDeps(quote, options.assertCurrent), { quote, ...options });
12047
12528
  }
12048
12529
  /**
12049
12530
  * The half of the swap deps that only ever talks to the routing API.
@@ -12331,41 +12812,65 @@ var OwneySDK = class {
12331
12812
  */
12332
12813
  async swapAndDeposit(options) {
12333
12814
  const state = this.requireState();
12815
+ options.assertCurrent?.();
12816
+ if (options.agentId) {
12817
+ this.validateAssetSupport(
12818
+ this.getAgent(options.agentId),
12819
+ options.to.chainId,
12820
+ options.to.symbol
12821
+ );
12822
+ }
12334
12823
  const api = this.swapApi();
12335
12824
  const quoted = await api.quote({
12336
12825
  from: options.from,
12337
12826
  to: options.to,
12338
12827
  walletAddress: state.walletAddress
12339
12828
  });
12829
+ options.assertCurrent?.();
12340
12830
  const quote = quoted.requiresOnchainOrder ? await this.wrapNativeSource(
12341
12831
  quoted,
12342
12832
  state.walletAddress,
12343
- options.onSwapProgress
12833
+ options.onSwapProgress,
12834
+ void 0,
12835
+ options.assertCurrent
12344
12836
  ) : quoted;
12837
+ options.assertCurrent?.();
12345
12838
  await this.ensureSwapChain(quote.src.chainId);
12839
+ options.assertCurrent?.();
12346
12840
  const swap = await this.runSwap(quote, {
12347
12841
  walletAddress: state.walletAddress,
12842
+ ...options.assertCurrent ? { assertCurrent: options.assertCurrent } : {},
12348
12843
  ...options.slippage === void 0 ? {} : { slippage: options.slippage },
12349
12844
  ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
12350
12845
  });
12351
12846
  options.onSwapProgress?.("depositing");
12352
12847
  try {
12848
+ options.assertCurrent?.();
12353
12849
  await this.ensureSwapChain(quote.dst.chainId);
12850
+ options.assertCurrent?.();
12354
12851
  await this.walletSeesBalance(
12355
12852
  quote.dst.chainId,
12356
12853
  quote.dst.address,
12357
12854
  BigInt(swap.received)
12358
12855
  );
12856
+ options.assertCurrent?.();
12359
12857
  const amount = await this.settleSwapDepositAmount(
12360
12858
  quote.dst.chainId,
12361
12859
  quote.dst.address,
12362
12860
  options.to.symbol,
12363
- BigInt(swap.received)
12861
+ BigInt(swap.received),
12862
+ options.assertCurrent
12364
12863
  );
12864
+ options.assertCurrent?.();
12865
+ await options.onBeforeDeposit?.();
12866
+ options.assertCurrent?.();
12365
12867
  const deposit = await this.deposit({
12366
12868
  amount,
12367
12869
  asset: options.to.symbol,
12368
- ...options.agentId ? { agentId: options.agentId } : {}
12870
+ ...options.agentId ? { agentId: options.agentId } : {},
12871
+ ...options.onDepositProgress ? { onProgress: options.onDepositProgress } : {},
12872
+ ...options.onBeforeDepositSubmission ? { onBeforeSubmission: options.onBeforeDepositSubmission } : {},
12873
+ ...options.assertCurrent ? { assertSubmissionCurrent: options.assertCurrent } : {}
12369
12874
  });
12370
12875
  return { swap, deposit, deposited: amount };
12371
12876
  } catch (error) {
@@ -13682,7 +14187,7 @@ var OwneySDK = class {
13682
14187
  * @param expectedChainId Chain captured by the deposit that requested approval.
13683
14188
  * @returns the approval transaction hash.
13684
14189
  */
13685
- async approvePermit2(asset = "WETH", requiredAmount = 0n, expectedChainId) {
14190
+ async approvePermit2(asset = "WETH", requiredAmount = 0n, expectedChainId, assertCurrent) {
13686
14191
  const state = this.requireState();
13687
14192
  const chainId = expectedChainId ?? this.requireChainId();
13688
14193
  this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
@@ -13703,9 +14208,13 @@ var OwneySDK = class {
13703
14208
  const wallet = createWalletClient5({
13704
14209
  account: state.walletAddress,
13705
14210
  chain: VIEM_CHAIN2[chainId],
13706
- transport: custom5(provider)
14211
+ transport: custom5({ request: (args) => {
14212
+ assertCurrent?.();
14213
+ return provider.request(args);
14214
+ } })
13707
14215
  });
13708
14216
  await ensureWalletOnChain(walletChainClient, wallet, chainId);
14217
+ assertCurrent?.();
13709
14218
  const hash2 = await wallet.writeContract({
13710
14219
  address: token,
13711
14220
  abi: ERC20_ALLOWANCE_ABI,
@@ -13879,7 +14388,7 @@ var OwneySDK = class {
13879
14388
  };
13880
14389
 
13881
14390
  // src/agents/zyfai/zyfai.siwx.ts
13882
- import { getAddress as getAddress6 } from "viem";
14391
+ import { getAddress as getAddress7 } from "viem";
13883
14392
  import { SiweMessage as SiweMessage2 } from "siwe";
13884
14393
  import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
13885
14394
 
@@ -14006,7 +14515,7 @@ function buildSIWXConfig(deps) {
14006
14515
  issuedAt,
14007
14516
  toString() {
14008
14517
  return new SiweMessage2({
14009
- address: getAddress6(accountAddress),
14518
+ address: getAddress7(accountAddress),
14010
14519
  chainId: numericChainId(chainId),
14011
14520
  domain,
14012
14521
  uri,