@owney/sdk 0.7.27-beta.0 → 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.verifiedVaults.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,36 +5976,38 @@ 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.readVaultLiquidity(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
- const group = (await checks).slice(index * 3, index * 3 + 3);
5711
- if (!active || group.length !== 3 || group.some((value) => value.status !== "success")) return null;
5989
+ const group = (await checks).slice(index * 2, index * 2 + 2);
5990
+ if (!active2 || group.length !== 2 || group.some((value) => value.status !== "success")) return null;
5712
5991
  this.assertSession(generation, chainId, asset);
5713
- const [underlying, shares, withdrawable] = 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 || typeof withdrawable !== "bigint" || withdrawable < 0n) return null;
5715
- const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: withdrawable.toString() };
5716
- if (shares > 0n && withdrawable === 0n) {
5992
+ const [underlying, shares] = group.map((value) => value.result);
5993
+ if (typeof underlying !== "string" || !(0, import_viem14.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return null;
5994
+ const verified = { ...position2, assetsRaw: shares.toString(), withdrawableAssetsRaw: "0" };
5995
+ if (shares > 0n) {
5717
5996
  let positiveValue = false;
5718
5997
  const valuations = await Promise.all(["convertToAssets", "previewRedeem"].map(async (functionName) => {
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 (typeof value !== "bigint" || value < 0n) return null;
6006
+ if (!active2 || typeof value !== "bigint" || value < 0n) return null;
6007
+ this.assertSession(generation, chainId, asset);
5728
6008
  if (value > 0n && !positiveValue) {
5729
6009
  positiveValue = true;
6010
+ verified.withdrawableAssetsRaw = value.toString();
5730
6011
  verifiedPositions.push(verified);
5731
6012
  }
5732
6013
  return value;
@@ -5734,7 +6015,12 @@ var YieldseekerAgent = class {
5734
6015
  return null;
5735
6016
  }
5736
6017
  }));
5737
- if (positiveValue) return verified;
6018
+ if (positiveValue) {
6019
+ const redemptionValue = valuations[1];
6020
+ if (redemptionValue !== null && redemptionValue > 0n)
6021
+ verified.withdrawableAssetsRaw = redemptionValue.toString();
6022
+ return verified;
6023
+ }
5738
6024
  if (blockNumber === void 0 || valuations.some((value) => value !== 0n)) return null;
5739
6025
  zeroValuePositions.add(verified);
5740
6026
  }
@@ -5745,7 +6031,7 @@ var YieldseekerAgent = class {
5745
6031
  }
5746
6032
  });
5747
6033
  const result = await boundedVerification(Promise.all(reads), timeoutMs);
5748
- active = false;
6034
+ active2 = false;
5749
6035
  this.assertSession(generation, chainId, asset);
5750
6036
  return {
5751
6037
  verified: result !== void 0 && result.every((position2) => position2 !== null),
@@ -5757,13 +6043,14 @@ var YieldseekerAgent = class {
5757
6043
  const response = await this.walletRequest(
5758
6044
  state,
5759
6045
  chainId,
5760
- this.agentPath(context, "yield-positions")
6046
+ this.agentPath(context, "vault-positions")
5761
6047
  );
5762
6048
  this.assertSession(generation, chainId, asset);
5763
- if (!Array.isArray(response?.yieldPositions)) throw this.invalidResponse("yield positions");
5764
- return validatePositions(response.yieldPositions);
6049
+ if (!Array.isArray(response?.vaultPositions)) throw this.invalidResponse("vault positions");
6050
+ return validatePositions(response.vaultPositions);
5765
6051
  };
5766
6052
  if (amount === void 0) {
6053
+ rememberPositions(priorPositions);
5767
6054
  rememberPositions(funds.indexedPositions);
5768
6055
  rememberPositions(positions);
5769
6056
  positionsStatus = hasPositions(positions) ? "remaining" : "empty";
@@ -5790,7 +6077,7 @@ var YieldseekerAgent = class {
5790
6077
  throw new OwneyError(
5791
6078
  "WITHDRAW_FAILED",
5792
6079
  "Yieldseeker vault funds are not currently withdrawable.",
5793
- { asset, positionsStatus, remainingPositions: positions.map((position2) => position2.yieldOption.address) },
6080
+ { asset, positionsStatus, remainingPositions: positions.map((position2) => position2.vaultOption.address) },
5794
6081
  this.id
5795
6082
  );
5796
6083
  }
@@ -5839,6 +6126,7 @@ var YieldseekerAgent = class {
5839
6126
  let latestUnwindBlock;
5840
6127
  const unwind = async (position2, assetsRaw) => {
5841
6128
  this.assertSession(generation, chainId, asset);
6129
+ assertOperation?.();
5842
6130
  let unwindTransactionHash;
5843
6131
  try {
5844
6132
  const response = await this.walletRequest(
@@ -5847,7 +6135,8 @@ var YieldseekerAgent = class {
5847
6135
  this.agentPath(context, "withdraw-from-position"),
5848
6136
  {
5849
6137
  method: "POST",
5850
- body: { chainId, vaultAddress: position2.yieldOption.address, assetsRaw: assetsRaw.toString() }
6138
+ body: { chainId, vaultAddress: position2.vaultOption.address, assetsRaw: assetsRaw.toString() },
6139
+ assertCurrent: assertOperation
5851
6140
  }
5852
6141
  );
5853
6142
  if (!this.isTransactionHash(response?.transactionHash)) throw this.invalidResponse("position withdrawal");
@@ -5866,7 +6155,7 @@ var YieldseekerAgent = class {
5866
6155
  ...error instanceof OwneyError ? error.details : {},
5867
6156
  withdrawalPhase: "unwind",
5868
6157
  reason: "YIELDSEEKER_UNWIND_UNRESOLVED",
5869
- vaultAddress: position2.yieldOption.address,
6158
+ vaultAddress: position2.vaultOption.address,
5870
6159
  unwindTransactionHash,
5871
6160
  ownerTransferSubmitted: false
5872
6161
  },
@@ -5879,7 +6168,7 @@ var YieldseekerAgent = class {
5879
6168
  };
5880
6169
  if (amount === void 0) {
5881
6170
  for (let sweep = 0; sweep < YIELDSEEKER_MAX_WITHDRAW_SWEEPS && hasPositions(positions); sweep++) {
5882
- 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)]));
5883
6172
  let unwound = false;
5884
6173
  for (const position2 of positions) {
5885
6174
  const available = BigInt(position2.withdrawableAssetsRaw);
@@ -5896,14 +6185,16 @@ var YieldseekerAgent = class {
5896
6185
  if (catalog) rememberPositions(catalog);
5897
6186
  this.assertSession(generation, chainId, asset);
5898
6187
  if (proof.verified) {
5899
- positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.yieldOption.address.toLowerCase()))];
5900
- } else if (catalog) positions = catalog;
5901
- else {
6188
+ positions = [...proof.positions, ...(catalog ?? []).filter((position2) => !knownBeforeRefresh.has(position2.vaultOption.address.toLowerCase()))];
6189
+ } else if (catalog) {
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()))];
6192
+ } else {
5902
6193
  positionsStatus = "unavailable";
5903
6194
  break;
5904
6195
  }
5905
6196
  positionsStatus = hasPositions(positions) ? "remaining" : "empty";
5906
- 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;
5907
6198
  }
5908
6199
  } else {
5909
6200
  for (const position2 of positions) {
@@ -5916,7 +6207,7 @@ var YieldseekerAgent = class {
5916
6207
  }
5917
6208
  }
5918
6209
  if (amount !== void 0 && remaining > 0n) {
5919
- throw this.invalidResponse("yield positions", {
6210
+ throw this.invalidResponse("vault positions", {
5920
6211
  reason: "Withdrawable positions could not cover the request.",
5921
6212
  remaining: remaining.toString()
5922
6213
  });
@@ -5948,15 +6239,22 @@ var YieldseekerAgent = class {
5948
6239
  delta: maxSettlementBasis.delta - payout,
5949
6240
  expectedNetDeposits: maxSettlementBasis.expectedNetDeposits - payout
5950
6241
  } : this.snapshotMovement(state, chainId, context, -payout);
5951
- const account = (0, import_viem13.getAddress)(state.walletAddress);
6242
+ const account = (0, import_viem14.getAddress)(state.walletAddress);
5952
6243
  let receipt;
5953
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);
5954
6252
  let submissionEvidence = base7;
5955
- const data = amount === void 0 ? (0, import_viem13.encodeFunctionData)({
6253
+ const data = amount === void 0 ? (0, import_viem14.encodeFunctionData)({
5956
6254
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
5957
6255
  functionName: "withdrawAllAssetToUser",
5958
6256
  args: [account, metadata.address]
5959
- }) : (0, import_viem13.encodeFunctionData)({
6257
+ }) : (0, import_viem14.encodeFunctionData)({
5960
6258
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
5961
6259
  functionName: "withdrawAssetToUser",
5962
6260
  args: [account, metadata.address, payout]
@@ -5968,11 +6266,12 @@ var YieldseekerAgent = class {
5968
6266
  client: this.getReceiptClient(),
5969
6267
  apiKey: this.owneyApiKey,
5970
6268
  routingApiBaseUrl: this.routingApiBaseUrl,
6269
+ assertCurrent: assertOperation,
5971
6270
  yieldseekerSignature: await this.auth.getToken(state, chainId),
5972
6271
  refreshYieldseekerSignature: (rejected) => this.auth.refreshToken(state, chainId, rejected),
5973
6272
  userId: context.user.userId,
5974
6273
  agentId: context.agent.agentId,
5975
- agentWallet: (0, import_viem13.getAddress)(context.wallet.walletAddress),
6274
+ agentWallet: (0, import_viem14.getAddress)(context.wallet.walletAddress),
5976
6275
  asset: metadata.address,
5977
6276
  callData: data,
5978
6277
  onPending: (pending) => {
@@ -5980,6 +6279,7 @@ var YieldseekerAgent = class {
5980
6279
  notifyFunding(onProgress, submissionEvidence);
5981
6280
  },
5982
6281
  onBeforeSubmission: async (pending) => {
6282
+ assertOperation?.();
5983
6283
  await onBeforeSubmission?.({ ...base7, ...pending });
5984
6284
  onOwnerTransferAttempted?.();
5985
6285
  }
@@ -5988,15 +6288,26 @@ var YieldseekerAgent = class {
5988
6288
  receipt = submitted.receipt;
5989
6289
  } else {
5990
6290
  onOwnerTransferAttempted?.();
5991
- txHash = await this.submitTransaction(state, chainId, {
5992
- from: account,
5993
- to: (0, import_viem13.getAddress)(context.wallet.walletAddress),
5994
- data,
5995
- value: "0",
5996
- chainId
5997
- }, (hash2) => notifyFunding(onProgress, { ...base7, txHash: hash2 }), (value) => {
5998
- receipt = value;
5999
- });
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
+ );
6000
6311
  }
6001
6312
  const evidence = receipt ? inspectFundingReceipt(receipt, { ...submissionEvidence, txHash }, metadata.address, "withdraw") : { ...submissionEvidence, txHash, transfer: "pending", delivery: "pending" };
6002
6313
  notifyFunding(onProgress, evidence);
@@ -6041,7 +6352,7 @@ var YieldseekerAgent = class {
6041
6352
  asset,
6042
6353
  positionsStatus,
6043
6354
  remainingPositions: positionsStatus === "remaining" ? positions.filter((position2) => !zeroValuePositions.has(position2) && (BigInt(position2.assetsRaw) > 0n || BigInt(position2.withdrawableAssetsRaw) > 0n)).map((position2) => ({
6044
- vaultAddress: position2.yieldOption.address,
6355
+ vaultAddress: position2.vaultOption.address,
6045
6356
  sharesRaw: position2.assetsRaw,
6046
6357
  withdrawableAmountRaw: position2.withdrawableAssetsRaw
6047
6358
  })) : void 0,
@@ -6050,6 +6361,7 @@ var YieldseekerAgent = class {
6050
6361
  this.id
6051
6362
  );
6052
6363
  }
6364
+ if (evidence.maxCompletion) evidence.maxCompletion.vaults = [...knownPositions.keys()];
6053
6365
  if (!proof.verified) completion = "unverified";
6054
6366
  }
6055
6367
  if (explicitEmptyPayout)
@@ -6082,7 +6394,7 @@ var YieldseekerAgent = class {
6082
6394
  }
6083
6395
  }
6084
6396
  }
6085
- /** A withdrawal-only liquidity floor; display snapshots never authorize it. */
6397
+ /** Fresh position amounts for withdrawal planning, net of reserved fees. */
6086
6398
  async getWithdrawableBalance(state, chainId, asset, minimumAmount) {
6087
6399
  this.assertChain(chainId);
6088
6400
  this.assertAsset(asset);
@@ -6107,18 +6419,76 @@ var YieldseekerAgent = class {
6107
6419
  );
6108
6420
  return proof.available;
6109
6421
  }
6110
- async readVaultLiquidity(options, wallet, blockNumber) {
6111
- const owner = (0, import_viem13.getAddress)(wallet);
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
+ }
6481
+ async readVaultShares(options, wallet, blockNumber) {
6482
+ const owner = (0, import_viem14.getAddress)(wallet);
6112
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));
6113
6484
  const results = await Promise.all(batches.map((batch) => this.getReceiptClient().multicall({
6114
6485
  blockNumber,
6115
6486
  allowFailure: true,
6116
6487
  contracts: batch.flatMap((option) => {
6117
- const address = (0, import_viem13.getAddress)(option.address);
6488
+ const address = (0, import_viem14.getAddress)(option.address);
6118
6489
  return [
6119
- { address, abi: import_viem13.erc4626Abi, functionName: "asset" },
6120
- { address, abi: import_viem13.erc20Abi, functionName: "balanceOf", args: [owner] },
6121
- { address, abi: import_viem13.erc4626Abi, functionName: "maxWithdraw", args: [owner] }
6490
+ { address, abi: import_viem14.erc4626Abi, functionName: "asset" },
6491
+ { address, abi: import_viem14.erc20Abi, functionName: "balanceOf", args: [owner] }
6122
6492
  ];
6123
6493
  })
6124
6494
  })));
@@ -6128,23 +6498,23 @@ var YieldseekerAgent = class {
6128
6498
  const underlying = YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase();
6129
6499
  const seen = /* @__PURE__ */ new Set();
6130
6500
  for (const value of values) {
6131
- const option = value?.yieldOption;
6132
- 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))
6133
- 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");
6134
6504
  seen.add(option.address.toLowerCase());
6135
6505
  }
6136
6506
  return values;
6137
6507
  }
6138
6508
  withdrawalVaultHints(state, chainId, context) {
6139
6509
  const key2 = this.contextKey(state, chainId, context.asset);
6140
- const positions = this.readCache.get(`positions:${key2}`)?.value?.yieldPositions ?? [];
6141
- 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]));
6142
6512
  const historic = this.readCache.get(`historic:${key2}`)?.value?.position;
6143
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()));
6144
- for (const option of this.yieldOptions.get(context.asset)?.value ?? []) {
6145
- 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;
6146
6516
  hints.set(option.address.toLowerCase(), {
6147
- yieldOption: option,
6517
+ vaultOption: option,
6148
6518
  assetsRaw: "0",
6149
6519
  assetsBase: "0",
6150
6520
  assetsUsd: 0,
@@ -6178,7 +6548,7 @@ var YieldseekerAgent = class {
6178
6548
  async resolveWithdrawalLiquidity(state, chainId, context, positions, previousPositions = [], requested) {
6179
6549
  const generation = this.readGeneration;
6180
6550
  const contextKey = this.contextKey(state, chainId, context.asset);
6181
- const version = this.portfolioVersion(contextKey);
6551
+ const version = this.liquidityVersions.get(contextKey) ?? 0;
6182
6552
  const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
6183
6553
  const unavailable = () => new OwneyError(
6184
6554
  "WITHDRAW_BALANCE_UNAVAILABLE",
@@ -6186,31 +6556,30 @@ var YieldseekerAgent = class {
6186
6556
  { asset: context.asset, reason: "YIELDSEEKER_LIQUIDITY_UNVERIFIED" },
6187
6557
  this.id
6188
6558
  );
6189
- let active = true;
6559
+ let active2 = true;
6190
6560
  let knownProof;
6191
- const hasUnverifiedIndexedFunds = (proof) => proof.indexedPositions.some((position2) => !proof.verifiedVaults.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);
6192
6562
  const assertReadCurrent = () => {
6193
6563
  this.assertSession(generation, chainId, context.asset);
6194
- if (!active || this.portfolioVersion(contextKey) !== version) throw unavailable();
6564
+ if (!active2 || (this.liquidityVersions.get(contextKey) ?? 0) !== version) throw unavailable();
6195
6565
  };
6196
6566
  const read2 = async () => {
6197
6567
  const client = this.getReceiptClient();
6198
6568
  const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
6199
- const wallet = (0, import_viem13.getAddress)(context.wallet.walletAddress);
6569
+ const wallet = (0, import_viem14.getAddress)(context.wallet.walletAddress);
6200
6570
  assertReadCurrent();
6201
6571
  if (typeof blockNumber !== "bigint") throw unavailable();
6202
6572
  const indexedPositions = this.validateWithdrawalPositions(positions ?? [], chainId, context.asset);
6203
6573
  const candidates = /* @__PURE__ */ new Map();
6204
6574
  for (const position2 of this.validateWithdrawalPositions([...previousPositions], chainId, context.asset))
6205
- candidates.set(position2.yieldOption.address.toLowerCase(), position2.yieldOption);
6206
- 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);
6207
6577
  const options = [...candidates.values()];
6208
6578
  if (options.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
6209
- const checksRead = this.readVaultLiquidity(options, wallet, blockNumber).then((value) => ({ value }), (error) => ({ error }));
6210
6579
  const [idle, feeTracker] = await Promise.all([
6211
6580
  client.readContract({
6212
6581
  address: metadata.address,
6213
- abi: import_viem13.erc20Abi,
6582
+ abi: import_viem14.erc20Abi,
6214
6583
  functionName: "balanceOf",
6215
6584
  args: [wallet],
6216
6585
  blockNumber
@@ -6222,10 +6591,10 @@ var YieldseekerAgent = class {
6222
6591
  blockNumber
6223
6592
  })
6224
6593
  ]);
6225
- 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();
6226
6595
  assertReadCurrent();
6227
6596
  const fees = await client.readContract({
6228
- address: (0, import_viem13.getAddress)(feeTracker),
6597
+ address: (0, import_viem14.getAddress)(feeTracker),
6229
6598
  abi: YIELDSEEKER_FEE_TRACKER_ABI,
6230
6599
  functionName: "getFeesOwed",
6231
6600
  args: [wallet],
@@ -6233,7 +6602,7 @@ var YieldseekerAgent = class {
6233
6602
  });
6234
6603
  if (typeof fees !== "bigint" || fees < 0n) throw unavailable();
6235
6604
  const verified = [];
6236
- const verifiedVaults = [];
6605
+ const resolvedVaults = [];
6237
6606
  let deployed = 0n;
6238
6607
  const proof = (complete) => ({
6239
6608
  blockNumber,
@@ -6241,53 +6610,102 @@ var YieldseekerAgent = class {
6241
6610
  fees,
6242
6611
  available: idle + deployed > fees ? idle + deployed - fees : 0n,
6243
6612
  positions: [...verified],
6244
- verifiedVaults: [...verifiedVaults],
6613
+ resolvedVaults: [...resolvedVaults],
6245
6614
  indexedPositions,
6246
6615
  complete
6247
6616
  });
6248
6617
  assertReadCurrent();
6249
6618
  knownProof = proof(false);
6250
6619
  if (requested !== void 0 && knownProof.available >= requested) return knownProof;
6251
- const collect = (options2, results) => {
6620
+ const indexed = new Map(indexedPositions.map((position2) => [position2.vaultOption.address.toLowerCase(), position2]));
6621
+ const indexedValue = indexedPositions.reduce((sum, position2) => sum + BigInt(position2.withdrawableAssetsRaw), 0n);
6622
+ const missingKnownPosition = previousPositions.some((position2) => !indexed.has(position2.vaultOption.address.toLowerCase()));
6623
+ const unvaluedPosition = indexedPositions.some((position2) => BigInt(position2.assetsRaw) > 0n && BigInt(position2.withdrawableAssetsRaw) === 0n);
6624
+ const indexedAvailable = idle + indexedValue > fees ? idle + indexedValue - fees : 0n;
6625
+ if (indexedValue > 0n && !missingKnownPosition && !unvaluedPosition && (requested === void 0 || indexedAvailable >= requested)) {
6626
+ const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
6627
+ for (const position2 of indexedPositions) if (BigInt(position2.withdrawableAssetsRaw) > 0n)
6628
+ known.add(position2.vaultOption.address.toLowerCase());
6629
+ this.balanceVaults.set(contextKey, known);
6630
+ return {
6631
+ blockNumber,
6632
+ idle,
6633
+ fees,
6634
+ available: indexedAvailable,
6635
+ positions: indexedPositions,
6636
+ resolvedVaults: [...indexed.keys()],
6637
+ indexedPositions,
6638
+ complete: true
6639
+ };
6640
+ }
6641
+ const collect = async (options2, results) => {
6252
6642
  assertReadCurrent();
6253
6643
  const known = this.balanceVaults.get(contextKey) ?? /* @__PURE__ */ new Set();
6254
6644
  this.balanceVaults.set(contextKey, known);
6255
- let valid2 = true;
6256
- options2.forEach((option, index) => {
6257
- const group = results.slice(index * 3, index * 3 + 3);
6258
- const shareProbe = group[1];
6259
- if (shareProbe?.status === "success" && typeof shareProbe.result === "bigint" && shareProbe.result > 0n)
6260
- known.add(option.address.toLowerCase());
6261
- if (group.length !== 3 || group.some((value) => value.status !== "success")) {
6262
- valid2 = false;
6263
- return;
6264
- }
6265
- const [underlying, shares, withdrawable] = group.map((value) => value.result);
6266
- if (typeof underlying !== "string" || !(0, import_viem13.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n || typeof withdrawable !== "bigint" || withdrawable < 0n || shares === 0n && withdrawable > 0n) {
6267
- valid2 = false;
6268
- return;
6269
- }
6270
- verifiedVaults.push(option.address.toLowerCase());
6271
- deployed += withdrawable;
6272
- verified.push({
6273
- yieldOption: option,
6645
+ const valid2 = await Promise.all(options2.map(async (option, index) => {
6646
+ const group = results.slice(index * 2, index * 2 + 2);
6647
+ if (group.length !== 2 || group.some((value2) => value2.status !== "success")) return false;
6648
+ const [underlying, shares] = group.map((value2) => value2.result);
6649
+ if (typeof underlying !== "string" || !(0, import_viem14.isAddress)(underlying) || underlying.toLowerCase() !== metadata.address.toLowerCase() || typeof shares !== "bigint" || shares < 0n) return false;
6650
+ if (shares > 0n) known.add(option.address.toLowerCase());
6651
+ const position2 = {
6652
+ vaultOption: option,
6274
6653
  assetsRaw: shares.toString(),
6275
6654
  assetsBase: "0",
6276
6655
  assetsUsd: 0,
6277
- withdrawableAssetsRaw: withdrawable.toString(),
6278
- withdrawableAssetsBase: withdrawable.toString()
6279
- });
6280
- });
6281
- return valid2;
6656
+ withdrawableAssetsRaw: "0",
6657
+ withdrawableAssetsBase: "0"
6658
+ };
6659
+ resolvedVaults.push(option.address.toLowerCase());
6660
+ verified.push(position2);
6661
+ knownProof = proof(false);
6662
+ if (shares === 0n) return true;
6663
+ let preview;
6664
+ try {
6665
+ const value2 = await client.readContract({
6666
+ address: (0, import_viem14.getAddress)(option.address),
6667
+ abi: import_viem14.erc4626Abi,
6668
+ functionName: "previewRedeem",
6669
+ args: [shares],
6670
+ blockNumber
6671
+ });
6672
+ if (typeof value2 === "bigint" && value2 >= 0n) preview = value2;
6673
+ } catch {
6674
+ }
6675
+ assertReadCurrent();
6676
+ let value = preview;
6677
+ if (value === void 0 || value === 0n) {
6678
+ try {
6679
+ const converted = await client.readContract({
6680
+ address: (0, import_viem14.getAddress)(option.address),
6681
+ abi: import_viem14.erc4626Abi,
6682
+ functionName: "convertToAssets",
6683
+ args: [shares],
6684
+ blockNumber
6685
+ });
6686
+ if (typeof converted === "bigint" && converted >= 0n && (converted > 0n || preview === 0n)) value = converted;
6687
+ else value = void 0;
6688
+ } catch {
6689
+ value = void 0;
6690
+ }
6691
+ }
6692
+ assertReadCurrent();
6693
+ if (value === void 0) return false;
6694
+ position2.withdrawableAssetsRaw = value.toString();
6695
+ position2.withdrawableAssetsBase = value.toString();
6696
+ deployed += value;
6697
+ knownProof = proof(false);
6698
+ return true;
6699
+ }));
6700
+ return valid2.every(Boolean);
6282
6701
  };
6283
- const checks = await checksRead;
6702
+ const checks = await this.readVaultShares(options, wallet, blockNumber);
6284
6703
  assertReadCurrent();
6285
- if ("error" in checks) throw checks.error;
6286
- const allKnownVerified = collect(options, checks.value);
6704
+ const allKnownVerified = await collect(options, checks);
6287
6705
  knownProof = proof(false);
6288
- const indexed = new Map(indexedPositions.map((position2) => [position2.yieldOption.address.toLowerCase(), position2]));
6706
+ if (requested !== void 0 && knownProof.available >= requested) return knownProof;
6289
6707
  const discrepancy = verified.some((position2) => {
6290
- const row = indexed.get(position2.yieldOption.address.toLowerCase());
6708
+ const row = indexed.get(position2.vaultOption.address.toLowerCase());
6291
6709
  return row ? BigInt(position2.withdrawableAssetsRaw) < BigInt(row.withdrawableAssetsRaw) : BigInt(position2.assetsRaw) > 0n;
6292
6710
  });
6293
6711
  const typedCoveredByIdle = requested !== void 0 && idle >= requested + fees;
@@ -6296,16 +6714,16 @@ var YieldseekerAgent = class {
6296
6714
  assertReadCurrent();
6297
6715
  return proof(true);
6298
6716
  }
6299
- const catalog = await this.loadYieldOptions(context.asset).catch(() => void 0);
6717
+ const catalog = await this.loadVaultOptions(context.asset).catch(() => void 0);
6300
6718
  assertReadCurrent();
6301
6719
  const eligible = /* @__PURE__ */ new Map();
6302
6720
  for (const option of catalog ?? []) {
6303
- 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))
6304
6722
  eligible.set(option.address.toLowerCase(), option);
6305
6723
  }
6306
6724
  const extra = [...eligible].filter(([address]) => !candidates.has(address)).map(([, option]) => option);
6307
6725
  if (options.length + extra.length > YIELDSEEKER_WITHDRAWAL_CANDIDATE_LIMIT) throw unavailable();
6308
- const allExtraVerified = collect(extra, await this.readVaultLiquidity(extra, wallet, blockNumber));
6726
+ const allExtraVerified = await collect(extra, await this.readVaultShares(extra, wallet, blockNumber));
6309
6727
  assertReadCurrent();
6310
6728
  const result = proof(eligible.size > 0 && allKnownVerified && allExtraVerified);
6311
6729
  if (result.available === 0n && !result.complete && !hasUnverifiedIndexedFunds(result)) throw unavailable();
@@ -6322,7 +6740,7 @@ var YieldseekerAgent = class {
6322
6740
  if (error instanceof OwneyError) throw error;
6323
6741
  throw unavailable();
6324
6742
  } finally {
6325
- active = false;
6743
+ active2 = false;
6326
6744
  if (timer) clearTimeout(timer);
6327
6745
  }
6328
6746
  }
@@ -6336,7 +6754,7 @@ var YieldseekerAgent = class {
6336
6754
  this.balanceContexts.set(read2, { context, generation: this.readGeneration });
6337
6755
  const key2 = this.contextKey(state, chainId, context.asset);
6338
6756
  const known = this.balanceVaults.get(key2) ?? /* @__PURE__ */ new Set();
6339
- 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());
6340
6758
  this.balanceVaults.set(key2, known);
6341
6759
  }
6342
6760
  return balance;
@@ -6351,7 +6769,7 @@ var YieldseekerAgent = class {
6351
6769
  const key2 = this.contextKey(state, evidence.chainId, evidence.token);
6352
6770
  const generation = this.readGeneration, version = this.portfolioVersion(key2);
6353
6771
  if (read2.readVersion !== version) return void 0;
6354
- const wallet = (0, import_viem13.getAddress)(context.wallet.walletAddress);
6772
+ const wallet = (0, import_viem14.getAddress)(context.wallet.walletAddress);
6355
6773
  const asset = YIELDSEEKER_ASSET_METADATA[evidence.token];
6356
6774
  const client = this.getReceiptClient();
6357
6775
  const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
@@ -6359,10 +6777,10 @@ var YieldseekerAgent = class {
6359
6777
  const vaults = [...this.balanceVaults.get(key2) ?? []];
6360
6778
  const [invested, idle, tracker] = await Promise.all([
6361
6779
  remainingVaultBalance(client, wallet, asset.address, vaults, blockNumber),
6362
- 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 }),
6363
6781
  client.readContract({ address: wallet, abi: YIELDSEEKER_AGENT_WALLET_ABI, functionName: "feeTracker", blockNumber })
6364
6782
  ]);
6365
- 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;
6366
6784
  const fees = await client.readContract({
6367
6785
  address: tracker,
6368
6786
  abi: YIELDSEEKER_FEE_TRACKER_ABI,
@@ -6375,7 +6793,7 @@ var YieldseekerAgent = class {
6375
6793
  const total = idle + invested > fees ? idle + invested - fees : 0n;
6376
6794
  const displayed = balance.assetBalances?.find((item) => item.chainId === evidence.chainId && item.asset === evidence.token);
6377
6795
  if (!displayed || !evidence.confirmedAmount || !/^\d+$/.test(evidence.confirmedAmount)) return void 0;
6378
- const amount = (0, import_viem13.parseUnits)(displayed.amount, asset.decimals);
6796
+ const amount = (0, import_viem14.parseUnits)(displayed.amount, asset.decimals);
6379
6797
  if (amount < 0n) return void 0;
6380
6798
  const displayUnit = evidence.token === "USDC" ? 5000n : 500000000000n;
6381
6799
  const movementUnit = BigInt(evidence.confirmedAmount) / 1000n;
@@ -6430,9 +6848,9 @@ var YieldseekerAgent = class {
6430
6848
  if (!context) return void 0;
6431
6849
  const idle = await withPaidRpcDiagnostics(() => this.getReceiptClient().readContract({
6432
6850
  address: YIELDSEEKER_ASSET_METADATA[asset].address,
6433
- abi: import_viem13.erc20Abi,
6851
+ abi: import_viem14.erc20Abi,
6434
6852
  functionName: "balanceOf",
6435
- args: [(0, import_viem13.getAddress)(context.wallet.walletAddress)]
6853
+ args: [(0, import_viem14.getAddress)(context.wallet.walletAddress)]
6436
6854
  }), chainId, "eth_call", this.id);
6437
6855
  this.assertSession(generation, chainId, asset);
6438
6856
  if (typeof idle !== "bigint" || idle < 0n) throw this.invalidResponse("wallet deposit balance");
@@ -6474,13 +6892,13 @@ var YieldseekerAgent = class {
6474
6892
  });
6475
6893
  const catalog = await Promise.all(
6476
6894
  [...new Set(contexts.map((context) => context.asset))].map(
6477
- (contextAsset) => this.loadYieldOptions(contextAsset)
6895
+ (contextAsset) => this.loadVaultOptions(contextAsset)
6478
6896
  )
6479
6897
  );
6480
6898
  const vaultAddresses = new Set(
6481
6899
  catalog.flat().filter(
6482
- (yieldOption) => yieldOption.chainId === chainId && (0, import_viem13.isAddress)(yieldOption.address)
6483
- ).map((yieldOption) => yieldOption.address.toLowerCase())
6900
+ (vaultOption) => vaultOption.chainId === chainId && (0, import_viem14.isAddress)(vaultOption.address)
6901
+ ).map((vaultOption) => vaultOption.address.toLowerCase())
6484
6902
  );
6485
6903
  return mapYieldseekerHistory(contexts, {
6486
6904
  limit: options?.limit ?? 10,
@@ -6505,30 +6923,30 @@ var YieldseekerAgent = class {
6505
6923
  const assets = requested ? [requested] : ["USDC", "WETH"];
6506
6924
  const values = await Promise.all(
6507
6925
  assets.map(async (asset) => {
6508
- return { asset, yieldOptions: await this.loadYieldOptions(asset) };
6926
+ return { asset, vaultOptions: await this.loadVaultOptions(asset) };
6509
6927
  })
6510
6928
  );
6511
6929
  return mapYieldseekerAgentApy(values, days);
6512
6930
  }
6513
- async loadYieldOptions(asset) {
6514
- const cached = this.yieldOptions.get(asset);
6931
+ async loadVaultOptions(asset) {
6932
+ const cached = this.vaultOptions.get(asset);
6515
6933
  if (cached && cached.expiresAt > Date.now()) return cached.value;
6516
- const pending = this.pendingYieldOptions.get(asset);
6934
+ const pending = this.pendingVaultOptions.get(asset);
6517
6935
  if (pending) return pending;
6518
6936
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
6519
6937
  const request2 = this.api.request(
6520
- `/chains/8453/assets/${metadata.address}/yield-options`
6938
+ `/chains/8453/assets/${metadata.address}/vault-options`
6521
6939
  ).then((response) => {
6522
- if (!Array.isArray(response?.yieldOptions)) {
6523
- throw this.invalidResponse("yield options");
6940
+ if (!Array.isArray(response?.vaultOptions)) {
6941
+ throw this.invalidResponse("vault options");
6524
6942
  }
6525
- this.yieldOptions.set(asset, {
6526
- expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
6527
- value: response.yieldOptions
6943
+ this.vaultOptions.set(asset, {
6944
+ expiresAt: Date.now() + YIELDSEEKER_VAULT_OPTIONS_CACHE_MS,
6945
+ value: response.vaultOptions
6528
6946
  });
6529
- return response.yieldOptions;
6530
- }).finally(() => this.pendingYieldOptions.delete(asset));
6531
- this.pendingYieldOptions.set(asset, request2);
6947
+ return response.vaultOptions;
6948
+ }).finally(() => this.pendingVaultOptions.delete(asset));
6949
+ this.pendingVaultOptions.set(asset, request2);
6532
6950
  return request2;
6533
6951
  }
6534
6952
  userKey(state, chainId) {
@@ -6608,7 +7026,7 @@ var YieldseekerAgent = class {
6608
7026
  `/users/${user.userId}/agents/${agent.agentId}/wallet`
6609
7027
  ).then((walletResponse) => {
6610
7028
  this.assertSession(generation, chainId, asset);
6611
- if (!walletResponse?.agentWallet || !(0, import_viem13.isAddress)(walletResponse.agentWallet.walletAddress)) {
7029
+ if (!walletResponse?.agentWallet || !(0, import_viem14.isAddress)(walletResponse.agentWallet.walletAddress)) {
6612
7030
  throw this.invalidResponse("agent wallet");
6613
7031
  }
6614
7032
  const context = {
@@ -6638,7 +7056,7 @@ var YieldseekerAgent = class {
6638
7056
  this.users.set(key2, persisted);
6639
7057
  return persisted;
6640
7058
  }
6641
- const walletAddress = (0, import_viem13.getAddress)(state.walletAddress);
7059
+ const walletAddress = (0, import_viem14.getAddress)(state.walletAddress);
6642
7060
  const generation = this.readGeneration;
6643
7061
  let user = null;
6644
7062
  try {
@@ -6838,9 +7256,10 @@ var YieldseekerAgent = class {
6838
7256
  expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
6839
7257
  };
6840
7258
  }
6841
- advancePortfolioVersion(contextKey) {
7259
+ advancePortfolioVersion(contextKey, { snapshotOnly = false } = {}) {
6842
7260
  const version = this.portfolioVersion(contextKey) + 1;
6843
7261
  this.portfolioVersions.set(contextKey, version);
7262
+ if (!snapshotOnly) this.liquidityVersions.set(contextKey, version);
6844
7263
  this.snapshotFailures.delete(contextKey);
6845
7264
  for (const kind of ["snapshot", "positions", "historic", "actions"]) {
6846
7265
  const key2 = `${kind}:${contextKey}`;
@@ -6888,7 +7307,7 @@ var YieldseekerAgent = class {
6888
7307
  if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
6889
7308
  const failure = this.snapshotFailures.get(contextKey);
6890
7309
  if (failure && failure.retryAt > Date.now()) throw failure.error;
6891
- this.advancePortfolioVersion(contextKey);
7310
+ this.advancePortfolioVersion(contextKey, { snapshotOnly: true });
6892
7311
  }
6893
7312
  const version = this.portfolioVersion(contextKey);
6894
7313
  const generation = this.readGeneration;
@@ -6971,17 +7390,17 @@ var YieldseekerAgent = class {
6971
7390
  }
6972
7391
  async loadPortfolioParts(state, chainId, context, options, contextKey) {
6973
7392
  const [positions, historic, actions] = await Promise.all([
6974
- options.includePositions === false ? Promise.resolve({ yieldPositions: [] }) : this.cachedRead(
7393
+ options.includePositions === false ? Promise.resolve({ vaultPositions: [] }) : this.cachedRead(
6975
7394
  `positions:${contextKey}`,
6976
7395
  this.portfolioCacheMs(contextKey),
6977
7396
  async () => {
6978
7397
  const response = await this.walletRequest(
6979
7398
  state,
6980
7399
  chainId,
6981
- this.agentPath(context, "yield-positions")
7400
+ this.agentPath(context, "vault-positions")
6982
7401
  );
6983
- if (!Array.isArray(response?.yieldPositions)) {
6984
- throw this.invalidResponse("yield positions");
7402
+ if (!Array.isArray(response?.vaultPositions)) {
7403
+ throw this.invalidResponse("vault positions");
6985
7404
  }
6986
7405
  return response;
6987
7406
  }
@@ -7006,7 +7425,7 @@ var YieldseekerAgent = class {
7006
7425
  ) : Promise.resolve(void 0)
7007
7426
  ]);
7008
7427
  return {
7009
- positions: positions.yieldPositions,
7428
+ positions: positions.vaultPositions,
7010
7429
  ...historic?.position ? { historic: historic.position } : {},
7011
7430
  ...actions?.actions ? { actions: actions.actions } : {}
7012
7431
  };
@@ -7020,7 +7439,7 @@ var YieldseekerAgent = class {
7020
7439
  this.agentPath(context, "deploy"),
7021
7440
  { method: "POST", body: {} }
7022
7441
  );
7023
- 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) {
7024
7443
  throw this.invalidResponse("agent deployment", {
7025
7444
  reason: "Deploy did not return the expected Agent Wallet."
7026
7445
  });
@@ -7077,10 +7496,10 @@ var YieldseekerAgent = class {
7077
7496
  }
7078
7497
  async providerRequest(state, chainId, path, options = {}) {
7079
7498
  this.assertChain(chainId);
7080
- const request2 = (signature2) => this.api.request(path, {
7081
- ...options,
7082
- signature: signature2
7083
- });
7499
+ const request2 = (signature2) => {
7500
+ options.assertCurrent?.();
7501
+ return this.api.request(path, { method: options.method, body: options.body, signature: signature2 });
7502
+ };
7084
7503
  let signature = await this.auth.getToken(state, chainId);
7085
7504
  try {
7086
7505
  return await request2(signature);
@@ -7129,18 +7548,18 @@ var YieldseekerAgent = class {
7129
7548
  return hash3;
7130
7549
  }
7131
7550
  this.assertTransaction(transaction, state, chainId);
7132
- const account = (0, import_viem13.getAddress)(state.walletAddress);
7133
- const walletClient = (0, import_viem13.createWalletClient)({
7551
+ const account = (0, import_viem14.getAddress)(state.walletAddress);
7552
+ const walletClient = (0, import_viem14.createWalletClient)({
7134
7553
  account,
7135
7554
  chain: import_chains4.base,
7136
- transport: onBeforeSubmission ? (0, import_viem13.custom)({ request: async (args) => {
7555
+ transport: onBeforeSubmission ? (0, import_viem14.custom)({ request: async (args) => {
7137
7556
  if (args.method === "eth_sendTransaction") await onBeforeSubmission();
7138
7557
  return state.provider.request(args);
7139
- } }, { retryCount: 0 }) : (0, import_viem13.custom)(state.provider)
7558
+ } }, { retryCount: 0 }) : (0, import_viem14.custom)(state.provider)
7140
7559
  });
7141
- const walletChainClient = (0, import_viem13.createPublicClient)({
7560
+ const walletChainClient = (0, import_viem14.createPublicClient)({
7142
7561
  chain: import_chains4.base,
7143
- transport: (0, import_viem13.custom)(state.provider)
7562
+ transport: (0, import_viem14.custom)(state.provider)
7144
7563
  });
7145
7564
  await ensureWalletOnChain(
7146
7565
  walletChainClient,
@@ -7150,7 +7569,7 @@ var YieldseekerAgent = class {
7150
7569
  const hash2 = await walletClient.sendTransaction({
7151
7570
  account,
7152
7571
  chain: import_chains4.base,
7153
- to: (0, import_viem13.getAddress)(transaction.to),
7572
+ to: (0, import_viem14.getAddress)(transaction.to),
7154
7573
  data: transaction.data,
7155
7574
  value: BigInt(transaction.value)
7156
7575
  });
@@ -7254,12 +7673,12 @@ var YieldseekerAgent = class {
7254
7673
  return receipt;
7255
7674
  }
7256
7675
  assertTransaction(transaction, state, chainId) {
7257
- 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)) {
7258
7677
  throw this.invalidResponse("transaction");
7259
7678
  }
7260
7679
  }
7261
7680
  assertAgent(agent) {
7262
- 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) {
7263
7682
  throw this.invalidResponse("agent");
7264
7683
  }
7265
7684
  }
@@ -7382,45 +7801,57 @@ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
7382
7801
  // src/lib/swap/swap-api.ts
7383
7802
  async function request(baseUrl, apiKey, path, init) {
7384
7803
  const url = `${baseUrl}/api/v1/swap${path}`;
7385
- const res = await fetch(url, {
7386
- method: init?.method ?? "GET",
7387
- headers: {
7388
- "Content-Type": "application/json",
7389
- "x-owney-api-key": apiKey
7390
- },
7391
- ...init ? { body: JSON.stringify(init.body) } : {}
7392
- });
7393
- if (!res.ok) {
7394
- const text = await res.text().catch(() => "");
7395
- if (res.status === 429) {
7396
- throw new OwneyError(
7397
- "SWAP_RATE_LIMITED",
7398
- "Swap provider is rate limiting, retry shortly",
7399
- { statusCode: res.status }
7400
- );
7401
- }
7402
- if (res.status === 403) {
7403
- throw new OwneyError(
7404
- "SWAP_DISABLED",
7405
- "Swap is not enabled for this organization",
7406
- { statusCode: res.status }
7407
- );
7408
- }
7409
- throw new OwneyError(
7410
- "SWAP_REQUEST_FAILED",
7411
- `Swap API error ${res.status}: ${text}`,
7412
- { statusCode: res.status, responseBody: text }
7413
- );
7414
- }
7415
- const json = await res.json();
7416
- if (!json.success) {
7417
- 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(
7418
7848
  "SWAP_REQUEST_FAILED",
7419
- `Swap API request failed: ${json.message ?? "unknown error"}`,
7420
- { message: json.message }
7421
- );
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();
7422
7854
  }
7423
- return json.data;
7424
7855
  }
7425
7856
  function createSwapApi(baseUrl, apiKey) {
7426
7857
  return {
@@ -7544,7 +7975,7 @@ function createSwapApi(baseUrl, apiKey) {
7544
7975
  }
7545
7976
 
7546
7977
  // src/lib/swap/swap.rpc.ts
7547
- var import_viem14 = require("viem");
7978
+ var import_viem15 = require("viem");
7548
7979
  var DEFAULT_RPC_URLS = {
7549
7980
  // NOT cloudflare-eth.com, which used to lead this list. It answers
7550
7981
  // eth_chainId happily, errors on eth_blockNumber, and — the part that
@@ -7584,10 +8015,10 @@ var DEFAULT_RPC_URLS = {
7584
8015
  };
7585
8016
  function swapReadTransport(chainId, overrides) {
7586
8017
  const override = overrides?.[chainId];
7587
- if (override) return (0, import_viem14.http)(override);
8018
+ if (override) return (0, import_viem15.http)(override);
7588
8019
  const urls = DEFAULT_RPC_URLS[chainId];
7589
- if (!urls || urls.length === 0) return (0, import_viem14.http)();
7590
- 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)));
7591
8022
  }
7592
8023
  function receiptTimeoutMs(chainId) {
7593
8024
  return chainId === 1 ? 6e5 : 18e4;
@@ -7716,8 +8147,12 @@ async function awaitBalanceIncrease(options) {
7716
8147
  let lastError;
7717
8148
  for (; ; ) {
7718
8149
  try {
7719
- const balance = await readBalance();
7720
- 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) {
7721
8156
  const arrived = balance - baseline;
7722
8157
  debugLog("owney-sdk", "arrival: funds landed", {
7723
8158
  arrived: arrived.toString()
@@ -7743,12 +8178,12 @@ async function awaitBalanceIncrease(options) {
7743
8178
  lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
7744
8179
  } : { arrived: null };
7745
8180
  }
7746
- await sleep(pollMs);
8181
+ await sleep(Math.min(pollMs, Math.max(0, deadline - Date.now())));
7747
8182
  }
7748
8183
  }
7749
8184
 
7750
8185
  // src/lib/swap/swap.secrets.ts
7751
- var import_viem15 = require("viem");
8186
+ var import_viem16 = require("viem");
7752
8187
  var SECRET_BYTES = 32;
7753
8188
  function randomBytes(length) {
7754
8189
  const bytes = new Uint8Array(length);
@@ -7770,9 +8205,9 @@ function mintSecrets(count) {
7770
8205
  const secrets = [];
7771
8206
  const secretHashes = [];
7772
8207
  for (let i = 0; i < count; i++) {
7773
- const secret = (0, import_viem15.toHex)(randomBytes(SECRET_BYTES));
8208
+ const secret = (0, import_viem16.toHex)(randomBytes(SECRET_BYTES));
7774
8209
  secrets.push(secret);
7775
- secretHashes.push((0, import_viem15.keccak256)(secret));
8210
+ secretHashes.push((0, import_viem16.keccak256)(secret));
7776
8211
  }
7777
8212
  return { secrets, secretHashes };
7778
8213
  }
@@ -7796,7 +8231,7 @@ var DEFAULT_POLL_MS2 = 5e3;
7796
8231
  var MAX_BACKOFF_MS = 3e4;
7797
8232
  var backoffFor = (failures, base7) => Math.min(base7 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
7798
8233
  var DEFAULT_TIMEOUT_MS2 = 15 * 60 * 1e3;
7799
- async function runFusionOrder(deps, options) {
8234
+ async function runFusionOrderInternal(deps, options) {
7800
8235
  const {
7801
8236
  orderHash,
7802
8237
  secrets,
@@ -7805,39 +8240,51 @@ async function runFusionOrder(deps, options) {
7805
8240
  timeoutMs = DEFAULT_TIMEOUT_MS2
7806
8241
  } = options;
7807
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
+ };
7808
8263
  let failures = 0;
7809
8264
  const published = /* @__PURE__ */ new Set();
7810
8265
  onStage?.("swapping");
7811
8266
  for (; ; ) {
7812
- if (deps.now() >= deadline) {
7813
- throw new OwneyError(
7814
- "SWAP_REQUEST_FAILED",
7815
- "Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
7816
- // Marks the one failure that means "we stopped watching a live order",
7817
- // as opposed to a verdict or a prompt nobody answered. The executor
7818
- // checks the wallet before believing it: resolvers do not stop filling
7819
- // because we stopped looking (ROUT-242, comment 15980).
7820
- { orderHash, settlementUnconfirmed: true }
7821
- );
7822
- }
8267
+ assertTime();
7823
8268
  let ready;
7824
8269
  try {
7825
- ready = await deps.readyForSecrets(orderHash);
8270
+ ready = await read2(() => deps.readyForSecrets(orderHash));
7826
8271
  } catch {
7827
8272
  ready = {};
7828
8273
  }
8274
+ assertTime();
7829
8275
  for (const fill of ready.fills ?? []) {
8276
+ assertTime();
7830
8277
  if (published.has(fill.idx)) continue;
7831
8278
  const secret = secrets[fill.idx];
7832
8279
  if (secret === void 0) {
7833
8280
  throw new OwneyError(
7834
8281
  "SWAP_REQUEST_FAILED",
7835
8282
  `Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
7836
- { orderHash, fillIndex: fill.idx }
8283
+ { orderHash, fillIndex: fill.idx, settlementUnconfirmed: true, outcome: "submitted-unknown" }
7837
8284
  );
7838
8285
  }
7839
8286
  try {
7840
- await deps.submitSecret(orderHash, secret);
8287
+ await read2(() => deps.submitSecret(orderHash, secret));
7841
8288
  published.add(fill.idx);
7842
8289
  } catch {
7843
8290
  failures += 1;
@@ -7845,29 +8292,34 @@ async function runFusionOrder(deps, options) {
7845
8292
  }
7846
8293
  let status;
7847
8294
  try {
7848
- ({ 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;
7849
8298
  failures = 0;
7850
8299
  } catch {
7851
8300
  failures += 1;
7852
- await deps.sleep(backoffFor(failures, pollIntervalMs));
8301
+ await pause(backoffFor(failures, pollIntervalMs));
7853
8302
  continue;
7854
8303
  }
7855
8304
  if (status === "refunding") onStage?.("refunding");
7856
8305
  if (isSwapTerminal(status)) {
7857
- if (status === "executed") {
8306
+ if (status === "executed" || status === "filled") {
7858
8307
  onStage?.("swapped");
7859
8308
  return { status, filled: true };
7860
8309
  }
7861
- if (status === "refunded") onStage?.("refunded");
8310
+ if (status === "refunded") onStage?.(partial ? "swap-partial" : "refunded");
7862
8311
  throw new OwneyError(
7863
8312
  status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
7864
- 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.",
7865
- { 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" }
7866
8315
  );
7867
8316
  }
7868
- await deps.sleep(pollIntervalMs);
8317
+ await pause(pollIntervalMs);
7869
8318
  }
7870
8319
  }
8320
+ function runFusionOrder(deps, options) {
8321
+ return observeOrder(options.orderHash, () => runFusionOrderInternal(deps, options));
8322
+ }
7871
8323
 
7872
8324
  // src/lib/swap/swap.same-chain-runner.ts
7873
8325
  var DEFAULT_POLL_MS3 = 5e3;
@@ -7892,7 +8344,7 @@ function explain(status) {
7892
8344
  return "The swap did not complete. Nothing was exchanged.";
7893
8345
  }
7894
8346
  }
7895
- async function runSameChainOrder(deps, options) {
8347
+ async function runSameChainOrderInternal(deps, options) {
7896
8348
  const {
7897
8349
  orderHash,
7898
8350
  onStage,
@@ -7900,23 +8352,39 @@ async function runSameChainOrder(deps, options) {
7900
8352
  timeoutMs = DEFAULT_TIMEOUT_MS3
7901
8353
  } = options;
7902
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
+ };
7903
8375
  let failures = 0;
7904
8376
  onStage?.("swapping");
7905
8377
  for (; ; ) {
7906
- if (deps.now() >= deadline) {
7907
- throw new OwneyError(
7908
- "SWAP_ORDER_EXPIRED",
7909
- "No one filled the swap in time. Nothing was exchanged and your funds are still in your wallet.",
7910
- { orderHash }
7911
- );
7912
- }
8378
+ assertTime();
7913
8379
  let status;
7914
8380
  try {
7915
- ({ 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;
7916
8384
  failures = 0;
7917
8385
  } catch {
7918
8386
  failures += 1;
7919
- await deps.sleep(backoffFor2(failures, pollIntervalMs));
8387
+ await pause(backoffFor2(failures, pollIntervalMs));
7920
8388
  continue;
7921
8389
  }
7922
8390
  if (isSwapTerminal(status)) {
@@ -7926,17 +8394,19 @@ async function runSameChainOrder(deps, options) {
7926
8394
  }
7927
8395
  throw new OwneyError(
7928
8396
  status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
7929
- explain(status),
7930
- { 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" }
7931
8399
  );
7932
8400
  }
7933
- await deps.sleep(pollIntervalMs);
8401
+ await pause(pollIntervalMs);
7934
8402
  }
7935
8403
  }
8404
+ function runSameChainOrder(deps, options) {
8405
+ return observeOrder(options.orderHash, () => runSameChainOrderInternal(deps, options));
8406
+ }
7936
8407
 
7937
8408
  // src/lib/swap/swap.secret-store.ts
7938
8409
  var KEY_PREFIX4 = "owney.swap.order";
7939
- var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
7940
8410
  var storage4 = () => {
7941
8411
  if (typeof window === "undefined") return null;
7942
8412
  try {
@@ -7948,10 +8418,13 @@ var storage4 = () => {
7948
8418
  var keyFor2 = (orderHash) => `${KEY_PREFIX4}.${orderHash}`;
7949
8419
  function saveOrder(order) {
7950
8420
  const store = storage4();
7951
- if (!store) return;
8421
+ if (!store) return false;
7952
8422
  try {
7953
- 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;
7954
8426
  } catch {
8427
+ return false;
7955
8428
  }
7956
8429
  }
7957
8430
  function clearOrder(orderHash) {
@@ -7962,7 +8435,7 @@ function clearOrder(orderHash) {
7962
8435
  } catch {
7963
8436
  }
7964
8437
  }
7965
- function listOrders(now = Date.now()) {
8438
+ function listOrders(_now = Date.now()) {
7966
8439
  const store = storage4();
7967
8440
  if (!store) return [];
7968
8441
  const out = [];
@@ -7977,10 +8450,6 @@ function listOrders(now = Date.now()) {
7977
8450
  if (!raw2) continue;
7978
8451
  try {
7979
8452
  const parsed = JSON.parse(raw2);
7980
- if (now - parsed.createdAt > MAX_AGE_MS) {
7981
- store.removeItem(key2);
7982
- continue;
7983
- }
7984
8453
  if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
7985
8454
  out.push(parsed);
7986
8455
  }
@@ -8021,12 +8490,11 @@ async function awaitWalletPrompt(prompt, what) {
8021
8490
  if (timer) clearTimeout(timer);
8022
8491
  }
8023
8492
  }
8024
- function stoppedWatching(error) {
8025
- return error instanceof OwneyError && error.details?.settlementUnconfirmed === true;
8026
- }
8027
8493
  var DEFAULT_SLIPPAGE = 1;
8028
- async function affordableAmount(deps, quoted) {
8029
- 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" });
8030
8498
  if (balance >= quoted) return quoted;
8031
8499
  debugLog("owney-sdk", "swap: trimming to the current source balance", {
8032
8500
  quoted: quoted.toString(),
@@ -8037,48 +8505,72 @@ async function affordableAmount(deps, quoted) {
8037
8505
  }
8038
8506
  async function executeSwap(deps, options) {
8039
8507
  const { quote, walletAddress, onStage } = options;
8040
- debugLog("owney-sdk", "swap: start", {
8041
- rail: quote.rail,
8042
- from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
8043
- to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
8044
- expected: quote.dst.amount,
8045
- floor: quote.dstAmountMin
8046
- });
8047
- const before = await deps.readTargetBalance();
8048
- debugLog("owney-sdk", "swap: target balance before", before.toString());
8049
- let result;
8050
- 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
+ };
8051
8546
  try {
8052
- result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
8053
- } catch (error) {
8054
- if (!stoppedWatching(error)) throw error;
8055
- railError = error;
8056
- debugLog("owney-sdk", "swap: rail failed, checking whether funds landed", {
8057
- 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
8058
8553
  });
8059
- }
8060
- const { arrived } = await awaitBalanceIncrease({
8061
- readBalance: () => deps.readTargetBalance(),
8062
- baseline: before,
8063
- ...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs },
8064
- ...options.arrivalPollMs === void 0 ? {} : { pollMs: options.arrivalPollMs }
8065
- });
8066
- if (arrived === null) {
8067
- if (railError) throw railError;
8068
- throw new OwneyError(
8069
- "SWAP_REQUEST_FAILED",
8070
- "The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
8071
- { rail: quote.rail, ...result ?? {} }
8072
- );
8073
- }
8074
- if (railError) {
8075
- debugLog("owney-sdk", "swap: rail gave up but the funds are here", {
8076
- 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 }
8077
8562
  });
8078
- const orderHash = railError.details?.orderHash;
8079
- 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;
8080
8573
  }
8081
- return { received: arrived.toString(), ...result ?? {} };
8082
8574
  }
8083
8575
  async function runClassic(deps, options) {
8084
8576
  const {
@@ -8089,7 +8581,7 @@ async function runClassic(deps, options) {
8089
8581
  onStage
8090
8582
  } = options;
8091
8583
  const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
8092
- 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);
8093
8585
  const swapTxRequest = {
8094
8586
  from: {
8095
8587
  chainId: quote.src.chainId,
@@ -8121,25 +8613,36 @@ async function runClassic(deps, options) {
8121
8613
  "owney-sdk",
8122
8614
  "swap: re-fetching classic calldata after approval"
8123
8615
  );
8616
+ amount = await affordableAmount(deps, amount, options.sourceReserve);
8617
+ swapTxRequest.from.amount = amount.toString();
8124
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" });
8125
8621
  }
8126
8622
  }
8127
8623
  onStage?.("signing");
8128
8624
  await deps.ensureChain(quote.src.chainId);
8129
8625
  debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
8626
+ options.assertCurrent?.();
8627
+ options.onTransactionRequested?.();
8130
8628
  const txHash = await deps.sendTransaction({
8131
8629
  to: tx.to,
8132
8630
  data: tx.data,
8133
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;
8134
8636
  });
8135
8637
  onStage?.("swapped");
8136
- return { txHash };
8638
+ return { txHash, spent: amount.toString() };
8137
8639
  }
8138
8640
  async function runSameChainFusion(deps, options, walletAddress) {
8139
8641
  const { quote, direction, onStage } = options;
8140
8642
  const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
8141
- const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
8142
- 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);
8143
8646
  onStage?.("building");
8144
8647
  debugLog("owney-sdk", "swap: building same-chain fusion order");
8145
8648
  const built = await deps.api.buildOrder({
@@ -8163,36 +8666,42 @@ async function runSameChainFusion(deps, options, walletAddress) {
8163
8666
  "sign the swap"
8164
8667
  );
8165
8668
  debugLog("owney-sdk", "swap: signed, submitting same-chain order");
8166
- await deps.api.submitOrder({
8167
- // Without this the routing API hands the order to the Fusion+ relayer,
8168
- // which does not know it and answers with an error that names nothing.
8169
- rail: "fusion",
8170
- srcChainId: quote.src.chainId,
8171
- order: built.order,
8172
- signature,
8173
- quoteId: built.quoteId,
8174
- ...built.extension ? { extension: built.extension } : {}
8175
- });
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
+ }
8176
8685
  try {
8177
- options.onOrderSubmitted?.(built.orderHash);
8686
+ options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable: true });
8178
8687
  } catch {
8179
8688
  }
8180
8689
  await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
8181
8690
  orderHash: built.orderHash,
8182
8691
  ...onStage ? { onStage } : {}
8183
8692
  });
8184
- return { orderHash: built.orderHash };
8693
+ return { orderHash: built.orderHash, spent: amount.toString() };
8185
8694
  }
8186
8695
  async function authoriseSpend(deps, options, amount, isNativeSource) {
8187
8696
  const { quote, onStage } = options;
8188
- if (!quote.spender || isNativeSource) return void 0;
8697
+ if (!quote.spender || isNativeSource) return { paid: false };
8189
8698
  const current = await deps.readAllowance(quote.spender);
8190
8699
  debugLog("owney-sdk", "swap: fusion allowance", {
8191
8700
  spender: quote.spender,
8192
8701
  current: current.toString(),
8193
8702
  needed: amount.toString()
8194
8703
  });
8195
- if (current >= amount) return void 0;
8704
+ if (current >= amount) return { paid: false };
8196
8705
  onStage?.("approving");
8197
8706
  await deps.ensureChain(quote.src.chainId);
8198
8707
  const permit = quote.permitSupported ? await awaitWalletPrompt(
@@ -8203,11 +8712,18 @@ async function authoriseSpend(deps, options, amount, isNativeSource) {
8203
8712
  debugLog("owney-sdk", "swap: permitting limit order protocol", {
8204
8713
  bytes: (permit.length - 2) / 2
8205
8714
  });
8206
- return permit;
8715
+ return { permit, paid: false };
8207
8716
  }
8208
8717
  await deps.approve(quote.spender, MAX_UINT256);
8209
8718
  debugLog("owney-sdk", "swap: approved limit order protocol");
8210
- 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;
8211
8727
  }
8212
8728
  async function runFusion(deps, options, walletAddress) {
8213
8729
  const { quote, direction, onStage } = options;
@@ -8219,8 +8735,9 @@ async function runFusion(deps, options, walletAddress) {
8219
8735
  { chainId: quote.src.chainId, symbol: quote.src.symbol }
8220
8736
  );
8221
8737
  }
8222
- const amount = await affordableAmount(deps, BigInt(quote.src.amount));
8223
- 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);
8224
8741
  const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
8225
8742
  onStage?.("building");
8226
8743
  debugLog("owney-sdk", "swap: building fusion order", {
@@ -8239,8 +8756,9 @@ async function runFusion(deps, options, walletAddress) {
8239
8756
  ...direction ? { direction } : {},
8240
8757
  ...permit ? { permit } : {}
8241
8758
  });
8242
- saveOrder({
8759
+ const durable = saveOrder({
8243
8760
  orderHash: built.orderHash,
8761
+ walletAddress,
8244
8762
  secrets,
8245
8763
  srcChainId: quote.src.chainId,
8246
8764
  srcSymbol: quote.src.symbol,
@@ -8250,33 +8768,39 @@ async function runFusion(deps, options, walletAddress) {
8250
8768
  createdAt: Date.now()
8251
8769
  });
8252
8770
  debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
8253
- onStage?.("signing");
8254
- await deps.ensureChain(quote.src.chainId);
8255
- debugLog("owney-sdk", "swap: awaiting signature in wallet", {
8256
- signingOnChain: quote.src.chainId
8257
- });
8258
- const signature = await awaitWalletPrompt(
8259
- () => deps.signTypedData(built.typedData),
8260
- "sign the swap"
8261
- );
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
+ }
8262
8782
  debugLog("owney-sdk", "swap: signed, submitting to relayer");
8263
- await deps.api.submitOrder({
8264
- srcChainId: quote.src.chainId,
8265
- // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
8266
- // the envelope here gets a bare 500 from the relayer.
8267
- order: built.order,
8268
- signature,
8269
- quoteId: built.quoteId,
8270
- // Single-fill orders must NOT carry secretHashes — the relayer rejects
8271
- // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
8272
- // order's hashlock, so repeating it here is redundant, and only a
8273
- // multi-fill order (a Merkle tree of hashes) needs them listed.
8274
- ...secretHashes.length > 1 ? { secretHashes } : {},
8275
- ...built.extension ? { extension: built.extension } : {}
8276
- });
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
+ }
8277
8801
  debugLog("owney-sdk", "swap: order submitted, polling escrows");
8278
8802
  try {
8279
- options.onOrderSubmitted?.(built.orderHash);
8803
+ options.onOrderSubmitted?.(built.orderHash, { spent: amount.toString(), expected: built.dstAmount, durable });
8280
8804
  } catch {
8281
8805
  }
8282
8806
  try {
@@ -8286,13 +8810,25 @@ async function runFusion(deps, options, walletAddress) {
8286
8810
  ...onStage ? { onStage } : {}
8287
8811
  });
8288
8812
  } catch (error) {
8289
- 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") {
8290
8814
  clearOrder(built.orderHash);
8291
8815
  }
8292
8816
  throw error;
8293
8817
  }
8294
8818
  clearOrder(built.orderHash);
8295
- 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
+ );
8296
8832
  }
8297
8833
 
8298
8834
  // src/lib/swap/swap.withdrawal-settlement.ts
@@ -8302,8 +8838,8 @@ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8302
8838
  async function awaitWithdrawalPayouts(options) {
8303
8839
  const payouts = new Map(Object.entries(options.results).map(([id, result]) => [id, { result, evidence: result.evidence }]));
8304
8840
  const timeoutMs = Math.max(0, options.timeoutMs ?? 18e4);
8305
- const deadline = Date.now() + timeoutMs;
8306
- let active = true;
8841
+ const deadline = options.deadline ?? Date.now() + timeoutMs;
8842
+ let active2 = true;
8307
8843
  let timer;
8308
8844
  const incomplete = (reason, agentId) => new OwneyError(
8309
8845
  "WITHDRAW_SWAP_INCOMPLETE",
@@ -8321,7 +8857,7 @@ async function awaitWithdrawalPayouts(options) {
8321
8857
  );
8322
8858
  const assertActive = () => {
8323
8859
  options.assertCurrent();
8324
- if (!active || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
8860
+ if (!active2 || Date.now() > deadline) throw incomplete("PAYOUT_CONFIRMATION_TIMEOUT");
8325
8861
  };
8326
8862
  const pause = async () => {
8327
8863
  assertActive();
@@ -8332,10 +8868,11 @@ async function awaitWithdrawalPayouts(options) {
8332
8868
  if (evidence.agentId !== id || evidence.chainId !== options.chainId || evidence.token !== options.token || evidence.walletAddress.toLowerCase() !== options.walletAddress.toLowerCase())
8333
8869
  throw incomplete("PAYOUT_SCOPE_MISMATCH", id);
8334
8870
  };
8335
- const record = (id, payout, evidence) => {
8871
+ const record2 = (id, payout, evidence) => {
8336
8872
  assertActive();
8337
8873
  validate(id, evidence);
8338
8874
  payout.evidence = evidence;
8875
+ payout.result.evidence = evidence;
8339
8876
  if (evidence.transfer === "confirmed" && evidence.delivery === "confirmed") payout.amount = rawAmount(evidence.confirmedAmount);
8340
8877
  options.onProgress(evidence);
8341
8878
  };
@@ -8400,8 +8937,30 @@ async function awaitWithdrawalPayouts(options) {
8400
8937
  if (payout.amount === void 0 || payout.amount <= 0n) throw incomplete("INVALID_PAYOUT_AMOUNT", id);
8401
8938
  if (["yieldseeker", "zyfai"].includes(id) && (!hash(evidence.txHash) || rawAmount(evidence.blockNumber) === void 0 || !evidence.logIndexes?.length))
8402
8939
  throw incomplete("PAYOUT_RECEIPT_UNVERIFIED", id);
8403
- if (options.requested === void 0 && payout.result.completion === "unverified")
8404
- 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
+ }
8405
8964
  const intended = rawAmount(evidence.intendedAmount);
8406
8965
  if (intended !== void 0 && payout.amount < intended && verifiedFee(payout) !== intended - payout.amount)
8407
8966
  throw incomplete("PAYOUT_SHORTFALL", id);
@@ -8411,24 +8970,24 @@ async function awaitWithdrawalPayouts(options) {
8411
8970
  let next = evidence;
8412
8971
  if (!next.txHash && (next.userOpHash || next.callsId)) {
8413
8972
  next = await options.resolve(next);
8414
- record(id, payout, next);
8973
+ record2(id, payout, next);
8415
8974
  }
8416
8975
  if (next.transfer === "failed" || next.delivery === "failed") continue;
8417
8976
  if (next.txHash) {
8418
8977
  next = await options.inspect(next, false);
8419
- record(id, payout, next);
8978
+ record2(id, payout, next);
8420
8979
  if (id === "zyfai" && next.transfer === "unknown" && rawAmount(next.blockNumber) !== void 0 && next.logIndexes?.length === 0 && next.transferLogs?.length === 0 && !next.userOpHash && !next.callsId) {
8421
8980
  const found = await findPayout(id, next);
8422
8981
  if (found) {
8423
8982
  next = found;
8424
- record(id, payout, next);
8983
+ record2(id, payout, next);
8425
8984
  }
8426
8985
  }
8427
8986
  } else if (!next.userOpHash && !next.callsId) {
8428
8987
  const found = await findPayout(id, next);
8429
8988
  if (found) {
8430
8989
  next = found;
8431
- record(id, payout, next);
8990
+ record2(id, payout, next);
8432
8991
  }
8433
8992
  }
8434
8993
  if (next.transfer === "confirmed" && next.delivery === "confirmed" || next.transfer === "failed" || next.delivery === "failed") continue;
@@ -8472,10 +9031,10 @@ async function awaitWithdrawalPayouts(options) {
8472
9031
  };
8473
9032
  try {
8474
9033
  return await Promise.race([read2(), new Promise((_, reject) => {
8475
- timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), timeoutMs);
9034
+ timer = setTimeout(() => reject(incomplete("PAYOUT_CONFIRMATION_TIMEOUT")), Math.max(0, deadline - Date.now()));
8476
9035
  })]);
8477
9036
  } finally {
8478
- active = false;
9037
+ active2 = false;
8479
9038
  if (timer) clearTimeout(timer);
8480
9039
  }
8481
9040
  }
@@ -8493,8 +9052,8 @@ function verifiedFee(payout) {
8493
9052
  }
8494
9053
 
8495
9054
  // src/lib/swap/swap.permit.ts
8496
- var import_viem16 = require("viem");
8497
- var PERMIT_ABI = (0, import_viem16.parseAbi)([
9055
+ var import_viem17 = require("viem");
9056
+ var PERMIT_ABI = (0, import_viem17.parseAbi)([
8498
9057
  "function nonces(address owner) view returns (uint256)",
8499
9058
  "function name() view returns (string)",
8500
9059
  "function version() view returns (string)",
@@ -8521,7 +9080,7 @@ async function tryRead(read2) {
8521
9080
  function matchDomain(name, versions, chainId, token, onChainSeparator) {
8522
9081
  for (const version of versions) {
8523
9082
  const domain = { name, version, chainId, verifyingContract: token };
8524
- if ((0, import_viem16.domainSeparator)({ domain }) === onChainSeparator) return domain;
9083
+ if ((0, import_viem17.domainSeparator)({ domain }) === onChainSeparator) return domain;
8525
9084
  }
8526
9085
  return null;
8527
9086
  }
@@ -8574,9 +9133,9 @@ async function buildErc2612Permit(reads, input) {
8574
9133
  primaryType: "Permit",
8575
9134
  message: { owner, spender, value, nonce, deadline }
8576
9135
  });
8577
- const { r, s, v, yParity } = (0, import_viem16.parseSignature)(signature);
9136
+ const { r, s, v, yParity } = (0, import_viem17.parseSignature)(signature);
8578
9137
  const recoveryV = v ?? BigInt(yParity + 27);
8579
- return (0, import_viem16.encodeAbiParameters)(
9138
+ return (0, import_viem17.encodeAbiParameters)(
8580
9139
  [
8581
9140
  { type: "address" },
8582
9141
  { type: "address" },
@@ -8592,7 +9151,7 @@ async function buildErc2612Permit(reads, input) {
8592
9151
 
8593
9152
  // src/lib/swap/swap.withdraw-swap.ts
8594
9153
  var KEY_PREFIX5 = "owney.swap.withdraw";
8595
- var MAX_AGE_MS2 = 24 * 60 * 60 * 1e3;
9154
+ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
8596
9155
  var storage5 = () => {
8597
9156
  if (typeof window === "undefined") return null;
8598
9157
  try {
@@ -8617,15 +9176,19 @@ var notify = () => {
8617
9176
  }
8618
9177
  }
8619
9178
  };
8620
- function saveWithdrawSwap(record) {
9179
+ function saveWithdrawSwap(record2) {
8621
9180
  const store = storage5();
8622
- if (!store) return;
9181
+ if (!store) return false;
8623
9182
  try {
8624
- 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;
8625
9187
  } catch {
8626
- return;
9188
+ return false;
8627
9189
  }
8628
9190
  notify();
9191
+ return true;
8629
9192
  }
8630
9193
  function updateWithdrawSwap(orderHash, patch) {
8631
9194
  const store = storage5();
@@ -8648,13 +9211,17 @@ function clearWithdrawSwap(orderHash) {
8648
9211
  const store = storage5();
8649
9212
  if (!store) return;
8650
9213
  try {
8651
- 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));
8652
9219
  } catch {
8653
9220
  return;
8654
9221
  }
8655
9222
  notify();
8656
9223
  }
8657
- function listWithdrawSwaps(now = Date.now()) {
9224
+ function listWithdrawSwaps(now = Date.now(), includeDismissed = false) {
8658
9225
  const store = storage5();
8659
9226
  if (!store) return [];
8660
9227
  const out = [];
@@ -8669,11 +9236,11 @@ function listWithdrawSwaps(now = Date.now()) {
8669
9236
  if (!raw2) continue;
8670
9237
  try {
8671
9238
  const parsed = JSON.parse(raw2);
8672
- if (now - parsed.createdAt > MAX_AGE_MS2) {
9239
+ if (["landed", "refunded", "not-swapped"].includes(parsed.status) && now - (parsed.settledAt ?? parsed.createdAt) > MAX_AGE_MS) {
8673
9240
  store.removeItem(key2);
8674
9241
  continue;
8675
9242
  }
8676
- out.push(parsed);
9243
+ if (includeDismissed || !parsed.dismissed) out.push(parsed);
8677
9244
  } catch {
8678
9245
  store.removeItem(key2);
8679
9246
  }
@@ -8683,10 +9250,15 @@ function listWithdrawSwaps(now = Date.now()) {
8683
9250
  }
8684
9251
  return out.sort((a, b) => b.createdAt - a.createdAt);
8685
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
+ }
8686
9258
 
8687
9259
  // src/lib/swap/swap.pending-deposit.ts
8688
9260
  var KEY_PREFIX6 = "owney.swap.pending-deposit";
8689
- var MAX_AGE_MS3 = 24 * 60 * 60 * 1e3;
9261
+ var MAX_AGE_MS2 = 24 * 60 * 60 * 1e3;
8690
9262
  var storage6 = () => {
8691
9263
  if (typeof window === "undefined") return null;
8692
9264
  try {
@@ -8729,7 +9301,7 @@ function listPendingDeposits(now = Date.now()) {
8729
9301
  if (!raw2) continue;
8730
9302
  try {
8731
9303
  const parsed = JSON.parse(raw2);
8732
- if (now - parsed.createdAt > MAX_AGE_MS3) {
9304
+ if (now - parsed.createdAt > MAX_AGE_MS2) {
8733
9305
  store.removeItem(key2);
8734
9306
  continue;
8735
9307
  }
@@ -8778,7 +9350,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
8778
9350
  }
8779
9351
 
8780
9352
  // src/lib/helpers/withdraw-helper.ts
8781
- var import_viem17 = require("viem");
9353
+ var import_viem18 = require("viem");
8782
9354
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
8783
9355
  return agents.map((agent) => {
8784
9356
  let invalidBalance = false;
@@ -8788,7 +9360,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
8788
9360
  return 0n;
8789
9361
  }
8790
9362
  const [whole, fraction] = value.split(".");
8791
- 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);
8792
9364
  };
8793
9365
  const agentBalance = aggregated[agent.id];
8794
9366
  const tokenBalance = agentBalance?.tokens.find(
@@ -8988,11 +9560,11 @@ function aggregateDailyEarnings(series, tokenSymbol) {
8988
9560
  const byAsset = /* @__PURE__ */ new Map();
8989
9561
  for (const account of series) {
8990
9562
  for (const { asset, points } of account.assets) {
8991
- const symbol = asset.toUpperCase();
8992
- if (!points.length || wanted && symbol !== wanted) continue;
8993
- const components = byAsset.get(symbol) ?? [];
9563
+ const symbol2 = asset.toUpperCase();
9564
+ if (!points.length || wanted && symbol2 !== wanted) continue;
9565
+ const components = byAsset.get(symbol2) ?? [];
8994
9566
  components.push([...points].sort((a, b) => a.date.localeCompare(b.date)));
8995
- byAsset.set(symbol, components);
9567
+ byAsset.set(symbol2, components);
8996
9568
  }
8997
9569
  }
8998
9570
  return [...byAsset.entries()].map(([asset, components]) => {
@@ -9025,111 +9597,6 @@ var import_chains6 = require("viem/chains");
9025
9597
 
9026
9598
  // src/lib/sponsored-token-batch.ts
9027
9599
  var import_viem19 = require("viem");
9028
-
9029
- // src/lib/permit2-batch.ts
9030
- var import_viem18 = require("viem");
9031
- var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
9032
- var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
9033
- var ERC2612_PERMIT_TYPES = {
9034
- Permit: [
9035
- { name: "owner", type: "address" },
9036
- { name: "spender", type: "address" },
9037
- { name: "value", type: "uint256" },
9038
- { name: "nonce", type: "uint256" },
9039
- { name: "deadline", type: "uint256" }
9040
- ]
9041
- };
9042
- var ERC2612_READ_ABI = (0, import_viem18.parseAbi)([
9043
- "function name() view returns (string)",
9044
- "function version() view returns (string)",
9045
- "function nonces(address owner) view returns (uint256)"
9046
- ]);
9047
- function erc2612TypedData(input) {
9048
- return {
9049
- domain: {
9050
- name: input.permit.tokenName,
9051
- version: input.permit.tokenVersion,
9052
- chainId: input.chainId,
9053
- verifyingContract: input.token
9054
- },
9055
- types: ERC2612_PERMIT_TYPES,
9056
- primaryType: "Permit",
9057
- message: {
9058
- owner: input.owner,
9059
- spender: BATCH_PERMIT2_ADDRESS,
9060
- value: BigInt(input.permit.value),
9061
- nonce: BigInt(input.permit.nonce),
9062
- deadline: BigInt(input.permit.deadline)
9063
- }
9064
- };
9065
- }
9066
- var PERMIT_BATCH_TYPES = {
9067
- PermitBatchWitnessTransferFrom: [
9068
- { name: "permitted", type: "TokenPermissions[]" },
9069
- { name: "spender", type: "address" },
9070
- { name: "nonce", type: "uint256" },
9071
- { name: "deadline", type: "uint256" },
9072
- { name: "witness", type: "Deposit" }
9073
- ],
9074
- Deposit: [{ name: "recipients", type: "address[]" }],
9075
- TokenPermissions: [
9076
- { name: "token", type: "address" },
9077
- { name: "amount", type: "uint256" }
9078
- ]
9079
- };
9080
- var PERMIT2_BATCH_ABI = (0, import_viem18.parseAbi)([
9081
- "struct TokenPermissions { address token; uint256 amount; }",
9082
- "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
9083
- "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
9084
- "function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
9085
- "function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
9086
- ]);
9087
- function batchPermit(b) {
9088
- return {
9089
- permitted: b.transfers.map((t) => ({
9090
- token: b.token,
9091
- amount: BigInt(t.amount)
9092
- })),
9093
- nonce: BigInt(b.nonce),
9094
- deadline: BigInt(b.deadline)
9095
- };
9096
- }
9097
- function batchTypedData(b, spender) {
9098
- return {
9099
- domain: {
9100
- name: "Permit2",
9101
- chainId: b.chainId,
9102
- verifyingContract: BATCH_PERMIT2_ADDRESS
9103
- },
9104
- types: PERMIT_BATCH_TYPES,
9105
- primaryType: "PermitBatchWitnessTransferFrom",
9106
- message: {
9107
- ...batchPermit(b),
9108
- spender,
9109
- witness: { recipients: b.transfers.map((t) => t.to) }
9110
- }
9111
- };
9112
- }
9113
- var BATCH_WITNESS_TYPE = "Deposit witness)Deposit(address[] recipients)TokenPermissions(address token,uint256 amount)";
9114
- function batchWitness(b) {
9115
- return (0, import_viem18.hashStruct)({
9116
- types: PERMIT_BATCH_TYPES,
9117
- primaryType: "Deposit",
9118
- data: { recipients: b.transfers.map((t) => t.to) }
9119
- });
9120
- }
9121
- function batchArgs(b) {
9122
- return [
9123
- batchPermit(b),
9124
- b.transfers.map((t) => ({ to: t.to, requestedAmount: BigInt(t.amount) })),
9125
- b.from,
9126
- batchWitness(b),
9127
- BATCH_WITNESS_TYPE,
9128
- b.signature
9129
- ];
9130
- }
9131
-
9132
- // src/lib/sponsored-token-batch.ts
9133
9600
  var memory = /* @__PURE__ */ new Map();
9134
9601
  var settledRecords = /* @__PURE__ */ new Map();
9135
9602
  var expiredRecords = /* @__PURE__ */ new Map();
@@ -9157,14 +9624,14 @@ async function releaseExpiredTokenBatch(i) {
9157
9624
  let timer;
9158
9625
  let expired = false;
9159
9626
  const check = async () => {
9160
- let active = read(key2);
9161
- if (active && i.expectedTxHash) {
9162
- const current = JSON.parse(active);
9627
+ let active2 = read(key2);
9628
+ if (active2 && i.expectedTxHash) {
9629
+ const current = JSON.parse(active2);
9163
9630
  if (!current.serializedTransaction || (0, import_viem19.keccak256)(current.serializedTransaction).toLowerCase() !== i.expectedTxHash.toLowerCase())
9164
- active = void 0;
9631
+ active2 = void 0;
9165
9632
  }
9166
9633
  const archiveKey = i.expectedTxHash ? `${key2}:expired:${i.expectedTxHash.toLowerCase()}` : void 0;
9167
- 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))));
9168
9635
  if (!saved) return;
9169
9636
  const body = JSON.parse(saved);
9170
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;
@@ -9188,8 +9655,8 @@ async function releaseExpiredTokenBatch(i) {
9188
9655
  args: [i.owner, nonce >> 8n],
9189
9656
  blockNumber: block.number
9190
9657
  });
9191
- if (expired || typeof bitmap !== "bigint" || bitmap < 0n || bitmap > MAX_UINT256 || (bitmap & 1n << (nonce & 255n)) !== 0n || active && read(key2) !== active) return;
9192
- 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) {
9193
9660
  const terminalKey = `${key2}:expired:${txHash.toLowerCase()}`;
9194
9661
  expiredRecords.set(terminalKey, saved);
9195
9662
  let persisted = typeof window === "undefined";
@@ -9247,15 +9714,15 @@ function clear(key2, body) {
9247
9714
  function sponsorTokenBatch(i) {
9248
9715
  const key2 = pendingKey(i);
9249
9716
  const plan = planOf(i.transfers);
9250
- const active = inflight.get(key2);
9251
- if (active) {
9252
- 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)
9253
9720
  return Promise.reject(
9254
9721
  new Error(
9255
9722
  "A token deposit is already in progress. Wait for its result before depositing again."
9256
9723
  )
9257
9724
  );
9258
- return active.promise;
9725
+ return active2.promise;
9259
9726
  }
9260
9727
  const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
9261
9728
  inflight.set(key2, { plan, promise });
@@ -9409,7 +9876,7 @@ async function execute(i, key2, plan) {
9409
9876
  "DEPOSIT_INSUFFICIENT_BALANCE",
9410
9877
  "Insufficient token balance for this deposit."
9411
9878
  );
9412
- if (allowance < total && (i.chainId !== 8453 || !(0, import_viem19.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
9879
+ if (allowance < total && !supportsSponsoredErc2612(i.chainId, i.token))
9413
9880
  throw new OwneyError(
9414
9881
  "PERMIT2_APPROVAL_REQUIRED",
9415
9882
  "token deposits need a one-time Permit2 approval."
@@ -9445,7 +9912,7 @@ async function execute(i, key2, plan) {
9445
9912
  const signature2 = await i.wallet.signTypedData({
9446
9913
  account: i.owner,
9447
9914
  ...erc2612TypedData({
9448
- chainId: 8453,
9915
+ chainId: i.chainId,
9449
9916
  token: i.token,
9450
9917
  owner: i.owner,
9451
9918
  permit: unsignedPermit
@@ -10073,12 +10540,12 @@ var OwneySDK = class {
10073
10540
  return [...this.getActiveAgents().keys()].sort();
10074
10541
  }
10075
10542
  getActiveAgents() {
10076
- const active = /* @__PURE__ */ new Map();
10543
+ const active2 = /* @__PURE__ */ new Map();
10077
10544
  for (const id of this.activeAgents) {
10078
10545
  const agent = this.agents.get(id);
10079
- if (agent) active.set(id, agent);
10546
+ if (agent) active2.set(id, agent);
10080
10547
  }
10081
- return active;
10548
+ return active2;
10082
10549
  }
10083
10550
  /**
10084
10551
  * Read the org's agent policy once and reuse it. fetchOrgAgentConfig never
@@ -10661,10 +11128,10 @@ var OwneySDK = class {
10661
11128
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
10662
11129
  * two independent auto-recovery mechanisms:
10663
11130
  *
10664
- * 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
10665
11132
  * app did not supply its own callback and the attempt fails with
10666
11133
  * `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
10667
- * 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
10668
11135
  * its sponsored deposit and never reaches this branch. Other tokens send
10669
11136
  * the one-time user-paid Permit2 approval via `approvePermit2()` and
10670
11137
  * retry the SAME sponsored attempt once. Bounded to one approval attempt
@@ -11171,8 +11638,8 @@ var OwneySDK = class {
11171
11638
  async ensureSwapChain(chainId) {
11172
11639
  const provider = this.requireConnectedProvider();
11173
11640
  const state = this.requireState();
11174
- const chain = swapChain(chainId);
11175
- if (!chain) {
11641
+ const chain2 = swapChain(chainId);
11642
+ if (!chain2) {
11176
11643
  throw new OwneyError(
11177
11644
  "CHAIN_UNSUPPORTED",
11178
11645
  `Chain ${chainId} is not supported`,
@@ -11180,10 +11647,10 @@ var OwneySDK = class {
11180
11647
  );
11181
11648
  }
11182
11649
  await ensureWalletOnChain(
11183
- (0, import_viem21.createPublicClient)({ chain, transport: (0, import_viem21.custom)(provider) }),
11650
+ (0, import_viem21.createPublicClient)({ chain: chain2, transport: (0, import_viem21.custom)(provider) }),
11184
11651
  (0, import_viem21.createWalletClient)({
11185
11652
  account: state.walletAddress,
11186
- chain,
11653
+ chain: chain2,
11187
11654
  transport: (0, import_viem21.custom)(provider)
11188
11655
  }),
11189
11656
  chainId
@@ -11207,15 +11674,15 @@ var OwneySDK = class {
11207
11674
  async wrapNativeSource(quote, walletAddress, onStage, direction) {
11208
11675
  const wrapper = requireWrapper(quote);
11209
11676
  const chainId = quote.src.chainId;
11210
- const chain = swapChain(chainId);
11677
+ const chain2 = swapChain(chainId);
11211
11678
  const provider = this.requireConnectedProvider();
11212
11679
  const publicClient = (0, import_viem21.createPublicClient)({
11213
- chain,
11680
+ chain: chain2,
11214
11681
  transport: swapReadTransport(chainId, this.zyfaiRpcUrls)
11215
11682
  });
11216
11683
  const wallet = (0, import_viem21.createWalletClient)({
11217
11684
  account: walletAddress,
11218
- chain,
11685
+ chain: chain2,
11219
11686
  transport: (0, import_viem21.custom)(provider)
11220
11687
  });
11221
11688
  onStage?.("wrapping");
@@ -11242,7 +11709,7 @@ var OwneySDK = class {
11242
11709
  data: tx.data,
11243
11710
  value: BigInt(tx.value),
11244
11711
  account: walletAddress,
11245
- chain
11712
+ chain: chain2
11246
11713
  });
11247
11714
  onSubmitted?.(hash2);
11248
11715
  const receipt = await publicClient.waitForTransactionReceipt({
@@ -11312,15 +11779,27 @@ var OwneySDK = class {
11312
11779
  * Kept as a builder rather than baked into the executor so the whole swap
11313
11780
  * flow stays testable without a provider — the executor never imports viem.
11314
11781
  */
11315
- buildSwapDeps(quote) {
11782
+ buildSwapDeps(quote, assertOperation, onTransactionHash) {
11316
11783
  const state = this.requireState();
11317
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
+ };
11318
11791
  const srcChain = swapChain(quote.src.chainId);
11319
11792
  const dstChain = swapChain(quote.dst.chainId);
11320
11793
  const wallet = (0, import_viem21.createWalletClient)({
11321
11794
  account: state.walletAddress,
11322
11795
  chain: srcChain,
11323
- 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 })
11324
11803
  });
11325
11804
  const srcPublic = (0, import_viem21.createPublicClient)({
11326
11805
  chain: srcChain,
@@ -11349,6 +11828,7 @@ var OwneySDK = class {
11349
11828
  });
11350
11829
  },
11351
11830
  sendTransaction: async (tx) => {
11831
+ assertWallet();
11352
11832
  const hash2 = await wallet.sendTransaction({
11353
11833
  to: tx.to,
11354
11834
  data: tx.data,
@@ -11356,24 +11836,34 @@ var OwneySDK = class {
11356
11836
  account: state.walletAddress,
11357
11837
  chain: srcChain
11358
11838
  });
11839
+ onTransactionHash?.(hash2);
11359
11840
  const receipt = await srcPublic.waitForTransactionReceipt({
11360
11841
  timeout: receiptTimeoutMs(quote.src.chainId),
11361
11842
  hash: hash2,
11362
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
+ );
11363
11850
  });
11364
11851
  if (receipt.status !== "success") {
11365
11852
  throw new OwneyError(
11366
11853
  "SWAP_REQUEST_FAILED",
11367
11854
  `Swap transaction reverted (tx ${hash2})`,
11368
- { hash: hash2 }
11855
+ { txHash: hash2, outcome: "not-submitted", executionFailed: true }
11369
11856
  );
11370
11857
  }
11371
11858
  return hash2;
11372
11859
  },
11373
- signTypedData: (typedData) => wallet.signTypedData({
11374
- account: state.walletAddress,
11375
- ...typedData
11376
- }),
11860
+ signTypedData: (typedData) => {
11861
+ assertWallet();
11862
+ return wallet.signTypedData({
11863
+ account: state.walletAddress,
11864
+ ...typedData
11865
+ });
11866
+ },
11377
11867
  // Chain-bound like every other read here: the wallet provider's chain is
11378
11868
  // not ours to rely on mid-swap.
11379
11869
  readSourceBalance: async () => {
@@ -11395,6 +11885,7 @@ var OwneySDK = class {
11395
11885
  args: [state.walletAddress, spender]
11396
11886
  }),
11397
11887
  approve: async (spender, amount) => {
11888
+ assertWallet();
11398
11889
  const hash2 = await wallet.writeContract({
11399
11890
  address: quote.src.address,
11400
11891
  abi: ERC20_ALLOWANCE_ABI,
@@ -11403,14 +11894,22 @@ var OwneySDK = class {
11403
11894
  account: state.walletAddress,
11404
11895
  chain: srcChain
11405
11896
  });
11406
- await srcPublic.waitForTransactionReceipt({
11897
+ const receipt = await srcPublic.waitForTransactionReceipt({
11407
11898
  hash: hash2,
11408
11899
  confirmations: 1,
11409
11900
  timeout: receiptTimeoutMs(quote.src.chainId)
11410
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
+ );
11411
11907
  return hash2;
11412
11908
  },
11413
- ensureChain: (chainId) => this.ensureSwapChain(chainId),
11909
+ ensureChain: (chainId) => {
11910
+ assertWallet();
11911
+ return this.ensureSwapChain(chainId);
11912
+ },
11414
11913
  /**
11415
11914
  * Sign the approval instead of paying for it. Null means the token
11416
11915
  * cannot be permitted (WETH has no `permit`) or its domain could not be
@@ -11430,10 +11929,13 @@ var OwneySDK = class {
11430
11929
  spender,
11431
11930
  value: amount,
11432
11931
  chainId: quote.src.chainId,
11433
- signTypedData: (payload) => wallet.signTypedData({
11434
- account: state.walletAddress,
11435
- ...payload
11436
- })
11932
+ signTypedData: (payload) => {
11933
+ assertWallet();
11934
+ return wallet.signTypedData({
11935
+ account: state.walletAddress,
11936
+ ...payload
11937
+ });
11938
+ }
11437
11939
  }
11438
11940
  ),
11439
11941
  /**
@@ -11483,36 +11985,20 @@ var OwneySDK = class {
11483
11985
  );
11484
11986
  }
11485
11987
  /**
11486
- * How much of a landed swap to actually deposit.
11487
- *
11488
- * The swap reports what ARRIVED, and the deposit used to be handed that
11489
- * figure directly. It is the entire arrival, so there is no slack: anything
11490
- * that spends the target asset between the two fails the deposit outright.
11491
- *
11492
- * Something does. A first WETH deposit needs a one-time Permit2 approval,
11493
- * and that approval is a transaction. A wallet holding no native gas token -
11494
- * the normal state of someone who just swapped everything INTO WETH - pays
11495
- * for it out of the WETH. The deposit then asked for ~2e12 wei more than was
11496
- * left and refused with DEPOSIT_INSUFFICIENT_BALANCE, under a wrapper saying
11497
- * the funds were safe but unadded (ROUT-242).
11498
- *
11499
- * So the approval happens here, before the amount is settled, and the amount
11500
- * comes from a read taken after it. Two things this deliberately is not:
11501
- *
11502
- * - It is not a balance sweep. `received` is a DELTA over the pre-swap
11503
- * balance, so a wallet already holding WETH reads far above it; depositing
11504
- * the balance would move funds the user never offered. The arrival is the
11505
- * ceiling and the balance is only ever allowed to lower it.
11506
- * - It does not touch USDC, which moves on signed typed data and so spends
11507
- * nothing of the user's in between, nor a paymaster-configured client,
11508
- * 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.
11509
11993
  */
11510
- async settleSwapDepositAmount(chainId, token, symbol, received) {
11511
- if (symbol !== "WETH" || this.paymasterServiceUrl) {
11994
+ async settleSwapDepositAmount(chainId, token, symbol2, received) {
11995
+ if (symbol2 !== "WETH" && symbol2 !== "USDC" || this.paymasterServiceUrl || supportsSponsoredErc2612(chainId, token)) {
11512
11996
  return received.toString();
11513
11997
  }
11514
11998
  const allowance = await this.readTokenPermit2Allowance(chainId, token);
11515
- if (allowance < received) await this.approvePermit2();
11999
+ if (allowance < received) {
12000
+ await this.approvePermit2(symbol2, received, chainId);
12001
+ }
11516
12002
  const balance = await this.readTokenBalance(chainId, token);
11517
12003
  return (balance < received ? balance : received).toString();
11518
12004
  }
@@ -11639,21 +12125,30 @@ var OwneySDK = class {
11639
12125
  * Every order is attempted even if an earlier one throws: they are unrelated
11640
12126
  * escrows, and one ending badly must not abandon the next one's secrets.
11641
12127
  * `unfinished` is not a verdict — the order may still settle, so unless the
11642
- * chain has said refunded or cancelled its preimages are kept.
12128
+ * provider has confirmed full settlement or refund, its preimages are kept.
11643
12129
  */
11644
12130
  async resumePendingSwaps(options = {}) {
11645
- const pending = listOrders();
11646
- if (pending.length === 0) {
11647
- await this.resumeSameChainSwaps(options);
11648
- return [];
11649
- }
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));
11650
12144
  const runner = this.swapRunnerDeps();
11651
- const results = [];
11652
- for (const order of pending) {
12145
+ const sameChain = this.resumeSameChainSwaps(options);
12146
+ const results = await mapConcurrent(pending, async (order) => {
11653
12147
  try {
11654
12148
  await runFusionOrder(runner, {
11655
12149
  orderHash: order.orderHash,
11656
12150
  secrets: order.secrets,
12151
+ hasPartialFill: listWithdrawSwaps(Date.now(), true).some((record2) => record2.orderHash === order.orderHash && record2.status === "partially-filled"),
11657
12152
  ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
11658
12153
  ...options.onStage ? {
11659
12154
  onStage: (stage) => options.onStage(order.orderHash, stage)
@@ -11661,25 +12156,23 @@ var OwneySDK = class {
11661
12156
  });
11662
12157
  clearOrder(order.orderHash);
11663
12158
  updateWithdrawSwap(order.orderHash, { status: "landed" });
11664
- results.push({
11665
- orderHash: order.orderHash,
11666
- status: "completed"
11667
- });
12159
+ return { orderHash: order.orderHash, status: "completed" };
11668
12160
  } catch (error) {
11669
- 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") {
11670
12162
  clearOrder(order.orderHash);
11671
12163
  updateWithdrawSwap(order.orderHash, { status: "refunded" });
11672
12164
  } else {
11673
- 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" });
11674
12167
  }
11675
- results.push({
12168
+ return {
11676
12169
  orderHash: order.orderHash,
11677
12170
  status: "unfinished",
11678
12171
  ...error instanceof OwneyError ? { error } : {}
11679
- });
12172
+ };
11680
12173
  }
11681
- }
11682
- await this.resumeSameChainSwaps(options);
12174
+ });
12175
+ await sameChain;
11683
12176
  return results;
11684
12177
  }
11685
12178
  /**
@@ -11700,34 +12193,32 @@ var OwneySDK = class {
11700
12193
  * anything else.
11701
12194
  */
11702
12195
  async resumeSameChainSwaps(options = {}) {
11703
- const running = listWithdrawSwaps().filter(
11704
- (record) => record.status === "swapping" && record.srcChainId === record.dstChainId
11705
- );
11706
- 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) => {
11707
12198
  const runner = {
11708
12199
  // The chain bound in. That product keys its orders per chain, and the
11709
12200
  // Fusion+ endpoint answers 404 for one — which would read as an order
11710
12201
  // that vanished rather than one still in the auction.
11711
- orderStatus: (h) => this.swapApi().orderStatus(h, record.srcChainId),
12202
+ orderStatus: (h) => this.swapApi().orderStatus(h, record2.srcChainId),
11712
12203
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
11713
12204
  now: () => Date.now()
11714
12205
  };
11715
12206
  try {
11716
12207
  await runSameChainOrder(runner, {
11717
- orderHash: record.orderHash,
12208
+ orderHash: record2.orderHash,
12209
+ hasPartialFill: record2.status === "partially-filled",
11718
12210
  ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
11719
12211
  ...options.onStage ? {
11720
- onStage: (stage) => options.onStage(record.orderHash, stage)
12212
+ onStage: (stage) => options.onStage(record2.orderHash, stage)
11721
12213
  } : {}
11722
12214
  });
11723
- updateWithdrawSwap(record.orderHash, { status: "landed" });
12215
+ updateWithdrawSwap(record2.orderHash, { status: "landed" });
11724
12216
  } catch (error) {
11725
- const code = error instanceof OwneyError ? error.code : void 0;
11726
- updateWithdrawSwap(record.orderHash, {
11727
- status: code === "SWAP_ORDER_EXPIRED" || code === "SWAP_ORDER_CANCELLED" ? "refunded" : "unknown"
11728
- });
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" });
11729
12220
  }
11730
- }
12221
+ });
11731
12222
  }
11732
12223
  /**
11733
12224
  * What the wallet actually holds, across every supported chain, restricted
@@ -11883,6 +12374,92 @@ var OwneySDK = class {
11883
12374
  );
11884
12375
  }
11885
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
+ }
11886
12463
  /**
11887
12464
  * Withdraw from an agent and swap the proceeds into whatever the user wants
11888
12465
  * to hold, delivered to their own wallet.
@@ -11911,7 +12488,14 @@ var OwneySDK = class {
11911
12488
  const state = this.requireState();
11912
12489
  const activeChainId = this.requireChainId();
11913
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;
11914
12496
  const assertCurrent = () => {
12497
+ options.assertCurrent?.();
12498
+ if (!active2) throw new OwneyError("WITHDRAW_SWAP_INCOMPLETE", "This withdrawal attempt is no longer active.", { swapSubmitted: false });
11915
12499
  if (this.state !== state || this.connectionRevision !== revision || state.chainId !== activeChainId)
11916
12500
  throw new OwneyError("NOT_CONNECTED", "Wallet changed during withdrawal and swap.");
11917
12501
  };
@@ -11953,30 +12537,95 @@ var OwneySDK = class {
11953
12537
  functionName: "balanceOf",
11954
12538
  args: [state.walletAddress]
11955
12539
  });
11956
- const baselineBlock = await srcPublic.getBlockNumber({ cacheTime: 0 });
11957
- 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);
11958
12550
  assertCurrent();
11959
12551
  debugLog("owney-sdk", "withdrawAndSwap: baseline", {
11960
12552
  asset: `${asset.symbol}@${asset.chainId}`,
11961
12553
  baseline: baseline.toString()
11962
12554
  });
11963
12555
  options.onSwapProgress?.("withdrawing");
11964
- const submittedAt = Date.now();
11965
- 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
+ };
11966
12594
  const evidence = /* @__PURE__ */ new Map();
11967
12595
  const onProgress = (next) => {
11968
12596
  if (next.chainId !== activeChainId || next.token !== asset.symbol || next.walletAddress.toLowerCase() !== state.walletAddress.toLowerCase()) return;
11969
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
+ }
11970
12609
  notifyFunding(options.onWithdrawalProgress, next);
11971
12610
  };
11972
12611
  let withdraw;
11973
12612
  try {
11974
- withdraw = await this.withdraw({
12613
+ withdraw = saved ? { agentResult: saved.recovery.results, totalWithdrawn: null } : await bounded(() => this.withdraw({
11975
12614
  asset: asset.symbol,
11976
12615
  ...requested === void 0 ? {} : { amount: requested.toString() },
11977
12616
  ...options.agentId ? { agentId: options.agentId } : {},
11978
- onProgress
11979
- });
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
+ ));
11980
12629
  } catch (error) {
11981
12630
  const details = error instanceof OwneyError ? error.details : void 0;
11982
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));
@@ -11998,6 +12647,9 @@ var OwneySDK = class {
11998
12647
  const results = "agentResult" in withdraw ? withdraw.agentResult : {
11999
12648
  [options.agentId ?? withdraw.evidence?.agentId ?? "unknown"]: withdraw
12000
12649
  };
12650
+ record2.recovery.results = results;
12651
+ record2.recovery.phase = "verifying";
12652
+ saveWithdrawSwap(record2);
12001
12653
  const arrived = await awaitWithdrawalPayouts({
12002
12654
  results,
12003
12655
  requested,
@@ -12009,7 +12661,8 @@ var OwneySDK = class {
12009
12661
  baselineBlock,
12010
12662
  assertCurrent,
12011
12663
  readBalance: readWalletBalance,
12012
- timeoutMs: options.arrivalTimeoutMs,
12664
+ deadline: confirmationDeadline,
12665
+ verifyCompletion: (value) => this.getAgent(value.agentId).verifyWithdrawalCompletion?.(state, value) ?? Promise.resolve({ status: "unavailable" }),
12013
12666
  inspect: (value, fromHistory) => this.inspectFunding(value, "withdraw", { requireAmountMatch: fromHistory, refreshBalance: false }),
12014
12667
  resolve: (value) => this.resolveSponsoredWithdrawalEvidence(value, { submittedAt }),
12015
12668
  history: (value, fromDate, filters) => this.getFundingHistory(value, fromDate, filters),
@@ -12017,34 +12670,89 @@ var OwneySDK = class {
12017
12670
  });
12018
12671
  assertCurrent();
12019
12672
  const withdrawn = arrived.toString();
12673
+ record2.withdrawn = withdrawn;
12674
+ record2.status = "ready";
12675
+ record2.recovery.phase = "ready";
12676
+ hadDurableRecord = saveWithdrawSwap(record2) || hadDurableRecord;
12020
12677
  options.onSwapProgress?.("withdrawn");
12021
12678
  try {
12022
- const quote = await this.swapApi().quote({
12679
+ const quote = await bounded(() => this.swapApi().quote({
12023
12680
  from: { ...options.from, amount: withdrawn },
12024
12681
  to: options.to,
12025
12682
  direction: "withdraw",
12026
12683
  walletAddress: state.walletAddress
12027
- });
12684
+ }), 2e4, () => new OwneyError("SWAP_REQUEST_FAILED", "Swap pricing timed out.", { outcome: "not-submitted" }));
12028
12685
  assertCurrent();
12029
- 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
+ );
12030
12691
  assertCurrent();
12031
- 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,
12032
12705
  quote,
12033
12706
  walletAddress: state.walletAddress,
12034
12707
  direction: "withdraw",
12035
12708
  ...options.slippage === void 0 ? {} : { slippage: options.slippage },
12036
12709
  ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {},
12037
- 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) => {
12038
12728
  releaseSubmission();
12039
- 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
+ });
12040
12741
  }
12041
12742
  });
12743
+ record2.status = "landed";
12744
+ record2.received = swap.received;
12745
+ record2.spent = swap.spent;
12746
+ saveWithdrawSwap(record2);
12042
12747
  return { withdraw, withdrawn, swap };
12043
12748
  } catch (error) {
12044
12749
  throw new OwneyError(
12045
12750
  "WITHDRAW_SWAP_FAILED",
12046
- `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.`,
12047
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 } : {},
12048
12756
  withdrawn,
12049
12757
  asset: asset.symbol,
12050
12758
  chainId: asset.chainId,
@@ -12055,7 +12763,25 @@ var OwneySDK = class {
12055
12763
  }
12056
12764
  );
12057
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;
12058
12783
  } finally {
12784
+ active2 = false;
12059
12785
  releaseSubmission();
12060
12786
  }
12061
12787
  }
@@ -12088,7 +12814,15 @@ var OwneySDK = class {
12088
12814
  const attempted = /* @__PURE__ */ new Set();
12089
12815
  this.withdrawalSubmissions.add(submissionKey);
12090
12816
  try {
12091
- 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
+ );
12092
12826
  } catch (error) {
12093
12827
  for (const id of ids) if (!attempted.has(id)) {
12094
12828
  notifyFunding(options.onProgress, {
@@ -12178,7 +12912,7 @@ var OwneySDK = class {
12178
12912
  progress(evidence);
12179
12913
  adapterReported = false;
12180
12914
  try {
12181
- if (options.onBeforeSubmission)
12915
+ if (options.onBeforeSubmission || options.assertCurrent)
12182
12916
  return await agent.withdraw(
12183
12917
  state,
12184
12918
  chainId,
@@ -12186,7 +12920,8 @@ var OwneySDK = class {
12186
12920
  requested2,
12187
12921
  progress,
12188
12922
  options.gasMode ?? "auto",
12189
- (next) => options.onBeforeSubmission({ ...evidence, ...next })
12923
+ options.onBeforeSubmission ? (next) => options.onBeforeSubmission({ ...evidence, ...next }) : void 0,
12924
+ options.assertCurrent
12190
12925
  );
12191
12926
  return await (options.gasMode === void 0 ? agent.withdraw(state, chainId, token, requested2, progress) : agent.withdraw(state, chainId, token, requested2, progress, options.gasMode));
12192
12927
  } catch (error) {