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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -39,8 +39,216 @@ __export(index_exports, {
39
39
  });
40
40
  module.exports = __toCommonJS(index_exports);
41
41
 
42
- // src/lib/funding-evidence.ts
42
+ // src/errors.ts
43
+ var OwneyError = class extends Error {
44
+ code;
45
+ details;
46
+ agentId;
47
+ constructor(code, message, details, agentId) {
48
+ const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
49
+ super(`${prefix} ${message}`);
50
+ this.name = "OwneyError";
51
+ this.code = code;
52
+ this.details = details;
53
+ this.agentId = agentId;
54
+ }
55
+ };
56
+ var AgentNotFoundError = class extends OwneyError {
57
+ constructor(agentId, available) {
58
+ super(
59
+ "AGENT_NOT_FOUND",
60
+ `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
61
+ { agentId, available },
62
+ agentId
63
+ );
64
+ this.name = "AgentNotFoundError";
65
+ }
66
+ };
67
+ var NotConnectedError = class extends OwneyError {
68
+ constructor() {
69
+ super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
70
+ this.name = "NotConnectedError";
71
+ }
72
+ };
73
+ var AgentChainIncompatibleError = class extends OwneyError {
74
+ incompatibleAgents;
75
+ connectedChainId;
76
+ constructor(incompatibleAgents, connectedChainId) {
77
+ const details = incompatibleAgents.map(
78
+ ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
79
+ ).join("; ");
80
+ super(
81
+ "AGENT_CHAIN_INCOMPATIBLE",
82
+ `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
83
+ { incompatibleAgents, connectedChainId }
84
+ );
85
+ this.name = "AgentChainIncompatibleError";
86
+ this.incompatibleAgents = incompatibleAgents;
87
+ this.connectedChainId = connectedChainId;
88
+ }
89
+ };
90
+
91
+ // src/lib/swap/swap.observation.ts
92
+ var active = /* @__PURE__ */ new Set();
93
+ var isOrderObserved = (key2) => active.has(key2);
94
+ async function observeOrder(key2, run) {
95
+ const busy = () => new OwneyError(
96
+ "SWAP_REQUEST_FAILED",
97
+ "This order is already being observed.",
98
+ { orderHash: key2, watchActive: true, settlementUnconfirmed: true }
99
+ );
100
+ if (active.has(key2)) throw busy();
101
+ active.add(key2);
102
+ try {
103
+ if (typeof navigator !== "undefined" && navigator.locks)
104
+ return await navigator.locks.request(`owney.swap.observe:${key2}`, { ifAvailable: true }, (lock) => {
105
+ if (!lock) throw busy();
106
+ return run();
107
+ });
108
+ return await run();
109
+ } finally {
110
+ active.delete(key2);
111
+ }
112
+ }
113
+
114
+ // src/lib/swap/swap.deadline.ts
115
+ async function bounded(work, timeoutMs, error) {
116
+ if (timeoutMs <= 0) throw error();
117
+ let timer;
118
+ try {
119
+ return await Promise.race([
120
+ Promise.resolve().then(work),
121
+ new Promise((_, reject) => {
122
+ timer = setTimeout(() => reject(error()), timeoutMs);
123
+ })
124
+ ]);
125
+ } finally {
126
+ clearTimeout(timer);
127
+ }
128
+ }
129
+ async function mapConcurrent(items, run, concurrency = 3) {
130
+ const results = new Array(items.length);
131
+ let next = 0;
132
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, async () => {
133
+ for (; ; ) {
134
+ const index = next++;
135
+ if (index >= items.length) return;
136
+ results[index] = await run(items[index]);
137
+ }
138
+ }));
139
+ return results;
140
+ }
141
+
142
+ // src/lib/permit2-batch.ts
43
143
  var import_viem = require("viem");
144
+ var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
145
+ var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
146
+ var ARBITRUM_USDC_ADDRESS = "0xaf88d065e77c8cc2239327c5edb3a432268e5831";
147
+ function supportsSponsoredErc2612(chainId, token) {
148
+ const expected = chainId === 8453 ? BASE_USDC_ADDRESS : chainId === 42161 ? ARBITRUM_USDC_ADDRESS : void 0;
149
+ return expected !== void 0 && token.toLowerCase() === expected.toLowerCase();
150
+ }
151
+ var ERC2612_PERMIT_TYPES = {
152
+ Permit: [
153
+ { name: "owner", type: "address" },
154
+ { name: "spender", type: "address" },
155
+ { name: "value", type: "uint256" },
156
+ { name: "nonce", type: "uint256" },
157
+ { name: "deadline", type: "uint256" }
158
+ ]
159
+ };
160
+ var ERC2612_READ_ABI = (0, import_viem.parseAbi)([
161
+ "function name() view returns (string)",
162
+ "function version() view returns (string)",
163
+ "function nonces(address owner) view returns (uint256)"
164
+ ]);
165
+ function erc2612TypedData(input) {
166
+ return {
167
+ domain: {
168
+ name: input.permit.tokenName,
169
+ version: input.permit.tokenVersion,
170
+ chainId: input.chainId,
171
+ verifyingContract: input.token
172
+ },
173
+ types: ERC2612_PERMIT_TYPES,
174
+ primaryType: "Permit",
175
+ message: {
176
+ owner: input.owner,
177
+ spender: BATCH_PERMIT2_ADDRESS,
178
+ value: BigInt(input.permit.value),
179
+ nonce: BigInt(input.permit.nonce),
180
+ deadline: BigInt(input.permit.deadline)
181
+ }
182
+ };
183
+ }
184
+ var PERMIT_BATCH_TYPES = {
185
+ PermitBatchWitnessTransferFrom: [
186
+ { name: "permitted", type: "TokenPermissions[]" },
187
+ { name: "spender", type: "address" },
188
+ { name: "nonce", type: "uint256" },
189
+ { name: "deadline", type: "uint256" },
190
+ { name: "witness", type: "Deposit" }
191
+ ],
192
+ Deposit: [{ name: "recipients", type: "address[]" }],
193
+ TokenPermissions: [
194
+ { name: "token", type: "address" },
195
+ { name: "amount", type: "uint256" }
196
+ ]
197
+ };
198
+ var PERMIT2_BATCH_ABI = (0, import_viem.parseAbi)([
199
+ "struct TokenPermissions { address token; uint256 amount; }",
200
+ "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
201
+ "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
202
+ "function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
203
+ "function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
204
+ ]);
205
+ function batchPermit(b) {
206
+ return {
207
+ permitted: b.transfers.map((t) => ({
208
+ token: b.token,
209
+ amount: BigInt(t.amount)
210
+ })),
211
+ nonce: BigInt(b.nonce),
212
+ deadline: BigInt(b.deadline)
213
+ };
214
+ }
215
+ function batchTypedData(b, spender) {
216
+ return {
217
+ domain: {
218
+ name: "Permit2",
219
+ chainId: b.chainId,
220
+ verifyingContract: BATCH_PERMIT2_ADDRESS
221
+ },
222
+ types: PERMIT_BATCH_TYPES,
223
+ primaryType: "PermitBatchWitnessTransferFrom",
224
+ message: {
225
+ ...batchPermit(b),
226
+ spender,
227
+ witness: { recipients: b.transfers.map((t) => t.to) }
228
+ }
229
+ };
230
+ }
231
+ var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
232
+ function batchWitness(b) {
233
+ return (0, import_viem.hashStruct)({
234
+ types: PERMIT_BATCH_TYPES,
235
+ primaryType: "Deposit",
236
+ data: { recipients: b.transfers.map((t) => t.to) }
237
+ });
238
+ }
239
+ function batchArgs(b) {
240
+ return [
241
+ batchPermit(b),
242
+ b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
243
+ b.from,
244
+ batchWitness(b),
245
+ BATCH_WITNESS_TYPE,
246
+ b.signature
247
+ ];
248
+ }
249
+
250
+ // src/lib/funding-evidence.ts
251
+ var import_viem2 = require("viem");
44
252
  var import_account_abstraction = require("viem/account-abstraction");
45
253
  function notifyFunding(callback, evidence) {
46
254
  try {
@@ -49,9 +257,9 @@ function notifyFunding(callback, evidence) {
49
257
  } catch {
50
258
  }
51
259
  }
52
- var fullWithdrawalAbi = (0, import_viem.parseAbi)(["function withdrawAllAssetToUser(address recipient, address asset)"]);
53
- var withdrawalEventAbi = (0, import_viem.parseAbi)(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
54
- var accountExecutionAbi = (0, import_viem.parseAbi)([
260
+ var fullWithdrawalAbi = (0, import_viem2.parseAbi)(["function withdrawAllAssetToUser(address recipient, address asset)"]);
261
+ var withdrawalEventAbi = (0, import_viem2.parseAbi)(["event WithdrewTokenToUser(address indexed owner, address indexed recipient, address indexed token, uint256 amount)"]);
262
+ var accountExecutionAbi = (0, import_viem2.parseAbi)([
55
263
  "function execute(address target,uint256 value,bytes data)",
56
264
  "function executeBatch((address target,uint256 value,bytes data)[] calls)"
57
265
  ]);
@@ -61,7 +269,7 @@ function yieldseekerExecution(receipt, evidence) {
61
269
  const operationEvents = receipt.logs.flatMap((log) => {
62
270
  if (![entryPoint06, entryPoint07].includes(log.address.toLowerCase())) return [];
63
271
  try {
64
- const event = (0, import_viem.decodeEventLog)({ abi: import_account_abstraction.entryPoint06Abi, data: log.data, topics: log.topics });
272
+ const event = (0, import_viem2.decodeEventLog)({ abi: import_account_abstraction.entryPoint06Abi, data: log.data, topics: log.topics });
65
273
  return event.eventName === "UserOperationEvent" ? [{ ...event.args, logIndex: log.logIndex }] : [];
66
274
  } catch {
67
275
  return [];
@@ -84,7 +292,7 @@ function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receip
84
292
  if (evidence.agentId !== "yieldseeker" || transaction.chainId !== evidence.chainId || transaction.hash.toLowerCase() !== evidence.txHash?.toLowerCase() || !evidence.agentWallet) return false;
85
293
  const matchesFullCall = (data) => {
86
294
  try {
87
- const decoded = (0, import_viem.decodeFunctionData)({ abi: fullWithdrawalAbi, data });
295
+ const decoded = (0, import_viem2.decodeFunctionData)({ abi: fullWithdrawalAbi, data });
88
296
  return decoded.args[0].toLowerCase() === evidence.walletAddress.toLowerCase() && decoded.args[1].toLowerCase() === tokenAddress.toLowerCase();
89
297
  } catch {
90
298
  return false;
@@ -95,7 +303,7 @@ function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receip
95
303
  const address = transaction.to?.toLowerCase();
96
304
  if (address !== entryPoint06 && address !== entryPoint07) return false;
97
305
  const version = address === entryPoint06 ? "0.6" : "0.7";
98
- const decoded = (0, import_viem.decodeFunctionData)({ abi: version === "0.6" ? import_account_abstraction.entryPoint06Abi : import_account_abstraction.entryPoint07Abi, data: transaction.input });
306
+ const decoded = (0, import_viem2.decodeFunctionData)({ abi: version === "0.6" ? import_account_abstraction.entryPoint06Abi : import_account_abstraction.entryPoint07Abi, data: transaction.input });
99
307
  if (decoded.functionName !== "handleOps") return false;
100
308
  const operations = decoded.args[0].filter((operation2) => [evidence.agentWallet.toLowerCase(), evidence.walletAddress.toLowerCase()].includes(operation2.sender.toLowerCase()));
101
309
  if (operations.length !== 1) return false;
@@ -111,14 +319,14 @@ function isFullYieldseekerWithdrawal(transaction, evidence, tokenAddress, receip
111
319
  if (!receipt?.logs.some((log) => {
112
320
  if (log.address.toLowerCase() !== address) return false;
113
321
  try {
114
- const event = (0, import_viem.decodeEventLog)({ abi: import_account_abstraction.entryPoint06Abi, data: log.data, topics: log.topics });
322
+ const event = (0, import_viem2.decodeEventLog)({ abi: import_account_abstraction.entryPoint06Abi, data: log.data, topics: log.topics });
115
323
  return event.eventName === "UserOperationEvent" && event.args.userOpHash.toLowerCase() === hash2.toLowerCase() && event.args.sender.toLowerCase() === operation.sender.toLowerCase() && event.args.success;
116
324
  } catch {
117
325
  return false;
118
326
  }
119
327
  })) return false;
120
328
  if (operation.sender.toLowerCase() === evidence.agentWallet.toLowerCase()) return matchesFullCall(operation.callData);
121
- const execution = (0, import_viem.decodeFunctionData)({ abi: accountExecutionAbi, data: operation.callData });
329
+ const execution = (0, import_viem2.decodeFunctionData)({ abi: accountExecutionAbi, data: operation.callData });
122
330
  const calls = execution.functionName === "execute" ? [{ target: execution.args[0], value: execution.args[1], data: execution.args[2] }] : execution.args[0];
123
331
  return calls.length === 1 && calls[0].value === 0n && calls[0].target.toLowerCase() === evidence.agentWallet.toLowerCase() && matchesFullCall(calls[0].data);
124
332
  } catch {
@@ -161,7 +369,7 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction, optio
161
369
  if (seen.has(log.logIndex)) continue;
162
370
  seen.add(log.logIndex);
163
371
  try {
164
- const event = (0, import_viem.decodeEventLog)({ abi: import_viem.erc20Abi, data: log.data, topics: log.topics });
372
+ const event = (0, import_viem2.decodeEventLog)({ abi: import_viem2.erc20Abi, data: log.data, topics: log.topics });
165
373
  if (event.eventName !== "Transfer") continue;
166
374
  const { from, to, value } = event.args;
167
375
  if (direction === "withdraw" && value === 0n && from.toLowerCase() === agent && to.toLowerCase() === owner)
@@ -185,7 +393,7 @@ function inspectFundingReceipt(receipt, evidence, tokenAddress, direction, optio
185
393
  const emptyPayout = direction === "withdraw" && evidence.agentId === "yieldseeker" && amount === 0n && zeroTransferLogs.length > 0 && execution.logs.some((log) => {
186
394
  if (log.address.toLowerCase() !== agent) return false;
187
395
  try {
188
- const { args } = (0, import_viem.decodeEventLog)({ abi: withdrawalEventAbi, data: log.data, topics: log.topics });
396
+ const { args } = (0, import_viem2.decodeEventLog)({ abi: withdrawalEventAbi, data: log.data, topics: log.topics });
189
397
  return args.owner.toLowerCase() === owner && args.recipient.toLowerCase() === owner && args.token.toLowerCase() === tokenAddress.toLowerCase() && args.amount === 0n;
190
398
  } catch {
191
399
  return false;
@@ -244,57 +452,8 @@ function registerDepositBatch(callback, transfer) {
244
452
  batchCallbacks.set(callback, transfer);
245
453
  }
246
454
 
247
- // src/errors.ts
248
- var OwneyError = class extends Error {
249
- code;
250
- details;
251
- agentId;
252
- constructor(code, message, details, agentId) {
253
- const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
254
- super(`${prefix} ${message}`);
255
- this.name = "OwneyError";
256
- this.code = code;
257
- this.details = details;
258
- this.agentId = agentId;
259
- }
260
- };
261
- var AgentNotFoundError = class extends OwneyError {
262
- constructor(agentId, available) {
263
- super(
264
- "AGENT_NOT_FOUND",
265
- `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
266
- { agentId, available },
267
- agentId
268
- );
269
- this.name = "AgentNotFoundError";
270
- }
271
- };
272
- var NotConnectedError = class extends OwneyError {
273
- constructor() {
274
- super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
275
- this.name = "NotConnectedError";
276
- }
277
- };
278
- var AgentChainIncompatibleError = class extends OwneyError {
279
- incompatibleAgents;
280
- connectedChainId;
281
- constructor(incompatibleAgents, connectedChainId) {
282
- const details = incompatibleAgents.map(
283
- ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
284
- ).join("; ");
285
- super(
286
- "AGENT_CHAIN_INCOMPATIBLE",
287
- `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
288
- { incompatibleAgents, connectedChainId }
289
- );
290
- this.name = "AgentChainIncompatibleError";
291
- this.incompatibleAgents = incompatibleAgents;
292
- this.connectedChainId = connectedChainId;
293
- }
294
- };
295
-
296
455
  // src/lib/transfer-auth.ts
297
- var import_viem2 = require("viem");
456
+ var import_viem3 = require("viem");
298
457
 
299
458
  // src/lib/sponsor-client.ts
300
459
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
@@ -410,7 +569,7 @@ async function postSponsorBatchTransfer(input) {
410
569
  }
411
570
 
412
571
  // src/lib/permit2.ts
413
- var import_viem3 = require("viem");
572
+ var import_viem4 = require("viem");
414
573
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
415
574
  var MAX_UINT256 = 2n ** 256n - 1n;
416
575
  function permit2ApprovalAmount(requiredAmount) {
@@ -451,7 +610,7 @@ var ERC20_ALLOWANCE_ABI = [
451
610
  function randomPermit2Nonce() {
452
611
  const bytes = new Uint8Array(32);
453
612
  globalThis.crypto.getRandomValues(bytes);
454
- return BigInt((0, import_viem3.bytesToHex)(bytes));
613
+ return BigInt((0, import_viem4.bytesToHex)(bytes));
455
614
  }
456
615
  async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
457
616
  return publicClient.readContract({
@@ -559,21 +718,21 @@ function rateLimitDelay(error, now = Date.now()) {
559
718
  }
560
719
  if (typeof value !== "object" || seen.has(value)) return;
561
720
  seen.add(value);
562
- const record = value;
563
- if ([record.status, record.statusCode, record.code].some(
721
+ const record2 = value;
722
+ if ([record2.status, record2.statusCode, record2.code].some(
564
723
  (code) => String(code) === "429"
565
724
  )) {
566
725
  limited = true;
567
726
  }
568
727
  for (const key2 of ["retryAfterSeconds", "retryAfter"]) {
569
- const seconds = Number(record[key2]);
728
+ const seconds = Number(record2[key2]);
570
729
  if (Number.isFinite(seconds) && seconds > 0)
571
730
  delay = Math.max(delay, seconds * 1e3);
572
731
  }
573
- const retryAt = Number(record.retryAt);
732
+ const retryAt = Number(record2.retryAt);
574
733
  if (Number.isFinite(retryAt) && retryAt > now)
575
734
  delay = Math.max(delay, retryAt - now);
576
- const headers = record.headers;
735
+ const headers = record2.headers;
577
736
  const header = typeof headers?.get === "function" ? headers.get("Retry-After") : headers?.["retry-after"] ?? headers?.["Retry-After"];
578
737
  if (typeof header === "string" || typeof header === "number") {
579
738
  const seconds = Number(header);
@@ -589,10 +748,10 @@ function rateLimitDelay(error, now = Date.now()) {
589
748
  "data",
590
749
  "fields"
591
750
  ]) {
592
- visit(record[key2], depth + 1);
751
+ visit(record2[key2], depth + 1);
593
752
  }
594
753
  for (const key2 of ["agentErrors", "failures"]) {
595
- const entries = record[key2];
754
+ const entries = record2[key2];
596
755
  if (entries && typeof entries === "object") {
597
756
  for (const entry of Object.values(entries)) visit(entry, depth + 1);
598
757
  }
@@ -610,7 +769,7 @@ function rateLimitDiagnostics(error) {
610
769
  if (depth > 6 || !value || typeof value !== "object" || seen.has(value))
611
770
  return;
612
771
  seen.add(value);
613
- const record = value;
772
+ const record2 = value;
614
773
  for (const key2 of [
615
774
  "rpcSource",
616
775
  "chainId",
@@ -618,12 +777,12 @@ function rateLimitDiagnostics(error) {
618
777
  "providerRequestId",
619
778
  "statusCode"
620
779
  ]) {
621
- const candidate = record[key2];
780
+ const candidate = record2[key2];
622
781
  if (candidate !== void 0 && details[key2] === void 0)
623
782
  details = { ...details, [key2]: candidate };
624
783
  }
625
784
  for (const key2 of ["details", "cause", "response", "fields", "error"])
626
- visit(record[key2], depth + 1);
785
+ visit(record2[key2], depth + 1);
627
786
  }
628
787
  visit(error);
629
788
  return {
@@ -691,12 +850,19 @@ var AgentReads = class {
691
850
  };
692
851
 
693
852
  // src/agents/zyfai/zyfai.withdraw-submission.ts
853
+ var BELOW_MINIMUM = /^Idle balance (\d+(?:\.\d+)?) (USDC|WETH|ETH|EURC) is below the minimum withdrawal of (\d+(?:\.\d+)?) \2\.?$/;
694
854
  var listenersByClient = /* @__PURE__ */ new WeakMap();
695
855
  function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
696
856
  const client = sdk.httpClient;
697
857
  if (!client || typeof client.get !== "function" || typeof client.post !== "function")
698
- return { attempted: () => void 0, rejected: () => false, request: () => void 0, stop: () => {
699
- } };
858
+ return {
859
+ attempted: () => void 0,
860
+ rejected: () => false,
861
+ request: () => void 0,
862
+ belowMinimum: () => void 0,
863
+ stop: () => {
864
+ }
865
+ };
700
866
  let listeners2 = listenersByClient.get(client);
701
867
  if (!listeners2) {
702
868
  listeners2 = /* @__PURE__ */ new Set();
@@ -716,6 +882,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
716
882
  method: method === "get" ? "GET" : "POST",
717
883
  path: method === "get" ? "/users/withdraw" : "/users/partial-withdraw"
718
884
  };
885
+ let belowMinimum2;
719
886
  const notify2 = (outcome) => {
720
887
  if (outcome === "rejected" && matching.length !== 1) return;
721
888
  const evidence = matching.length === 1 ? { ...request3 } : {
@@ -726,7 +893,7 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
726
893
  };
727
894
  for (const observer of matching) {
728
895
  try {
729
- observer.notify(outcome, evidence);
896
+ observer.notify(outcome, evidence, outcome === "rejected" ? belowMinimum2 : void 0);
730
897
  } catch {
731
898
  }
732
899
  }
@@ -751,10 +918,12 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
751
918
  return await original.apply(this, callArgs);
752
919
  } catch (error) {
753
920
  const message = error instanceof Error ? error.message : "";
921
+ const minimum = BELOW_MINIMUM.exec(message);
922
+ if (minimum) belowMinimum2 = { symbol: minimum[2], idle: minimum[1], minimum: minimum[3] };
754
923
  const status = error?.response?.status;
755
924
  if (request3.httpStatus === void 0 && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
756
925
  request3.httpStatus = status;
757
- request3.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : request3.httpStatus === 400 && message === "No balance to withdraw" ? "insufficient-balance" : /^Idle balance \d+(?:\.\d+)? (USDC|WETH|ETH|EURC) is below the minimum withdrawal of \d+(?:\.\d+)? \1\.?$/.test(message) ? "insufficient-balance" : /^User doesn't have any active session key\.?$/.test(message) ? "session-key-missing" : method === "post" && request3.httpStatus === 400 && message === "Partial withdrawal already in progress" ? "withdrawal-in-progress" : message === "Network error: Unable to reach the server" ? "network" : "other";
926
+ request3.failure = message === "Unauthorized: Invalid API key" ? "unauthorized" : message === "Forbidden: Access denied" ? "forbidden" : message === "Rate limit exceeded. Please try again later." ? "rate-limited" : request3.httpStatus === 400 && message === "No balance to withdraw" ? "insufficient-balance" : minimum ? "insufficient-balance" : /^User doesn't have any active session key\.?$/.test(message) ? "session-key-missing" : method === "post" && request3.httpStatus === 400 && message === "Partial withdrawal already in progress" ? "withdrawal-in-progress" : message === "Network error: Unable to reach the server" ? "network" : "other";
758
927
  if (["unauthorized", "forbidden", "rate-limited", "insufficient-balance", "session-key-missing", "withdrawal-in-progress"].includes(request3.failure) && (request3.httpStatus === void 0 || request3.httpStatus >= 400 && request3.httpStatus < 500)) notify2("rejected");
759
928
  throw error;
760
929
  } finally {
@@ -767,32 +936,45 @@ function observeWithdrawalSubmission(sdk, chainId, token, assertCurrent) {
767
936
  let submitted = false;
768
937
  let rejected = false;
769
938
  let request2 = { attempted: false };
770
- const listener = { chainId, token, assertCurrent, notify: (outcome, evidence) => {
939
+ let belowMinimum;
940
+ const listener = { chainId, token, assertCurrent, notify: (outcome, evidence, detail) => {
771
941
  request2 = evidence;
772
942
  if (outcome === "attempted") submitted = true;
773
- else if (outcome === "rejected") rejected = true;
943
+ else if (outcome === "rejected") {
944
+ rejected = true;
945
+ belowMinimum = detail;
946
+ }
774
947
  } };
775
948
  listeners2.add(listener);
776
- return { attempted: () => submitted, rejected: () => rejected, request: () => ({ ...request2 }), stop: () => {
777
- listeners2.delete(listener);
778
- } };
949
+ return {
950
+ attempted: () => submitted,
951
+ rejected: () => rejected,
952
+ request: () => ({ ...request2 }),
953
+ belowMinimum: () => belowMinimum && { ...belowMinimum },
954
+ stop: () => {
955
+ listeners2.delete(listener);
956
+ }
957
+ };
779
958
  }
780
959
 
781
960
  // src/agents/zyfai/zyfai.withdraw-empty.ts
782
- function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
783
- const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
961
+ var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
962
+ var symbol = (value) => typeof value === "string" ? value.toUpperCase() === "ETH" ? "WETH" : value.toUpperCase() : void 0;
963
+ var chain = (value) => {
964
+ if (typeof value === "number") return value;
965
+ if (typeof value !== "string") return void 0;
966
+ return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
967
+ };
968
+ var zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
969
+ var units = (value) => typeof value === "string" && /^(?:0x[0-9a-f]+|\d+)$/i.test(value) ? BigInt(value) : void 0;
970
+ function snapshotPortfolio(raw2) {
784
971
  const response = record(raw2);
785
972
  const portfolio = record(response?.portfolio);
786
973
  const chains = record(portfolio?.portfolioByChain);
787
- if (response?.success === false || !chains || !Array.isArray(portfolio?.positions) || !Array.isArray(portfolio.staleBalances)) return false;
788
- if (portfolio.pendingAsyncWithdrawals !== void 0 && (!Array.isArray(portfolio.pendingAsyncWithdrawals) || portfolio.pendingAsyncWithdrawals.length > 0)) return false;
789
- const symbol = (value) => typeof value === "string" ? value.toUpperCase() === "ETH" ? "WETH" : value.toUpperCase() : void 0;
790
- const chain = (value) => {
791
- if (typeof value === "number") return value;
792
- if (typeof value !== "string") return void 0;
793
- return /^\d+$/.test(value) ? Number(value) : { base: 8453, arbitrum: 42161, ethereum: 1, mainnet: 1 }[value.trim().toLowerCase()];
794
- };
795
- const zero = (value) => typeof value === "string" && /^(?:0x)?0+$/i.test(value);
974
+ if (response?.success === false || !portfolio || !chains || !Array.isArray(portfolio.positions) || !Array.isArray(portfolio.staleBalances)) return void 0;
975
+ return { portfolio, chains, entries: [...portfolio.positions, ...portfolio.staleBalances] };
976
+ }
977
+ function chainBalancesMatch(chains, chainId, token, accept) {
796
978
  for (const [id, assets] of Object.entries(chains)) {
797
979
  const idNumber = chain(id);
798
980
  if (idNumber === void 0) return false;
@@ -802,10 +984,13 @@ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
802
984
  for (const [asset, value] of Object.entries(entries)) {
803
985
  if (symbol(asset) !== token) continue;
804
986
  const balance = record(value);
805
- if (!balance || !zero(balance.balance)) return false;
987
+ if (!balance || !accept(balance)) return false;
806
988
  }
807
989
  }
808
- for (const value of [...portfolio.positions, ...portfolio.staleBalances]) {
990
+ return true;
991
+ }
992
+ function positionsEmpty(entries, chainId, token) {
993
+ for (const value of entries) {
809
994
  const entry = record(value);
810
995
  if (!entry) return false;
811
996
  const id = chain(entry.chainId ?? entry.chain);
@@ -817,9 +1002,26 @@ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
817
1002
  }
818
1003
  return true;
819
1004
  }
1005
+ function hasEmptyWithdrawalSnapshot(raw2, chainId, token) {
1006
+ const snapshot = snapshotPortfolio(raw2);
1007
+ if (!snapshot) return false;
1008
+ const pending = snapshot.portfolio.pendingAsyncWithdrawals;
1009
+ if (pending !== void 0 && (!Array.isArray(pending) || pending.length > 0)) return false;
1010
+ return chainBalancesMatch(snapshot.chains, chainId, token, (balance) => zero(balance.balance)) && positionsEmpty(snapshot.entries, chainId, token);
1011
+ }
1012
+ function hasIdleOnlyWithdrawalSnapshot(raw2, chainId, token, decimals, idle) {
1013
+ const snapshot = snapshotPortfolio(raw2);
1014
+ if (!snapshot) return false;
1015
+ const pending = snapshot.portfolio.pendingAsyncWithdrawals;
1016
+ if (pending !== void 0 && (!Array.isArray(pending) || pending.some((value) => record(value)?.status !== "CLAIMED"))) return false;
1017
+ return chainBalancesMatch(snapshot.chains, chainId, token, (balance) => {
1018
+ const amount = units(balance.balance);
1019
+ return amount !== void 0 && amount <= idle && (balance.decimals === void 0 || balance.decimals === decimals);
1020
+ }) && positionsEmpty(snapshot.entries, chainId, token);
1021
+ }
820
1022
 
821
1023
  // src/agents/zyfai/zyfai.deposit.ts
822
- var import_viem4 = require("viem");
1024
+ var import_viem5 = require("viem");
823
1025
 
824
1026
  // src/types/config.ts
825
1027
  var P_SUPPORTED_CHAINS = {
@@ -869,20 +1071,20 @@ var SupportedAssets = [
869
1071
  ];
870
1072
 
871
1073
  // src/agents/zyfai/zyfai.deposit.ts
872
- async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWallet, assertCurrent, onBeforeSubmission }) {
1074
+ async function submitZyfaiDeposit({ sdk, state, chain: chain2, amount, asset, smartWallet, assertCurrent, onBeforeSubmission }) {
873
1075
  const owner = state.walletAddress;
874
1076
  const provider = state.provider;
875
- const token = SupportedAssets.find((value) => value.chainId === chain.id && value.symbol === asset);
876
- if (!token || !(0, import_viem4.isAddress)(owner) || !(0, import_viem4.isAddress)(smartWallet) || !/^\d+$/.test(amount) || BigInt(amount) <= 0n)
877
- throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId: chain.id, asset }, "zyfai");
1077
+ const token = SupportedAssets.find((value) => value.chainId === chain2.id && value.symbol === asset);
1078
+ if (!token || !(0, import_viem5.isAddress)(owner) || !(0, import_viem5.isAddress)(smartWallet) || !/^\d+$/.test(amount) || BigInt(amount) <= 0n)
1079
+ throw new OwneyError("AGENT_INVALID_RESPONSE", "Invalid Zyfai deposit transfer.", { chainId: chain2.id, asset }, "zyfai");
878
1080
  assertCurrent();
879
- const prepared = await sdk.prepareEnterPosition({ userAddress: owner, chainId: chain.id, amount, asset, strategy: "aggressive" });
1081
+ const prepared = await sdk.prepareEnterPosition({ userAddress: owner, chainId: chain2.id, amount, asset, strategy: "aggressive" });
880
1082
  assertCurrent();
881
- const expectedData = (0, import_viem4.encodeFunctionData)({ abi: import_viem4.erc20Abi, functionName: "transfer", args: [smartWallet, BigInt(amount)] });
1083
+ const expectedData = (0, import_viem5.encodeFunctionData)({ abi: import_viem5.erc20Abi, functionName: "transfer", args: [smartWallet, BigInt(amount)] });
882
1084
  const transfer = prepared?.transfer;
883
1085
  if (!transfer || transfer.safeAddress?.toLowerCase() !== smartWallet.toLowerCase() || transfer.tokenAddress?.toLowerCase() !== token.address.toLowerCase() || transfer.to?.toLowerCase() !== token.address.toLowerCase() || transfer.data?.toLowerCase() !== expectedData.toLowerCase() || transfer.value !== "0")
884
- throw new OwneyError("AGENT_INVALID_RESPONSE", "Zyfai prepared a different deposit transfer.", { chainId: chain.id, asset }, "zyfai");
885
- const wallet = (0, import_viem4.createWalletClient)({ account: owner, chain, transport: (0, import_viem4.custom)({
1086
+ throw new OwneyError("AGENT_INVALID_RESPONSE", "Zyfai prepared a different deposit transfer.", { chainId: chain2.id, asset }, "zyfai");
1087
+ const wallet = (0, import_viem5.createWalletClient)({ account: owner, chain: chain2, transport: (0, import_viem5.custom)({
886
1088
  request: async (args) => {
887
1089
  assertCurrent();
888
1090
  if (args.method === "eth_sendTransaction") {
@@ -891,7 +1093,7 @@ async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWalle
891
1093
  provider.request({ method: "eth_chainId" })
892
1094
  ]);
893
1095
  assertCurrent();
894
- if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase() || Number(chainId) !== chain.id)
1096
+ if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase() || Number(chainId) !== chain2.id)
895
1097
  throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai deposit.", void 0, "zyfai");
896
1098
  await onBeforeSubmission?.();
897
1099
  assertCurrent();
@@ -899,12 +1101,12 @@ async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWalle
899
1101
  return provider.request(args);
900
1102
  }
901
1103
  }, { retryCount: 0 }) });
902
- return wallet.sendTransaction({ account: owner, chain, to: token.address, data: expectedData, value: 0n });
1104
+ return wallet.sendTransaction({ account: owner, chain: chain2, to: token.address, data: expectedData, value: 0n });
903
1105
  }
904
1106
 
905
1107
  // src/agents/zyfai/zyfai.agent.ts
906
1108
  var import_sdk = require("@zyfai/sdk");
907
- var import_viem7 = require("viem");
1109
+ var import_viem8 = require("viem");
908
1110
  var import_chains = require("viem/chains");
909
1111
 
910
1112
  // src/lib/debug.ts
@@ -955,13 +1157,13 @@ function assetAliases(asset) {
955
1157
  const normalised = asset.toLowerCase();
956
1158
  return normalised === "weth" ? ["weth", "eth"] : [normalised];
957
1159
  }
958
- function matchesAsset(symbol, asset) {
959
- return assetAliases(asset).includes(symbol.toLowerCase());
1160
+ function matchesAsset(symbol2, asset) {
1161
+ return assetAliases(asset).includes(symbol2.toLowerCase());
960
1162
  }
961
1163
 
962
1164
  // src/agents/zyfai/zyfai.mapper.ts
963
- var import_viem5 = require("viem");
964
- var exactHexAmount = (raw2, decimals) => (0, import_viem5.formatUnits)(BigInt(/^0x/i.test(raw2) ? raw2 : `0x${raw2}`), decimals);
1165
+ var import_viem6 = require("viem");
1166
+ var exactHexAmount = (raw2, decimals) => (0, import_viem6.formatUnits)(BigInt(/^0x/i.test(raw2) ? raw2 : `0x${raw2}`), decimals);
965
1167
  var DECIMALS_BY_SYMBOL = {
966
1168
  usdc: 6,
967
1169
  weth: 18,
@@ -970,10 +1172,10 @@ var DECIMALS_BY_SYMBOL = {
970
1172
  wbtc: 8,
971
1173
  cbbtc: 8
972
1174
  };
973
- function tokenDecimals(symbol, explicit) {
1175
+ function tokenDecimals(symbol2, explicit) {
974
1176
  if (typeof explicit === "number" && Number.isFinite(explicit))
975
1177
  return explicit;
976
- return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
1178
+ return DECIMALS_BY_SYMBOL[(symbol2 ?? "").toLowerCase()] ?? 6;
977
1179
  }
978
1180
  function mapWithdraw(raw2) {
979
1181
  return {
@@ -993,10 +1195,10 @@ var NAME_TO_CHAIN_ID = Object.entries(
993
1195
  acc[name.toLowerCase()] = Number(id);
994
1196
  return acc;
995
1197
  }, {});
996
- function resolveChainId(chain) {
997
- const asNum = Number(chain);
1198
+ function resolveChainId(chain2) {
1199
+ const asNum = Number(chain2);
998
1200
  if (Number.isFinite(asNum) && asNum > 0) return asNum;
999
- return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
1201
+ return NAME_TO_CHAIN_ID[chain2.trim().toLowerCase()] ?? NaN;
1000
1202
  }
1001
1203
  function mapPendingAllocations(raw2) {
1002
1204
  if (!Array.isArray(raw2)) return void 0;
@@ -1036,16 +1238,16 @@ function mapBalances(raw2, _chainId, smartWallet) {
1036
1238
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
1037
1239
  const positionChainId = Number(chainIdKey);
1038
1240
  for (const [
1039
- symbol,
1241
+ symbol2,
1040
1242
  { balance, balanceWithFee, decimals }
1041
1243
  ] of Object.entries(tokensBySymbol ?? {})) {
1042
1244
  if (balanceWithFee === void 0 && balance === void 0) throw new Error("Zyfai asset balance is unavailable.");
1043
- const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol, decimals));
1245
+ const amount = exactHexAmount(balanceWithFee ?? balance, tokenDecimals(symbol2, decimals));
1044
1246
  totalBalance += Number(amount);
1045
1247
  tokens.push({
1046
1248
  chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
1047
1249
  chainId: positionChainId,
1048
- asset: symbol,
1250
+ asset: symbol2,
1049
1251
  amount
1050
1252
  });
1051
1253
  }
@@ -1060,7 +1262,7 @@ function mapBalances(raw2, _chainId, smartWallet) {
1060
1262
  // legacy `amount` fallback is hex. Runtime
1061
1263
  // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
1062
1264
  // when present, otherwise fall back to the per-symbol lookup.
1063
- amount: p.underlyingAmountWithFee ?? p.underlyingAmount ? (0, import_viem5.formatUnits)(
1265
+ amount: p.underlyingAmountWithFee ?? p.underlyingAmount ? (0, import_viem6.formatUnits)(
1064
1266
  BigInt(p.underlyingAmountWithFee ?? p.underlyingAmount ?? "0"),
1065
1267
  tokenDecimals(
1066
1268
  p.token_symbol,
@@ -1113,12 +1315,12 @@ function mapEarnings(raw2, smartWallet) {
1113
1315
  totalEarningsByChain
1114
1316
  )) {
1115
1317
  const positionChainId = Number(chainIdKey);
1116
- for (const [symbol, earnings] of Object.entries(tokensBySymbol ?? {})) {
1318
+ for (const [symbol2, earnings] of Object.entries(tokensBySymbol ?? {})) {
1117
1319
  const amount = Number(earnings);
1118
1320
  tokens.push({
1119
1321
  chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
1120
1322
  chainId: positionChainId,
1121
- asset: symbol,
1323
+ asset: symbol2,
1122
1324
  amount: String(amount)
1123
1325
  });
1124
1326
  }
@@ -1138,8 +1340,8 @@ function mapWeightedApyByChain(raw2) {
1138
1340
  const chainId = Number(chainKey);
1139
1341
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
1140
1342
  const perAsset = {};
1141
- for (const [symbol, apy] of Object.entries(tokenApy)) {
1142
- const token = symbol;
1343
+ for (const [symbol2, apy] of Object.entries(tokenApy)) {
1344
+ const token = symbol2;
1143
1345
  if (!SUPPORTED_TOKENS.includes(token)) continue;
1144
1346
  perAsset[token] = apy;
1145
1347
  }
@@ -1370,8 +1572,8 @@ function computeAllocationApy(positions) {
1370
1572
  function earningsForToken(bucket, chainId, asset) {
1371
1573
  const tokens = bucket?.[String(chainId)];
1372
1574
  if (!tokens) return null;
1373
- for (const [symbol, value] of Object.entries(tokens)) {
1374
- if (!matchesAsset(symbol, asset)) continue;
1575
+ for (const [symbol2, value] of Object.entries(tokens)) {
1576
+ if (!matchesAsset(symbol2, asset)) continue;
1375
1577
  const amount = Number(value);
1376
1578
  return Number.isFinite(amount) ? amount : null;
1377
1579
  }
@@ -1388,8 +1590,8 @@ function assetsInSnapshot(entry, chainId) {
1388
1590
  entry.current_earnings_by_token,
1389
1591
  entry.total_earnings_by_token
1390
1592
  ]) {
1391
- for (const symbol of Object.keys(bucket?.[key2] ?? {})) {
1392
- seen.add(symbol.toUpperCase());
1593
+ for (const symbol2 of Object.keys(bucket?.[key2] ?? {})) {
1594
+ seen.add(symbol2.toUpperCase());
1393
1595
  }
1394
1596
  }
1395
1597
  return [...seen];
@@ -1724,7 +1926,7 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
1724
1926
  }
1725
1927
 
1726
1928
  // src/lib/paid-rpc.ts
1727
- var import_viem6 = require("viem");
1929
+ var import_viem7 = require("viem");
1728
1930
  var PAID_RPC_RETRY_COUNT = 3;
1729
1931
  var PAID_RPC_RETRY_DELAY_MS = 1e3;
1730
1932
  function configuredRpcProxyBaseUrl(baseUrl) {
@@ -1755,14 +1957,14 @@ function resolveRpcUrls(rpcUrls, apiKey = "", baseUrl) {
1755
1957
  42161: resolveRpcUrl(rpcUrls, 42161, apiKey, baseUrl)
1756
1958
  };
1757
1959
  }
1758
- function createPaidRpcClient(chain, rpcUrls) {
1960
+ function createPaidRpcClient(chain2, rpcUrls) {
1759
1961
  const url = resolveRpcUrl(
1760
1962
  rpcUrls,
1761
- chain.id
1963
+ chain2.id
1762
1964
  );
1763
- return (0, import_viem6.createPublicClient)({
1764
- chain,
1765
- transport: (0, import_viem6.http)(url, {
1965
+ return (0, import_viem7.createPublicClient)({
1966
+ chain: chain2,
1967
+ transport: (0, import_viem7.http)(url, {
1766
1968
  retryCount: PAID_RPC_RETRY_COUNT,
1767
1969
  retryDelay: PAID_RPC_RETRY_DELAY_MS
1768
1970
  })
@@ -1776,8 +1978,8 @@ function headerValue(error, name) {
1776
1978
  return;
1777
1979
  if (seen.has(candidate)) return;
1778
1980
  seen.add(candidate);
1779
- const record = candidate;
1780
- const headers = record.headers;
1981
+ const record2 = candidate;
1982
+ const headers = record2.headers;
1781
1983
  let found;
1782
1984
  if (typeof headers?.get === "function") {
1783
1985
  found = headers.get(name);
@@ -1790,7 +1992,7 @@ function headerValue(error, name) {
1790
1992
  return;
1791
1993
  }
1792
1994
  for (const key2 of ["cause", "details", "response", "error"])
1793
- visit(record[key2], depth + 1);
1995
+ visit(record2[key2], depth + 1);
1794
1996
  }
1795
1997
  visit(error);
1796
1998
  return value;
@@ -1803,16 +2005,16 @@ function statusCode(error) {
1803
2005
  return;
1804
2006
  if (seen.has(candidate)) return;
1805
2007
  seen.add(candidate);
1806
- const record = candidate;
2008
+ const record2 = candidate;
1807
2009
  for (const key2 of ["status", "statusCode"]) {
1808
- const parsed = Number(record[key2]);
2010
+ const parsed = Number(record2[key2]);
1809
2011
  if (Number.isInteger(parsed) && parsed >= 100 && parsed <= 599) {
1810
2012
  status = parsed;
1811
2013
  return;
1812
2014
  }
1813
2015
  }
1814
2016
  for (const key2 of ["cause", "details", "response", "error"])
1815
- visit(record[key2], depth + 1);
2017
+ visit(record2[key2], depth + 1);
1816
2018
  }
1817
2019
  visit(error);
1818
2020
  return status;
@@ -1859,7 +2061,7 @@ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
1859
2061
  }
1860
2062
 
1861
2063
  // src/agents/zyfai/zyfai.agent.ts
1862
- var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem7.parseAbi)([
2064
+ var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem8.parseAbi)([
1863
2065
  "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
1864
2066
  ]);
1865
2067
  var WETH_ADDRESS_BY_CHAIN = {
@@ -1872,6 +2074,17 @@ var SPLITTING_ASSETS = ["USDC", "WETH"];
1872
2074
  var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
1873
2075
  var EARNINGS_REFRESH_COOLDOWN_MS = 60 * 60 * 1e3;
1874
2076
  var WITHDRAW_BALANCE_TIMEOUT_MS = 2e3;
2077
+ function belowMinimumCeiling(rejection, token, decimals) {
2078
+ if ((rejection.symbol === "ETH" ? "WETH" : rejection.symbol) !== token) return void 0;
2079
+ const ceiling = (display) => {
2080
+ const places = display.split(".")[1]?.length ?? 0;
2081
+ if (places >= decimals) return void 0;
2082
+ return (0, import_viem8.parseUnits)(display, decimals) + 5n * 10n ** BigInt(decimals - places - 1);
2083
+ };
2084
+ const idle = ceiling(rejection.idle);
2085
+ const minimum = ceiling(rejection.minimum);
2086
+ return idle === void 0 || minimum === void 0 ? void 0 : idle < minimum ? idle : minimum;
2087
+ }
1875
2088
  var ZyfaiAgent = class _ZyfaiAgent {
1876
2089
  constructor(apiKey, rpcUrls, referralSource) {
1877
2090
  this.apiKey = apiKey;
@@ -2026,7 +2239,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2026
2239
  }, asset.address, entry.action === "Withdraw" ? "withdraw" : "deposit");
2027
2240
  if (evidence.transfer === "confirmed" && evidence.confirmedAmount) {
2028
2241
  Object.assign(tx, {
2029
- amount: (0, import_viem7.formatUnits)(BigInt(evidence.confirmedAmount), asset.decimals),
2242
+ amount: (0, import_viem8.formatUnits)(BigInt(evidence.confirmedAmount), asset.decimals),
2030
2243
  amountRaw: evidence.confirmedAmount,
2031
2244
  blockNumber: evidence.blockNumber,
2032
2245
  logIndexes: evidence.logIndexes,
@@ -2828,8 +3041,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
2828
3041
  // --- IAgent: Fund operations ---
2829
3042
  /** Only after a definitive endpoint rejection, combine a new provider
2830
3043
  * snapshot with the Safe's exact idle balance. Never parse its rounded error
2831
- * amount as proof of zero, or use this to skip sending a new MAX request. */
2832
- async assertNotEmptyRejectedWithdrawal(state, chainId, token, owner) {
3044
+ * amount as proof of zero, or use this to skip sending a new MAX request.
3045
+ * After Zyfai's minimum-withdrawal rejection, exact idle-only dust that fits
3046
+ * within the message's rounded amounts is reported as not withdrawable. */
3047
+ async assertNotEmptyRejectedWithdrawal(state, chainId, token, owner, belowMinimum) {
2833
3048
  const internals = this.sdk;
2834
3049
  const generation = this.earningsGeneration;
2835
3050
  let expired = false;
@@ -2853,29 +3068,38 @@ var ZyfaiAgent = class _ZyfaiAgent {
2853
3068
  this.sdk.getPortfolio(owner).catch(() => void 0)
2854
3069
  ]);
2855
3070
  assertScope();
2856
- if (!(0, import_viem7.isAddress)(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
3071
+ if (!(0, import_viem8.isAddress)(wallet.address) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase())
2857
3072
  throw new OwneyError(
2858
3073
  "AGENT_AUTH_FAILED",
2859
3074
  "Could not verify Zyfai's withdrawal wallet.",
2860
3075
  { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2861
3076
  this.id
2862
3077
  );
2863
- if (hasEmptyWithdrawalSnapshot(portfolio, chainId, token)) {
2864
- const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
2865
- const idle = asset ? await this.getPublicClient(chainId).readContract({
2866
- address: asset.address,
2867
- abi: import_viem7.erc20Abi,
2868
- functionName: "balanceOf",
2869
- args: [wallet.address]
2870
- }).catch(() => void 0) : void 0;
2871
- assertScope();
2872
- if (idle === 0n) throw new OwneyError(
3078
+ const asset = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === token);
3079
+ if (!asset || portfolio === void 0) return;
3080
+ const empty = hasEmptyWithdrawalSnapshot(portfolio, chainId, token);
3081
+ const ceiling = belowMinimum && belowMinimumCeiling(belowMinimum, token, asset.decimals);
3082
+ if (!empty && ceiling === void 0) return;
3083
+ const idle = await this.getPublicClient(chainId).readContract({
3084
+ address: asset.address,
3085
+ abi: import_viem8.erc20Abi,
3086
+ functionName: "balanceOf",
3087
+ args: [wallet.address]
3088
+ }).catch(() => void 0);
3089
+ assertScope();
3090
+ if (idle === 0n && empty) throw new OwneyError(
3091
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3092
+ "No funds remain in Zyfai for this asset.",
3093
+ { available: "0", chainId, token },
3094
+ this.id
3095
+ );
3096
+ if (typeof idle === "bigint" && idle > 0n && ceiling !== void 0 && idle < ceiling && hasIdleOnlyWithdrawalSnapshot(portfolio, chainId, token, asset.decimals, idle))
3097
+ throw new OwneyError(
2873
3098
  "WITHDRAW_INSUFFICIENT_BALANCE",
2874
- "No funds remain in Zyfai for this asset.",
2875
- { available: "0", chainId, token },
3099
+ "The remaining Zyfai balance is below Zyfai's minimum withdrawal.",
3100
+ { available: "0", chainId, token, reason: "ZYFAI_BELOW_MINIMUM_WITHDRAWAL", remainingRaw: idle.toString() },
2876
3101
  this.id
2877
3102
  );
2878
- }
2879
3103
  };
2880
3104
  let timer;
2881
3105
  try {
@@ -2926,7 +3150,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2926
3150
  throw unavailable();
2927
3151
  }
2928
3152
  }
2929
- async withdraw(state, chainId, token, amount, onProgress) {
3153
+ async withdraw(state, chainId, token, amount, onProgress, _gasMode, _onBeforeSubmission, assertOperation) {
2930
3154
  if (amount !== void 0 && (typeof amount !== "string" || !/^[0-9]+$/.test(amount) || BigInt(amount) <= 0n))
2931
3155
  throw new OwneyError("WITHDRAW_INVALID_AMOUNT", "Withdrawal amount must be a positive integer in token base units.", void 0, this.id);
2932
3156
  const validChainId = isValidChainId(chainId);
@@ -2962,7 +3186,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
2962
3186
  throw staleSessionError;
2963
3187
  }
2964
3188
  };
2965
- let observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertCurrent);
3189
+ const assertSubmission = () => {
3190
+ assertCurrent();
3191
+ assertOperation?.();
3192
+ };
3193
+ let observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
2966
3194
  const authToken = internals.httpClient?.authToken;
2967
3195
  let raw2;
2968
3196
  try {
@@ -2981,7 +3209,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2981
3209
  }
2982
3210
  if (!amount && observation.rejected() && observation.request()?.failure === "insufficient-balance") {
2983
3211
  try {
2984
- await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner);
3212
+ await this.assertNotEmptyRejectedWithdrawal(state, validChainId, token, owner, observation.belowMinimum());
2985
3213
  } catch (proofError) {
2986
3214
  if (proofError instanceof OwneyError && (proofError.code === "WITHDRAW_INSUFFICIENT_BALANCE" || proofError.code === "NOT_CONNECTED")) throw proofError;
2987
3215
  }
@@ -2994,9 +3222,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
2994
3222
  { reason: "ZYFAI_SESSION_UNAVAILABLE", phase: "prepare", chainId, token },
2995
3223
  this.id
2996
3224
  );
3225
+ assertSubmission();
2997
3226
  await this.recoverWithdrawalSession(state, validChainId, token, owner);
2998
3227
  observation.stop();
2999
- observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertCurrent);
3228
+ observation = observeWithdrawalSubmission(executionSdk, validChainId, token, assertSubmission);
3000
3229
  }
3001
3230
  }
3002
3231
  if (!raw2) throw new OwneyError(
@@ -3111,13 +3340,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
3111
3340
  this.sdk.getSmartWalletByEOA(owner)
3112
3341
  ]);
3113
3342
  assertScope();
3114
- if (!(0, import_viem7.isAddress)(wallet.address) || wallet.isDeployed !== false && (wallet.isDeployed !== true || wallet.isOwner !== true) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase()) throw unavailable();
3343
+ if (!(0, import_viem8.isAddress)(wallet.address) || wallet.isDeployed !== false && (wallet.isDeployed !== true || wallet.isOwner !== true) || !registered.smartWallet || registered.smartWallet.toLowerCase() !== wallet.address.toLowerCase()) throw unavailable();
3115
3344
  const client = this.getPublicClient(validChainId);
3116
3345
  const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
3117
3346
  assertScope();
3118
3347
  const idle = await client.readContract({
3119
3348
  address: asset.address,
3120
- abi: import_viem7.erc20Abi,
3349
+ abi: import_viem8.erc20Abi,
3121
3350
  functionName: "balanceOf",
3122
3351
  args: [wallet.address],
3123
3352
  blockNumber
@@ -3174,7 +3403,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
3174
3403
  if (blockNumber < BigInt(evidence.blockNumber)) return void 0;
3175
3404
  const idle = await client.readContract({
3176
3405
  address: asset.address,
3177
- abi: import_viem7.erc20Abi,
3406
+ abi: import_viem8.erc20Abi,
3178
3407
  functionName: "balanceOf",
3179
3408
  args: [balance.smartWallet],
3180
3409
  blockNumber
@@ -3379,12 +3608,12 @@ var ZyfaiAgent = class _ZyfaiAgent {
3379
3608
  };
3380
3609
 
3381
3610
  // src/agents/yieldseeker/yieldseeker.agent.ts
3382
- var import_viem13 = require("viem");
3611
+ var import_viem14 = require("viem");
3383
3612
  var import_chains4 = require("viem/chains");
3384
3613
 
3385
3614
  // src/agents/yieldseeker/yieldseeker.auth.ts
3386
3615
  var import_siwe = require("siwe");
3387
- var import_viem8 = require("viem");
3616
+ var import_viem9 = require("viem");
3388
3617
  var import_chains2 = require("viem/chains");
3389
3618
 
3390
3619
  // src/agents/yieldseeker/yieldseeker.auth-cache.ts
@@ -3499,7 +3728,7 @@ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
3499
3728
  return new import_siwe.SiweMessage({
3500
3729
  scheme: url.protocol.slice(0, -1),
3501
3730
  domain: url.host,
3502
- address: (0, import_viem8.getAddress)(address),
3731
+ address: (0, import_viem9.getAddress)(address),
3503
3732
  uri: url.origin,
3504
3733
  version: "1",
3505
3734
  chainId,
@@ -3524,6 +3753,11 @@ var YieldseekerAuth = class {
3524
3753
  key(state, chainId) {
3525
3754
  return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
3526
3755
  }
3756
+ /** Observation must never open a sign-in prompt. */
3757
+ peekToken(state, chainId) {
3758
+ const token = this.tokens.get(this.key(state, chainId)) ?? readYieldseekerSession(state.walletAddress, chainId);
3759
+ return token && this.isReusable(token, state, chainId) ? token : void 0;
3760
+ }
3527
3761
  async getToken(state, chainId) {
3528
3762
  const key2 = this.key(state, chainId);
3529
3763
  const scope = { address: state.walletAddress, chainId };
@@ -3594,15 +3828,15 @@ var YieldseekerAuth = class {
3594
3828
  clearYieldseekerSession(state.walletAddress, chainId);
3595
3829
  }
3596
3830
  async sign(state, chainId) {
3597
- const account = (0, import_viem8.getAddress)(state.walletAddress);
3598
- const publicClient = (0, import_viem8.createPublicClient)({
3831
+ const account = (0, import_viem9.getAddress)(state.walletAddress);
3832
+ const publicClient = (0, import_viem9.createPublicClient)({
3599
3833
  chain: import_chains2.base,
3600
- transport: (0, import_viem8.custom)(state.provider)
3834
+ transport: (0, import_viem9.custom)(state.provider)
3601
3835
  });
3602
- const walletClient = (0, import_viem8.createWalletClient)({
3836
+ const walletClient = (0, import_viem9.createWalletClient)({
3603
3837
  account,
3604
3838
  chain: import_chains2.base,
3605
- transport: (0, import_viem8.custom)(state.provider)
3839
+ transport: (0, import_viem9.custom)(state.provider)
3606
3840
  });
3607
3841
  await ensureWalletOnChain(
3608
3842
  publicClient,
@@ -3708,10 +3942,10 @@ var YieldseekerApiError = class extends Error {
3708
3942
  };
3709
3943
  function providerError(body, fallback2) {
3710
3944
  if (!body || typeof body !== "object") return { code: fallback2 };
3711
- const record = body;
3945
+ const record2 = body;
3712
3946
  return {
3713
- code: typeof record.message === "string" ? record.message : fallback2,
3714
- fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
3947
+ code: typeof record2.message === "string" ? record2.message : fallback2,
3948
+ fields: record2.fields && typeof record2.fields === "object" ? record2.fields : void 0
3715
3949
  };
3716
3950
  }
3717
3951
  var YieldseekerApiClient = class {
@@ -3769,7 +4003,7 @@ var YieldseekerApiClient = class {
3769
4003
  };
3770
4004
 
3771
4005
  // src/agents/yieldseeker/yieldseeker.mapper.ts
3772
- var import_viem9 = require("viem");
4006
+ var import_viem10 = require("viem");
3773
4007
 
3774
4008
  // src/lib/helpers/snapshot-apy.ts
3775
4009
  var DAY_MS = 864e5;
@@ -3836,7 +4070,7 @@ function raw(value, endpoint) {
3836
4070
  return BigInt(value);
3837
4071
  }
3838
4072
  function decimal(value, decimals, endpoint) {
3839
- return (0, import_viem9.formatUnits)(raw(value, endpoint), decimals);
4073
+ return (0, import_viem10.formatUnits)(raw(value, endpoint), decimals);
3840
4074
  }
3841
4075
  function balanceRaw(value, endpoint) {
3842
4076
  const amount = raw(value, endpoint);
@@ -3844,10 +4078,10 @@ function balanceRaw(value, endpoint) {
3844
4078
  return amount;
3845
4079
  }
3846
4080
  function balanceDecimal(value, decimals, endpoint) {
3847
- return (0, import_viem9.formatUnits)(balanceRaw(value, endpoint), decimals);
4081
+ return (0, import_viem10.formatUnits)(balanceRaw(value, endpoint), decimals);
3848
4082
  }
3849
4083
  function usd(rawAmount2, decimals, price) {
3850
- return Number((0, import_viem9.formatUnits)(rawAmount2, decimals)) * price;
4084
+ return Number((0, import_viem10.formatUnits)(rawAmount2, decimals)) * price;
3851
4085
  }
3852
4086
  function percent(value) {
3853
4087
  const result = Number(value);
@@ -3865,16 +4099,16 @@ function riskAdjustedApyForDays(option, days) {
3865
4099
  if (days === "30D") return option.riskAdjustedApy30dAverage;
3866
4100
  return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
3867
4101
  }
3868
- function assetAddressValue(record, address) {
3869
- const entry = Object.entries(record).find(
4102
+ function assetAddressValue(record2, address) {
4103
+ const entry = Object.entries(record2).find(
3870
4104
  ([key2]) => key2.toLowerCase() === address.toLowerCase()
3871
4105
  );
3872
4106
  return entry?.[1] ?? "0";
3873
4107
  }
3874
4108
  function position(value, asset, baseAssetDecimals) {
3875
- const option = value?.yieldOption;
3876
- if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem9.isAddress)(option.address)) {
3877
- return invalid("yield positions", "missing vault metadata");
4109
+ const option = value?.vaultOption;
4110
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem10.isAddress)(option.address)) {
4111
+ return invalid("vault positions", "missing vault metadata");
3878
4112
  }
3879
4113
  return {
3880
4114
  chain: "BASE",
@@ -3890,10 +4124,10 @@ function position(value, asset, baseAssetDecimals) {
3890
4124
  amount: balanceDecimal(
3891
4125
  value.assetsBase,
3892
4126
  baseAssetDecimals,
3893
- "yield positions"
4127
+ "vault positions"
3894
4128
  ),
3895
- amountRaw: balanceRaw(value.assetsRaw, "yield positions").toString(),
3896
- withdrawableAmountRaw: balanceRaw(value.withdrawableAssetsRaw, "yield positions").toString(),
4129
+ amountRaw: balanceRaw(value.assetsRaw, "vault positions").toString(),
4130
+ withdrawableAmountRaw: balanceRaw(value.withdrawableAssetsRaw, "vault positions").toString(),
3897
4131
  apy: percent(option.riskAdjustedApy),
3898
4132
  tvl: Number(option.totalDepositsUsd),
3899
4133
  liquidity: Number(option.withdrawableDepositsUsd)
@@ -3966,7 +4200,7 @@ function mapYieldseekerEarnings(contexts) {
3966
4200
  chain: "BASE",
3967
4201
  chainId: 8453,
3968
4202
  asset: context.asset,
3969
- amount: (0, import_viem9.formatUnits)(amount, context.snapshot.baseAssetDecimals)
4203
+ amount: (0, import_viem10.formatUnits)(amount, context.snapshot.baseAssetDecimals)
3970
4204
  });
3971
4205
  lifetimeEarnings += usd(
3972
4206
  amount,
@@ -4112,52 +4346,82 @@ function actionEntry(action) {
4112
4346
  rebalanceLog: []
4113
4347
  };
4114
4348
  }
4115
- function allocationEntry(action, context) {
4116
- if (action.actionType.toLowerCase() !== "autoseek") return void 0;
4117
- const hash2 = action.details?.depositTransactionHash;
4118
- const deposits = action.details?.deposits;
4119
- if (typeof hash2 !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(hash2) || !Array.isArray(deposits) || deposits.length === 0) return void 0;
4120
- const positions = [];
4121
- let amountRaw = 0n;
4122
- for (const deposit of deposits) {
4123
- if (!deposit || typeof deposit !== "object") return void 0;
4124
- const leg = deposit;
4125
- const symbol = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
4126
- if (symbol !== context.asset && !(context.asset === "WETH" && symbol === "ETH")) return void 0;
4127
- if (typeof leg.vaultAddress !== "string" || !(0, import_viem9.isAddress)(leg.vaultAddress)) return void 0;
4349
+ var TX_HASH = /^0x[a-fA-F0-9]{64}$/;
4350
+ function allocationLegs(value, context, requireAmount) {
4351
+ if (!Array.isArray(value)) return void 0;
4352
+ const legs = [];
4353
+ for (const item of value) {
4354
+ if (!item || typeof item !== "object") return void 0;
4355
+ const leg = item;
4356
+ const symbol2 = typeof leg.assetSymbol === "string" ? leg.assetSymbol.toUpperCase() : "";
4357
+ if ((requireAmount || symbol2) && symbol2 !== context.asset && !(context.asset === "WETH" && symbol2 === "ETH"))
4358
+ return void 0;
4359
+ if (typeof leg.vaultAddress !== "string" || !(0, import_viem10.isAddress)(leg.vaultAddress)) return void 0;
4128
4360
  const rawAmount2 = typeof leg.rawAmount === "string" && /^\d+$/.test(leg.rawAmount) ? BigInt(leg.rawAmount) : typeof leg.rawAmount === "number" && Number.isSafeInteger(leg.rawAmount) ? BigInt(leg.rawAmount) : void 0;
4129
- if (rawAmount2 === void 0 || rawAmount2 <= 0n) return void 0;
4130
- const option = context.positions.find((position2) => position2.yieldOption.address.toLowerCase() === leg.vaultAddress.toLowerCase())?.yieldOption;
4131
- positions.push({
4361
+ if (requireAmount && (rawAmount2 === void 0 || rawAmount2 <= 0n)) return void 0;
4362
+ const vault = leg.vaultAddress.toLowerCase();
4363
+ const option = context.positions.find((position2) => position2.vaultOption.address.toLowerCase() === vault)?.vaultOption;
4364
+ const receipt = context.historic?.assets?.find((asset) => asset.address.toLowerCase() === vault && !asset.isSpam);
4365
+ legs.push({
4132
4366
  protocol: typeof leg.vaultProvider === "string" && leg.vaultProvider ? leg.vaultProvider : option?.provider || "Vault",
4133
- pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name,
4134
- tokenSymbol: context.asset,
4135
- amount: (0, import_viem9.formatUnits)(rawAmount2, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
4367
+ pool: typeof leg.vaultName === "string" && leg.vaultName ? leg.vaultName : option?.name ?? (receipt?.name || receipt?.symbol || void 0),
4368
+ rawAmount: rawAmount2
4136
4369
  });
4137
- amountRaw += rawAmount2;
4138
4370
  }
4371
+ return legs;
4372
+ }
4373
+ function allocationEntry(action, context) {
4374
+ if (action.actionType.toLowerCase() !== "autoseek") return void 0;
4375
+ const hash2 = action.details?.depositTransactionHash;
4376
+ const withdrawHash = action.details?.withdrawTransactionHash;
4377
+ if (typeof hash2 !== "string" || !TX_HASH.test(hash2)) return null;
4378
+ const deposits = allocationLegs(action.details?.deposits, context, true);
4379
+ const withdrawals = action.details?.withdrawals == null ? [] : allocationLegs(action.details.withdrawals, context, false);
4380
+ if (!deposits?.length || !withdrawals) return null;
4381
+ const decimals = YIELDSEEKER_ASSET_METADATA[context.asset].decimals;
4382
+ const positions = deposits.map((leg) => ({
4383
+ protocol: leg.protocol,
4384
+ pool: leg.pool,
4385
+ tokenSymbol: context.asset,
4386
+ amount: (0, import_viem10.formatUnits)(leg.rawAmount, decimals)
4387
+ }));
4388
+ const amountRaw = deposits.reduce((total, leg) => total + leg.rawAmount, 0n);
4389
+ const rebalance = withdrawals.length > 0;
4390
+ const hashes = rebalance && typeof withdrawHash === "string" && TX_HASH.test(withdrawHash) && withdrawHash.toLowerCase() !== hash2.toLowerCase() ? [hash2, withdrawHash] : [hash2];
4391
+ const source = withdrawals.length === 1 ? withdrawals[0] : {
4392
+ protocol: [...new Set(withdrawals.map((leg) => leg.protocol))].join(" + "),
4393
+ pool: withdrawals.map((leg) => leg.pool).filter(Boolean).join(" + ") || void 0
4394
+ };
4139
4395
  return {
4140
4396
  agent: "yieldseeker",
4141
- action: "Deposit",
4397
+ action: rebalance ? "Rebalance" : "Deposit",
4142
4398
  date: action.createdDate,
4143
4399
  oldApy: null,
4144
4400
  newApy: null,
4145
4401
  positions,
4146
- rebalanceLog: [],
4402
+ rebalanceLog: rebalance ? deposits.map((leg) => ({
4403
+ fromProtocol: source.protocol,
4404
+ ...source.pool ? { fromPool: source.pool } : {},
4405
+ toProtocol: leg.protocol,
4406
+ ...leg.pool ? { toPool: leg.pool } : {},
4407
+ tokenSymbol: context.asset,
4408
+ amount: (0, import_viem10.formatUnits)(leg.rawAmount, decimals),
4409
+ status: "success"
4410
+ })) : [],
4147
4411
  transactions: [{
4148
- txHashes: [hash2],
4412
+ txHashes: hashes,
4149
4413
  chainId: context.agent.chainId,
4150
4414
  tokenAddress: context.agent.assetAddress,
4151
4415
  tokenSymbol: context.asset,
4152
4416
  agentWallet: context.wallet.walletAddress,
4153
4417
  amountRaw: String(amountRaw),
4154
- amount: (0, import_viem9.formatUnits)(amountRaw, YIELDSEEKER_ASSET_METADATA[context.asset].decimals)
4418
+ amount: (0, import_viem10.formatUnits)(amountRaw, decimals)
4155
4419
  }]
4156
4420
  };
4157
4421
  }
4158
4422
  function depositDestination(context, movement) {
4159
4423
  const to = movement.toAddress.toLowerCase();
4160
- const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
4424
+ const option = context.positions.find(({ vaultOption }) => vaultOption.chainId === movement.chainId && vaultOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && vaultOption.address.toLowerCase() === to)?.vaultOption;
4161
4425
  if (option) return { protocol: option.provider || "Vault", pool: option.name, tokenSymbol: context.asset };
4162
4426
  const receipt = context.historic?.assets.find((asset) => asset.chainId === movement.chainId && !asset.isSpam && asset.address.toLowerCase() === to && asset.address.toLowerCase() !== context.agent.assetAddress.toLowerCase());
4163
4427
  const minted = receipt && context.historic?.movements.some((candidate) => candidate.chainId === movement.chainId && candidate.transactionHash.toLowerCase() === movement.transactionHash.toLowerCase() && candidate.assetAddress.toLowerCase() === receipt.address.toLowerCase() && candidate.fromAddress.toLowerCase() === "0x0000000000000000000000000000000000000000" && candidate.toAddress.toLowerCase() === context.wallet.walletAddress.toLowerCase() && raw(candidate.assetAmount, "historic position") > 0n);
@@ -4222,6 +4486,23 @@ function mapYieldseekerHistory(contexts, options) {
4222
4486
  seenMovements.add(key2);
4223
4487
  return true;
4224
4488
  });
4489
+ const allocations = /* @__PURE__ */ new Map();
4490
+ const rebalanceHashes = /* @__PURE__ */ new Set();
4491
+ const actions = [];
4492
+ for (const action of context.actions ?? []) {
4493
+ const allocation = allocationEntry(action, context);
4494
+ if (allocation === null) continue;
4495
+ if (!allocation) {
4496
+ const entry = actionEntry(action);
4497
+ if (entry) actions.push(entry);
4498
+ continue;
4499
+ }
4500
+ const hash2 = allocation.transactions[0].txHashes[0].toLowerCase();
4501
+ if (allocations.has(hash2)) continue;
4502
+ allocations.set(hash2, allocation);
4503
+ if (allocation.action === "Rebalance")
4504
+ for (const value of allocation.transactions[0].txHashes) rebalanceHashes.add(value.toLowerCase());
4505
+ }
4225
4506
  const movementEntries = movements.map(
4226
4507
  (movement) => movementEntry(
4227
4508
  movement,
@@ -4232,19 +4513,10 @@ function mapYieldseekerHistory(contexts, options) {
4232
4513
  options.vaultAddresses,
4233
4514
  depositDestination(context, movement)
4234
4515
  )
4235
- ).filter((entry) => entry !== void 0);
4516
+ ).filter((entry) => entry !== void 0 && !((entry.action === "Deposit" || entry.action === "Top up") && rebalanceHashes.has(entry.transactions[0]?.txHashes[0]?.toLowerCase() ?? "")));
4236
4517
  const deposits = new Set(movementEntries.filter((entry) => entry.action === "Deposit").map((entry) => entry.transactions[0]?.txHashes[0]?.toLowerCase()));
4237
- const actions = (context.actions ?? []).flatMap((action) => {
4238
- const allocation = allocationEntry(action, context);
4239
- if (allocation) {
4240
- const hash2 = allocation.transactions[0].txHashes[0].toLowerCase();
4241
- if (deposits.has(hash2)) return [];
4242
- deposits.add(hash2);
4243
- return [allocation];
4244
- }
4245
- const entry = actionEntry(action);
4246
- return entry ? [entry] : [];
4247
- });
4518
+ for (const [hash2, allocation] of allocations)
4519
+ if (allocation.action === "Rebalance" || !deposits.has(hash2)) actions.push(allocation);
4248
4520
  return [...movementEntries, ...actions];
4249
4521
  });
4250
4522
  const grouped = /* @__PURE__ */ new Map();
@@ -4275,7 +4547,7 @@ function mapYieldseekerHistory(contexts, options) {
4275
4547
  const known = new Set(target.logIndexes ?? []);
4276
4548
  if (tx.logIndexes?.some((index) => !known.has(index))) {
4277
4549
  target.amountRaw = String(BigInt(target.amountRaw ?? "0") + BigInt(tx.amountRaw));
4278
- target.amount = (0, import_viem9.formatUnits)(
4550
+ target.amount = (0, import_viem10.formatUnits)(
4279
4551
  BigInt(target.amountRaw),
4280
4552
  YIELDSEEKER_ASSET_METADATA[target.tokenSymbol].decimals
4281
4553
  );
@@ -4322,8 +4594,8 @@ function mapYieldseekerProfile(address, contexts) {
4322
4594
  const protocols = /* @__PURE__ */ new Set();
4323
4595
  for (const context of contexts) {
4324
4596
  for (const current of context.positions) {
4325
- if (current.yieldOption?.provider) {
4326
- protocols.add(String(current.yieldOption.provider));
4597
+ if (current.vaultOption?.provider) {
4598
+ protocols.add(String(current.vaultOption.provider));
4327
4599
  }
4328
4600
  }
4329
4601
  }
@@ -4341,7 +4613,7 @@ function mapYieldseekerAgentApy(options, days) {
4341
4613
  const perAsset = {};
4342
4614
  const all = [];
4343
4615
  for (const entry of options) {
4344
- const apys = entry.yieldOptions.map(
4616
+ const apys = entry.vaultOptions.map(
4345
4617
  (option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
4346
4618
  ).filter(Number.isFinite);
4347
4619
  if (apys.length === 0) continue;
@@ -4356,7 +4628,7 @@ function mapYieldseekerAgentApy(options, days) {
4356
4628
  }
4357
4629
 
4358
4630
  // src/lib/remaining-vault-balance.ts
4359
- var import_viem10 = require("viem");
4631
+ var import_viem11 = require("viem");
4360
4632
  async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber) {
4361
4633
  const addresses = [...new Set(vaults.map((value) => value.toLowerCase()))];
4362
4634
  if (addresses.length > 64) return void 0;
@@ -4367,8 +4639,8 @@ async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber)
4367
4639
  blockNumber,
4368
4640
  allowFailure: true,
4369
4641
  contracts: batch.map((address) => ({
4370
- address: (0, import_viem10.getAddress)(address),
4371
- abi: import_viem10.erc20Abi,
4642
+ address: (0, import_viem11.getAddress)(address),
4643
+ abi: import_viem11.erc20Abi,
4372
4644
  functionName: "balanceOf",
4373
4645
  args: [wallet]
4374
4646
  }))
@@ -4378,15 +4650,15 @@ async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber)
4378
4650
  for (let index = 0; index < batch.length; index++) {
4379
4651
  const result = balances[index];
4380
4652
  if (result?.status !== "success" || typeof result.result !== "bigint" || result.result < 0n) return void 0;
4381
- if (result.result > 0n) funded.push({ address: (0, import_viem10.getAddress)(batch[index]), shares: result.result });
4653
+ if (result.result > 0n) funded.push({ address: (0, import_viem11.getAddress)(batch[index]), shares: result.result });
4382
4654
  }
4383
4655
  if (!funded.length) return 0n;
4384
4656
  const valuations = await client.multicall({
4385
4657
  blockNumber,
4386
4658
  allowFailure: true,
4387
4659
  contracts: funded.flatMap(({ address, shares }) => [
4388
- { address, abi: import_viem10.erc4626Abi, functionName: "asset" },
4389
- { address, abi: import_viem10.erc4626Abi, functionName: "convertToAssets", args: [shares] }
4660
+ { address, abi: import_viem11.erc4626Abi, functionName: "asset" },
4661
+ { address, abi: import_viem11.erc4626Abi, functionName: "convertToAssets", args: [shares] }
4390
4662
  ])
4391
4663
  });
4392
4664
  if (valuations.length !== funded.length * 2) return void 0;
@@ -4402,12 +4674,12 @@ async function remainingVaultBalance(client, wallet, asset, vaults, blockNumber)
4402
4674
  }
4403
4675
 
4404
4676
  // src/agents/yieldseeker/yieldseeker.wallet-version.ts
4405
- var import_viem11 = require("viem");
4677
+ var import_viem12 = require("viem");
4406
4678
  var YIELDSEEKER_FACTORY = "0x9c7410a0faCAC60850C46eE5b58B518daec95130";
4407
4679
  var YIELDSEEKER_V1_IMPLEMENTATION = "0x32403455357589523435dB18b414057D7cE05051";
4408
4680
  var YIELDSEEKER_V2_IMPLEMENTATION = "0x613Db9c924ADB7A7A90c11b02Cce20c913E3Fe50";
4409
4681
  var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
4410
- var walletAbi = (0, import_viem11.parseAbi)([
4682
+ var walletAbi = (0, import_viem12.parseAbi)([
4411
4683
  "function owner() view returns (address)",
4412
4684
  "function ownerAgentIndex() view returns (uint256)",
4413
4685
  "function baseAsset() view returns (address)",
@@ -4415,7 +4687,7 @@ var walletAbi = (0, import_viem11.parseAbi)([
4415
4687
  "function upgradeToLatest()",
4416
4688
  "event Upgraded(address indexed implementation)"
4417
4689
  ]);
4418
- var factoryAbi = (0, import_viem11.parseAbi)([
4690
+ var factoryAbi = (0, import_viem12.parseAbi)([
4419
4691
  "function userWallets(address owner, uint256 index) view returns (address)",
4420
4692
  "function agentWalletImplementation() view returns (address)"
4421
4693
  ]);
@@ -4423,9 +4695,9 @@ function yieldseekerOwnerPath(code) {
4423
4695
  return !code || code === "0x" || /^0xef0100[0-9a-f]{40}$/i.test(code) ? "eoa" : "smart";
4424
4696
  }
4425
4697
  async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNumber) {
4426
- const agentWallet = (0, import_viem11.getAddress)(wallet);
4427
- const expectedOwner = (0, import_viem11.getAddress)(owner);
4428
- const expectedAsset = (0, import_viem11.getAddress)(asset);
4698
+ const agentWallet = (0, import_viem12.getAddress)(wallet);
4699
+ const expectedOwner = (0, import_viem12.getAddress)(owner);
4700
+ const expectedAsset = (0, import_viem12.getAddress)(asset);
4429
4701
  const [code, ownerCode, slot, actualOwner, index, actualAsset, actualFactory, latest] = await Promise.all([
4430
4702
  client.getBytecode({ address: agentWallet, blockNumber }),
4431
4703
  client.getBytecode({ address: expectedOwner, blockNumber }),
@@ -4439,7 +4711,7 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNu
4439
4711
  if (!code || code === "0x" || !slot || /^0x0+$/.test(slot)) {
4440
4712
  throw new Error("Yieldseeker wallet is not a deployed ERC1967 proxy");
4441
4713
  }
4442
- const implementation = (0, import_viem11.getAddress)(`0x${slot.slice(-40)}`);
4714
+ const implementation = (0, import_viem12.getAddress)(`0x${slot.slice(-40)}`);
4443
4715
  const mapped = await client.readContract({
4444
4716
  address: YIELDSEEKER_FACTORY,
4445
4717
  abi: factoryAbi,
@@ -4447,8 +4719,8 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNu
4447
4719
  args: [expectedOwner, index],
4448
4720
  blockNumber
4449
4721
  });
4450
- const verified = (0, import_viem11.isAddressEqual)(actualOwner, expectedOwner) && (0, import_viem11.isAddressEqual)(actualAsset, expectedAsset) && (0, import_viem11.isAddressEqual)(actualFactory, YIELDSEEKER_FACTORY) && (0, import_viem11.isAddressEqual)(mapped, agentWallet);
4451
- const version = !verified ? "unknown" : (0, import_viem11.isAddressEqual)(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : (0, import_viem11.isAddressEqual)(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
4722
+ const verified = (0, import_viem12.isAddressEqual)(actualOwner, expectedOwner) && (0, import_viem12.isAddressEqual)(actualAsset, expectedAsset) && (0, import_viem12.isAddressEqual)(actualFactory, YIELDSEEKER_FACTORY) && (0, import_viem12.isAddressEqual)(mapped, agentWallet);
4723
+ const version = !verified ? "unknown" : (0, import_viem12.isAddressEqual)(implementation, YIELDSEEKER_V2_IMPLEMENTATION) ? "v2" : (0, import_viem12.isAddressEqual)(implementation, YIELDSEEKER_V1_IMPLEMENTATION) ? "v1" : "unknown";
4452
4724
  return {
4453
4725
  agentWallet,
4454
4726
  owner: actualOwner,
@@ -4457,13 +4729,13 @@ async function readYieldseekerWalletStatus(client, wallet, owner, asset, blockNu
4457
4729
  implementation,
4458
4730
  version,
4459
4731
  verified,
4460
- upgradeAvailable: version === "v1" && (0, import_viem11.isAddressEqual)(latest, YIELDSEEKER_V2_IMPLEMENTATION)
4732
+ upgradeAvailable: version === "v1" && (0, import_viem12.isAddressEqual)(latest, YIELDSEEKER_V2_IMPLEMENTATION)
4461
4733
  };
4462
4734
  }
4463
4735
  var YIELDSEEKER_UPGRADE_ABI = walletAbi;
4464
4736
 
4465
4737
  // src/agents/yieldseeker/yieldseeker.sponsored-withdraw.ts
4466
- var import_viem12 = require("viem");
4738
+ var import_viem13 = require("viem");
4467
4739
  var import_account_abstraction2 = require("viem/account-abstraction");
4468
4740
  var import_accounts = require("viem/accounts");
4469
4741
  var import_chains3 = require("viem/chains");
@@ -4550,7 +4822,7 @@ function rpcDiagnostics(data) {
4550
4822
  details.revertName = data.revertName;
4551
4823
  if (typeof data?.aaCode === "string" && /^AA[0-9]{2}$/.test(data.aaCode)) details.aaCode = data.aaCode;
4552
4824
  if (typeof data?.panicCode === "string" && /^0x[0-9a-fA-F]{1,2}$/.test(data.panicCode)) {
4553
- const panicCode = (0, import_viem12.toHex)(BigInt(data.panicCode));
4825
+ const panicCode = (0, import_viem13.toHex)(BigInt(data.panicCode));
4554
4826
  if (["0x0", "0x1", "0x11", "0x12", "0x21", "0x22", "0x31", "0x32", "0x41", "0x51"].includes(panicCode))
4555
4827
  details.panicCode = panicCode;
4556
4828
  }
@@ -4560,11 +4832,11 @@ function isHash(value) {
4560
4832
  return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
4561
4833
  }
4562
4834
  async function assertOwnerSession(state, owner) {
4563
- const [chain, accounts] = await Promise.all([
4835
+ const [chain2, accounts] = await Promise.all([
4564
4836
  state.provider.request({ method: "eth_chainId" }),
4565
4837
  state.provider.request({ method: "eth_accounts" })
4566
4838
  ]);
4567
- if (Number(chain) !== 8453)
4839
+ if (Number(chain2) !== 8453)
4568
4840
  throw new OwneyError(
4569
4841
  "CHAIN_MISMATCH",
4570
4842
  "Switch back to Base before withdrawing.",
@@ -4770,14 +5042,14 @@ async function submitEoa(input) {
4770
5042
  throw sponsorError("Base did not return EIP-1559 gas fees.", true);
4771
5043
  let operation = {
4772
5044
  sender: input.agentWallet,
4773
- nonce: (0, import_viem12.toHex)(nonce),
5045
+ nonce: (0, import_viem13.toHex)(nonce),
4774
5046
  initCode: "0x",
4775
5047
  callData: input.callData,
4776
- callGasLimit: (0, import_viem12.toHex)(3e5),
4777
- verificationGasLimit: (0, import_viem12.toHex)(3e5),
4778
- preVerificationGas: (0, import_viem12.toHex)(6e4),
4779
- maxFeePerGas: (0, import_viem12.toHex)(fees.maxFeePerGas * USER_OP_FEE_HEADROOM),
4780
- maxPriorityFeePerGas: (0, import_viem12.toHex)(fees.maxPriorityFeePerGas),
5048
+ callGasLimit: (0, import_viem13.toHex)(3e5),
5049
+ verificationGasLimit: (0, import_viem13.toHex)(3e5),
5050
+ preVerificationGas: (0, import_viem13.toHex)(6e4),
5051
+ maxFeePerGas: (0, import_viem13.toHex)(fees.maxFeePerGas * USER_OP_FEE_HEADROOM),
5052
+ maxPriorityFeePerGas: (0, import_viem13.toHex)(fees.maxPriorityFeePerGas),
4781
5053
  paymasterAndData: "0x",
4782
5054
  signature: "0x"
4783
5055
  };
@@ -4785,7 +5057,7 @@ async function submitEoa(input) {
4785
5057
  const stub = await rpc(
4786
5058
  input.rpcUrl,
4787
5059
  "pm_getPaymasterStubData",
4788
- [operation, ENTRY_POINT_V06, (0, import_viem12.toHex)(8453), {}],
5060
+ [operation, ENTRY_POINT_V06, (0, import_viem13.toHex)(8453), {}],
4789
5061
  "prepare"
4790
5062
  );
4791
5063
  if (!stub.paymasterAndData?.startsWith("0x"))
@@ -4800,7 +5072,7 @@ async function submitEoa(input) {
4800
5072
  const final = await rpc(
4801
5073
  input.rpcUrl,
4802
5074
  "pm_getPaymasterData",
4803
- [operation, ENTRY_POINT_V06, (0, import_viem12.toHex)(8453), {}],
5075
+ [operation, ENTRY_POINT_V06, (0, import_viem13.toHex)(8453), {}],
4804
5076
  "prepare"
4805
5077
  );
4806
5078
  if (!final.paymasterAndData?.startsWith("0x"))
@@ -4810,11 +5082,12 @@ async function submitEoa(input) {
4810
5082
  const userOpHash = operationHash(operation);
4811
5083
  await assertOwnerSession(input.state, input.owner);
4812
5084
  assertIntentFresh(input.expiresAt);
4813
- const wallet = (0, import_viem12.createWalletClient)({
5085
+ const wallet = (0, import_viem13.createWalletClient)({
4814
5086
  account: input.owner,
4815
5087
  chain: import_chains3.base,
4816
- transport: (0, import_viem12.custom)(input.state.provider)
5088
+ transport: (0, import_viem13.custom)(input.state.provider)
4817
5089
  });
5090
+ input.assertCurrent?.();
4818
5091
  const signature = await wallet.signMessage({ message: { raw: userOpHash } });
4819
5092
  operation = { ...operation, signature };
4820
5093
  await assertOwnerSession(input.state, input.owner);
@@ -4832,7 +5105,9 @@ async function submitEoa(input) {
4832
5105
  paymasterAndData: operation.paymasterAndData
4833
5106
  };
4834
5107
  savePending(input.onPending, { userOpHash, userOperation }, true);
5108
+ input.assertCurrent?.();
4835
5109
  await checkpoint(input.onBeforeSubmission, { userOpHash, userOperation });
5110
+ input.assertCurrent?.();
4836
5111
  const submittedHash = await rpc(
4837
5112
  input.rpcUrl,
4838
5113
  "eth_sendUserOperation",
@@ -4891,22 +5166,24 @@ async function submitSmart(input) {
4891
5166
  } catch {
4892
5167
  throw sponsorError("This account cannot check paymaster support on Base.", true);
4893
5168
  }
4894
- const supportsPaymaster = capabilities?.[(0, import_viem12.toHex)(8453)]?.paymasterService?.supported ?? capabilities?.["8453"]?.paymasterService?.supported ?? capabilities?.["0x0"]?.paymasterService?.supported;
5169
+ const supportsPaymaster = capabilities?.[(0, import_viem13.toHex)(8453)]?.paymasterService?.supported ?? capabilities?.["8453"]?.paymasterService?.supported ?? capabilities?.["0x0"]?.paymasterService?.supported;
4895
5170
  if (supportsPaymaster !== true)
4896
5171
  throw sponsorError("This account cannot use a paymaster on Base.", true);
4897
5172
  await assertOwnerSession(input.state, input.owner);
4898
5173
  let sent;
4899
5174
  assertIntentFresh(input.expiresAt);
4900
- const requestedId = (0, import_viem12.toHex)(globalThis.crypto.getRandomValues(new Uint8Array(32)));
5175
+ const requestedId = (0, import_viem13.toHex)(globalThis.crypto.getRandomValues(new Uint8Array(32)));
4901
5176
  savePending(input.onPending, { callsId: requestedId }, true);
5177
+ input.assertCurrent?.();
4902
5178
  await checkpoint(input.onBeforeSubmission, { callsId: requestedId });
5179
+ input.assertCurrent?.();
4903
5180
  try {
4904
5181
  sent = await input.state.provider.request({
4905
5182
  method: "wallet_sendCalls",
4906
5183
  params: [{
4907
5184
  version: "2.0.0",
4908
5185
  from: input.owner,
4909
- chainId: (0, import_viem12.toHex)(8453),
5186
+ chainId: (0, import_viem13.toHex)(8453),
4910
5187
  id: requestedId,
4911
5188
  atomicRequired: false,
4912
5189
  calls: [{ to: input.agentWallet, value: "0x0", data: input.callData }],
@@ -4981,7 +5258,7 @@ async function submitSmart(input) {
4981
5258
  );
4982
5259
  }
4983
5260
  async function submitYieldseekerSponsoredWithdrawal(input) {
4984
- const owner = (0, import_viem12.getAddress)(input.state.walletAddress);
5261
+ const owner = (0, import_viem13.getAddress)(input.state.walletAddress);
4985
5262
  const { intent, rpcUrl } = await requestIntent({
4986
5263
  apiKey: input.apiKey,
4987
5264
  routingApiBaseUrl: input.routingApiBaseUrl,
@@ -5000,7 +5277,7 @@ async function submitYieldseekerSponsoredWithdrawal(input) {
5000
5277
  return intent.ownerPath === "eoa" ? submitEoa(args) : submitSmart(args);
5001
5278
  }
5002
5279
  async function submitYieldseekerSponsoredUpgrade(input) {
5003
- const owner = (0, import_viem12.getAddress)(input.state.walletAddress);
5280
+ const owner = (0, import_viem13.getAddress)(input.state.walletAddress);
5004
5281
  const { intent, rpcUrl } = await requestIntent({
5005
5282
  apiKey: input.apiKey,
5006
5283
  routingApiBaseUrl: input.routingApiBaseUrl,
@@ -5026,7 +5303,7 @@ var OWNEY_AGENT_RULE_PRESET = "explorative";
5026
5303
  var YIELDSEEKER_USERNAME_PREFIX = "owney_";
5027
5304
  var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
5028
5305
  var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
5029
- var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
5306
+ var YIELDSEEKER_VAULT_OPTIONS_CACHE_MS = 6e4;
5030
5307
  var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
5031
5308
  var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
5032
5309
  var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
@@ -5129,6 +5406,7 @@ var YieldseekerAgent = class {
5129
5406
  pendingReads = /* @__PURE__ */ new Map();
5130
5407
  readGeneration = 0;
5131
5408
  portfolioVersions = /* @__PURE__ */ new Map();
5409
+ liquidityVersions = /* @__PURE__ */ new Map();
5132
5410
  snapshotFailures = /* @__PURE__ */ new Map();
5133
5411
  standardReadFailures = /* @__PURE__ */ new Map();
5134
5412
  reconcileUntil = /* @__PURE__ */ new Map();
@@ -5138,8 +5416,8 @@ var YieldseekerAgent = class {
5138
5416
  // settlement; keep slow reconciliation after the fast window expires.
5139
5417
  snapshotMovements = /* @__PURE__ */ new Map();
5140
5418
  movementEpochs = /* @__PURE__ */ new Map();
5141
- yieldOptions = /* @__PURE__ */ new Map();
5142
- pendingYieldOptions = /* @__PURE__ */ new Map();
5419
+ vaultOptions = /* @__PURE__ */ new Map();
5420
+ pendingVaultOptions = /* @__PURE__ */ new Map();
5143
5421
  constructor(owneyApiKey, options = {}) {
5144
5422
  this.owneyApiKey = owneyApiKey;
5145
5423
  this.routingApiBaseUrl = options.routingApiBaseUrl;
@@ -5168,8 +5446,8 @@ var YieldseekerAgent = class {
5168
5446
  if (useCachedV2 && cached && Date.now() - cached.at < 15 * 6e4) return { ...cached.status };
5169
5447
  const status = await readYieldseekerWalletStatus(
5170
5448
  this.getReceiptClient(),
5171
- (0, import_viem13.getAddress)(context.wallet.walletAddress),
5172
- (0, import_viem13.getAddress)(state.walletAddress),
5449
+ (0, import_viem14.getAddress)(context.wallet.walletAddress),
5450
+ (0, import_viem14.getAddress)(state.walletAddress),
5173
5451
  YIELDSEEKER_ASSET_METADATA[asset].address
5174
5452
  );
5175
5453
  this.rememberWalletStatus(key2, status);
@@ -5199,12 +5477,12 @@ var YieldseekerAgent = class {
5199
5477
  const pendingKey2 = `owney.yieldseeker.upgrade:${before.owner.toLowerCase()}:${before.agentWallet.toLowerCase()}`;
5200
5478
  const confirmUpgrade = async (hash2, receipt2) => {
5201
5479
  this.withdrawalVersionCache.delete(versionKey);
5202
- const upgradeTopic = (0, import_viem13.encodeEventTopics)({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded" })[0];
5480
+ const upgradeTopic = (0, import_viem14.encodeEventTopics)({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded" })[0];
5203
5481
  const lastUpgrade = receipt2?.logs.filter((log) => log.address.toLowerCase() === before.agentWallet.toLowerCase() && log.topics[0] === upgradeTopic).at(-1);
5204
5482
  const expectedUpgrade = (() => {
5205
5483
  if (!lastUpgrade || lastUpgrade.removed) return false;
5206
5484
  try {
5207
- const event = (0, import_viem13.decodeEventLog)({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded", data: lastUpgrade.data, topics: lastUpgrade.topics });
5485
+ const event = (0, import_viem14.decodeEventLog)({ abi: YIELDSEEKER_UPGRADE_ABI, eventName: "Upgraded", data: lastUpgrade.data, topics: lastUpgrade.topics });
5208
5486
  return event.args.implementation.toLowerCase() === YIELDSEEKER_V2_IMPLEMENTATION.toLowerCase();
5209
5487
  } catch {
5210
5488
  return false;
@@ -5342,7 +5620,7 @@ var YieldseekerAgent = class {
5342
5620
  { version: before.version },
5343
5621
  this.id
5344
5622
  );
5345
- const data = (0, import_viem13.encodeFunctionData)({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
5623
+ const data = (0, import_viem14.encodeFunctionData)({ abi: YIELDSEEKER_UPGRADE_ABI, functionName: "upgradeToLatest" });
5346
5624
  let txHash;
5347
5625
  let receipt;
5348
5626
  if (gasMode === "sponsored") {
@@ -5385,7 +5663,7 @@ var YieldseekerAgent = class {
5385
5663
  let submittedHash;
5386
5664
  try {
5387
5665
  txHash = await this.submitTransaction(state, chainId, {
5388
- from: (0, import_viem13.getAddress)(state.walletAddress),
5666
+ from: (0, import_viem14.getAddress)(state.walletAddress),
5389
5667
  to: before.agentWallet,
5390
5668
  data,
5391
5669
  value: "0",
@@ -5433,6 +5711,7 @@ var YieldseekerAgent = class {
5433
5711
  this.readCache.clear();
5434
5712
  this.pendingReads.clear();
5435
5713
  this.portfolioVersions.clear();
5714
+ this.liquidityVersions.clear();
5436
5715
  this.snapshotFailures.clear();
5437
5716
  this.standardReadFailures.clear();
5438
5717
  this.reconcileUntil.clear();
@@ -5492,12 +5771,12 @@ var YieldseekerAgent = class {
5492
5771
  } else {
5493
5772
  try {
5494
5773
  txHash = await this.submitTransaction(state, chainId, {
5495
- from: (0, import_viem13.getAddress)(state.walletAddress),
5774
+ from: (0, import_viem14.getAddress)(state.walletAddress),
5496
5775
  to: YIELDSEEKER_ASSET_METADATA[asset].address,
5497
- data: (0, import_viem13.encodeFunctionData)({
5498
- abi: import_viem13.erc20Abi,
5776
+ data: (0, import_viem14.encodeFunctionData)({
5777
+ abi: import_viem14.erc20Abi,
5499
5778
  functionName: "transfer",
5500
- args: [(0, import_viem13.getAddress)(context.wallet.walletAddress), BigInt(amount)]
5779
+ args: [(0, import_viem14.getAddress)(context.wallet.walletAddress), BigInt(amount)]
5501
5780
  }),
5502
5781
  value: "0",
5503
5782
  chainId
@@ -5540,7 +5819,7 @@ var YieldseekerAgent = class {
5540
5819
  notifyFunding(onProgress, evidence);
5541
5820
  return { txHash, smartWallet: context.wallet.walletAddress, amount, evidence };
5542
5821
  }
5543
- async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission) {
5822
+ async withdraw(state, chainId, asset, amount, onProgress, gasMode = "auto", onBeforeSubmission, assertOperation) {
5544
5823
  let ownerTransferAttempted = false;
5545
5824
  let evidence = {
5546
5825
  agentId: this.id,
@@ -5558,7 +5837,7 @@ var YieldseekerAgent = class {
5558
5837
  notifyFunding(onProgress, evidence);
5559
5838
  }, () => {
5560
5839
  ownerTransferAttempted = true;
5561
- }, gasMode, onBeforeSubmission);
5840
+ }, gasMode, onBeforeSubmission, assertOperation);
5562
5841
  } catch (error) {
5563
5842
  if (error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED") this.withdrawalVersionCache.clear();
5564
5843
  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)))
@@ -5571,7 +5850,7 @@ var YieldseekerAgent = class {
5571
5850
  throw error;
5572
5851
  }
5573
5852
  }
5574
- async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission) {
5853
+ async withdrawInternal(state, chainId, asset, amount, onProgress, onOwnerTransferAttempted, gasMode = "auto", onBeforeSubmission, assertOperation) {
5575
5854
  this.assertChain(chainId);
5576
5855
  this.assertAsset(asset);
5577
5856
  if (amount !== void 0 && BigInt(amount) <= 0n) {
@@ -5643,7 +5922,7 @@ var YieldseekerAgent = class {
5643
5922
  );
5644
5923
  const positionsRead = this.readWithdrawalPositions(state, chainId, context);
5645
5924
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
5646
- const agentWallet = (0, import_viem13.getAddress)(context.wallet.walletAddress);
5925
+ const agentWallet = (0, import_viem14.getAddress)(context.wallet.walletAddress);
5647
5926
  const readOwnerFunds = async (blockNumber) => {
5648
5927
  const feeTracker = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5649
5928
  address: agentWallet,
@@ -5652,18 +5931,18 @@ var YieldseekerAgent = class {
5652
5931
  blockNumber
5653
5932
  }), chainId, "eth_call", this.id);
5654
5933
  this.assertSession(generation, chainId, asset);
5655
- if (!(0, import_viem13.isAddress)(feeTracker) || /^0x0{40}$/i.test(feeTracker))
5934
+ if (!(0, import_viem14.isAddress)(feeTracker) || /^0x0{40}$/i.test(feeTracker))
5656
5935
  throw this.invalidResponse("wallet fee tracker");
5657
5936
  const [idle2, fees] = await Promise.all([
5658
5937
  withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5659
5938
  address: metadata.address,
5660
- abi: import_viem13.erc20Abi,
5939
+ abi: import_viem14.erc20Abi,
5661
5940
  functionName: "balanceOf",
5662
5941
  args: [agentWallet],
5663
5942
  blockNumber
5664
5943
  }), chainId, "eth_call", this.id),
5665
5944
  withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
5666
- address: (0, import_viem13.getAddress)(feeTracker),
5945
+ address: (0, import_viem14.getAddress)(feeTracker),
5667
5946
  abi: YIELDSEEKER_FEE_TRACKER_ABI,
5668
5947
  functionName: "getFeesOwed",
5669
5948
  args: [agentWallet],
@@ -5689,7 +5968,7 @@ var YieldseekerAgent = class {
5689
5968
  const initialFunds = funds;
5690
5969
  const idle = initialFunds.idle;
5691
5970
  const maxLiquidity = amount === void 0 ? funds : void 0;
5692
- let positions = [...funds.positions, ...funds.indexedPositions.filter((position2) => !funds.resolvedVaults.includes(position2.yieldOption.address.toLowerCase()))];
5971
+ let positions = [...funds.positions, ...funds.indexedPositions.filter((position2) => !funds.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()))];
5693
5972
  let positionsStatus = "empty";
5694
5973
  const validatePositions = (values) => this.validateWithdrawalPositions(values, chainId, asset);
5695
5974
  validatePositions(positions);
@@ -5697,21 +5976,21 @@ var YieldseekerAgent = class {
5697
5976
  const hasPositions = (values) => values.some((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n));
5698
5977
  const knownPositions = /* @__PURE__ */ new Map();
5699
5978
  const rememberPositions = (values) => {
5700
- for (const position2 of values) knownPositions.set(position2.yieldOption.address.toLowerCase(), position2);
5979
+ for (const position2 of values) knownPositions.set(position2.vaultOption.address.toLowerCase(), position2);
5701
5980
  };
5702
5981
  const verifyKnownPositions = async (blockNumber, values = [...knownPositions.values()], timeoutMs = YIELDSEEKER_MAX_POSITION_VERIFICATION_MS) => {
5703
5982
  const verifiedPositions = [];
5704
5983
  if (timeoutMs <= 0) return { verified: false, positions: verifiedPositions };
5705
- let active = true;
5706
- const checks = this.readVaultShares(values.map((position2) => position2.yieldOption), agentWallet, blockNumber).catch(() => []);
5984
+ let active2 = true;
5985
+ const checks = this.readVaultShares(values.map((position2) => position2.vaultOption), agentWallet, blockNumber).catch(() => []);
5707
5986
  const reads = values.map(async (position2, index) => {
5708
- const address = (0, import_viem13.getAddress)(position2.yieldOption.address);
5987
+ const address = (0, import_viem14.getAddress)(position2.vaultOption.address);
5709
5988
  try {
5710
5989
  const group = (await checks).slice(index * 2, index * 2 + 2);
5711
- if (!active || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
5990
+ if (!active2 || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
5712
5991
  this.assertSession(generation, chainId, asset);
5713
5992
  const [underlying, shares] = group.map((value) => value.result);
5714
- if (typeof underlying !== "string" || !(0, import_viem13.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
5993
+ if (typeof underlying !== "string" || !(0, import_viem14.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
5715
5994
  const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: "0" };
5716
5995
  if (shares > 0n) {
5717
5996
  let positiveValue = false;
@@ -5719,12 +5998,12 @@ var YieldseekerAgent = class {
5719
5998
  try {
5720
5999
  const value = await this.getReceiptClient().readContract({
5721
6000
  address,
5722
- abi: import_viem13.erc4626Abi,
6001
+ abi: import_viem14.erc4626Abi,
5723
6002
  functionName,
5724
6003
  args: [shares],
5725
6004
  blockNumber
5726
6005
  });
5727
- if (!active || typeof value !== "bigint" || value < 0n) return null;
6006
+ if (!active2 || typeof value !== "bigint" || value < 0n) return null;
5728
6007
  this.assertSession(generation, chainId, asset);
5729
6008
  if (value > 0n && !positiveValue) {
5730
6009
  positiveValue = true;
@@ -5752,7 +6031,7 @@ var YieldseekerAgent = class {
5752
6031
  }
5753
6032
  });
5754
6033
  const result = await boundedVerification(Promise.all(reads), timeoutMs);
5755
- active = false;
6034
+ active2 = false;
5756
6035
  this.assertSession(generation, chainId, asset);
5757
6036
  return {
5758
6037
  verified: result !== void 0 && result.every((position2) => position2 !== null),
@@ -5764,13 +6043,14 @@ var YieldseekerAgent = class {
5764
6043
  const response = await this.walletRequest(
5765
6044
  state,
5766
6045
  chainId,
5767
- this.agentPath(context, "yield-positions")
6046
+ this.agentPath(context, "vault-positions")
5768
6047
  );
5769
6048
  this.assertSession(generation, chainId, asset);
5770
- if (!Array.isArray(response?.yieldPositions)) throw this.invalidResponse("yield positions");
5771
- return validatePositions(response.yieldPositions);
6049
+ if (!Array.isArray(response?.vaultPositions)) throw this.invalidResponse("vault positions");
6050
+ return validatePositions(response.vaultPositions);
5772
6051
  };
5773
6052
  if (amount === void 0) {
6053
+ rememberPositions(priorPositions);
5774
6054
  rememberPositions(funds.indexedPositions);
5775
6055
  rememberPositions(positions);
5776
6056
  positionsStatus = hasPositions(positions) ? "remaining" : "empty";
@@ -5797,7 +6077,7 @@ var YieldseekerAgent = class {
5797
6077
  throw new OwneyError(
5798
6078
  "WITHDRAW_FAILED",
5799
6079
  "Yieldseeker vault funds are not currently withdrawable.",
5800
- { asset, positionsStatus, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
6080
+ { asset, positionsStatus, remainingPositions: positions.map((position2) => position2.vaultOption.address) },
5801
6081
  this.id
5802
6082
  );
5803
6083
  }
@@ -5846,6 +6126,7 @@ var YieldseekerAgent = class {
5846
6126
  let latestUnwindBlock;
5847
6127
  const unwind = async (position2, assetsRaw) => {
5848
6128
  this.assertSession(generation, chainId, asset);
6129
+ assertOperation?.();
5849
6130
  let unwindTransactionHash;
5850
6131
  try {
5851
6132
  const response = await this.walletRequest(
@@ -5854,7 +6135,8 @@ var YieldseekerAgent = class {
5854
6135
  this.agentPath(context, "withdraw-from-position"),
5855
6136
  {
5856
6137
  method: "POST",
5857
- body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
6138
+ body: { chainId, vaultAddress: position2.vaultOption.address, assetsRaw: assetsRaw.toString() },
6139
+ assertCurrent: assertOperation
5858
6140
  }
5859
6141
  );
5860
6142
  if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
@@ -5873,7 +6155,7 @@ var YieldseekerAgent = class {
5873
6155
  ...error instanceof OwneyError ? error.details : {},
5874
6156
  withdrawalPhase: "unwind",
5875
6157
  reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
5876
- vaultAddress: position2.yieldOption.address,
6158
+ vaultAddress: position2.vaultOption.address,
5877
6159
  unwindTransactionHash,
5878
6160
  ownerTransferSubmitted: false
5879
6161
  },
@@ -5886,7 +6168,7 @@ var YieldseekerAgent = class {
5886
6168
  };
5887
6169
  if (amount === void 0) {
5888
6170
  for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
5889
- const before = new Map(positions.map((position2) => [position2.yieldOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
6171
+ const before = new Map(positions.map((position2) => [position2.vaultOption.address.toLowerCase(), BigInt(position2.withdrawableAssetsRaw)]));
5890
6172
  let unwound = false;
5891
6173
  for (const position2 of positions) {
5892
6174
  const available = BigInt(position2.withdrawableAssetsRaw);
@@ -5903,16 +6185,16 @@ var YieldseekerAgent = class {
5903
6185
  if (catalog) rememberPositions(catalog);
5904
6186
  this.assertSession(generation, chainId, asset);
5905
6187
  if (proof.verified) {
5906
- positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.yieldOption.address.toLowerCase()))];
6188
+ positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.vaultOption.address.toLowerCase()))];
5907
6189
  } else if (catalog) {
5908
- const checked = new Set(proof.positions.map((position2) => position2.yieldOption.address.toLowerCase()));
5909
- positions = [...proof.positions, ...catalog.filter((position2) => !checked.has(position2.yieldOption.address.toLowerCase()))];
6190
+ const checked = new Set(proof.positions.map((position2) => position2.vaultOption.address.toLowerCase()));
6191
+ positions = [...proof.positions, ...catalog.filter((position2) => !checked.has(position2.vaultOption.address.toLowerCase()))];
5910
6192
  } else {
5911
6193
  positionsStatus = "unavailable";
5912
6194
  break;
5913
6195
  }
5914
6196
  positionsStatus = hasPositions(positions) ? "remaining" : "empty";
5915
- if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) >= before.get(position2.yieldOption.address.toLowerCase()))) break;
6197
+ if (positions.some((position2) => BigInt(position2.withdrawableAssetsRaw) > 0n && before.has(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) >= before.get(position2.vaultOption.address.toLowerCase()))) break;
5916
6198
  }
5917
6199
  } else {
5918
6200
  for (const position2 of positions) {
@@ -5925,7 +6207,7 @@ var YieldseekerAgent = class {
5925
6207
  }
5926
6208
  }
5927
6209
  if (amount !== void 0 && remaining > 0n) {
5928
- throw this.invalidResponse("yield positions", {
6210
+ throw this.invalidResponse("vault positions", {
5929
6211
  reason: "Withdrawable positions could not cover the request.",
5930
6212
  remaining: remaining.toString()
5931
6213
  });
@@ -5957,15 +6239,22 @@ var YieldseekerAgent = class {
5957
6239
  delta: maxSettlementBasis.delta - payout,
5958
6240
  expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
5959
6241
  } : this.snapshotMovement(state, chainId, context, -payout);
5960
- const account = (0, import_viem13.getAddress)(state.walletAddress);
6242
+ const account = (0, import_viem14.getAddress)(state.walletAddress);
5961
6243
  let receipt;
5962
6244
  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" };
6245
+ if (amount === void 0) base7.maxCompletion = {
6246
+ userId: context.user.userId,
6247
+ agentId: context.agent.agentId,
6248
+ vaults: [...knownPositions.keys()],
6249
+ inventoryComplete: funds.complete
6250
+ };
6251
+ if (base7.maxCompletion) notifyFunding(onProgress, base7);
5963
6252
  let submissionEvidence = base7;
5964
- const data = amount === void 0 ? (0, import_viem13.encodeFunctionData)({
6253
+ const data = amount === void 0 ? (0, import_viem14.encodeFunctionData)({
5965
6254
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
5966
6255
  functionName: "withdrawAllAssetToUser",
5967
6256
  args: [account, metadata.address]
5968
- }) : (0, import_viem13.encodeFunctionData)({
6257
+ }) : (0, import_viem14.encodeFunctionData)({
5969
6258
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
5970
6259
  functionName: "withdrawAssetToUser",
5971
6260
  args: [account, metadata.address, payout]
@@ -5977,11 +6266,12 @@ var YieldseekerAgent = class {
5977
6266
  client: this.getReceiptClient(),
5978
6267
  apiKey: this.owneyApiKey,
5979
6268
  routingApiBaseUrl: this.routingApiBaseUrl,
6269
+ assertCurrent: assertOperation,
5980
6270
  yieldseekerSignature: await this.auth.getToken(state, chainId),
5981
6271
  refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
5982
6272
  userId: context.user.userId,
5983
6273
  agentId: context.agent.agentId,
5984
- agentWallet: (0, import_viem13.getAddress)(context.wallet.walletAddress),
6274
+ agentWallet: (0, import_viem14.getAddress)(context.wallet.walletAddress),
5985
6275
  asset: metadata.address,
5986
6276
  callData: data,
5987
6277
  onPending: (pending) => {
@@ -5989,6 +6279,7 @@ var YieldseekerAgent = class {
5989
6279
  notifyFunding(onProgress, submissionEvidence);
5990
6280
  },
5991
6281
  onBeforeSubmission: async (pending) => {
6282
+ assertOperation?.();
5992
6283
  await onBeforeSubmission?.({ ...base7, ...pending });
5993
6284
  onOwnerTransferAttempted?.();
5994
6285
  }
@@ -5997,15 +6288,26 @@ var YieldseekerAgent = class {
5997
6288
  receipt = submitted.receipt;
5998
6289
  } else {
5999
6290
  onOwnerTransferAttempted?.();
6000
- txHash = await this.submitTransaction(state, chainId, {
6001
- from: account,
6002
- to: (0, import_viem13.getAddress)(context.wallet.walletAddress),
6003
- data,
6004
- value: "0",
6005
- chainId
6006
- }, (hash2) => notifyFunding(onProgress, { ...base7, txHash: hash2 }), (value) => {
6007
- receipt = value;
6008
- });
6291
+ txHash = await this.submitTransaction(
6292
+ state,
6293
+ chainId,
6294
+ {
6295
+ from: account,
6296
+ to: (0, import_viem14.getAddress)(context.wallet.walletAddress),
6297
+ data,
6298
+ value: "0",
6299
+ chainId
6300
+ },
6301
+ (hash2) => notifyFunding(onProgress, { ...base7, txHash: hash2 }),
6302
+ (value) => {
6303
+ receipt = value;
6304
+ },
6305
+ async () => {
6306
+ assertOperation?.();
6307
+ await onBeforeSubmission?.(base7);
6308
+ assertOperation?.();
6309
+ }
6310
+ );
6009
6311
  }
6010
6312
  const evidence = receipt ? inspectFundingReceipt(receipt, { ...submissionEvidence, txHash }, metadata.address, "withdraw") : { ...submissionEvidence, txHash, transfer: "pending", delivery: "pending" };
6011
6313
  notifyFunding(onProgress, evidence);
@@ -6050,7 +6352,7 @@ var YieldseekerAgent = class {
6050
6352
  asset,
6051
6353
  positionsStatus,
6052
6354
  remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
6053
- vaultAddress: position2.yieldOption.address,
6355
+ vaultAddress: position2.vaultOption.address,
6054
6356
  sharesRaw: position2.assetsRaw,
6055
6357
  withdrawableAmountRaw: position2.withdrawableAssetsRaw
6056
6358
  })) : void 0,
@@ -6059,6 +6361,7 @@ var YieldseekerAgent = class {
6059
6361
  this.id
6060
6362
  );
6061
6363
  }
6364
+ if (evidence.maxCompletion) evidence.maxCompletion.vaults = [...knownPositions.keys()];
6062
6365
  if (!proof.verified) completion = "unverified";
6063
6366
  }
6064
6367
  if (explicitEmptyPayout)
@@ -6116,17 +6419,76 @@ var YieldseekerAgent = class {
6116
6419
  );
6117
6420
  return proof.available;
6118
6421
  }
6422
+ /** Recheck MAX without re-unwinding, paying out, or requesting a signature. */
6423
+ async verifyWithdrawalCompletion(state, evidence) {
6424
+ const unknown = (reason2) => ({ status: "unavailable", reason: reason2 });
6425
+ const proof = evidence.maxCompletion;
6426
+ if (evidence.agentId !== this.id || evidence.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase() || evidence.chainId !== 8453 || !proof || !evidence.agentWallet || !(0, import_viem14.isAddress)(evidence.agentWallet) || !evidence.blockNumber || !/^\d+$/.test(evidence.blockNumber) || evidence.transfer !== "confirmed" || evidence.delivery !== "confirmed" || !this.isTransactionHash(evidence.txHash)) return unknown("PAYOUT_PROOF_UNAVAILABLE");
6427
+ this.assertAsset(evidence.token);
6428
+ const asset = evidence.token;
6429
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
6430
+ const signature = this.auth.peekToken(state, evidence.chainId);
6431
+ if (!signature) return unknown("AUTHENTICATION_REQUIRED");
6432
+ let reason = "CATALOG_UNAVAILABLE";
6433
+ try {
6434
+ return await bounded(async () => {
6435
+ const response = await this.api.request(
6436
+ `/users/${encodeURIComponent(proof.userId)}/agents/${encodeURIComponent(proof.agentId)}/vault-positions`,
6437
+ { signature }
6438
+ );
6439
+ if (!Array.isArray(response?.vaultPositions)) return unknown("CATALOG_INVALID");
6440
+ const positions = this.validateWithdrawalPositions(response.vaultPositions, evidence.chainId, asset);
6441
+ const vaults = new Set([...proof.vaults, ...positions.map((p) => p.vaultOption.address)].map((a) => a.toLowerCase()));
6442
+ if (!proof.inventoryComplete) {
6443
+ const catalog = await this.loadVaultOptions(asset);
6444
+ const eligible = catalog.filter((o) => o.chainId === evidence.chainId && o.assetAddress?.toLowerCase() === metadata.address.toLowerCase() && (0, import_viem14.isAddress)(o.address));
6445
+ if (!eligible.length) return unknown("INVENTORY_INCOMPLETE");
6446
+ eligible.forEach((o) => vaults.add(o.address.toLowerCase()));
6447
+ }
6448
+ if (vaults.size > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT || [...vaults].some((a) => !(0, import_viem14.isAddress)(a)))
6449
+ return unknown("INVENTORY_INVALID");
6450
+ reason = "VAULT_PROOF_UNAVAILABLE";
6451
+ const blockNumber = BigInt(evidence.blockNumber);
6452
+ const addresses = [...vaults];
6453
+ const shares = await this.readVaultShares(addresses.map((address) => ({ address })), evidence.agentWallet, blockNumber);
6454
+ const checks = await Promise.all(addresses.map(async (address, i) => {
6455
+ const group = shares.slice(i * 2, i * 2 + 2);
6456
+ if (group.length !== 2 || group.some((r) => r.status !== "success")) return "unavailable";
6457
+ const [underlying, value] = group.map((r) => r.result);
6458
+ if (typeof underlying !== "string" || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof value !== "bigint" || value < 0n) return "unavailable";
6459
+ if (value === 0n) return "complete";
6460
+ const amounts = await Promise.allSettled(["convertToAssets", "previewRedeem"].map((functionName) => this.getReceiptClient().readContract({
6461
+ address: (0, import_viem14.getAddress)(address),
6462
+ abi: import_viem14.erc4626Abi,
6463
+ functionName,
6464
+ args: [value],
6465
+ blockNumber
6466
+ })));
6467
+ if (amounts.some((r) => r.status === "fulfilled" && typeof r.value === "bigint" && r.value > 0n)) return "remaining";
6468
+ return amounts.every((r) => r.status === "fulfilled" && r.value === 0n) ? "complete" : "unavailable";
6469
+ }));
6470
+ const status = checks.includes("remaining") ? "remaining" : checks.includes("unavailable") ? "unavailable" : "complete";
6471
+ return {
6472
+ status,
6473
+ checkedVaults: addresses.length,
6474
+ ...status === "unavailable" ? { reason } : status === "remaining" ? { reason: "VAULT_REMAINDER" } : {}
6475
+ };
6476
+ }, 8e3, () => new Error("MAX proof read timed out"));
6477
+ } catch {
6478
+ return unknown(reason);
6479
+ }
6480
+ }
6119
6481
  async readVaultShares(options, wallet, blockNumber) {
6120
- const owner = (0, import_viem13.getAddress)(wallet);
6482
+ const owner = (0, import_viem14.getAddress)(wallet);
6121
6483
  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));
6122
6484
  const results = await Promise.all(batches.map((batch) => this.getReceiptClient().multicall({
6123
6485
  blockNumber,
6124
6486
  allowFailure: true,
6125
6487
  contracts: batch.flatMap((option) => {
6126
- const address = (0, import_viem13.getAddress)(option.address);
6488
+ const address = (0, import_viem14.getAddress)(option.address);
6127
6489
  return [
6128
- { address, abi: import_viem13.erc4626Abi, functionName: "asset" },
6129
- { address, abi: import_viem13.erc20Abi, functionName: "balanceOf", args: [owner] }
6490
+ { address, abi: import_viem14.erc4626Abi, functionName: "asset" },
6491
+ { address, abi: import_viem14.erc20Abi, functionName: "balanceOf", args: [owner] }
6130
6492
  ];
6131
6493
  })
6132
6494
  })));
@@ -6136,23 +6498,23 @@ var YieldseekerAgent = class {
6136
6498
  const underlying = YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase();
6137
6499
  const seen = /* @__PURE__ */ new Set();
6138
6500
  for (const value of values) {
6139
- const option = value?.yieldOption;
6140
- if (!option || typeof option.address !== "string" || !(0, import_viem13.isAddress)(option.address) || seen.has(option.address.toLowerCase()) || option.chainId !== void 0 && option.chainId !== chainId || option.assetAddress !== void 0 && (typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== underlying) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
6141
- throw this.invalidResponse("yield positions");
6501
+ const option = value?.vaultOption;
6502
+ if (!option || typeof option.address !== "string" || !(0, import_viem14.isAddress)(option.address) || seen.has(option.address.toLowerCase()) || option.chainId !== void 0 && option.chainId !== chainId || option.assetAddress !== void 0 && (typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== underlying) || typeof value.assetsRaw !== "string" || !/^\d+$/.test(value.assetsRaw) || typeof value.withdrawableAssetsRaw !== "string" || !/^\d+$/.test(value.withdrawableAssetsRaw))
6503
+ throw this.invalidResponse("vault positions");
6142
6504
  seen.add(option.address.toLowerCase());
6143
6505
  }
6144
6506
  return values;
6145
6507
  }
6146
6508
  withdrawalVaultHints(state, chainId, context) {
6147
6509
  const key2 = this.contextKey(state, chainId, context.asset);
6148
- const positions = this.readCache.get(`positions:${key2}`)?.value?.yieldPositions ?? [];
6149
- const hints = new Map(this.validateWithdrawalPositions(positions, chainId, context.asset).map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
6510
+ const positions = this.readCache.get(`positions:${key2}`)?.value?.vaultPositions ?? [];
6511
+ const hints = new Map(this.validateWithdrawalPositions(positions, chainId, context.asset).map((position2) => [position2.vaultOption.address.toLowerCase(), position2]));
6150
6512
  const historic = this.readCache.get(`historic:${key2}`)?.value?.position;
6151
6513
  const receivedAssets = new Set((historic?.movements ?? []).filter((movement) => movement.chainId === chainId && movement.agentWalletId === context.wallet.agentWalletId && movement.toAddress?.toLowerCase() === context.wallet.walletAddress.toLowerCase()).filter((movement) => typeof movement.assetAddress === "string").map((movement) => movement.assetAddress.toLowerCase()));
6152
- for (const option of this.yieldOptions.get(context.asset)?.value ?? []) {
6153
- if (option.chainId !== chainId || typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== YIELDSEEKER_ASSET_METADATA[context.asset].address.toLowerCase() || !(0, import_viem13.isAddress)(option.address) || !receivedAssets.has(option.address.toLowerCase()) || hints.has(option.address.toLowerCase())) continue;
6514
+ for (const option of this.vaultOptions.get(context.asset)?.value ?? []) {
6515
+ if (option.chainId !== chainId || typeof option.assetAddress !== "string" || option.assetAddress.toLowerCase() !== YIELDSEEKER_ASSET_METADATA[context.asset].address.toLowerCase() || !(0, import_viem14.isAddress)(option.address) || !receivedAssets.has(option.address.toLowerCase()) || hints.has(option.address.toLowerCase())) continue;
6154
6516
  hints.set(option.address.toLowerCase(), {
6155
- yieldOption: option,
6517
+ vaultOption: option,
6156
6518
  assetsRaw: "0",
6157
6519
  assetsBase: "0",
6158
6520
  assetsUsd: 0,
@@ -6186,7 +6548,7 @@ var YieldseekerAgent = class {
6186
6548
  async resolveWithdrawalLiquidity(state, chainId, context, positions, previousPositions = [], requested) {
6187
6549
  const generation = this.readGeneration;
6188
6550
  const contextKey = this.contextKey(state, chainId, context.asset);
6189
- const version = this.portfolioVersion(contextKey);
6551
+ const version = this.liquidityVersions.get(contextKey) ?? 0;
6190
6552
  const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
6191
6553
  const unavailable = () => new OwneyError(
6192
6554
  "WITHDRAW_BALANCE_UNAVAILABLE",
@@ -6194,30 +6556,30 @@ var YieldseekerAgent = class {
6194
6556
  { asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
6195
6557
  this.id
6196
6558
  );
6197
- let active = true;
6559
+ let active2 = true;
6198
6560
  let knownProof;
6199
- const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.resolvedVaults.includes(position2.yieldOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
6561
+ const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.resolvedVaults.includes(position2.vaultOption.address.toLowerCase()) && BigInt(position2.withdrawableAssetsRaw) > 0n);
6200
6562
  const assertReadCurrent = () => {
6201
6563
  this.assertSession(generation, chainId, context.asset);
6202
- if (!active || this.portfolioVersion(contextKey) !== version) throw unavailable();
6564
+ if (!active2 || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
6203
6565
  };
6204
6566
  const read2 = async () => {
6205
6567
  const client = this.getReceiptClient();
6206
6568
  const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
6207
- const wallet = (0, import_viem13.getAddress)(context.wallet.walletAddress);
6569
+ const wallet = (0, import_viem14.getAddress)(context.wallet.walletAddress);
6208
6570
  assertReadCurrent();
6209
6571
  if (typeof blockNumber !== "bigint") throw unavailable();
6210
6572
  const indexedPositions = this.validateWithdrawalPositions(positions ?? [], chainId, context.asset);
6211
6573
  const candidates = /* @__PURE__ */ new Map();
6212
6574
  for (const position2 of this.validateWithdrawalPositions([...previousPositions], chainId, context.asset))
6213
- candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
6214
- for (const position2 of indexedPositions) candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
6575
+ candidates.set(position2.vaultOption.address.toLowerCase(), position2.vaultOption);
6576
+ for (const position2 of indexedPositions) candidates.set(position2.vaultOption.address.toLowerCase(), position2.vaultOption);
6215
6577
  const options = [...candidates.values()];
6216
6578
  if (options.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
6217
6579
  const [idle, feeTracker] = await Promise.all([
6218
6580
  client.readContract({
6219
6581
  address: metadata.address,
6220
- abi: import_viem13.erc20Abi,
6582
+ abi: import_viem14.erc20Abi,
6221
6583
  functionName: "balanceOf",
6222
6584
  args: [wallet],
6223
6585
  blockNumber
@@ -6229,10 +6591,10 @@ var YieldseekerAgent = class {
6229
6591
  blockNumber
6230
6592
  })
6231
6593
  ]);
6232
- if (typeof idle !== "bigint" || idle < 0n || !(0, import_viem13.isAddress)(feeTracker) || /^0x0{40}$/i.test(feeTracker)) throw unavailable();
6594
+ if (typeof idle !== "bigint" || idle < 0n || !(0, import_viem14.isAddress)(feeTracker) || /^0x0{40}$/i.test(feeTracker)) throw unavailable();
6233
6595
  assertReadCurrent();
6234
6596
  const fees = await client.readContract({
6235
- address: (0, import_viem13.getAddress)(feeTracker),
6597
+ address: (0, import_viem14.getAddress)(feeTracker),
6236
6598
  abi: YIELDSEEKER_FEE_TRACKER_ABI,
6237
6599
  functionName: "getFeesOwed",
6238
6600
  args: [wallet],
@@ -6255,15 +6617,15 @@ var YieldseekerAgent = class {
6255
6617
  assertReadCurrent();
6256
6618
  knownProof = proof(false);
6257
6619
  if (requested !== void 0 && knownProof.available >= requested) return knownProof;
6258
- const indexed = new Map(indexedPositions.map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
6620
+ const indexed = new Map(indexedPositions.map((position2) => [position2.vaultOption.address.toLowerCase(), position2]));
6259
6621
  const indexedValue = indexedPositions.reduce((sum, position2) => sum + BigInt(position2.withdrawableAssetsRaw), 0n);
6260
- const missingKnownPosition = previousPositions.some((position2) => !indexed.has(position2.yieldOption.address.toLowerCase()));
6622
+ const missingKnownPosition = previousPositions.some((position2) => !indexed.has(position2.vaultOption.address.toLowerCase()));
6261
6623
  const unvaluedPosition = indexedPositions.some((position2) => BigInt(position2.assetsRaw) > 0n && BigInt(position2.withdrawableAssetsRaw) === 0n);
6262
6624
  const indexedAvailable = idle + indexedValue > fees ? idle + indexedValue - fees : 0n;
6263
6625
  if (indexedValue > 0n && !missingKnownPosition && !unvaluedPosition && (requested === void 0 || indexedAvailable >= requested)) {
6264
6626
  const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
6265
6627
  for (const position2 of indexedPositions) if (BigInt(position2.withdrawableAssetsRaw) > 0n)
6266
- known.add(position2.yieldOption.address.toLowerCase());
6628
+ known.add(position2.vaultOption.address.toLowerCase());
6267
6629
  this.balanceVaults.set(contextKey, known);
6268
6630
  return {
6269
6631
  blockNumber,
@@ -6284,10 +6646,10 @@ var YieldseekerAgent = class {
6284
6646
  const group = results.slice(index * 2, index * 2 + 2);
6285
6647
  if (group.length !== 2 || group.some((value2) => value2.status !== "success")) return false;
6286
6648
  const [underlying, shares] = group.map((value2) => value2.result);
6287
- if (typeof underlying !== "string" || !(0, import_viem13.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return false;
6649
+ if (typeof underlying !== "string" || !(0, import_viem14.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return false;
6288
6650
  if (shares > 0n) known.add(option.address.toLowerCase());
6289
6651
  const position2 = {
6290
- yieldOption: option,
6652
+ vaultOption: option,
6291
6653
  assetsRaw: shares.toString(),
6292
6654
  assetsBase: "0",
6293
6655
  assetsUsd: 0,
@@ -6301,8 +6663,8 @@ var YieldseekerAgent = class {
6301
6663
  let preview;
6302
6664
  try {
6303
6665
  const value2 = await client.readContract({
6304
- address: (0, import_viem13.getAddress)(option.address),
6305
- abi: import_viem13.erc4626Abi,
6666
+ address: (0, import_viem14.getAddress)(option.address),
6667
+ abi: import_viem14.erc4626Abi,
6306
6668
  functionName: "previewRedeem",
6307
6669
  args: [shares],
6308
6670
  blockNumber
@@ -6315,8 +6677,8 @@ var YieldseekerAgent = class {
6315
6677
  if (value === void 0 || value === 0n) {
6316
6678
  try {
6317
6679
  const converted = await client.readContract({
6318
- address: (0, import_viem13.getAddress)(option.address),
6319
- abi: import_viem13.erc4626Abi,
6680
+ address: (0, import_viem14.getAddress)(option.address),
6681
+ abi: import_viem14.erc4626Abi,
6320
6682
  functionName: "convertToAssets",
6321
6683
  args: [shares],
6322
6684
  blockNumber
@@ -6343,7 +6705,7 @@ var YieldseekerAgent = class {
6343
6705
  knownProof = proof(false);
6344
6706
  if (requested !== void 0 && knownProof.available >= requested) return knownProof;
6345
6707
  const discrepancy = verified.some((position2) => {
6346
- const row = indexed.get(position2.yieldOption.address.toLowerCase());
6708
+ const row = indexed.get(position2.vaultOption.address.toLowerCase());
6347
6709
  return row ? BigInt(position2.withdrawableAssetsRaw) < BigInt(row.withdrawableAssetsRaw) : BigInt(position2.assetsRaw) > 0n;
6348
6710
  });
6349
6711
  const typedCoveredByIdle = requested !== void 0 && idle >= requested + fees;
@@ -6352,11 +6714,11 @@ var YieldseekerAgent = class {
6352
6714
  assertReadCurrent();
6353
6715
  return proof(true);
6354
6716
  }
6355
- const catalog = await this.loadYieldOptions(context.asset).catch(() => void 0);
6717
+ const catalog = await this.loadVaultOptions(context.asset).catch(() => void 0);
6356
6718
  assertReadCurrent();
6357
6719
  const eligible = /* @__PURE__ */ new Map();
6358
6720
  for (const option of catalog ?? []) {
6359
- if (option?.chainId === chainId && typeof option.assetAddress === "string" && option.assetAddress.toLowerCase() === metadata.address.toLowerCase() && (0, import_viem13.isAddress)(option.address))
6721
+ if (option?.chainId === chainId && typeof option.assetAddress === "string" && option.assetAddress.toLowerCase() === metadata.address.toLowerCase() && (0, import_viem14.isAddress)(option.address))
6360
6722
  eligible.set(option.address.toLowerCase(), option);
6361
6723
  }
6362
6724
  const extra = [...eligible].filter(([address]) => !candidates.has(address)).map(([, option]) => option);
@@ -6378,7 +6740,7 @@ var YieldseekerAgent = class {
6378
6740
  if (error instanceof OwneyError) throw error;
6379
6741
  throw unavailable();
6380
6742
  } finally {
6381
- active = false;
6743
+ active2 = false;
6382
6744
  if (timer) clearTimeout(timer);
6383
6745
  }
6384
6746
  }
@@ -6392,7 +6754,7 @@ var YieldseekerAgent = class {
6392
6754
  this.balanceContexts.set(read2, { context, generation: this.readGeneration });
6393
6755
  const key2 = this.contextKey(state, chainId, context.asset);
6394
6756
  const known = this.balanceVaults.get(key2) ?? /* @__PURE__ */ new Set();
6395
- for (const position2 of context.positions) known.add(position2.yieldOption.address.toLowerCase());
6757
+ for (const position2 of context.positions) known.add(position2.vaultOption.address.toLowerCase());
6396
6758
  this.balanceVaults.set(key2, known);
6397
6759
  }
6398
6760
  return balance;
@@ -6407,7 +6769,7 @@ var YieldseekerAgent = class {
6407
6769
  const key2 = this.contextKey(state, evidence.chainId, evidence.token);
6408
6770
  const generation = this.readGeneration, version = this.portfolioVersion(key2);
6409
6771
  if (read2.readVersion !== version) return void 0;
6410
- const wallet = (0, import_viem13.getAddress)(context.wallet.walletAddress);
6772
+ const wallet = (0, import_viem14.getAddress)(context.wallet.walletAddress);
6411
6773
  const asset = YIELDSEEKER_ASSET_METADATA[evidence.token];
6412
6774
  const client = this.getReceiptClient();
6413
6775
  const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
@@ -6415,10 +6777,10 @@ var YieldseekerAgent = class {
6415
6777
  const vaults = [...this.balanceVaults.get(key2) ?? []];
6416
6778
  const [invested, idle, tracker] = await Promise.all([
6417
6779
  remainingVaultBalance(client, wallet, asset.address, vaults, blockNumber),
6418
- client.readContract({ address: asset.address, abi: import_viem13.erc20Abi, functionName: "balanceOf", args: [wallet], blockNumber }),
6780
+ client.readContract({ address: asset.address, abi: import_viem14.erc20Abi, functionName: "balanceOf", args: [wallet], blockNumber }),
6419
6781
  client.readContract({ address: wallet, abi: YIELDSEEKER_AGENT_WALLET_ABI, functionName: "feeTracker", blockNumber })
6420
6782
  ]);
6421
- if (invested === void 0 || typeof idle !== "bigint" || idle < 0n || !(0, import_viem13.isAddress)(tracker) || /^0x0{40}$/i.test(tracker)) return void 0;
6783
+ if (invested === void 0 || typeof idle !== "bigint" || idle < 0n || !(0, import_viem14.isAddress)(tracker) || /^0x0{40}$/i.test(tracker)) return void 0;
6422
6784
  const fees = await client.readContract({
6423
6785
  address: tracker,
6424
6786
  abi: YIELDSEEKER_FEE_TRACKER_ABI,
@@ -6431,7 +6793,7 @@ var YieldseekerAgent = class {
6431
6793
  const total = idle + invested > fees ? idle + invested - fees : 0n;
6432
6794
  const displayed = balance.assetBalances?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
6433
6795
  if (!displayed || !evidence.confirmedAmount || !/^\d+$/.test(evidence.confirmedAmount)) return void 0;
6434
- const amount = (0, import_viem13.parseUnits)(displayed.amount, asset.decimals);
6796
+ const amount = (0, import_viem14.parseUnits)(displayed.amount, asset.decimals);
6435
6797
  if (amount < 0n) return void 0;
6436
6798
  const displayUnit = evidence.token === "USDC" ? 5000n : 500000000000n;
6437
6799
  const movementUnit = BigInt(evidence.confirmedAmount) / 1000n;
@@ -6486,9 +6848,9 @@ var YieldseekerAgent = class {
6486
6848
  if (!context) return void 0;
6487
6849
  const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
6488
6850
  address: YIELDSEEKER_ASSET_METADATA[asset].address,
6489
- abi: import_viem13.erc20Abi,
6851
+ abi: import_viem14.erc20Abi,
6490
6852
  functionName: "balanceOf",
6491
- args: [(0, import_viem13.getAddress)(context.wallet.walletAddress)]
6853
+ args: [(0, import_viem14.getAddress)(context.wallet.walletAddress)]
6492
6854
  }), chainId, "eth_call", this.id);
6493
6855
  this.assertSession(generation, chainId, asset);
6494
6856
  if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
@@ -6530,13 +6892,13 @@ var YieldseekerAgent = class {
6530
6892
  });
6531
6893
  const catalog = await Promise.all(
6532
6894
  [...new Set(contexts.map((context) => context.asset))].map(
6533
- (contextAsset) => this.loadYieldOptions(contextAsset)
6895
+ (contextAsset) => this.loadVaultOptions(contextAsset)
6534
6896
  )
6535
6897
  );
6536
6898
  const vaultAddresses = new Set(
6537
6899
  catalog.flat().filter(
6538
- (yieldOption) => yieldOption.chainId === chainId && (0, import_viem13.isAddress)(yieldOption.address)
6539
- ).map((yieldOption) => yieldOption.address.toLowerCase())
6900
+ (vaultOption) => vaultOption.chainId === chainId && (0, import_viem14.isAddress)(vaultOption.address)
6901
+ ).map((vaultOption) => vaultOption.address.toLowerCase())
6540
6902
  );
6541
6903
  return mapYieldseekerHistory(contexts, {
6542
6904
  limit: options?.limit ?? 10,
@@ -6561,30 +6923,30 @@ var YieldseekerAgent = class {
6561
6923
  const assets = requested ? [requested] : ["USDC", "WETH"];
6562
6924
  const values = await Promise.all(
6563
6925
  assets.map(async (asset) => {
6564
- return { asset, yieldOptions: await this.loadYieldOptions(asset) };
6926
+ return { asset, vaultOptions: await this.loadVaultOptions(asset) };
6565
6927
  })
6566
6928
  );
6567
6929
  return mapYieldseekerAgentApy(values, days);
6568
6930
  }
6569
- async loadYieldOptions(asset) {
6570
- const cached = this.yieldOptions.get(asset);
6931
+ async loadVaultOptions(asset) {
6932
+ const cached = this.vaultOptions.get(asset);
6571
6933
  if (cached && cached.expiresAt > Date.now()) return cached.value;
6572
- const pending = this.pendingYieldOptions.get(asset);
6934
+ const pending = this.pendingVaultOptions.get(asset);
6573
6935
  if (pending) return pending;
6574
6936
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
6575
6937
  const request2 = this.api.request(
6576
- `/chains/8453/assets/${metadata.address}/yield-options`
6938
+ `/chains/8453/assets/${metadata.address}/vault-options`
6577
6939
  ).then((response) => {
6578
- if (!Array.isArray(response?.yieldOptions)) {
6579
- throw this.invalidResponse("yield options");
6940
+ if (!Array.isArray(response?.vaultOptions)) {
6941
+ throw this.invalidResponse("vault options");
6580
6942
  }
6581
- this.yieldOptions.set(asset, {
6582
- expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
6583
- value: response.yieldOptions
6943
+ this.vaultOptions.set(asset, {
6944
+ expiresAt: Date.now() + YIELDSEEKER_VAULT_OPTIONS_CACHE_MS,
6945
+ value: response.vaultOptions
6584
6946
  });
6585
- return response.yieldOptions;
6586
- }).finally(() => this.pendingYieldOptions.delete(asset));
6587
- this.pendingYieldOptions.set(asset, request2);
6947
+ return response.vaultOptions;
6948
+ }).finally(() => this.pendingVaultOptions.delete(asset));
6949
+ this.pendingVaultOptions.set(asset, request2);
6588
6950
  return request2;
6589
6951
  }
6590
6952
  userKey(state, chainId) {
@@ -6664,7 +7026,7 @@ var YieldseekerAgent = class {
6664
7026
  `/users/${user.userId}/agents/${agent.agentId}/wallet`
6665
7027
  ).then((walletResponse) => {
6666
7028
  this.assertSession(generation, chainId, asset);
6667
- if (!walletResponse?.agentWallet || !(0, import_viem13.isAddress)(walletResponse.agentWallet.walletAddress)) {
7029
+ if (!walletResponse?.agentWallet || !(0, import_viem14.isAddress)(walletResponse.agentWallet.walletAddress)) {
6668
7030
  throw this.invalidResponse("agent wallet");
6669
7031
  }
6670
7032
  const context = {
@@ -6694,7 +7056,7 @@ var YieldseekerAgent = class {
6694
7056
  this.users.set(key2, persisted);
6695
7057
  return persisted;
6696
7058
  }
6697
- const walletAddress = (0, import_viem13.getAddress)(state.walletAddress);
7059
+ const walletAddress = (0, import_viem14.getAddress)(state.walletAddress);
6698
7060
  const generation = this.readGeneration;
6699
7061
  let user = null;
6700
7062
  try {
@@ -6894,9 +7256,10 @@ var YieldseekerAgent = class {
6894
7256
  expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
6895
7257
  };
6896
7258
  }
6897
- advancePortfolioVersion(contextKey) {
7259
+ advancePortfolioVersion(contextKey, { snapshotOnly = false } = {}) {
6898
7260
  const version = this.portfolioVersion(contextKey) + 1;
6899
7261
  this.portfolioVersions.set(contextKey, version);
7262
+ if (!snapshotOnly) this.liquidityVersions.set(contextKey, version);
6900
7263
  this.snapshotFailures.delete(contextKey);
6901
7264
  for (const kind of ["snapshot", "positions", "historic", "actions"]) {
6902
7265
  const key2 = `${kind}:${contextKey}`;
@@ -6944,7 +7307,7 @@ var YieldseekerAgent = class {
6944
7307
  if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
6945
7308
  const failure = this.snapshotFailures.get(contextKey);
6946
7309
  if (failure && failure.retryAt > Date.now()) throw failure.error;
6947
- this.advancePortfolioVersion(contextKey);
7310
+ this.advancePortfolioVersion(contextKey, { snapshotOnly: true });
6948
7311
  }
6949
7312
  const version = this.portfolioVersion(contextKey);
6950
7313
  const generation = this.readGeneration;
@@ -7027,17 +7390,17 @@ var YieldseekerAgent = class {
7027
7390
  }
7028
7391
  async loadPortfolioParts(state, chainId, context, options, contextKey) {
7029
7392
  const [positions, historic, actions] = await Promise.all([
7030
- options.includePositions === false ? Promise.resolve({ yieldPositions: [] }) : this.cachedRead(
7393
+ options.includePositions === false ? Promise.resolve({ vaultPositions: [] }) : this.cachedRead(
7031
7394
  `positions:${contextKey}`,
7032
7395
  this.portfolioCacheMs(contextKey),
7033
7396
  async () => {
7034
7397
  const response = await this.walletRequest(
7035
7398
  state,
7036
7399
  chainId,
7037
- this.agentPath(context, "yield-positions")
7400
+ this.agentPath(context, "vault-positions")
7038
7401
  );
7039
- if (!Array.isArray(response?.yieldPositions)) {
7040
- throw this.invalidResponse("yield positions");
7402
+ if (!Array.isArray(response?.vaultPositions)) {
7403
+ throw this.invalidResponse("vault positions");
7041
7404
  }
7042
7405
  return response;
7043
7406
  }
@@ -7062,7 +7425,7 @@ var YieldseekerAgent = class {
7062
7425
  ) : Promise.resolve(void 0)
7063
7426
  ]);
7064
7427
  return {
7065
- positions: positions.yieldPositions,
7428
+ positions: positions.vaultPositions,
7066
7429
  ...historic?.position ? { historic: historic.position } : {},
7067
7430
  ...actions?.actions ? { actions: actions.actions } : {}
7068
7431
  };
@@ -7076,7 +7439,7 @@ var YieldseekerAgent = class {
7076
7439
  this.agentPath(context, "deploy"),
7077
7440
  { method: "POST", body: {} }
7078
7441
  );
7079
- if (!deployed?.agentWallet || !(0, import_viem13.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
7442
+ if (!deployed?.agentWallet || !(0, import_viem14.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
7080
7443
  throw this.invalidResponse("agent deployment", {
7081
7444
  reason: "Deploy did not return the expected Agent Wallet."
7082
7445
  });
@@ -7133,10 +7496,10 @@ var YieldseekerAgent = class {
7133
7496
  }
7134
7497
  async providerRequest(state, chainId, path, options = {}) {
7135
7498
  this.assertChain(chainId);
7136
- const request2 = (signature2) => this.api.request(path, {
7137
- ...options,
7138
- signature: signature2
7139
- });
7499
+ const request2 = (signature2) => {
7500
+ options.assertCurrent?.();
7501
+ return this.api.request(path, { method: options.method, body: options.body, signature: signature2 });
7502
+ };
7140
7503
  let signature = await this.auth.getToken(state, chainId);
7141
7504
  try {
7142
7505
  return await request2(signature);
@@ -7185,18 +7548,18 @@ var YieldseekerAgent = class {
7185
7548
  return hash3;
7186
7549
  }
7187
7550
  this.assertTransaction(transaction, state, chainId);
7188
- const account = (0, import_viem13.getAddress)(state.walletAddress);
7189
- const walletClient = (0, import_viem13.createWalletClient)({
7551
+ const account = (0, import_viem14.getAddress)(state.walletAddress);
7552
+ const walletClient = (0, import_viem14.createWalletClient)({
7190
7553
  account,
7191
7554
  chain: import_chains4.base,
7192
- transport: onBeforeSubmission ? (0, import_viem13.custom)({ request: async (args) => {
7555
+ transport: onBeforeSubmission ? (0, import_viem14.custom)({ request: async (args) => {
7193
7556
  if (args.method === "eth_sendTransaction") await onBeforeSubmission();
7194
7557
  return state.provider.request(args);
7195
- } }, { retryCount: 0 }) : (0, import_viem13.custom)(state.provider)
7558
+ } }, { retryCount: 0 }) : (0, import_viem14.custom)(state.provider)
7196
7559
  });
7197
- const walletChainClient = (0, import_viem13.createPublicClient)({
7560
+ const walletChainClient = (0, import_viem14.createPublicClient)({
7198
7561
  chain: import_chains4.base,
7199
- transport: (0, import_viem13.custom)(state.provider)
7562
+ transport: (0, import_viem14.custom)(state.provider)
7200
7563
  });
7201
7564
  await ensureWalletOnChain(
7202
7565
  walletChainClient,
@@ -7206,7 +7569,7 @@ var YieldseekerAgent = class {
7206
7569
  const hash2 = await walletClient.sendTransaction({
7207
7570
  account,
7208
7571
  chain: import_chains4.base,
7209
- to: (0, import_viem13.getAddress)(transaction.to),
7572
+ to: (0, import_viem14.getAddress)(transaction.to),
7210
7573
  data: transaction.data,
7211
7574
  value: BigInt(transaction.value)
7212
7575
  });
@@ -7310,12 +7673,12 @@ var YieldseekerAgent = class {
7310
7673
  return receipt;
7311
7674
  }
7312
7675
  assertTransaction(transaction, state, chainId) {
7313
- if (!transaction || typeof transaction.from !== "string" || !(0, import_viem13.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem13.isAddress)(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 || (0, import_viem13.getAddress)(transaction.from) !== (0, import_viem13.getAddress)(state.walletAddress)) {
7676
+ if (!transaction || typeof transaction.from !== "string" || !(0, import_viem14.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem14.isAddress)(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 || (0, import_viem14.getAddress)(transaction.from) !== (0, import_viem14.getAddress)(state.walletAddress)) {
7314
7677
  throw this.invalidResponse("transaction");
7315
7678
  }
7316
7679
  }
7317
7680
  assertAgent(agent) {
7318
- if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem13.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
7681
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem14.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
7319
7682
  throw this.invalidResponse("agent");
7320
7683
  }
7321
7684
  }
@@ -7438,45 +7801,57 @@ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
7438
7801
  // src/lib/swap/swap-api.ts
7439
7802
  async function request(baseUrl, apiKey, path, init) {
7440
7803
  const url = `${baseUrl}/api/v1/swap${path}`;
7441
- const res = await fetch(url, {
7442
- method: init?.method ?? "GET",
7443
- headers: {
7444
- "Content-Type": "application/json",
7445
- "x-owney-api-key": apiKey
7446
- },
7447
- ...init ? { body: JSON.stringify(init.body) } : {}
7448
- });
7449
- if (!res.ok) {
7450
- const text = await res.text().catch(() => "");
7451
- if (res.status === 429) {
7452
- throw new OwneyError(
7453
- "SWAP_RATE_LIMITED",
7454
- "Swap provider is rate limiting, retry shortly",
7455
- { statusCode: res.status }
7456
- );
7457
- }
7458
- if (res.status === 403) {
7459
- throw new OwneyError(
7460
- "SWAP_DISABLED",
7461
- "Swap is not enabled for this organization",
7462
- { statusCode: res.status }
7463
- );
7464
- }
7465
- throw new OwneyError(
7466
- "SWAP_REQUEST_FAILED",
7467
- `Swap API error ${res.status}: ${text}`,
7468
- { statusCode: res.status, responseBody: text }
7469
- );
7470
- }
7471
- const json = await res.json();
7472
- if (!json.success) {
7473
- throw new OwneyError(
7804
+ const controller = new AbortController();
7805
+ try {
7806
+ return await bounded(async () => {
7807
+ const res = await fetch(url, {
7808
+ signal: controller.signal,
7809
+ method: init?.method ?? "GET",
7810
+ headers: {
7811
+ "Content-Type": "application/json",
7812
+ "x-owney-api-key": apiKey
7813
+ },
7814
+ ...init ? { body: JSON.stringify(init.body) } : {}
7815
+ });
7816
+ if (!res.ok) {
7817
+ const text = await res.text().catch(() => "");
7818
+ if (res.status === 429) {
7819
+ throw new OwneyError(
7820
+ "SWAP_RATE_LIMITED",
7821
+ "Swap provider is rate limiting, retry shortly",
7822
+ { statusCode: res.status }
7823
+ );
7824
+ }
7825
+ if (res.status === 403) {
7826
+ throw new OwneyError(
7827
+ "SWAP_DISABLED",
7828
+ "Swap is not enabled for this organization",
7829
+ { statusCode: res.status }
7830
+ );
7831
+ }
7832
+ throw new OwneyError(
7833
+ "SWAP_REQUEST_FAILED",
7834
+ `Swap API error ${res.status}: ${text}`,
7835
+ { statusCode: res.status, responseBody: text }
7836
+ );
7837
+ }
7838
+ const json = await res.json();
7839
+ if (!json.success) {
7840
+ throw new OwneyError(
7841
+ "SWAP_REQUEST_FAILED",
7842
+ `Swap API request failed: ${json.message ?? "unknown error"}`,
7843
+ { message: json.message }
7844
+ );
7845
+ }
7846
+ return json.data;
7847
+ }, 2e4, () => new OwneyError(
7474
7848
  "SWAP_REQUEST_FAILED",
7475
- `Swap API request failed: ${json.message ?? "unknown error"}`,
7476
- { message: json.message }
7477
- );
7849
+ "Swap request timed out. Its outcome may still need confirmation.",
7850
+ { requestTimedOut: true, phase: path === "/order" ? "submit" : "read" }
7851
+ ));
7852
+ } finally {
7853
+ controller.abort();
7478
7854
  }
7479
- return json.data;
7480
7855
  }
7481
7856
  function createSwapApi(baseUrl, apiKey) {
7482
7857
  return {
@@ -7600,7 +7975,7 @@ function createSwapApi(baseUrl, apiKey) {
7600
7975
  }
7601
7976
 
7602
7977
  // src/lib/swap/swap.rpc.ts
7603
- var import_viem14 = require("viem");
7978
+ var import_viem15 = require("viem");
7604
7979
  var DEFAULT_RPC_URLS = {
7605
7980
  // NOT cloudflare-eth.com, which used to lead this list. It answers
7606
7981
  // eth_chainId happily, errors on eth_blockNumber, and — the part that
@@ -7640,10 +8015,10 @@ var DEFAULT_RPC_URLS = {
7640
8015
  };
7641
8016
  function swapReadTransport(chainId, overrides) {
7642
8017
  const override = overrides?.[chainId];
7643
- if (override) return (0, import_viem14.http)(override);
8018
+ if (override) return (0, import_viem15.http)(override);
7644
8019
  const urls = DEFAULT_RPC_URLS[chainId];
7645
- if (!urls || urls.length === 0) return (0, import_viem14.http)();
7646
- return (0, import_viem14.fallback)(urls.map((url) => (0, import_viem14.http)(url)));
8020
+ if (!urls || urls.length === 0) return (0, import_viem15.http)();
8021
+ return (0, import_viem15.fallback)(urls.map((url) => (0, import_viem15.http)(url)));
7647
8022
  }
7648
8023
  function receiptTimeoutMs(chainId) {
7649
8024
  return chainId === 1 ? 6e5 : 18e4;
@@ -7772,8 +8147,12 @@ async function awaitBalanceIncrease(options) {
7772
8147
  let lastError;
7773
8148
  for (; ; ) {
7774
8149
  try {
7775
- const balance = await readBalance();
7776
- if (balance > baseline) {
8150
+ const balance = await bounded(
8151
+ readBalance,
8152
+ Math.max(1, Math.min(1e4, deadline - Date.now())),
8153
+ () => new Error("Balance observation timed out")
8154
+ );
8155
+ if (Date.now() <= deadline && balance > baseline) {
7777
8156
  const arrived = balance - baseline;
7778
8157
  debugLog("owney-sdk", "arrival: funds landed", {
7779
8158
  arrived: arrived.toString()
@@ -7799,12 +8178,12 @@ async function awaitBalanceIncrease(options) {
7799
8178
  lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
7800
8179
  } : { arrived: null };
7801
8180
  }
7802
- await sleep(pollMs);
8181
+ await sleep(Math.min(pollMs, Math.max(0, deadline - Date.now())));
7803
8182
  }
7804
8183
  }
7805
8184
 
7806
8185
  // src/lib/swap/swap.secrets.ts
7807
- var import_viem15 = require("viem");
8186
+ var import_viem16 = require("viem");
7808
8187
  var SECRET_BYTES = 32;
7809
8188
  function randomBytes(length) {
7810
8189
  const bytes = new Uint8Array(length);
@@ -7826,9 +8205,9 @@ function mintSecrets(count) {
7826
8205
  const secrets = [];
7827
8206
  const secretHashes = [];
7828
8207
  for (let i = 0; i < count; i++) {
7829
- const secret = (0, import_viem15.toHex)(randomBytes(SECRET_BYTES));
8208
+ const secret = (0, import_viem16.toHex)(randomBytes(SECRET_BYTES));
7830
8209
  secrets.push(secret);
7831
- secretHashes.push((0, import_viem15.keccak256)(secret));
8210
+ secretHashes.push((0, import_viem16.keccak256)(secret));
7832
8211
  }
7833
8212
  return { secrets, secretHashes };
7834
8213
  }
@@ -7852,7 +8231,7 @@ var DEFAULT_POLL_MS2 = 5e3;
7852
8231
  var MAX_BACKOFF_MS = 3e4;
7853
8232
  var backoffFor = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
7854
8233
  var DEFAULT_TIMEOUT_MS2 = 15 * 60 * 1e3;
7855
- async function runFusionOrder(deps, options) {
8234
+ async function runFusionOrderInternal(deps, options) {
7856
8235
  const {
7857
8236
  orderHash,
7858
8237
  secrets,
@@ -7861,39 +8240,51 @@ async function runFusionOrder(deps, options) {
7861
8240
  timeoutMs = DEFAULT_TIMEOUT_MS2
7862
8241
  } = options;
7863
8242
  const deadline = deps.now() + timeoutMs;
8243
+ let partial = options.hasPartialFill === true;
8244
+ const timeout = () => new OwneyError(
8245
+ "SWAP_REQUEST_FAILED",
8246
+ "Swap settlement is not confirmed yet. Check its status before starting another swap.",
8247
+ { orderHash, settlementUnconfirmed: true, outcome: partial ? "partially-filled" : "submitted-unknown" }
8248
+ );
8249
+ const assertTime = () => {
8250
+ if (deps.now() >= deadline) throw timeout();
8251
+ };
8252
+ const read2 = async (work) => {
8253
+ assertTime();
8254
+ const result = await bounded(work, Math.min(1e4, deadline - deps.now()), timeout);
8255
+ assertTime();
8256
+ return result;
8257
+ };
8258
+ const pause = async (ms) => {
8259
+ assertTime();
8260
+ await bounded(() => deps.sleep(Math.min(ms, deadline - deps.now())), deadline - deps.now(), timeout);
8261
+ assertTime();
8262
+ };
7864
8263
  let failures = 0;
7865
8264
  const published = /* @__PURE__ */ new Set();
7866
8265
  onStage?.("swapping");
7867
8266
  for (; ; ) {
7868
- if (deps.now() >= deadline) {
7869
- throw new OwneyError(
7870
- "SWAP_REQUEST_FAILED",
7871
- "Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
7872
- // Marks the one failure that means "we stopped watching a live order",
7873
- // as opposed to a verdict or a prompt nobody answered. The executor
7874
- // checks the wallet before believing it: resolvers do not stop filling
7875
- // because we stopped looking (ROUT-242, comment 15980).
7876
- { orderHash, settlementUnconfirmed: true }
7877
- );
7878
- }
8267
+ assertTime();
7879
8268
  let ready;
7880
8269
  try {
7881
- ready = await deps.readyForSecrets(orderHash);
8270
+ ready = await read2(() => deps.readyForSecrets(orderHash));
7882
8271
  } catch {
7883
8272
  ready = {};
7884
8273
  }
8274
+ assertTime();
7885
8275
  for (const fill of ready.fills ?? []) {
8276
+ assertTime();
7886
8277
  if (published.has(fill.idx)) continue;
7887
8278
  const secret = secrets[fill.idx];
7888
8279
  if (secret === void 0) {
7889
8280
  throw new OwneyError(
7890
8281
  "SWAP_REQUEST_FAILED",
7891
8282
  `Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
7892
- { orderHash, fillIndex: fill.idx }
8283
+ { orderHash, fillIndex: fill.idx, settlementUnconfirmed: true, outcome: "submitted-unknown" }
7893
8284
  );
7894
8285
  }
7895
8286
  try {
7896
- await deps.submitSecret(orderHash, secret);
8287
+ await read2(() => deps.submitSecret(orderHash, secret));
7897
8288
  published.add(fill.idx);
7898
8289
  } catch {
7899
8290
  failures += 1;
@@ -7901,29 +8292,34 @@ async function runFusionOrder(deps, options) {
7901
8292
  }
7902
8293
  let status;
7903
8294
  try {
7904
- ({ status } = await deps.orderStatus(orderHash));
8295
+ const state = await read2(() => deps.orderStatus(orderHash));
8296
+ status = state.status;
8297
+ partial ||= status === "partially-filled" || !!state.fills?.length;
7905
8298
  failures = 0;
7906
8299
  } catch {
7907
8300
  failures += 1;
7908
- await deps.sleep(backoffFor(failures, pollIntervalMs));
8301
+ await pause(backoffFor(failures, pollIntervalMs));
7909
8302
  continue;
7910
8303
  }
7911
8304
  if (status === "refunding") onStage?.("refunding");
7912
8305
  if (isSwapTerminal(status)) {
7913
- if (status === "executed") {
8306
+ if (status === "executed" || status === "filled") {
7914
8307
  onStage?.("swapped");
7915
8308
  return { status, filled: true };
7916
8309
  }
7917
- if (status === "refunded") onStage?.("refunded");
8310
+ if (status === "refunded") onStage?.(partial ? "swap-partial" : "refunded");
7918
8311
  throw new OwneyError(
7919
8312
  status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
7920
- status === "refunded" ? "The swap did not complete and your funds have been returned." : "The swap did not complete in time. Your funds will be returned once the timelock expires.",
7921
- { orderHash, status }
8313
+ partial ? "The swap filled partly. Check the order and both balances before retrying." : status === "refunded" ? "The swap did not complete and your funds have been returned." : "The swap did not complete in time. Your funds will be returned once the timelock expires.",
8314
+ { orderHash, status, outcome: status === "refunded" && !partial ? "refunded" : partial ? "partially-filled" : "refund-pending" }
7922
8315
  );
7923
8316
  }
7924
- await deps.sleep(pollIntervalMs);
8317
+ await pause(pollIntervalMs);
7925
8318
  }
7926
8319
  }
8320
+ function runFusionOrder(deps, options) {
8321
+ return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options));
8322
+ }
7927
8323
 
7928
8324
  // src/lib/swap/swap.same-chain-runner.ts
7929
8325
  var DEFAULT_POLL_MS3 = 5e3;
@@ -7948,7 +8344,7 @@ function explain(status) {
7948
8344
  return "The swap did not complete. Nothing was exchanged.";
7949
8345
  }
7950
8346
  }
7951
- async function runSameChainOrder(deps, options) {
8347
+ async function runSameChainOrderInternal(deps, options) {
7952
8348
  const {
7953
8349
  orderHash,
7954
8350
  onStage,
@@ -7956,23 +8352,39 @@ async function runSameChainOrder(deps, options) {
7956
8352
  timeoutMs = DEFAULT_TIMEOUT_MS3
7957
8353
  } = options;
7958
8354
  const deadline = deps.now() + timeoutMs;
8355
+ let partial = options.hasPartialFill === true;
8356
+ const timeout = () => new OwneyError(
8357
+ "SWAP_REQUEST_FAILED",
8358
+ "Swap settlement is not confirmed yet. Check its status before starting another swap.",
8359
+ { orderHash, settlementUnconfirmed: true, outcome: partial ? "partially-filled" : "submitted-unknown" }
8360
+ );
8361
+ const assertTime = () => {
8362
+ if (deps.now() >= deadline) throw timeout();
8363
+ };
8364
+ const read2 = async (work) => {
8365
+ assertTime();
8366
+ const result = await bounded(work, Math.min(1e4, deadline - deps.now()), timeout);
8367
+ assertTime();
8368
+ return result;
8369
+ };
8370
+ const pause = async (ms) => {
8371
+ assertTime();
8372
+ await bounded(() => deps.sleep(Math.min(ms, deadline - deps.now())), deadline - deps.now(), timeout);
8373
+ assertTime();
8374
+ };
7959
8375
  let failures = 0;
7960
8376
  onStage?.("swapping");
7961
8377
  for (; ; ) {
7962
- if (deps.now() >= deadline) {
7963
- throw new OwneyError(
7964
- "SWAP_ORDER_EXPIRED",
7965
- "No one filled the swap in time. Nothing was exchanged and your funds are still in your wallet.",
7966
- { orderHash }
7967
- );
7968
- }
8378
+ assertTime();
7969
8379
  let status;
7970
8380
  try {
7971
- ({ status } = await deps.orderStatus(orderHash));
8381
+ const state = await read2(() => deps.orderStatus(orderHash));
8382
+ status = state.status;
8383
+ partial ||= status === "partially-filled" || !!state.fills?.length;
7972
8384
  failures = 0;
7973
8385
  } catch {
7974
8386
  failures += 1;
7975
- await deps.sleep(backoffFor2(failures, pollIntervalMs));
8387
+ await pause(backoffFor2(failures, pollIntervalMs));
7976
8388
  continue;
7977
8389
  }
7978
8390
  if (isSwapTerminal(status)) {
@@ -7982,17 +8394,19 @@ async function runSameChainOrder(deps, options) {
7982
8394
  }
7983
8395
  throw new OwneyError(
7984
8396
  status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
7985
- explain(status),
7986
- { orderHash, status }
8397
+ partial ? "The swap filled partly. Check the order and both balances before retrying." : explain(status),
8398
+ { orderHash, status, outcome: partial ? "partially-filled" : "not-submitted" }
7987
8399
  );
7988
8400
  }
7989
- await deps.sleep(pollIntervalMs);
8401
+ await pause(pollIntervalMs);
7990
8402
  }
7991
8403
  }
8404
+ function runSameChainOrder(deps, options) {
8405
+ return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options));
8406
+ }
7992
8407
 
7993
8408
  // src/lib/swap/swap.secret-store.ts
7994
8409
  var KEY_PREFIX4 = "owney.swap.order";
7995
- var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
7996
8410
  var storage4 = () => {
7997
8411
  if (typeof window === "undefined") return null;
7998
8412
  try {
@@ -8004,10 +8418,13 @@ var storage4 = () => {
8004
8418
  var keyFor2 = (orderHash) => `${KEY_PREFIX4}.${orderHash}`;
8005
8419
  function saveOrder(order) {
8006
8420
  const store = storage4();
8007
- if (!store) return;
8421
+ if (!store) return false;
8008
8422
  try {
8009
- store.setItem(keyFor2(order.orderHash), JSON.stringify(order));
8423
+ const value = JSON.stringify(order);
8424
+ store.setItem(keyFor2(order.orderHash), value);
8425
+ return store.getItem(keyFor2(order.orderHash)) === value;
8010
8426
  } catch {
8427
+ return false;
8011
8428
  }
8012
8429
  }
8013
8430
  function clearOrder(orderHash) {
@@ -8018,7 +8435,7 @@ function clearOrder(orderHash) {
8018
8435
  } catch {
8019
8436
  }
8020
8437
  }
8021
- function listOrders(now = Date.now()) {
8438
+ function listOrders(_now = Date.now()) {
8022
8439
  const store = storage4();
8023
8440
  if (!store) return [];
8024
8441
  const out = [];
@@ -8033,10 +8450,6 @@ function listOrders(now = Date.now()) {
8033
8450
  if (!raw2) continue;
8034
8451
  try {
8035
8452
  const parsed = JSON.parse(raw2);
8036
- if (now - parsed.createdAt > MAX_AGE_MS) {
8037
- store.removeItem(key2);
8038
- continue;
8039
- }
8040
8453
  if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
8041
8454
  out.push(parsed);
8042
8455
  }
@@ -8077,12 +8490,11 @@ async function awaitWalletPrompt(prompt, what) {
8077
8490
  if (timer) clearTimeout(timer);
8078
8491
  }
8079
8492
  }
8080
- function stoppedWatching(error) {
8081
- return error instanceof OwneyError && error.details?.settlementUnconfirmed === true;
8082
- }
8083
8493
  var DEFAULT_SLIPPAGE = 1;
8084
- async function affordableAmount(deps, quoted) {
8085
- const balance = await deps.readSourceBalance();
8494
+ async function affordableAmount(deps, quoted, reserve = 0n) {
8495
+ const raw2 = await deps.readSourceBalance();
8496
+ const balance = raw2 > reserve ? raw2 - reserve : 0n;
8497
+ if (balance <= 0n) throw new OwneyError("SWAP_REQUEST_FAILED", "No spendable source funds remain for this swap.", { outcome: "not-submitted" });
8086
8498
  if (balance >= quoted) return quoted;
8087
8499
  debugLog("owney-sdk", "swap: trimming to the current source balance", {
8088
8500
  quoted: quoted.toString(),
@@ -8093,48 +8505,72 @@ async function affordableAmount(deps, quoted) {
8093
8505
  }
8094
8506
  async function executeSwap(deps, options) {
8095
8507
  const { quote, walletAddress, onStage } = options;
8096
- debugLog("owney-sdk", "swap: start", {
8097
- rail: quote.rail,
8098
- from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
8099
- to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
8100
- expected: quote.dst.amount,
8101
- floor: quote.dstAmountMin
8102
- });
8103
- const before = await deps.readTargetBalance();
8104
- debugLog("owney-sdk", "swap: target balance before", before.toString());
8105
- let result;
8106
- let railError;
8508
+ let active2 = true;
8509
+ const original = deps;
8510
+ const assertActive = () => {
8511
+ if (!active2) throw new OwneyError("SWAP_REQUEST_FAILED", "This swap attempt is no longer active.");
8512
+ };
8513
+ const call = async (work, write = false) => {
8514
+ assertActive();
8515
+ if (write) options.assertCurrent?.();
8516
+ const value = await bounded(
8517
+ work,
8518
+ write ? 3e5 : 2e4,
8519
+ () => new OwneyError(
8520
+ "SWAP_REQUEST_FAILED",
8521
+ "The swap step could not be confirmed. Check its status before retrying.",
8522
+ { settlementUnconfirmed: write, outcome: write ? "submitted-unknown" : "not-submitted" }
8523
+ )
8524
+ );
8525
+ assertActive();
8526
+ if (write) options.assertCurrent?.();
8527
+ return value;
8528
+ };
8529
+ deps = {
8530
+ ...original,
8531
+ api: {
8532
+ ...original.api,
8533
+ buildOrder: (args) => call(() => original.api.buildOrder(args)),
8534
+ swapTx: (args) => call(() => original.api.swapTx(args)),
8535
+ submitOrder: (args) => call(() => original.api.submitOrder(args), true)
8536
+ },
8537
+ readTargetBalance: () => call(original.readTargetBalance),
8538
+ readSourceBalance: () => call(original.readSourceBalance),
8539
+ readAllowance: (spender) => call(() => original.readAllowance(spender)),
8540
+ ensureChain: (chain2) => call(() => original.ensureChain(chain2), true),
8541
+ approve: (spender, amount) => call(() => original.approve(spender, amount), true),
8542
+ sendTransaction: (tx) => call(() => original.sendTransaction(tx), true),
8543
+ signTypedData: (data) => call(() => original.signTypedData(data), true),
8544
+ ...original.buildPermit ? { buildPermit: (spender, amount) => call(() => original.buildPermit(spender, amount), true) } : {}
8545
+ };
8107
8546
  try {
8108
- result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
8109
- } catch (error) {
8110
- if (!stoppedWatching(error)) throw error;
8111
- railError = error;
8112
- debugLog("owney-sdk", "swap: rail failed, checking whether funds landed", {
8113
- message: error instanceof Error ? error.message : String(error)
8547
+ debugLog("owney-sdk", "swap: start", {
8548
+ rail: quote.rail,
8549
+ from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
8550
+ to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
8551
+ expected: quote.dst.amount,
8552
+ floor: quote.dstAmountMin
8114
8553
  });
8115
- }
8116
- const { arrived } = await awaitBalanceIncrease({
8117
- readBalance: () => deps.readTargetBalance(),
8118
- baseline: before,
8119
- ...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs },
8120
- ...options.arrivalPollMs === void 0 ? {} : { pollMs: options.arrivalPollMs }
8121
- });
8122
- if (arrived === null) {
8123
- if (railError) throw railError;
8124
- throw new OwneyError(
8125
- "SWAP_REQUEST_FAILED",
8126
- "The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
8127
- { rail: quote.rail, ...result ?? {} }
8128
- );
8129
- }
8130
- if (railError) {
8131
- debugLog("owney-sdk", "swap: rail gave up but the funds are here", {
8132
- received: arrived.toString()
8554
+ const before = await deps.readTargetBalance();
8555
+ debugLog("owney-sdk", "swap: target balance before", before.toString());
8556
+ const result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
8557
+ const { arrived } = await awaitBalanceIncrease({
8558
+ readBalance: () => deps.readTargetBalance(),
8559
+ baseline: before,
8560
+ ...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs },
8561
+ ...options.arrivalPollMs === void 0 ? {} : { pollMs: options.arrivalPollMs }
8133
8562
  });
8134
- const orderHash = railError.details?.orderHash;
8135
- if (orderHash) clearOrder(orderHash);
8563
+ if (arrived === null) {
8564
+ throw new OwneyError(
8565
+ "SWAP_REQUEST_FAILED",
8566
+ "The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
8567
+ { rail: quote.rail, ...result, outcome: "filled", settlementUnconfirmed: true }
8568
+ );
8569
+ }
8570
+ return { received: arrived.toString(), ...result };
8571
+ } finally {
8572
+ active2 = false;
8136
8573
  }
8137
- return { received: arrived.toString(), ...result ?? {} };
8138
8574
  }
8139
8575
  async function runClassic(deps, options) {
8140
8576
  const {
@@ -8145,7 +8581,7 @@ async function runClassic(deps, options) {
8145
8581
  onStage
8146
8582
  } = options;
8147
8583
  const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
8148
- const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
8584
+ let amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount), options.sourceReserve);
8149
8585
  const swapTxRequest = {
8150
8586
  from: {
8151
8587
  chainId: quote.src.chainId,
@@ -8177,25 +8613,36 @@ async function runClassic(deps, options) {
8177
8613
  "owney-sdk",
8178
8614
  "swap: re-fetching classic calldata after approval"
8179
8615
  );
8616
+ amount = await affordableAmount(deps, amount, options.sourceReserve);
8617
+ swapTxRequest.from.amount = amount.toString();
8180
8618
  ({ tx } = await deps.api.swapTx(swapTxRequest));
8619
+ if (await deps.readAllowance(tx.to) < amount)
8620
+ throw new OwneyError("SWAP_REQUEST_FAILED", "Swap approval was not confirmed for the current spender.", { outcome: "not-submitted" });
8181
8621
  }
8182
8622
  }
8183
8623
  onStage?.("signing");
8184
8624
  await deps.ensureChain(quote.src.chainId);
8185
8625
  debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
8626
+ options.assertCurrent?.();
8627
+ options.onTransactionRequested?.();
8186
8628
  const txHash = await deps.sendTransaction({
8187
8629
  to: tx.to,
8188
8630
  data: tx.data,
8189
8631
  value: tx.value ?? "0"
8632
+ }).catch((error) => {
8633
+ if (error?.code === 4001 || error?.code === "ACTION_REJECTED")
8634
+ throw new OwneyError("SWAP_REQUEST_FAILED", "The swap transaction was rejected in the wallet.", { outcome: "not-submitted" });
8635
+ throw error;
8190
8636
  });
8191
8637
  onStage?.("swapped");
8192
- return { txHash };
8638
+ return { txHash, spent: amount.toString() };
8193
8639
  }
8194
8640
  async function runSameChainFusion(deps, options, walletAddress) {
8195
8641
  const { quote, direction, onStage } = options;
8196
8642
  const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
8197
- const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
8198
- const permit = await authoriseSpend(deps, options, amount, isNativeSource);
8643
+ let amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount), options.sourceReserve);
8644
+ const { permit, paid } = await authoriseSpend(deps, options, amount, isNativeSource);
8645
+ amount = await authorisedAmount(deps, options, amount, paid);
8199
8646
  onStage?.("building");
8200
8647
  debugLog("owney-sdk", "swap: building same-chain fusion order");
8201
8648
  const built = await deps.api.buildOrder({
@@ -8219,36 +8666,42 @@ async function runSameChainFusion(deps, options, walletAddress) {
8219
8666
  "sign the swap"
8220
8667
  );
8221
8668
  debugLog("owney-sdk", "swap: signed, submitting same-chain order");
8222
- await deps.api.submitOrder({
8223
- // Without this the routing API hands the order to the Fusion+ relayer,
8224
- // which does not know it and answers with an error that names nothing.
8225
- rail: "fusion",
8226
- srcChainId: quote.src.chainId,
8227
- order: built.order,
8228
- signature,
8229
- quoteId: built.quoteId,
8230
- ...built.extension ? { extension: built.extension } : {}
8231
- });
8669
+ options.assertCurrent?.();
8670
+ options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
8671
+ try {
8672
+ await deps.api.submitOrder({
8673
+ // Without this the routing API hands the order to the Fusion+ relayer,
8674
+ // which does not know it and answers with an error that names nothing.
8675
+ rail: "fusion",
8676
+ srcChainId: quote.src.chainId,
8677
+ order: built.order,
8678
+ signature,
8679
+ quoteId: built.quoteId,
8680
+ ...built.extension ? { extension: built.extension } : {}
8681
+ });
8682
+ } catch (error) {
8683
+ throw submissionUnknown(error, built.orderHash);
8684
+ }
8232
8685
  try {
8233
- options.onOrderSubmitted?.(built.orderHash);
8686
+ options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable: true });
8234
8687
  } catch {
8235
8688
  }
8236
8689
  await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
8237
8690
  orderHash: built.orderHash,
8238
8691
  ...onStage ? { onStage } : {}
8239
8692
  });
8240
- return { orderHash: built.orderHash };
8693
+ return { orderHash: built.orderHash, spent: amount.toString() };
8241
8694
  }
8242
8695
  async function authoriseSpend(deps, options, amount, isNativeSource) {
8243
8696
  const { quote, onStage } = options;
8244
- if (!quote.spender || isNativeSource) return void 0;
8697
+ if (!quote.spender || isNativeSource) return { paid: false };
8245
8698
  const current = await deps.readAllowance(quote.spender);
8246
8699
  debugLog("owney-sdk", "swap: fusion allowance", {
8247
8700
  spender: quote.spender,
8248
8701
  current: current.toString(),
8249
8702
  needed: amount.toString()
8250
8703
  });
8251
- if (current >= amount) return void 0;
8704
+ if (current >= amount) return { paid: false };
8252
8705
  onStage?.("approving");
8253
8706
  await deps.ensureChain(quote.src.chainId);
8254
8707
  const permit = quote.permitSupported ? await awaitWalletPrompt(
@@ -8259,11 +8712,18 @@ async function authoriseSpend(deps, options, amount, isNativeSource) {
8259
8712
  debugLog("owney-sdk", "swap: permitting limit order protocol", {
8260
8713
  bytes: (permit.length - 2) / 2
8261
8714
  });
8262
- return permit;
8715
+ return { permit, paid: false };
8263
8716
  }
8264
8717
  await deps.approve(quote.spender, MAX_UINT256);
8265
8718
  debugLog("owney-sdk", "swap: approved limit order protocol");
8266
- return void 0;
8719
+ return { paid: true };
8720
+ }
8721
+ async function authorisedAmount(deps, options, amount, paid) {
8722
+ if (!paid) return amount;
8723
+ const remaining = await affordableAmount(deps, amount, options.sourceReserve);
8724
+ if (!options.quote.spender || await deps.readAllowance(options.quote.spender) < remaining)
8725
+ throw new OwneyError("SWAP_REQUEST_FAILED", "Swap approval was not confirmed.", { outcome: "not-submitted" });
8726
+ return remaining;
8267
8727
  }
8268
8728
  async function runFusion(deps, options, walletAddress) {
8269
8729
  const { quote, direction, onStage } = options;
@@ -8275,8 +8735,9 @@ async function runFusion(deps, options, walletAddress) {
8275
8735
  { chainId: quote.src.chainId, symbol: quote.src.symbol }
8276
8736
  );
8277
8737
  }
8278
- const amount = await affordableAmount(deps, BigInt(quote.src.amount));
8279
- const permit = await authoriseSpend(deps, options, amount, false);
8738
+ let amount = await affordableAmount(deps, BigInt(quote.src.amount), options.sourceReserve);
8739
+ const { permit, paid } = await authoriseSpend(deps, options, amount, false);
8740
+ amount = await authorisedAmount(deps, options, amount, paid);
8280
8741
  const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
8281
8742
  onStage?.("building");
8282
8743
  debugLog("owney-sdk", "swap: building fusion order", {
@@ -8295,8 +8756,9 @@ async function runFusion(deps, options, walletAddress) {
8295
8756
  ...direction ? { direction } : {},
8296
8757
  ...permit ? { permit } : {}
8297
8758
  });
8298
- saveOrder({
8759
+ const durable = saveOrder({
8299
8760
  orderHash: built.orderHash,
8761
+ walletAddress,
8300
8762
  secrets,
8301
8763
  srcChainId: quote.src.chainId,
8302
8764
  srcSymbol: quote.src.symbol,
@@ -8306,33 +8768,39 @@ async function runFusion(deps, options, walletAddress) {
8306
8768
  createdAt: Date.now()
8307
8769
  });
8308
8770
  debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
8309
- onStage?.("signing");
8310
- await deps.ensureChain(quote.src.chainId);
8311
- debugLog("owney-sdk", "swap: awaiting signature in wallet", {
8312
- signingOnChain: quote.src.chainId
8313
- });
8314
- const signature = await awaitWalletPrompt(
8315
- () => deps.signTypedData(built.typedData),
8316
- "sign the swap"
8317
- );
8771
+ let signature;
8772
+ try {
8773
+ onStage?.("signing");
8774
+ await deps.ensureChain(quote.src.chainId);
8775
+ signature = await awaitWalletPrompt(() => deps.signTypedData(built.typedData), "sign the swap");
8776
+ options.assertCurrent?.();
8777
+ options.onOrderPrepared?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount });
8778
+ } catch (error) {
8779
+ clearOrder(built.orderHash);
8780
+ throw error;
8781
+ }
8318
8782
  debugLog("owney-sdk", "swap: signed, submitting to relayer");
8319
- await deps.api.submitOrder({
8320
- srcChainId: quote.src.chainId,
8321
- // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
8322
- // the envelope here gets a bare 500 from the relayer.
8323
- order: built.order,
8324
- signature,
8325
- quoteId: built.quoteId,
8326
- // Single-fill orders must NOT carry secretHashes — the relayer rejects
8327
- // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
8328
- // order's hashlock, so repeating it here is redundant, and only a
8329
- // multi-fill order (a Merkle tree of hashes) needs them listed.
8330
- ...secretHashes.length > 1 ? { secretHashes } : {},
8331
- ...built.extension ? { extension: built.extension } : {}
8332
- });
8783
+ try {
8784
+ await deps.api.submitOrder({
8785
+ srcChainId: quote.src.chainId,
8786
+ // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
8787
+ // the envelope here gets a bare 500 from the relayer.
8788
+ order: built.order,
8789
+ signature,
8790
+ quoteId: built.quoteId,
8791
+ // Single-fill orders must NOT carry secretHashes — the relayer rejects
8792
+ // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
8793
+ // order's hashlock, so repeating it here is redundant, and only a
8794
+ // multi-fill order (a Merkle tree of hashes) needs them listed.
8795
+ ...secretHashes.length > 1 ? { secretHashes } : {},
8796
+ ...built.extension ? { extension: built.extension } : {}
8797
+ });
8798
+ } catch (error) {
8799
+ throw submissionUnknown(error, built.orderHash);
8800
+ }
8333
8801
  debugLog("owney-sdk", "swap: order submitted, polling escrows");
8334
8802
  try {
8335
- options.onOrderSubmitted?.(built.orderHash);
8803
+ options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
8336
8804
  } catch {
8337
8805
  }
8338
8806
  try {
@@ -8342,13 +8810,25 @@ async function runFusion(deps, options, walletAddress) {
8342
8810
  ...onStage ? { onStage } : {}
8343
8811
  });
8344
8812
  } catch (error) {
8345
- if (error instanceof OwneyError && (error.code === "SWAP_ORDER_REFUNDED" || error.code === "SWAP_ORDER_CANCELLED")) {
8813
+ if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
8346
8814
  clearOrder(built.orderHash);
8347
8815
  }
8348
8816
  throw error;
8349
8817
  }
8350
8818
  clearOrder(built.orderHash);
8351
- return { orderHash: built.orderHash };
8819
+ return { orderHash: built.orderHash, spent: amount.toString() };
8820
+ }
8821
+ function submissionUnknown(error, orderHash) {
8822
+ return new OwneyError(
8823
+ "SWAP_REQUEST_FAILED",
8824
+ "The order submission could not be confirmed. Check this order before starting another swap.",
8825
+ {
8826
+ orderHash,
8827
+ settlementUnconfirmed: true,
8828
+ outcome: "submitted-unknown",
8829
+ ...error instanceof OwneyError ? { causeCode: error.code } : {}
8830
+ }
8831
+ );
8352
8832
  }
8353
8833
 
8354
8834
  // src/lib/swap/swap.withdrawal-settlement.ts
@@ -8358,8 +8838,8 @@ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8358
8838
  async function awaitWithdrawalPayouts(options) {
8359
8839
  const payouts = new Map(Object.entries(options.results).map(([id, result]) => [id, { result, evidence: result.evidence }]));
8360
8840
  const timeoutMs = Math.max(0, options.timeoutMs ?? 18e4);
8361
- const deadline = Date.now() + timeoutMs;
8362
- let active = true;
8841
+ const deadline = options.deadline ?? Date.now() + timeoutMs;
8842
+ let active2 = true;
8363
8843
  let timer;
8364
8844
  const incomplete = (reason, agentId) => new OwneyError(
8365
8845
  "WITHDRAW_SWAP_INCOMPLETE",
@@ -8377,7 +8857,7 @@ async function awaitWithdrawalPayouts(options) {
8377
8857
  );
8378
8858
  const assertActive = () => {
8379
8859
  options.assertCurrent();
8380
- if (!active || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
8860
+ if (!active2 || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
8381
8861
  };
8382
8862
  const pause = async () => {
8383
8863
  assertActive();
@@ -8388,10 +8868,11 @@ async function awaitWithdrawalPayouts(options) {
8388
8868
  if (evidence.agentId !== id || evidence.chainId !== options.chainId || evidence.token !== options.token || evidence.walletAddress.toLowerCase() !== options.walletAddress.toLowerCase())
8389
8869
  throw incomplete("PAYOUT_SCOPE_MISMATCH", id);
8390
8870
  };
8391
- const record = (id, payout, evidence) => {
8871
+ const record2 = (id, payout, evidence) => {
8392
8872
  assertActive();
8393
8873
  validate(id, evidence);
8394
8874
  payout.evidence = evidence;
8875
+ payout.result.evidence = evidence;
8395
8876
  if (evidence.transfer === "confirmed" && evidence.delivery === "confirmed") payout.amount = rawAmount(evidence.confirmedAmount);
8396
8877
  options.onProgress(evidence);
8397
8878
  };
@@ -8456,8 +8937,30 @@ async function awaitWithdrawalPayouts(options) {
8456
8937
  if (payout.amount === void 0 || payout.amount <= 0n) throw incomplete("INVALID_PAYOUT_AMOUNT", id);
8457
8938
  if (["yieldseeker", "zyfai"].includes(id) && (!hash(evidence.txHash) || rawAmount(evidence.blockNumber) === void 0 || !evidence.logIndexes?.length))
8458
8939
  throw incomplete("PAYOUT_RECEIPT_UNVERIFIED", id);
8459
- if (options.requested === void 0 && payout.result.completion === "unverified")
8460
- throw incomplete("MAX_COMPLETION_UNVERIFIED", id);
8940
+ if (options.requested === void 0 && payout.result.completion === "unverified") {
8941
+ if (!options.verifyCompletion || id === "yieldseeker" && !evidence.maxCompletion)
8942
+ throw incomplete("MAX_COMPLETION_UNVERIFIED", id);
8943
+ let proof = { status: "unavailable" };
8944
+ try {
8945
+ proof = await bounded(
8946
+ () => options.verifyCompletion(evidence),
8947
+ Math.min(1e4, deadline - Date.now()),
8948
+ () => incomplete("MAX_COMPLETION_UNVERIFIED", id)
8949
+ );
8950
+ } catch {
8951
+ assertActive();
8952
+ }
8953
+ assertActive();
8954
+ evidence.maxCompletionCheck = proof;
8955
+ options.onProgress(evidence);
8956
+ if (proof.status === "remaining") throw incomplete("MAX_REMAINDER_VERIFIED", id);
8957
+ if (proof.status !== "complete") {
8958
+ await pause();
8959
+ continue;
8960
+ }
8961
+ payout.result.completion = void 0;
8962
+ payout.result.type = "full";
8963
+ }
8461
8964
  const intended = rawAmount(evidence.intendedAmount);
8462
8965
  if (intended !== void 0 && payout.amount < intended && verifiedFee(payout) !== intended - payout.amount)
8463
8966
  throw incomplete("PAYOUT_SHORTFALL", id);
@@ -8467,24 +8970,24 @@ async function awaitWithdrawalPayouts(options) {
8467
8970
  let next = evidence;
8468
8971
  if (!next.txHash && (next.userOpHash || next.callsId)) {
8469
8972
  next = await options.resolve(next);
8470
- record(id, payout, next);
8973
+ record2(id, payout, next);
8471
8974
  }
8472
8975
  if (next.transfer === "failed" || next.delivery === "failed") continue;
8473
8976
  if (next.txHash) {
8474
8977
  next = await options.inspect(next, false);
8475
- record(id, payout, next);
8978
+ record2(id, payout, next);
8476
8979
  if (id === "zyfai" && next.transfer === "unknown" && rawAmount(next.blockNumber) !== void 0 && next.logIndexes?.length === 0 && next.transferLogs?.length === 0 && !next.userOpHash && !next.callsId) {
8477
8980
  const found = await findPayout(id, next);
8478
8981
  if (found) {
8479
8982
  next = found;
8480
- record(id, payout, next);
8983
+ record2(id, payout, next);
8481
8984
  }
8482
8985
  }
8483
8986
  } else if (!next.userOpHash && !next.callsId) {
8484
8987
  const found = await findPayout(id, next);
8485
8988
  if (found) {
8486
8989
  next = found;
8487
- record(id, payout, next);
8990
+ record2(id, payout, next);
8488
8991
  }
8489
8992
  }
8490
8993
  if (next.transfer === "confirmed" && next.delivery === "confirmed" || next.transfer === "failed" || next.delivery === "failed") continue;
@@ -8528,10 +9031,10 @@ async function awaitWithdrawalPayouts(options) {
8528
9031
  };
8529
9032
  try {
8530
9033
  return await Promise.race([read2(), new Promise((_, reject) => {
8531
- timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), timeoutMs);
9034
+ timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), Math.max(0, deadline - Date.now()));
8532
9035
  })]);
8533
9036
  } finally {
8534
- active = false;
9037
+ active2 = false;
8535
9038
  if (timer) clearTimeout(timer);
8536
9039
  }
8537
9040
  }
@@ -8549,8 +9052,8 @@ function verifiedFee(payout) {
8549
9052
  }
8550
9053
 
8551
9054
  // src/lib/swap/swap.permit.ts
8552
- var import_viem16 = require("viem");
8553
- var PERMIT_ABI = (0, import_viem16.parseAbi)([
9055
+ var import_viem17 = require("viem");
9056
+ var PERMIT_ABI = (0, import_viem17.parseAbi)([
8554
9057
  "function nonces(address owner) view returns (uint256)",
8555
9058
  "function name() view returns (string)",
8556
9059
  "function version() view returns (string)",
@@ -8577,7 +9080,7 @@ async function tryRead(read2) {
8577
9080
  function matchDomain(name, versions, chainId, token, onChainSeparator) {
8578
9081
  for (const version of versions) {
8579
9082
  const domain = { name, version, chainId, verifyingContract: token };
8580
- if ((0, import_viem16.domainSeparator)({ domain }) === onChainSeparator) return domain;
9083
+ if ((0, import_viem17.domainSeparator)({ domain }) === onChainSeparator) return domain;
8581
9084
  }
8582
9085
  return null;
8583
9086
  }
@@ -8630,9 +9133,9 @@ async function buildErc2612Permit(reads, input) {
8630
9133
  primaryType: "Permit",
8631
9134
  message: { owner, spender, value, nonce, deadline }
8632
9135
  });
8633
- const { r, s, v, yParity } = (0, import_viem16.parseSignature)(signature);
9136
+ const { r, s, v, yParity } = (0, import_viem17.parseSignature)(signature);
8634
9137
  const recoveryV = v ?? BigInt(yParity + 27);
8635
- return (0, import_viem16.encodeAbiParameters)(
9138
+ return (0, import_viem17.encodeAbiParameters)(
8636
9139
  [
8637
9140
  { type: "address" },
8638
9141
  { type: "address" },
@@ -8648,7 +9151,7 @@ async function buildErc2612Permit(reads, input) {
8648
9151
 
8649
9152
  // src/lib/swap/swap.withdraw-swap.ts
8650
9153
  var KEY_PREFIX5 = "owney.swap.withdraw";
8651
- var MAX_AGE_MS2 = 24 * 60 * 60 * 1e3;
9154
+ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
8652
9155
  var storage5 = () => {
8653
9156
  if (typeof window === "undefined") return null;
8654
9157
  try {
@@ -8673,15 +9176,19 @@ var notify = () => {
8673
9176
  }
8674
9177
  }
8675
9178
  };
8676
- function saveWithdrawSwap(record) {
9179
+ function saveWithdrawSwap(record2) {
8677
9180
  const store = storage5();
8678
- if (!store) return;
9181
+ if (!store) return false;
8679
9182
  try {
8680
- store.setItem(keyFor3(record.orderHash), JSON.stringify(record));
9183
+ const current = store.getItem(keyFor3(record2.orderHash));
9184
+ const value = JSON.stringify({ ...current ? JSON.parse(current) : {}, ...record2 });
9185
+ store.setItem(keyFor3(record2.orderHash), value);
9186
+ if (store.getItem(keyFor3(record2.orderHash)) !== value) return false;
8681
9187
  } catch {
8682
- return;
9188
+ return false;
8683
9189
  }
8684
9190
  notify();
9191
+ return true;
8685
9192
  }
8686
9193
  function updateWithdrawSwap(orderHash, patch) {
8687
9194
  const store = storage5();
@@ -8704,13 +9211,17 @@ function clearWithdrawSwap(orderHash) {
8704
9211
  const store = storage5();
8705
9212
  if (!store) return;
8706
9213
  try {
8707
- store.removeItem(keyFor3(orderHash));
9214
+ const raw2 = store.getItem(keyFor3(orderHash));
9215
+ const record2 = raw2 ? JSON.parse(raw2) : void 0;
9216
+ if (record2 && !["landed", "refunded", "not-swapped"].includes(record2.status))
9217
+ store.setItem(keyFor3(orderHash), JSON.stringify({ ...record2, dismissed: true }));
9218
+ else store.removeItem(keyFor3(orderHash));
8708
9219
  } catch {
8709
9220
  return;
8710
9221
  }
8711
9222
  notify();
8712
9223
  }
8713
- function listWithdrawSwaps(now = Date.now()) {
9224
+ function listWithdrawSwaps(now = Date.now(), includeDismissed = false) {
8714
9225
  const store = storage5();
8715
9226
  if (!store) return [];
8716
9227
  const out = [];
@@ -8725,11 +9236,11 @@ function listWithdrawSwaps(now = Date.now()) {
8725
9236
  if (!raw2) continue;
8726
9237
  try {
8727
9238
  const parsed = JSON.parse(raw2);
8728
- if (now - parsed.createdAt > MAX_AGE_MS2) {
9239
+ if (["landed", "refunded", "not-swapped"].includes(parsed.status) && now - (parsed.settledAt ?? parsed.createdAt) > MAX_AGE_MS) {
8729
9240
  store.removeItem(key2);
8730
9241
  continue;
8731
9242
  }
8732
- out.push(parsed);
9243
+ if (includeDismissed || !parsed.dismissed) out.push(parsed);
8733
9244
  } catch {
8734
9245
  store.removeItem(key2);
8735
9246
  }
@@ -8739,10 +9250,15 @@ function listWithdrawSwaps(now = Date.now()) {
8739
9250
  }
8740
9251
  return out.sort((a, b) => b.createdAt - a.createdAt);
8741
9252
  }
9253
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
9254
+ window.addEventListener("storage", (event) => {
9255
+ if (event.key === null || event.key.startsWith(`${KEY_PREFIX5}.`)) notify();
9256
+ });
9257
+ }
8742
9258
 
8743
9259
  // src/lib/swap/swap.pending-deposit.ts
8744
9260
  var KEY_PREFIX6 = "owney.swap.pending-deposit";
8745
- var MAX_AGE_MS3 = 24 * 60 * 60 * 1e3;
9261
+ var MAX_AGE_MS2 = 24 * 60 * 60 * 1e3;
8746
9262
  var storage6 = () => {
8747
9263
  if (typeof window === "undefined") return null;
8748
9264
  try {
@@ -8785,7 +9301,7 @@ function listPendingDeposits(now = Date.now()) {
8785
9301
  if (!raw2) continue;
8786
9302
  try {
8787
9303
  const parsed = JSON.parse(raw2);
8788
- if (now - parsed.createdAt > MAX_AGE_MS3) {
9304
+ if (now - parsed.createdAt > MAX_AGE_MS2) {
8789
9305
  store.removeItem(key2);
8790
9306
  continue;
8791
9307
  }
@@ -8834,7 +9350,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
8834
9350
  }
8835
9351
 
8836
9352
  // src/lib/helpers/withdraw-helper.ts
8837
- var import_viem17 = require("viem");
9353
+ var import_viem18 = require("viem");
8838
9354
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
8839
9355
  return agents.map((agent) => {
8840
9356
  let invalidBalance = false;
@@ -8844,7 +9360,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
8844
9360
  return 0n;
8845
9361
  }
8846
9362
  const [whole, fraction] = value.split(".");
8847
- return raw2 ? BigInt(value) : (0, import_viem17.parseUnits)(fraction === void 0 ? whole : `${whole}.${fraction.slice(0, decimals)}`, decimals);
9363
+ return raw2 ? BigInt(value) : (0, import_viem18.parseUnits)(fraction === void 0 ? whole : `${whole}.${fraction.slice(0, decimals)}`, decimals);
8848
9364
  };
8849
9365
  const agentBalance = aggregated[agent.id];
8850
9366
  const tokenBalance = agentBalance?.tokens.find(
@@ -9044,11 +9560,11 @@ function aggregateDailyEarnings(series, tokenSymbol) {
9044
9560
  const byAsset = /* @__PURE__ */ new Map();
9045
9561
  for (const account of series) {
9046
9562
  for (const { asset, points } of account.assets) {
9047
- const symbol = asset.toUpperCase();
9048
- if (!points.length || wanted && symbol !== wanted) continue;
9049
- const components = byAsset.get(symbol) ?? [];
9563
+ const symbol2 = asset.toUpperCase();
9564
+ if (!points.length || wanted && symbol2 !== wanted) continue;
9565
+ const components = byAsset.get(symbol2) ?? [];
9050
9566
  components.push([...points].sort((a, b) => a.date.localeCompare(b.date)));
9051
- byAsset.set(symbol, components);
9567
+ byAsset.set(symbol2, components);
9052
9568
  }
9053
9569
  }
9054
9570
  return [...byAsset.entries()].map(([asset, components]) => {
@@ -9081,111 +9597,6 @@ var import_chains6 = require("viem/chains");
9081
9597
 
9082
9598
  // src/lib/sponsored-token-batch.ts
9083
9599
  var import_viem19 = require("viem");
9084
-
9085
- // src/lib/permit2-batch.ts
9086
- var import_viem18 = require("viem");
9087
- var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
9088
- var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
9089
- var ERC2612_PERMIT_TYPES = {
9090
- Permit: [
9091
- { name: "owner", type: "address" },
9092
- { name: "spender", type: "address" },
9093
- { name: "value", type: "uint256" },
9094
- { name: "nonce", type: "uint256" },
9095
- { name: "deadline", type: "uint256" }
9096
- ]
9097
- };
9098
- var ERC2612_READ_ABI = (0, import_viem18.parseAbi)([
9099
- "function name() view returns (string)",
9100
- "function version() view returns (string)",
9101
- "function nonces(address owner) view returns (uint256)"
9102
- ]);
9103
- function erc2612TypedData(input) {
9104
- return {
9105
- domain: {
9106
- name: input.permit.tokenName,
9107
- version: input.permit.tokenVersion,
9108
- chainId: input.chainId,
9109
- verifyingContract: input.token
9110
- },
9111
- types: ERC2612_PERMIT_TYPES,
9112
- primaryType: "Permit",
9113
- message: {
9114
- owner: input.owner,
9115
- spender: BATCH_PERMIT2_ADDRESS,
9116
- value: BigInt(input.permit.value),
9117
- nonce: BigInt(input.permit.nonce),
9118
- deadline: BigInt(input.permit.deadline)
9119
- }
9120
- };
9121
- }
9122
- var PERMIT_BATCH_TYPES = {
9123
- PermitBatchWitnessTransferFrom: [
9124
- { name: "permitted", type: "TokenPermissions[]" },
9125
- { name: "spender", type: "address" },
9126
- { name: "nonce", type: "uint256" },
9127
- { name: "deadline", type: "uint256" },
9128
- { name: "witness", type: "Deposit" }
9129
- ],
9130
- Deposit: [{ name: "recipients", type: "address[]" }],
9131
- TokenPermissions: [
9132
- { name: "token", type: "address" },
9133
- { name: "amount", type: "uint256" }
9134
- ]
9135
- };
9136
- var PERMIT2_BATCH_ABI = (0, import_viem18.parseAbi)([
9137
- "struct TokenPermissions { address token; uint256 amount; }",
9138
- "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
9139
- "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
9140
- "function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
9141
- "function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
9142
- ]);
9143
- function batchPermit(b) {
9144
- return {
9145
- permitted: b.transfers.map((t) => ({
9146
- token: b.token,
9147
- amount: BigInt(t.amount)
9148
- })),
9149
- nonce: BigInt(b.nonce),
9150
- deadline: BigInt(b.deadline)
9151
- };
9152
- }
9153
- function batchTypedData(b, spender) {
9154
- return {
9155
- domain: {
9156
- name: "Permit2",
9157
- chainId: b.chainId,
9158
- verifyingContract: BATCH_PERMIT2_ADDRESS
9159
- },
9160
- types: PERMIT_BATCH_TYPES,
9161
- primaryType: "PermitBatchWitnessTransferFrom",
9162
- message: {
9163
- ...batchPermit(b),
9164
- spender,
9165
- witness: { recipients: b.transfers.map((t) => t.to) }
9166
- }
9167
- };
9168
- }
9169
- var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
9170
- function batchWitness(b) {
9171
- return (0, import_viem18.hashStruct)({
9172
- types: PERMIT_BATCH_TYPES,
9173
- primaryType: "Deposit",
9174
- data: { recipients: b.transfers.map((t) => t.to) }
9175
- });
9176
- }
9177
- function batchArgs(b) {
9178
- return [
9179
- batchPermit(b),
9180
- b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
9181
- b.from,
9182
- batchWitness(b),
9183
- BATCH_WITNESS_TYPE,
9184
- b.signature
9185
- ];
9186
- }
9187
-
9188
- // src/lib/sponsored-token-batch.ts
9189
9600
  var memory = /* @__PURE__ */ new Map();
9190
9601
  var settledRecords = /* @__PURE__ */ new Map();
9191
9602
  var expiredRecords = /* @__PURE__ */ new Map();
@@ -9213,14 +9624,14 @@ async function releaseExpiredTokenBatch(i) {
9213
9624
  let timer;
9214
9625
  let expired = false;
9215
9626
  const check = async () => {
9216
- let active = read(key2);
9217
- if (active && i.expectedTxHash) {
9218
- const current = JSON.parse(active);
9627
+ let active2 = read(key2);
9628
+ if (active2 && i.expectedTxHash) {
9629
+ const current = JSON.parse(active2);
9219
9630
  if (!current.serializedTransaction || (0, import_viem19.keccak256)(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
9220
- active = void 0;
9631
+ active2 = void 0;
9221
9632
  }
9222
9633
  const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
9223
- const saved = active ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
9634
+ const saved = active2 ?? (archiveKey && (expiredRecords.get(archiveKey) ?? (typeof window === "undefined" ? void 0 : window.localStorage.getItem(archiveKey))));
9224
9635
  if (!saved) return;
9225
9636
  const body = JSON.parse(saved);
9226
9637
  if (!body.serializedTransaction || body.chainId !== i.chainId || !(0, import_viem19.isAddressEqual)(body.from, i.owner) || !(0, import_viem19.isAddressEqual)(body.token, i.token) || !body.transfers?.length) return;
@@ -9244,8 +9655,8 @@ async function releaseExpiredTokenBatch(i) {
9244
9655
  args: [i.owner, nonce >> 8n],
9245
9656
  blockNumber: block.number
9246
9657
  });
9247
- if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active && read(key2) !== active) return;
9248
- if (active) {
9658
+ if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active2 && read(key2) !== active2) return;
9659
+ if (active2) {
9249
9660
  const terminalKey = `${key2}:expired:${txHash.toLowerCase()}`;
9250
9661
  expiredRecords.set(terminalKey, saved);
9251
9662
  let persisted = typeof window === "undefined";
@@ -9303,15 +9714,15 @@ function clear(key2, body) {
9303
9714
  function sponsorTokenBatch(i) {
9304
9715
  const key2 = pendingKey(i);
9305
9716
  const plan = planOf(i.transfers);
9306
- const active = inflight.get(key2);
9307
- if (active) {
9308
- if (active.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
9717
+ const active2 = inflight.get(key2);
9718
+ if (active2) {
9719
+ if (active2.plan !== plan || i.resumeTxHash && pendingTokenBatch(i)?.txHash !== i.resumeTxHash)
9309
9720
  return Promise.reject(
9310
9721
  new Error(
9311
9722
  "A token deposit is already in progress. Wait for its result before depositing again."
9312
9723
  )
9313
9724
  );
9314
- return active.promise;
9725
+ return active2.promise;
9315
9726
  }
9316
9727
  const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
9317
9728
  inflight.set(key2, { plan, promise });
@@ -9465,7 +9876,7 @@ async function execute(i, key2, plan) {
9465
9876
  "DEPOSIT_INSUFFICIENT_BALANCE",
9466
9877
  "Insufficient token balance for this deposit."
9467
9878
  );
9468
- if (allowance < total && (i.chainId !== 8453 || !(0, import_viem19.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
9879
+ if (allowance < total && !supportsSponsoredErc2612(i.chainId, i.token))
9469
9880
  throw new OwneyError(
9470
9881
  "PERMIT2_APPROVAL_REQUIRED",
9471
9882
  "token deposits need a one-time Permit2 approval."
@@ -9501,7 +9912,7 @@ async function execute(i, key2, plan) {
9501
9912
  const signature2 = await i.wallet.signTypedData({
9502
9913
  account: i.owner,
9503
9914
  ...erc2612TypedData({
9504
- chainId: 8453,
9915
+ chainId: i.chainId,
9505
9916
  token: i.token,
9506
9917
  owner: i.owner,
9507
9918
  permit: unsignedPermit
@@ -10129,12 +10540,12 @@ var OwneySDK = class {
10129
10540
  return [...this.getActiveAgents().keys()].sort();
10130
10541
  }
10131
10542
  getActiveAgents() {
10132
- const active = /* @__PURE__ */ new Map();
10543
+ const active2 = /* @__PURE__ */ new Map();
10133
10544
  for (const id of this.activeAgents) {
10134
10545
  const agent = this.agents.get(id);
10135
- if (agent) active.set(id, agent);
10546
+ if (agent) active2.set(id, agent);
10136
10547
  }
10137
- return active;
10548
+ return active2;
10138
10549
  }
10139
10550
  /**
10140
10551
  * Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
@@ -10717,10 +11128,10 @@ var OwneySDK = class {
10717
11128
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
10718
11129
  * two independent auto-recovery mechanisms:
10719
11130
  *
10720
- * 1. Missing Permit2 allowance outside the atomic Base-USDC path: when the
11131
+ * 1. Missing Permit2 allowance outside the sponsored ERC-2612 path: when the
10721
11132
  * app did not supply its own callback and the attempt fails with
10722
11133
  * `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
10723
- * for that token. Base USDC bundles a gasless ERC-2612 approval inside
11134
+ * for that token. Native Base/Arbitrum USDC uses gasless ERC-2612 approval in
10724
11135
  * its sponsored deposit and never reaches this branch. Other tokens send
10725
11136
  * the one-time user-paid Permit2 approval via `approvePermit2()` and
10726
11137
  * retry the SAME sponsored attempt once. Bounded to one approval attempt
@@ -11227,8 +11638,8 @@ var OwneySDK = class {
11227
11638
  async ensureSwapChain(chainId) {
11228
11639
  const provider = this.requireConnectedProvider();
11229
11640
  const state = this.requireState();
11230
- const chain = swapChain(chainId);
11231
- if (!chain) {
11641
+ const chain2 = swapChain(chainId);
11642
+ if (!chain2) {
11232
11643
  throw new OwneyError(
11233
11644
  "CHAIN_UNSUPPORTED",
11234
11645
  `Chain ${chainId} is not supported`,
@@ -11236,10 +11647,10 @@ var OwneySDK = class {
11236
11647
  );
11237
11648
  }
11238
11649
  await ensureWalletOnChain(
11239
- (0, import_viem21.createPublicClient)({ chain, transport: (0, import_viem21.custom)(provider) }),
11650
+ (0, import_viem21.createPublicClient)({ chain: chain2, transport: (0, import_viem21.custom)(provider) }),
11240
11651
  (0, import_viem21.createWalletClient)({
11241
11652
  account: state.walletAddress,
11242
- chain,
11653
+ chain: chain2,
11243
11654
  transport: (0, import_viem21.custom)(provider)
11244
11655
  }),
11245
11656
  chainId
@@ -11263,15 +11674,15 @@ var OwneySDK = class {
11263
11674
  async wrapNativeSource(quote, walletAddress, onStage, direction) {
11264
11675
  const wrapper = requireWrapper(quote);
11265
11676
  const chainId = quote.src.chainId;
11266
- const chain = swapChain(chainId);
11677
+ const chain2 = swapChain(chainId);
11267
11678
  const provider = this.requireConnectedProvider();
11268
11679
  const publicClient = (0, import_viem21.createPublicClient)({
11269
- chain,
11680
+ chain: chain2,
11270
11681
  transport: swapReadTransport(chainId, this.zyfaiRpcUrls)
11271
11682
  });
11272
11683
  const wallet = (0, import_viem21.createWalletClient)({
11273
11684
  account: walletAddress,
11274
- chain,
11685
+ chain: chain2,
11275
11686
  transport: (0, import_viem21.custom)(provider)
11276
11687
  });
11277
11688
  onStage?.("wrapping");
@@ -11298,7 +11709,7 @@ var OwneySDK = class {
11298
11709
  data: tx.data,
11299
11710
  value: BigInt(tx.value),
11300
11711
  account: walletAddress,
11301
- chain
11712
+ chain: chain2
11302
11713
  });
11303
11714
  onSubmitted?.(hash2);
11304
11715
  const receipt = await publicClient.waitForTransactionReceipt({
@@ -11368,15 +11779,27 @@ var OwneySDK = class {
11368
11779
  * Kept as a builder rather than baked into the executor so the whole swap
11369
11780
  * flow stays testable without a provider — the executor never imports viem.
11370
11781
  */
11371
- buildSwapDeps(quote) {
11782
+ buildSwapDeps(quote, assertOperation, onTransactionHash) {
11372
11783
  const state = this.requireState();
11373
11784
  const provider = this.requireConnectedProvider();
11785
+ const revision = this.connectionRevision;
11786
+ const assertWallet = () => {
11787
+ assertOperation?.();
11788
+ if (this.state !== state || this.connectionRevision !== revision)
11789
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed before swap authorization.");
11790
+ };
11374
11791
  const srcChain = swapChain(quote.src.chainId);
11375
11792
  const dstChain = swapChain(quote.dst.chainId);
11376
11793
  const wallet = (0, import_viem21.createWalletClient)({
11377
11794
  account: state.walletAddress,
11378
11795
  chain: srcChain,
11379
- transport: (0, import_viem21.custom)(provider)
11796
+ // viem can await chain/account preparation before dispatching a wallet
11797
+ // request. Recheck at the transport boundary so an expired attempt or
11798
+ // replaced session cannot send after that internal await completes.
11799
+ transport: (0, import_viem21.custom)({ request: (args) => {
11800
+ assertWallet();
11801
+ return provider.request(args);
11802
+ } }, { retryCount: 0 })
11380
11803
  });
11381
11804
  const srcPublic = (0, import_viem21.createPublicClient)({
11382
11805
  chain: srcChain,
@@ -11405,6 +11828,7 @@ var OwneySDK = class {
11405
11828
  });
11406
11829
  },
11407
11830
  sendTransaction: async (tx) => {
11831
+ assertWallet();
11408
11832
  const hash2 = await wallet.sendTransaction({
11409
11833
  to: tx.to,
11410
11834
  data: tx.data,
@@ -11412,24 +11836,34 @@ var OwneySDK = class {
11412
11836
  account: state.walletAddress,
11413
11837
  chain: srcChain
11414
11838
  });
11839
+ onTransactionHash?.(hash2);
11415
11840
  const receipt = await srcPublic.waitForTransactionReceipt({
11416
11841
  timeout: receiptTimeoutMs(quote.src.chainId),
11417
11842
  hash: hash2,
11418
11843
  confirmations: 1
11844
+ }).catch(() => {
11845
+ throw new OwneyError(
11846
+ "SWAP_REQUEST_FAILED",
11847
+ "Swap receipt is not confirmed yet.",
11848
+ { txHash: hash2, settlementUnconfirmed: true, outcome: "submitted-unknown" }
11849
+ );
11419
11850
  });
11420
11851
  if (receipt.status !== "success") {
11421
11852
  throw new OwneyError(
11422
11853
  "SWAP_REQUEST_FAILED",
11423
11854
  `Swap transaction reverted (tx ${hash2})`,
11424
- { hash: hash2 }
11855
+ { txHash: hash2, outcome: "not-submitted", executionFailed: true }
11425
11856
  );
11426
11857
  }
11427
11858
  return hash2;
11428
11859
  },
11429
- signTypedData: (typedData) => wallet.signTypedData({
11430
- account: state.walletAddress,
11431
- ...typedData
11432
- }),
11860
+ signTypedData: (typedData) => {
11861
+ assertWallet();
11862
+ return wallet.signTypedData({
11863
+ account: state.walletAddress,
11864
+ ...typedData
11865
+ });
11866
+ },
11433
11867
  // Chain-bound like every other read here: the wallet provider's chain is
11434
11868
  // not ours to rely on mid-swap.
11435
11869
  readSourceBalance: async () => {
@@ -11451,6 +11885,7 @@ var OwneySDK = class {
11451
11885
  args: [state.walletAddress, spender]
11452
11886
  }),
11453
11887
  approve: async (spender, amount) => {
11888
+ assertWallet();
11454
11889
  const hash2 = await wallet.writeContract({
11455
11890
  address: quote.src.address,
11456
11891
  abi: ERC20_ALLOWANCE_ABI,
@@ -11459,14 +11894,22 @@ var OwneySDK = class {
11459
11894
  account: state.walletAddress,
11460
11895
  chain: srcChain
11461
11896
  });
11462
- await srcPublic.waitForTransactionReceipt({
11897
+ const receipt = await srcPublic.waitForTransactionReceipt({
11463
11898
  hash: hash2,
11464
11899
  confirmations: 1,
11465
11900
  timeout: receiptTimeoutMs(quote.src.chainId)
11466
11901
  });
11902
+ if (receipt.status !== "success") throw new OwneyError(
11903
+ "SWAP_REQUEST_FAILED",
11904
+ "Swap approval reverted.",
11905
+ { hash: hash2, phase: "approval", outcome: "not-submitted" }
11906
+ );
11467
11907
  return hash2;
11468
11908
  },
11469
- ensureChain: (chainId) => this.ensureSwapChain(chainId),
11909
+ ensureChain: (chainId) => {
11910
+ assertWallet();
11911
+ return this.ensureSwapChain(chainId);
11912
+ },
11470
11913
  /**
11471
11914
  * Sign the approval instead of paying for it. Null means the token
11472
11915
  * cannot be permitted (WETH has no `permit`) or its domain could not be
@@ -11486,10 +11929,13 @@ var OwneySDK = class {
11486
11929
  spender,
11487
11930
  value: amount,
11488
11931
  chainId: quote.src.chainId,
11489
- signTypedData: (payload) => wallet.signTypedData({
11490
- account: state.walletAddress,
11491
- ...payload
11492
- })
11932
+ signTypedData: (payload) => {
11933
+ assertWallet();
11934
+ return wallet.signTypedData({
11935
+ account: state.walletAddress,
11936
+ ...payload
11937
+ });
11938
+ }
11493
11939
  }
11494
11940
  ),
11495
11941
  /**
@@ -11539,36 +11985,20 @@ var OwneySDK = class {
11539
11985
  );
11540
11986
  }
11541
11987
  /**
11542
- * How much of a landed swap to actually deposit.
11543
- *
11544
- * The swap reports what ARRIVED, and the deposit used to be handed that
11545
- * figure directly. It is the entire arrival, so there is no slack: anything
11546
- * that spends the target asset between the two fails the deposit outright.
11547
- *
11548
- * Something does. A first WETH deposit needs a one-time Permit2 approval,
11549
- * and that approval is a transaction. A wallet holding no native gas token -
11550
- * the normal state of someone who just swapped everything INTO WETH - pays
11551
- * for it out of the WETH. The deposit then asked for ~2e12 wei more than was
11552
- * left and refused with DEPOSIT_INSUFFICIENT_BALANCE, under a wrapper saying
11553
- * the funds were safe but unadded (ROUT-242).
11554
- *
11555
- * So the approval happens here, before the amount is settled, and the amount
11556
- * comes from a read taken after it. Two things this deliberately is not:
11557
- *
11558
- * - It is not a balance sweep. `received` is a DELTA over the pre-swap
11559
- * balance, so a wallet already holding WETH reads far above it; depositing
11560
- * the balance would move funds the user never offered. The arrival is the
11561
- * ceiling and the balance is only ever allowed to lower it.
11562
- * - It does not touch USDC, which moves on signed typed data and so spends
11563
- * nothing of the user's in between, nor a paymaster-configured client,
11564
- * where the sponsor covers the transfer and Permit2 never applies.
11988
+ * Finish any user-paid Permit2 approval before measuring the deposit.
11989
+ * Wallets can charge approval gas in the received token (ROUT-242/544).
11990
+ * Cap the result at the swap arrival to preserve pre-existing wallet funds.
11991
+ * Native Base/Arbitrum USDC uses sponsored ERC-2612; configured paymaster
11992
+ * wallet calls also cover approval without spending the received tokens.
11565
11993
  */
11566
- async settleSwapDepositAmount(chainId, token, symbol, received) {
11567
- if (symbol !== "WETH" || this.paymasterServiceUrl) {
11994
+ async settleSwapDepositAmount(chainId, token, symbol2, received) {
11995
+ if (symbol2 !== "WETH" && symbol2 !== "USDC" || this.paymasterServiceUrl || supportsSponsoredErc2612(chainId, token)) {
11568
11996
  return received.toString();
11569
11997
  }
11570
11998
  const allowance = await this.readTokenPermit2Allowance(chainId, token);
11571
- if (allowance < received) await this.approvePermit2();
11999
+ if (allowance < received) {
12000
+ await this.approvePermit2(symbol2, received, chainId);
12001
+ }
11572
12002
  const balance = await this.readTokenBalance(chainId, token);
11573
12003
  return (balance < received ? balance : received).toString();
11574
12004
  }
@@ -11695,21 +12125,30 @@ var OwneySDK = class {
11695
12125
  * Every order is attempted even if an earlier one throws: they are unrelated
11696
12126
  * escrows, and one ending badly must not abandon the next one's secrets.
11697
12127
  * `unfinished` is not a verdict — the order may still settle, so unless the
11698
- * chain has said refunded or cancelled its preimages are kept.
12128
+ * provider has confirmed full settlement or refund, its preimages are kept.
11699
12129
  */
11700
12130
  async resumePendingSwaps(options = {}) {
11701
- const pending = listOrders();
11702
- if (pending.length === 0) {
11703
- await this.resumeSameChainSwaps(options);
11704
- return [];
11705
- }
12131
+ await mapConcurrent(listWithdrawSwaps(Date.now(), true).filter((record2) => record2.txHash && record2.status === "unknown" && (!options.orderHash || options.orderHash === record2.orderHash)), async (record2) => {
12132
+ try {
12133
+ const rpc2 = (0, import_viem21.createPublicClient)({ chain: swapChain(record2.srcChainId), transport: swapReadTransport(record2.srcChainId, this.zyfaiRpcUrls) });
12134
+ const receipt = await bounded(
12135
+ () => rpc2.getTransactionReceipt({ hash: record2.txHash }),
12136
+ 8e3,
12137
+ () => new Error("Receipt unavailable")
12138
+ );
12139
+ updateWithdrawSwap(record2.orderHash, { status: receipt.status === "success" ? "landed" : "not-swapped" });
12140
+ } catch {
12141
+ }
12142
+ });
12143
+ const pending = listOrders().filter((order) => !isOrderObserved(order.orderHash) && (!options.orderHash || order.orderHash === options.orderHash));
11706
12144
  const runner = this.swapRunnerDeps();
11707
- const results = [];
11708
- for (const order of pending) {
12145
+ const sameChain = this.resumeSameChainSwaps(options);
12146
+ const results = await mapConcurrent(pending, async (order) => {
11709
12147
  try {
11710
12148
  await runFusionOrder(runner, {
11711
12149
  orderHash: order.orderHash,
11712
12150
  secrets: order.secrets,
12151
+ hasPartialFill: listWithdrawSwaps(Date.now(), true).some((record2) => record2.orderHash === order.orderHash && record2.status === "partially-filled"),
11713
12152
  ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
11714
12153
  ...options.onStage ? {
11715
12154
  onStage: (stage) => options.onStage(order.orderHash, stage)
@@ -11717,25 +12156,23 @@ var OwneySDK = class {
11717
12156
  });
11718
12157
  clearOrder(order.orderHash);
11719
12158
  updateWithdrawSwap(order.orderHash, { status: "landed" });
11720
- results.push({
11721
- orderHash: order.orderHash,
11722
- status: "completed"
11723
- });
12159
+ return { orderHash: order.orderHash, status: "completed" };
11724
12160
  } catch (error) {
11725
- if (error instanceof OwneyError && (error.code === "SWAP_ORDER_REFUNDED" || error.code === "SWAP_ORDER_CANCELLED")) {
12161
+ if (error instanceof OwneyError && error.code === "SWAP_ORDER_REFUNDED" && error.details?.outcome === "refunded") {
11726
12162
  clearOrder(order.orderHash);
11727
12163
  updateWithdrawSwap(order.orderHash, { status: "refunded" });
11728
12164
  } else {
11729
- updateWithdrawSwap(order.orderHash, { status: "unknown" });
12165
+ if (!(error instanceof OwneyError && error.details?.watchActive))
12166
+ updateWithdrawSwap(order.orderHash, { status: error instanceof OwneyError && error.details?.outcome === "refund-pending" ? "refund-pending" : error instanceof OwneyError && error.details?.outcome === "partially-filled" ? "partially-filled" : "unknown" });
11730
12167
  }
11731
- results.push({
12168
+ return {
11732
12169
  orderHash: order.orderHash,
11733
12170
  status: "unfinished",
11734
12171
  ...error instanceof OwneyError ? { error } : {}
11735
- });
12172
+ };
11736
12173
  }
11737
- }
11738
- await this.resumeSameChainSwaps(options);
12174
+ });
12175
+ await sameChain;
11739
12176
  return results;
11740
12177
  }
11741
12178
  /**
@@ -11756,34 +12193,32 @@ var OwneySDK = class {
11756
12193
  * anything else.
11757
12194
  */
11758
12195
  async resumeSameChainSwaps(options = {}) {
11759
- const running = listWithdrawSwaps().filter(
11760
- (record) => record.status === "swapping" && record.srcChainId === record.dstChainId
11761
- );
11762
- for (const record of running) {
12196
+ const running = listWithdrawSwaps(Date.now(), true).filter((record2) => ["swapping", "unknown", "partially-filled", "refund-pending"].includes(record2.status) && record2.srcChainId === record2.dstChainId && /^0x[0-9a-f]{64}$/i.test(record2.orderHash) && !isOrderObserved(record2.orderHash) && (!options.orderHash || record2.orderHash === options.orderHash));
12197
+ await mapConcurrent(running, async (record2) => {
11763
12198
  const runner = {
11764
12199
  // The chain bound in. That product keys its orders per chain, and the
11765
12200
  // Fusion+ endpoint answers 404 for one — which would read as an order
11766
12201
  // that vanished rather than one still in the auction.
11767
- orderStatus: (h) => this.swapApi().orderStatus(h, record.srcChainId),
12202
+ orderStatus: (h) => this.swapApi().orderStatus(h, record2.srcChainId),
11768
12203
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
11769
12204
  now: () => Date.now()
11770
12205
  };
11771
12206
  try {
11772
12207
  await runSameChainOrder(runner, {
11773
- orderHash: record.orderHash,
12208
+ orderHash: record2.orderHash,
12209
+ hasPartialFill: record2.status === "partially-filled",
11774
12210
  ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
11775
12211
  ...options.onStage ? {
11776
- onStage: (stage) => options.onStage(record.orderHash, stage)
12212
+ onStage: (stage) => options.onStage(record2.orderHash, stage)
11777
12213
  } : {}
11778
12214
  });
11779
- updateWithdrawSwap(record.orderHash, { status: "landed" });
12215
+ updateWithdrawSwap(record2.orderHash, { status: "landed" });
11780
12216
  } catch (error) {
11781
- const code = error instanceof OwneyError ? error.code : void 0;
11782
- updateWithdrawSwap(record.orderHash, {
11783
- status: code === "SWAP_ORDER_EXPIRED" || code === "SWAP_ORDER_CANCELLED" ? "refunded" : "unknown"
11784
- });
12217
+ if (error instanceof OwneyError && error.details?.watchActive) return;
12218
+ const outcome = error instanceof OwneyError ? error.details?.outcome : void 0;
12219
+ updateWithdrawSwap(record2.orderHash, { status: outcome === "not-submitted" ? "not-swapped" : outcome === "partially-filled" ? "partially-filled" : "unknown" });
11785
12220
  }
11786
- }
12221
+ });
11787
12222
  }
11788
12223
  /**
11789
12224
  * What the wallet actually holds, across every supported chain, restricted
@@ -11939,6 +12374,92 @@ var OwneySDK = class {
11939
12374
  );
11940
12375
  }
11941
12376
  }
12377
+ /** Read-only confirmation of a saved pre-swap withdrawal. Never withdraws or swaps. */
12378
+ async recheckWithdrawalSwap(id) {
12379
+ const record2 = listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === id);
12380
+ const state = this.requireState();
12381
+ if (!record2?.recovery || record2.walletAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || record2.srcChainId !== this.requireChainId() || record2.recovery.phase === "submitted")
12382
+ throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "Reconnect the original wallet and source chain to check this withdrawal.");
12383
+ if (this.withdrawalSwapSubmissions.has(`${state.walletAddress.toLowerCase()}:${record2.srcChainId}:${record2.srcSymbol}`))
12384
+ throw new OwneyError("WITHDRAW_IN_PROGRESS", "This withdrawal is still being observed by its original attempt.");
12385
+ const revision = this.connectionRevision;
12386
+ const asset = SupportedAssets.find((a) => a.chainId === record2.srcChainId && a.symbol === record2.srcSymbol);
12387
+ if (!asset || !record2.recovery.results)
12388
+ throw new OwneyError(
12389
+ "WITHDRAW_SWAP_INCOMPLETE",
12390
+ "Some withdrawal results remain unknown. Check Activity and balances before continuing.",
12391
+ { reason: "WITHDRAWAL_RESULTS_UNAVAILABLE", swapSubmitted: false }
12392
+ );
12393
+ const rpc2 = (0, import_viem21.createPublicClient)({
12394
+ chain: VIEM_CHAIN2[record2.srcChainId],
12395
+ transport: swapReadTransport(record2.srcChainId, this.zyfaiRpcUrls)
12396
+ });
12397
+ const recovery = record2.recovery;
12398
+ const assertRecoveryCurrent = () => {
12399
+ if (this.state !== state || this.connectionRevision !== revision)
12400
+ throw new OwneyError("NOT_CONNECTED", "Wallet changed during confirmation.");
12401
+ const latest = listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === id);
12402
+ if (!latest?.recovery || latest.recovery.phase === "submitted")
12403
+ throw new OwneyError("WITHDRAW_IN_PROGRESS", "This intent has already advanced. Check its current order.");
12404
+ };
12405
+ const persist = () => {
12406
+ assertRecoveryCurrent();
12407
+ saveWithdrawSwap(record2);
12408
+ };
12409
+ for (const [id2, result] of Object.entries(recovery.results)) {
12410
+ const evidence = recovery.evidence[id2] ?? result.evidence;
12411
+ if (evidence) result.evidence = { ...evidence, transfer: "pending", delivery: "pending" };
12412
+ if (id2 === "yieldseeker" && recovery.requested === void 0) result.completion = "unverified";
12413
+ }
12414
+ try {
12415
+ const arrived = await awaitWithdrawalPayouts({
12416
+ results: recovery.results,
12417
+ requested: recovery.requested === void 0 ? void 0 : BigInt(recovery.requested),
12418
+ walletAddress: state.walletAddress,
12419
+ chainId: record2.srcChainId,
12420
+ token: asset.symbol,
12421
+ submittedAt: recovery.submittedAt,
12422
+ baseline: BigInt(recovery.baseline),
12423
+ baselineBlock: BigInt(recovery.baselineBlock),
12424
+ timeoutMs: 3e4,
12425
+ assertCurrent: assertRecoveryCurrent,
12426
+ readBalance: () => rpc2.readContract({ address: asset.address, abi: import_viem21.erc20Abi, functionName: "balanceOf", args: [state.walletAddress] }),
12427
+ inspect: (value, fromHistory) => this.inspectFunding(value, "withdraw", { requireAmountMatch: fromHistory, refreshBalance: false }),
12428
+ resolve: (value) => this.resolveSponsoredWithdrawalEvidence(value, { submittedAt: recovery.submittedAt }),
12429
+ history: (value, fromDate, filters) => this.getFundingHistory(value, fromDate, filters),
12430
+ verifyCompletion: (value) => this.getAgent(value.agentId).verifyWithdrawalCompletion?.(state, value) ?? Promise.resolve({ status: "unavailable" }),
12431
+ onProgress: (value) => {
12432
+ recovery.evidence[value.agentId] = value;
12433
+ persist();
12434
+ }
12435
+ });
12436
+ record2.withdrawn = arrived.toString();
12437
+ record2.status = "ready";
12438
+ recovery.phase = "ready";
12439
+ recovery.reason = void 0;
12440
+ persist();
12441
+ return record2;
12442
+ } catch (error) {
12443
+ record2.status = "withdrawal-incomplete";
12444
+ recovery.reason = error instanceof OwneyError ? String(error.details?.reason ?? error.code) : "CONFIRMATION_UNAVAILABLE";
12445
+ try {
12446
+ persist();
12447
+ } catch {
12448
+ }
12449
+ throw error;
12450
+ }
12451
+ }
12452
+ /** Explicit foreground continuation: spends verified payouts, never withdraws again. */
12453
+ async continueWithdrawalSwap(id, callbacks = {}) {
12454
+ const record2 = await this.recheckWithdrawalSwap(id);
12455
+ return this.withdrawAndSwap({
12456
+ from: { chainId: record2.srcChainId, symbol: record2.srcSymbol },
12457
+ to: { chainId: record2.dstChainId, symbol: record2.dstSymbol, decimals: record2.dstDecimals },
12458
+ ...record2.recovery?.agentId ? { agentId: record2.recovery.agentId } : {},
12459
+ recoveryId: id,
12460
+ ...callbacks
12461
+ });
12462
+ }
11942
12463
  /**
11943
12464
  * Withdraw from an agent and swap the proceeds into whatever the user wants
11944
12465
  * to hold, delivered to their own wallet.
@@ -11967,7 +12488,14 @@ var OwneySDK = class {
11967
12488
  const state = this.requireState();
11968
12489
  const activeChainId = this.requireChainId();
11969
12490
  const revision = this.connectionRevision;
12491
+ let active2 = true;
12492
+ let record2;
12493
+ let hadDurableRecord = false;
12494
+ const startedAt = Date.now();
12495
+ let confirmationDeadline = Infinity;
11970
12496
  const assertCurrent = () => {
12497
+ options.assertCurrent?.();
12498
+ if (!active2) throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal attempt is no longer active.", { swapSubmitted: false });
11971
12499
  if (this.state !== state || this.connectionRevision !== revision || state.chainId !== activeChainId)
11972
12500
  throw new OwneyError("NOT_CONNECTED", "Wallet changed during withdrawal and swap.");
11973
12501
  };
@@ -12009,30 +12537,95 @@ var OwneySDK = class {
12009
12537
  functionName: "balanceOf",
12010
12538
  args: [state.walletAddress]
12011
12539
  });
12012
- const baselineBlock = await srcPublic.getBlockNumber({ cacheTime: 0 });
12013
- const baseline = await readWalletBalance();
12540
+ const saved = options.recoveryId ? listWithdrawSwaps(Date.now(), true).find((row) => row.orderHash === options.recoveryId) : void 0;
12541
+ if (options.recoveryId && (!saved?.recovery?.results || saved.recovery.phase !== "ready" || saved.walletAddress?.toLowerCase() !== state.walletAddress.toLowerCase() || saved.srcChainId !== activeChainId || saved.srcSymbol !== asset.symbol || saved.dstChainId !== options.to.chainId || saved.dstSymbol !== options.to.symbol))
12542
+ throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal is not ready to continue.");
12543
+ const prepareTimeout = () => new OwneyError(
12544
+ "WITHDRAW_SWAP_INCOMPLETE",
12545
+ "Could not read the source wallet. No new withdrawal was submitted.",
12546
+ { reason: "BASELINE_UNAVAILABLE", swapSubmitted: false }
12547
+ );
12548
+ const baselineBlock = saved ? BigInt(saved.recovery.baselineBlock) : await bounded(() => srcPublic.getBlockNumber({ cacheTime: 0 }), 2e4, prepareTimeout);
12549
+ const baseline = saved ? BigInt(saved.recovery.baseline) : await bounded(readWalletBalance, 2e4, prepareTimeout);
12014
12550
  assertCurrent();
12015
12551
  debugLog("owney-sdk", "withdrawAndSwap: baseline", {
12016
12552
  asset: `${asset.symbol}@${asset.chainId}`,
12017
12553
  baseline: baseline.toString()
12018
12554
  });
12019
12555
  options.onSwapProgress?.("withdrawing");
12020
- const submittedAt = Date.now();
12021
- const requested = options.amount === void 0 ? void 0 : (0, import_viem21.parseUnits)(options.amount, asset.decimals);
12556
+ const submittedAt = saved?.recovery?.submittedAt ?? Date.now();
12557
+ const requested = saved ? saved.recovery.requested === void 0 ? void 0 : BigInt(saved.recovery.requested) : options.amount === void 0 ? void 0 : (0, import_viem21.parseUnits)(options.amount, asset.decimals);
12558
+ confirmationDeadline = Date.now() + (options.arrivalTimeoutMs ?? 18e4);
12559
+ const attemptId = saved?.recovery?.attemptId ?? `withdraw-${globalThis.crypto.randomUUID()}`;
12560
+ record2 = saved ?? {
12561
+ orderHash: attemptId,
12562
+ walletAddress: state.walletAddress,
12563
+ status: "withdrawal-pending",
12564
+ withdrawn: "0",
12565
+ expected: "0",
12566
+ createdAt: Date.now(),
12567
+ srcSymbol: asset.symbol,
12568
+ srcChainId: activeChainId,
12569
+ srcDecimals: asset.decimals,
12570
+ dstSymbol: options.to.symbol,
12571
+ dstChainId: options.to.chainId,
12572
+ dstDecimals: options.to.decimals ?? 18,
12573
+ recovery: {
12574
+ attemptId,
12575
+ phase: "withdrawing",
12576
+ baseline: baseline.toString(),
12577
+ baselineBlock: baselineBlock.toString(),
12578
+ submittedAt,
12579
+ deadline: confirmationDeadline,
12580
+ requested: requested?.toString(),
12581
+ agentId: options.agentId,
12582
+ evidence: {}
12583
+ }
12584
+ };
12585
+ hadDurableRecord = saveWithdrawSwap(record2);
12586
+ const assertSubmission = () => {
12587
+ assertCurrent();
12588
+ if (Date.now() >= confirmationDeadline) throw new OwneyError(
12589
+ "WITHDRAW_SWAP_INCOMPLETE",
12590
+ "Withdrawal confirmation timed out. Check Activity before retrying.",
12591
+ { reason: "PAYOUT_CONFIRMATION_TIMEOUT", swapSubmitted: false }
12592
+ );
12593
+ };
12022
12594
  const evidence = /* @__PURE__ */ new Map();
12023
12595
  const onProgress = (next) => {
12024
12596
  if (next.chainId !== activeChainId || next.token !== asset.symbol || next.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase()) return;
12025
12597
  evidence.set(next.agentId, next);
12598
+ if (record2?.recovery) {
12599
+ record2.recovery.evidence[next.agentId] = next;
12600
+ if (active2) saveWithdrawSwap(record2);
12601
+ else {
12602
+ const latest = listWithdrawSwaps(Date.now(), true).find((row) => row.recovery?.attemptId === record2.recovery.attemptId);
12603
+ if (latest?.recovery) updateWithdrawSwap(latest.orderHash, { recovery: {
12604
+ ...latest.recovery,
12605
+ evidence: { ...latest.recovery.evidence, [next.agentId]: next }
12606
+ } });
12607
+ }
12608
+ }
12026
12609
  notifyFunding(options.onWithdrawalProgress, next);
12027
12610
  };
12028
12611
  let withdraw;
12029
12612
  try {
12030
- withdraw = await this.withdraw({
12613
+ withdraw = saved ? { agentResult: saved.recovery.results, totalWithdrawn: null } : await bounded(() => this.withdraw({
12031
12614
  asset: asset.symbol,
12032
12615
  ...requested === void 0 ? {} : { amount: requested.toString() },
12033
12616
  ...options.agentId ? { agentId: options.agentId } : {},
12034
- onProgress
12035
- });
12617
+ onProgress,
12618
+ assertCurrent: assertSubmission,
12619
+ onBeforeSubmission: (next) => {
12620
+ assertSubmission();
12621
+ onProgress(next);
12622
+ },
12623
+ observationDeadline: confirmationDeadline
12624
+ }), Math.max(0, confirmationDeadline - Date.now()), () => new OwneyError(
12625
+ "WITHDRAW_SWAP_INCOMPLETE",
12626
+ "Withdrawal confirmation timed out. Check Activity and balances before trying again.",
12627
+ { reason: "WITHDRAWAL_DISPATCH_TIMEOUT", swapSubmitted: false, withdrawalEvidence: Object.fromEntries(evidence) }
12628
+ ));
12036
12629
  } catch (error) {
12037
12630
  const details = error instanceof OwneyError ? error.details : void 0;
12038
12631
  const moved = [...evidence.values()].some((value) => value.transfer !== "failed" && (value.delivery === "confirmed" || value.transfer === "unknown" || value.txHash || value.userOpHash || value.callsId || value.withdrawalRequest?.attempted));
@@ -12054,6 +12647,9 @@ var OwneySDK = class {
12054
12647
  const results = "agentResult" in withdraw ? withdraw.agentResult : {
12055
12648
  [options.agentId ?? withdraw.evidence?.agentId ?? "unknown"]: withdraw
12056
12649
  };
12650
+ record2.recovery.results = results;
12651
+ record2.recovery.phase = "verifying";
12652
+ saveWithdrawSwap(record2);
12057
12653
  const arrived = await awaitWithdrawalPayouts({
12058
12654
  results,
12059
12655
  requested,
@@ -12065,7 +12661,8 @@ var OwneySDK = class {
12065
12661
  baselineBlock,
12066
12662
  assertCurrent,
12067
12663
  readBalance: readWalletBalance,
12068
- timeoutMs: options.arrivalTimeoutMs,
12664
+ deadline: confirmationDeadline,
12665
+ verifyCompletion: (value) => this.getAgent(value.agentId).verifyWithdrawalCompletion?.(state, value) ?? Promise.resolve({ status: "unavailable" }),
12069
12666
  inspect: (value, fromHistory) => this.inspectFunding(value, "withdraw", { requireAmountMatch: fromHistory, refreshBalance: false }),
12070
12667
  resolve: (value) => this.resolveSponsoredWithdrawalEvidence(value, { submittedAt }),
12071
12668
  history: (value, fromDate, filters) => this.getFundingHistory(value, fromDate, filters),
@@ -12073,34 +12670,89 @@ var OwneySDK = class {
12073
12670
  });
12074
12671
  assertCurrent();
12075
12672
  const withdrawn = arrived.toString();
12673
+ record2.withdrawn = withdrawn;
12674
+ record2.status = "ready";
12675
+ record2.recovery.phase = "ready";
12676
+ hadDurableRecord = saveWithdrawSwap(record2) || hadDurableRecord;
12076
12677
  options.onSwapProgress?.("withdrawn");
12077
12678
  try {
12078
- const quote = await this.swapApi().quote({
12679
+ const quote = await bounded(() => this.swapApi().quote({
12079
12680
  from: { ...options.from, amount: withdrawn },
12080
12681
  to: options.to,
12081
12682
  direction: "withdraw",
12082
12683
  walletAddress: state.walletAddress
12083
- });
12684
+ }), 2e4, () => new OwneyError("SWAP_REQUEST_FAILED", "Swap pricing timed out.", { outcome: "not-submitted" }));
12084
12685
  assertCurrent();
12085
- await this.ensureSwapChain(quote.src.chainId);
12686
+ await bounded(
12687
+ () => this.ensureSwapChain(quote.src.chainId),
12688
+ 18e4,
12689
+ () => new OwneyError("SWAP_WALLET_PROMPT_TIMEOUT", "The wallet did not confirm its chain. No swap was submitted.", { outcome: "not-submitted" })
12690
+ );
12086
12691
  assertCurrent();
12087
- const swap = await executeSwap(this.buildSwapDeps(quote), {
12692
+ const swap = await executeSwap(this.buildSwapDeps(quote, assertCurrent, (hash2) => {
12693
+ record2.txHash = hash2;
12694
+ saveWithdrawSwap(record2);
12695
+ }), {
12696
+ onTransactionRequested: () => {
12697
+ assertCurrent();
12698
+ record2.status = "unknown";
12699
+ record2.recovery.phase = "submitted";
12700
+ if (!saveWithdrawSwap(record2) && hadDurableRecord)
12701
+ throw new OwneyError("SWAP_REQUEST_FAILED", "Recovery storage is unavailable. No swap was submitted.", { outcome: "not-submitted" });
12702
+ },
12703
+ assertCurrent,
12704
+ sourceReserve: baseline,
12088
12705
  quote,
12089
12706
  walletAddress: state.walletAddress,
12090
12707
  direction: "withdraw",
12091
12708
  ...options.slippage === void 0 ? {} : { slippage: options.slippage },
12092
12709
  ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
12093
- onOrderSubmitted: (orderHash) => {
12710
+ onOrderPrepared: (orderHash, details) => {
12711
+ assertCurrent();
12712
+ const previous = record2.orderHash;
12713
+ record2.orderHash = orderHash;
12714
+ record2.status = "unknown";
12715
+ record2.recovery.phase = "submitted";
12716
+ record2.spent = details.spent;
12717
+ record2.expected = details.expected;
12718
+ if (!saveWithdrawSwap(record2) && hadDurableRecord) {
12719
+ record2.orderHash = previous;
12720
+ throw new OwneyError("SWAP_REQUEST_FAILED", "Recovery storage is unavailable. No swap was submitted.", { outcome: "not-submitted" });
12721
+ }
12722
+ if (previous !== orderHash) {
12723
+ updateWithdrawSwap(previous, { status: "not-swapped" });
12724
+ clearWithdrawSwap(previous);
12725
+ }
12726
+ },
12727
+ onOrderSubmitted: (orderHash, details) => {
12094
12728
  releaseSubmission();
12095
- options.onOrderSubmitted?.({ orderHash, withdrawn, expected: quote.dst.amount });
12729
+ record2.orderHash = orderHash;
12730
+ record2.status = "swapping";
12731
+ record2.recovery.phase = "submitted";
12732
+ record2.createdAt = Date.now();
12733
+ const durable = saveWithdrawSwap(record2);
12734
+ options.onOrderSubmitted?.({
12735
+ orderHash,
12736
+ withdrawn,
12737
+ expected: details?.expected ?? quote.dst.amount,
12738
+ spent: details?.spent,
12739
+ durable: durable && details?.durable !== false
12740
+ });
12096
12741
  }
12097
12742
  });
12743
+ record2.status = "landed";
12744
+ record2.received = swap.received;
12745
+ record2.spent = swap.spent;
12746
+ saveWithdrawSwap(record2);
12098
12747
  return { withdraw, withdrawn, swap };
12099
12748
  } catch (error) {
12100
12749
  throw new OwneyError(
12101
12750
  "WITHDRAW_SWAP_FAILED",
12102
- `Withdrew ${withdrawn} ${asset.symbol} to your wallet, but the swap to ${options.to.symbol} did not complete. The funds are in your wallet as ${asset.symbol}.`,
12751
+ `The withdrawal completed, but the swap to ${options.to.symbol} could not be confirmed. Check the order and both balances before retrying.`,
12103
12752
  {
12753
+ outcome: record2.recovery?.phase === "ready" ? "not-submitted" : "submitted-unknown",
12754
+ ...error instanceof OwneyError ? error.details : {},
12755
+ ...record2.orderHash.startsWith("0x") ? { orderHash: record2.orderHash, settlementUnconfirmed: true } : {},
12104
12756
  withdrawn,
12105
12757
  asset: asset.symbol,
12106
12758
  chainId: asset.chainId,
@@ -12111,7 +12763,25 @@ var OwneySDK = class {
12111
12763
  }
12112
12764
  );
12113
12765
  }
12766
+ } catch (error) {
12767
+ if (record2) {
12768
+ if (error instanceof OwneyError && error.details) Object.assign(error.details, {
12769
+ attemptId: record2.recovery?.attemptId,
12770
+ phase: record2.recovery?.phase,
12771
+ elapsedMs: Date.now() - startedAt,
12772
+ txHash: record2.txHash,
12773
+ withdrawalEvidence: record2.recovery?.evidence
12774
+ });
12775
+ const details = error instanceof OwneyError ? error.details : void 0;
12776
+ const outcome = details?.outcome;
12777
+ if (record2.recovery && outcome === "not-submitted") record2.recovery.phase = "ready";
12778
+ record2.status = outcome === "refunded" ? "refunded" : outcome === "refund-pending" ? "refund-pending" : outcome === "partially-filled" ? "partially-filled" : record2.recovery?.phase === "submitted" || details?.settlementUnconfirmed ? "unknown" : record2.recovery?.phase === "ready" ? "ready" : "withdrawal-incomplete";
12779
+ if (record2.recovery) record2.recovery.reason = typeof details?.reason === "string" ? details.reason : void 0;
12780
+ saveWithdrawSwap(record2);
12781
+ }
12782
+ throw error;
12114
12783
  } finally {
12784
+ active2 = false;
12115
12785
  releaseSubmission();
12116
12786
  }
12117
12787
  }
@@ -12144,7 +12814,15 @@ var OwneySDK = class {
12144
12814
  const attempted = /* @__PURE__ */ new Set();
12145
12815
  this.withdrawalSubmissions.add(submissionKey);
12146
12816
  try {
12147
- return await this.withdrawFromAgents(options, state, chainId, attempted);
12817
+ return options.observationDeadline === void 0 ? await this.withdrawFromAgents(options, state, chainId, attempted) : await bounded(
12818
+ () => this.withdrawFromAgents(options, state, chainId, attempted),
12819
+ Math.max(0, options.observationDeadline - Date.now()),
12820
+ () => new OwneyError(
12821
+ "WITHDRAW_UNCONFIRMED",
12822
+ "Withdrawal dispatch could not be confirmed before the deadline.",
12823
+ { phase: "observe", reason: "WITHDRAWAL_DISPATCH_TIMEOUT" }
12824
+ )
12825
+ );
12148
12826
  } catch (error) {
12149
12827
  for (const id of ids) if (!attempted.has(id)) {
12150
12828
  notifyFunding(options.onProgress, {
@@ -12234,7 +12912,7 @@ var OwneySDK = class {
12234
12912
  progress(evidence);
12235
12913
  adapterReported = false;
12236
12914
  try {
12237
- if (options.onBeforeSubmission)
12915
+ if (options.onBeforeSubmission || options.assertCurrent)
12238
12916
  return await agent.withdraw(
12239
12917
  state,
12240
12918
  chainId,
@@ -12242,7 +12920,8 @@ var OwneySDK = class {
12242
12920
  requested2,
12243
12921
  progress,
12244
12922
  options.gasMode ?? "auto",
12245
- (next) => options.onBeforeSubmission({ ...evidence, ...next })
12923
+ options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0,
12924
+ options.assertCurrent
12246
12925
  );
12247
12926
  return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
12248
12927
  } catch (error) {