@owney/sdk 0.7.25-beta.1 → 0.7.25-beta.10

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
@@ -159,6 +159,34 @@ function rateLimitDelay(error, now = Date.now()) {
159
159
  }
160
160
 
161
161
  // src/lib/agent-reads.ts
162
+ function rateLimitDiagnostics(error) {
163
+ const seen = /* @__PURE__ */ new Set();
164
+ let details = {};
165
+ function visit(value, depth = 0) {
166
+ if (depth > 6 || !value || typeof value !== "object" || seen.has(value))
167
+ return;
168
+ seen.add(value);
169
+ const record = value;
170
+ for (const key2 of [
171
+ "rpcSource",
172
+ "chainId",
173
+ "rpcMethod",
174
+ "providerRequestId",
175
+ "statusCode"
176
+ ]) {
177
+ const candidate = record[key2];
178
+ if (candidate !== void 0 && details[key2] === void 0)
179
+ details = { ...details, [key2]: candidate };
180
+ }
181
+ for (const key2 of ["details", "cause", "response", "fields", "error"])
182
+ visit(record[key2], depth + 1);
183
+ }
184
+ visit(error);
185
+ return {
186
+ rpcSource: details.rpcSource ?? "agent-api",
187
+ ...details
188
+ };
189
+ }
162
190
  var AgentReads = class {
163
191
  inFlight = /* @__PURE__ */ new Map();
164
192
  cooldowns = /* @__PURE__ */ new Map();
@@ -166,14 +194,15 @@ var AgentReads = class {
166
194
  clearInFlight() {
167
195
  this.inFlight.clear();
168
196
  }
169
- limited(agentId, until) {
197
+ limited(agentId, until, diagnostics) {
170
198
  return new OwneyError(
171
199
  "AGENT_RATE_LIMITED",
172
200
  "Too many requests. Please wait before trying again.",
173
201
  {
174
202
  statusCode: 429,
175
203
  retryAt: until,
176
- retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3))
204
+ retryAfterSeconds: Math.max(0, Math.ceil((until - Date.now()) / 1e3)),
205
+ ...diagnostics
177
206
  },
178
207
  agentId
179
208
  );
@@ -181,7 +210,9 @@ var AgentReads = class {
181
210
  run(agentId, key2, fetch2) {
182
211
  const cooldown = this.cooldowns.get(agentId);
183
212
  if (cooldown && cooldown.until > Date.now()) {
184
- return Promise.reject(this.limited(agentId, cooldown.until));
213
+ return Promise.reject(
214
+ this.limited(agentId, cooldown.until, cooldown.diagnostics)
215
+ );
185
216
  }
186
217
  const requestKey = JSON.stringify([agentId, key2]);
187
218
  const existing = this.inFlight.get(requestKey);
@@ -202,8 +233,9 @@ var AgentReads = class {
202
233
  Math.min(3e4 * 2 ** (failures - 1), 3e5)
203
234
  );
204
235
  const until = Math.max(previous?.until ?? 0, Date.now() + delay);
205
- this.cooldowns.set(agentId, { until, failures });
206
- throw this.limited(agentId, until);
236
+ const diagnostics = rateLimitDiagnostics(error);
237
+ this.cooldowns.set(agentId, { until, failures, diagnostics });
238
+ throw this.limited(agentId, until, diagnostics);
207
239
  }
208
240
  ).finally(() => {
209
241
  if (this.inFlight.get(requestKey) === promise)
@@ -216,7 +248,7 @@ var AgentReads = class {
216
248
 
217
249
  // src/agents/zyfai/zyfai.agent.ts
218
250
  var import_sdk = require("@zyfai/sdk");
219
- var import_viem = require("viem");
251
+ var import_viem2 = require("viem");
220
252
  var import_chains = require("viem/chains");
221
253
 
222
254
  // src/types/config.ts
@@ -1097,15 +1129,145 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
1097
1129
  return !protocolListsEqual(current.protocols, desiredProtocols);
1098
1130
  }
1099
1131
 
1132
+ // src/lib/paid-rpc.ts
1133
+ var import_viem = require("viem");
1134
+ var PAID_RPC_RETRY_COUNT = 3;
1135
+ var PAID_RPC_RETRY_DELAY_MS = 1e3;
1136
+ function configuredRpcProxyBaseUrl(baseUrl) {
1137
+ const configured = (baseUrl ?? "")?.trim();
1138
+ if (!configured) {
1139
+ throw new Error(
1140
+ "OWNEY_ROUTING_API_BASE_URL is required for RPC proxy requests unless routingApiBaseUrl or all rpcUrls are configured."
1141
+ );
1142
+ }
1143
+ return configured;
1144
+ }
1145
+ function rpcProxyUrl(chainId, apiKey, baseUrl) {
1146
+ const url = new URL(
1147
+ `${configuredRpcProxyBaseUrl(baseUrl).replace(/\/$/, "")}/api/v1/rpc/${chainId}`
1148
+ );
1149
+ if (apiKey) url.searchParams.set("apiKey", apiKey);
1150
+ return url.toString();
1151
+ }
1152
+ function resolveRpcUrl(rpcUrls, chainId, apiKey = "", baseUrl) {
1153
+ const url = rpcUrls?.[chainId]?.trim();
1154
+ if (url) return url;
1155
+ return rpcProxyUrl(chainId, apiKey, baseUrl);
1156
+ }
1157
+ function resolveRpcUrls(rpcUrls, apiKey = "", baseUrl) {
1158
+ return {
1159
+ 1: resolveRpcUrl(rpcUrls, 1, apiKey, baseUrl),
1160
+ 8453: resolveRpcUrl(rpcUrls, 8453, apiKey, baseUrl),
1161
+ 42161: resolveRpcUrl(rpcUrls, 42161, apiKey, baseUrl)
1162
+ };
1163
+ }
1164
+ function createPaidRpcClient(chain, rpcUrls) {
1165
+ const url = resolveRpcUrl(
1166
+ rpcUrls,
1167
+ chain.id
1168
+ );
1169
+ return (0, import_viem.createPublicClient)({
1170
+ chain,
1171
+ transport: (0, import_viem.http)(url, {
1172
+ retryCount: PAID_RPC_RETRY_COUNT,
1173
+ retryDelay: PAID_RPC_RETRY_DELAY_MS
1174
+ })
1175
+ });
1176
+ }
1177
+ function headerValue(error, name) {
1178
+ const seen = /* @__PURE__ */ new Set();
1179
+ let value;
1180
+ function visit(candidate, depth = 0) {
1181
+ if (value || depth > 6 || !candidate || typeof candidate !== "object")
1182
+ return;
1183
+ if (seen.has(candidate)) return;
1184
+ seen.add(candidate);
1185
+ const record = candidate;
1186
+ const headers = record.headers;
1187
+ let found;
1188
+ if (typeof headers?.get === "function") {
1189
+ found = headers.get(name);
1190
+ } else {
1191
+ const headerRecord = headers;
1192
+ found = headerRecord?.[name] ?? headerRecord?.[name.toLowerCase()];
1193
+ }
1194
+ if (typeof found === "string" && found) {
1195
+ value = found;
1196
+ return;
1197
+ }
1198
+ for (const key2 of ["cause", "details", "response", "error"])
1199
+ visit(record[key2], depth + 1);
1200
+ }
1201
+ visit(error);
1202
+ return value;
1203
+ }
1204
+ function statusCode(error) {
1205
+ const seen = /* @__PURE__ */ new Set();
1206
+ let status;
1207
+ function visit(candidate, depth = 0) {
1208
+ if (status || depth > 6 || !candidate || typeof candidate !== "object")
1209
+ return;
1210
+ if (seen.has(candidate)) return;
1211
+ seen.add(candidate);
1212
+ const record = candidate;
1213
+ for (const key2 of ["status", "statusCode"]) {
1214
+ const parsed = Number(record[key2]);
1215
+ if (Number.isInteger(parsed) && parsed >= 100 && parsed <= 599) {
1216
+ status = parsed;
1217
+ return;
1218
+ }
1219
+ }
1220
+ for (const key2 of ["cause", "details", "response", "error"])
1221
+ visit(record[key2], depth + 1);
1222
+ }
1223
+ visit(error);
1224
+ return status;
1225
+ }
1226
+ function paidRpcError(error, chainId, rpcMethod, agentId) {
1227
+ const delay = rateLimitDelay(error);
1228
+ const providerRequestId = headerValue(error, "x-alchemy-request-id") ?? headerValue(error, "x-request-id");
1229
+ if (delay === void 0) {
1230
+ return new OwneyError(
1231
+ "AGENT_API_ERROR",
1232
+ "The blockchain RPC request failed.",
1233
+ {
1234
+ rpcSource: "paid-rpc",
1235
+ chainId,
1236
+ rpcMethod,
1237
+ ...statusCode(error) ? { statusCode: statusCode(error) } : {},
1238
+ ...providerRequestId ? { providerRequestId } : {}
1239
+ },
1240
+ agentId
1241
+ );
1242
+ }
1243
+ const retryAt = Date.now() + delay;
1244
+ return new OwneyError(
1245
+ "AGENT_RATE_LIMITED",
1246
+ "The blockchain RPC is rate limited. Please wait before trying again.",
1247
+ {
1248
+ rpcSource: "paid-rpc",
1249
+ chainId,
1250
+ rpcMethod,
1251
+ statusCode: 429,
1252
+ retryAt,
1253
+ retryAfterSeconds: Math.max(0, Math.ceil(delay / 1e3)),
1254
+ ...providerRequestId ? { providerRequestId } : {}
1255
+ },
1256
+ agentId
1257
+ );
1258
+ }
1259
+ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
1260
+ try {
1261
+ return await operation();
1262
+ } catch (error) {
1263
+ throw paidRpcError(error, chainId, rpcMethod, agentId);
1264
+ }
1265
+ }
1266
+
1100
1267
  // src/agents/zyfai/zyfai.agent.ts
1101
- var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem.parseAbi)([
1268
+ var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem2.parseAbi)([
1102
1269
  "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
1103
1270
  ]);
1104
- var DEFAULT_ZYFAI_RPC_URLS = {
1105
- 8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
1106
- 42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
1107
- 1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
1108
- };
1109
1271
  var WETH_ADDRESS_BY_CHAIN = {
1110
1272
  8453: "0x4200000000000000000000000000000000000006",
1111
1273
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -1171,7 +1333,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1171
1333
  earningsSnapshot = null;
1172
1334
  earningsGeneration = 0;
1173
1335
  constructor(apiKey, rpcUrls, referralSource) {
1174
- this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
1336
+ this.rpcUrls = resolveRpcUrls(rpcUrls);
1175
1337
  this.sdk = new import_sdk.ZyfaiSDK({
1176
1338
  apiKey,
1177
1339
  rpcUrls: this.rpcUrls,
@@ -1185,10 +1347,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
1185
1347
  getPublicClient(chainId) {
1186
1348
  const cached = this.publicClients.get(chainId);
1187
1349
  if (cached) return cached;
1188
- const client = (0, import_viem.createPublicClient)({
1189
- chain: VIEM_CHAIN[chainId],
1190
- transport: (0, import_viem.http)(this.rpcUrls[chainId])
1191
- });
1350
+ const client = createPaidRpcClient(
1351
+ VIEM_CHAIN[chainId],
1352
+ this.rpcUrls
1353
+ );
1192
1354
  this.publicClients.set(chainId, client);
1193
1355
  return client;
1194
1356
  }
@@ -2156,7 +2318,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2156
2318
  };
2157
2319
 
2158
2320
  // src/agents/yieldseeker/yieldseeker.agent.ts
2159
- var import_viem6 = require("viem");
2321
+ var import_viem7 = require("viem");
2160
2322
  var import_chains3 = require("viem/chains");
2161
2323
 
2162
2324
  // src/lib/chain-guard.ts
@@ -2195,7 +2357,7 @@ async function ensureWalletOnChain(pub, wallet, expected) {
2195
2357
  }
2196
2358
 
2197
2359
  // src/lib/transfer-auth.ts
2198
- var import_viem2 = require("viem");
2360
+ var import_viem3 = require("viem");
2199
2361
 
2200
2362
  // src/lib/sponsor-client.ts
2201
2363
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
@@ -2311,13 +2473,14 @@ async function postSponsorBatchTransfer(input) {
2311
2473
  }
2312
2474
 
2313
2475
  // src/lib/permit2.ts
2314
- var import_viem3 = require("viem");
2476
+ var import_viem4 = require("viem");
2315
2477
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2478
+ var MAX_UINT256 = 2n ** 256n - 1n;
2316
2479
  function permit2ApprovalAmount(requiredAmount) {
2317
2480
  if (requiredAmount <= 0n) {
2318
2481
  throw new Error("Permit2 approval requires a positive deposit amount");
2319
2482
  }
2320
- return requiredAmount;
2483
+ return MAX_UINT256;
2321
2484
  }
2322
2485
  var ERC20_ALLOWANCE_ABI = [
2323
2486
  {
@@ -2351,14 +2514,15 @@ var ERC20_ALLOWANCE_ABI = [
2351
2514
  function randomPermit2Nonce() {
2352
2515
  const bytes = new Uint8Array(32);
2353
2516
  globalThis.crypto.getRandomValues(bytes);
2354
- return BigInt((0, import_viem3.bytesToHex)(bytes));
2517
+ return BigInt((0, import_viem4.bytesToHex)(bytes));
2355
2518
  }
2356
- async function readPermit2Allowance(publicClient, token, owner) {
2519
+ async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
2357
2520
  return publicClient.readContract({
2358
2521
  address: token,
2359
2522
  abi: ERC20_ALLOWANCE_ABI,
2360
2523
  functionName: "allowance",
2361
- args: [owner, PERMIT2_ADDRESS]
2524
+ args: [owner, PERMIT2_ADDRESS],
2525
+ ...blockNumber === void 0 ? {} : { blockNumber }
2362
2526
  });
2363
2527
  }
2364
2528
  async function readErc20Balance(publicClient, token, owner) {
@@ -2393,7 +2557,7 @@ function makeVerificationAwareDepositCallback(implementation) {
2393
2557
 
2394
2558
  // src/agents/yieldseeker/yieldseeker.auth.ts
2395
2559
  var import_siwe = require("siwe");
2396
- var import_viem4 = require("viem");
2560
+ var import_viem5 = require("viem");
2397
2561
  var import_chains2 = require("viem/chains");
2398
2562
 
2399
2563
  // src/agents/yieldseeker/yieldseeker.auth-cache.ts
@@ -2506,7 +2670,7 @@ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
2506
2670
  return new import_siwe.SiweMessage({
2507
2671
  scheme: url.protocol.slice(0, -1),
2508
2672
  domain: url.host,
2509
- address: (0, import_viem4.getAddress)(address),
2673
+ address: (0, import_viem5.getAddress)(address),
2510
2674
  uri: url.origin,
2511
2675
  version: "1",
2512
2676
  chainId,
@@ -2592,15 +2756,15 @@ var YieldseekerAuth = class {
2592
2756
  clearYieldseekerSession(state.walletAddress, chainId);
2593
2757
  }
2594
2758
  async sign(state, chainId) {
2595
- const account = (0, import_viem4.getAddress)(state.walletAddress);
2596
- const publicClient = (0, import_viem4.createPublicClient)({
2759
+ const account = (0, import_viem5.getAddress)(state.walletAddress);
2760
+ const publicClient = (0, import_viem5.createPublicClient)({
2597
2761
  chain: import_chains2.base,
2598
- transport: (0, import_viem4.custom)(state.provider)
2762
+ transport: (0, import_viem5.custom)(state.provider)
2599
2763
  });
2600
- const walletClient = (0, import_viem4.createWalletClient)({
2764
+ const walletClient = (0, import_viem5.createWalletClient)({
2601
2765
  account,
2602
2766
  chain: import_chains2.base,
2603
- transport: (0, import_viem4.custom)(state.provider)
2767
+ transport: (0, import_viem5.custom)(state.provider)
2604
2768
  });
2605
2769
  await ensureWalletOnChain(
2606
2770
  publicClient,
@@ -2741,10 +2905,11 @@ var YieldseekerApiClient = class {
2741
2905
  const payload = await response.json().catch(() => null);
2742
2906
  if (!response.ok) {
2743
2907
  const error = providerError(payload, `HTTP_${response.status}`);
2908
+ const retryAfter = response.headers.get("Retry-After");
2744
2909
  throw new YieldseekerApiError(
2745
2910
  response.status,
2746
2911
  error.code,
2747
- error.fields
2912
+ retryAfter ? { ...error.fields, headers: { "Retry-After": retryAfter } } : error.fields
2748
2913
  );
2749
2914
  }
2750
2915
  if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
@@ -2766,7 +2931,7 @@ var YieldseekerApiClient = class {
2766
2931
  };
2767
2932
 
2768
2933
  // src/agents/yieldseeker/yieldseeker.mapper.ts
2769
- var import_viem5 = require("viem");
2934
+ var import_viem6 = require("viem");
2770
2935
 
2771
2936
  // src/lib/helpers/snapshot-apy.ts
2772
2937
  var DAY_MS = 864e5;
@@ -2833,10 +2998,10 @@ function raw(value, endpoint) {
2833
2998
  return BigInt(value);
2834
2999
  }
2835
3000
  function decimal(value, decimals, endpoint) {
2836
- return (0, import_viem5.formatUnits)(raw(value, endpoint), decimals);
3001
+ return (0, import_viem6.formatUnits)(raw(value, endpoint), decimals);
2837
3002
  }
2838
3003
  function usd(rawAmount, decimals, price) {
2839
- return Number((0, import_viem5.formatUnits)(rawAmount, decimals)) * price;
3004
+ return Number((0, import_viem6.formatUnits)(rawAmount, decimals)) * price;
2840
3005
  }
2841
3006
  function percent(value) {
2842
3007
  const result = Number(value);
@@ -2862,7 +3027,7 @@ function assetAddressValue(record, address) {
2862
3027
  }
2863
3028
  function position(value, asset, baseAssetDecimals) {
2864
3029
  const option = value?.yieldOption;
2865
- if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem5.isAddress)(option.address)) {
3030
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem6.isAddress)(option.address)) {
2866
3031
  return invalid("yield positions", "missing vault metadata");
2867
3032
  }
2868
3033
  return {
@@ -2875,13 +3040,14 @@ function position(value, asset, baseAssetDecimals) {
2875
3040
  // differ from the underlying asset. Yieldseeker already converts it to
2876
3041
  // underlying base-asset units in `assetsBase`; pair that value with the
2877
3042
  // snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
2878
- // share quantity separately because withdraw-from-position expects it.
3043
+ // share quantity separate from the withdrawable underlying asset amount.
2879
3044
  amount: decimal(
2880
3045
  value.assetsBase,
2881
3046
  baseAssetDecimals,
2882
3047
  "yield positions"
2883
3048
  ),
2884
3049
  amountRaw: String(value.assetsRaw),
3050
+ withdrawableAmountRaw: String(value.withdrawableAssetsRaw),
2885
3051
  apy: percent(option.riskAdjustedApy),
2886
3052
  tvl: Number(option.totalDepositsUsd),
2887
3053
  liquidity: Number(option.withdrawableDepositsUsd)
@@ -2943,7 +3109,7 @@ function mapYieldseekerEarnings(contexts) {
2943
3109
  chain: "BASE",
2944
3110
  chainId: 8453,
2945
3111
  asset: context.asset,
2946
- amount: (0, import_viem5.formatUnits)(amount, context.snapshot.baseAssetDecimals)
3112
+ amount: (0, import_viem6.formatUnits)(amount, context.snapshot.baseAssetDecimals)
2947
3113
  });
2948
3114
  lifetimeEarnings += usd(
2949
3115
  amount,
@@ -3206,10 +3372,18 @@ function mapYieldseekerAgentApy(options, days) {
3206
3372
 
3207
3373
  // src/agents/yieldseeker/yieldseeker.agent.ts
3208
3374
  var OWNEY_AGENT_NAME = "owney";
3375
+ var OWNEY_AGENT_RULE_PRESET = "explorative";
3209
3376
  var YIELDSEEKER_USERNAME_PREFIX = "owney_";
3210
3377
  var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
3211
3378
  var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
3212
3379
  var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
3380
+ var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
3381
+ var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
3382
+ var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
3383
+ var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
3384
+ var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
3385
+ var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
3386
+ var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
3213
3387
  function generateYieldseekerUsername() {
3214
3388
  const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
3215
3389
  return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
@@ -3253,6 +3427,7 @@ function query(params) {
3253
3427
  var YieldseekerAgent = class {
3254
3428
  id = "yieldseeker";
3255
3429
  balanceComposition = "tokens-plus-positions";
3430
+ withdrawalRequiresWalletApproval = true;
3256
3431
  supportedChainIds = [8453];
3257
3432
  supportedAssets = [
3258
3433
  {
@@ -3266,11 +3441,26 @@ var YieldseekerAgent = class {
3266
3441
  ];
3267
3442
  api;
3268
3443
  auth;
3444
+ rpcUrls;
3445
+ receiptClient;
3269
3446
  transactionExecutor;
3270
3447
  unwindReceiptWaiter;
3271
3448
  agentContexts = /* @__PURE__ */ new Map();
3272
3449
  users = /* @__PURE__ */ new Map();
3273
3450
  pendingAgents = /* @__PURE__ */ new Map();
3451
+ pendingWalletContexts = /* @__PURE__ */ new Map();
3452
+ readCache = /* @__PURE__ */ new Map();
3453
+ pendingReads = /* @__PURE__ */ new Map();
3454
+ readGeneration = 0;
3455
+ portfolioVersions = /* @__PURE__ */ new Map();
3456
+ snapshotFailures = /* @__PURE__ */ new Map();
3457
+ standardReadFailures = /* @__PURE__ */ new Map();
3458
+ reconcileUntil = /* @__PURE__ */ new Map();
3459
+ activityRefreshUntil = /* @__PURE__ */ new Map();
3460
+ // null means a confirmed movement has no reliable baseline. Do not guess
3461
+ // settlement; keep slow reconciliation after the fast window expires.
3462
+ snapshotMovements = /* @__PURE__ */ new Map();
3463
+ movementEpochs = /* @__PURE__ */ new Map();
3274
3464
  yieldOptions = /* @__PURE__ */ new Map();
3275
3465
  pendingYieldOptions = /* @__PURE__ */ new Map();
3276
3466
  constructor(owneyApiKey, options = {}) {
@@ -3280,10 +3470,16 @@ var YieldseekerAgent = class {
3280
3470
  options.fetchFn
3281
3471
  );
3282
3472
  this.auth = new YieldseekerAuth(options.auth);
3473
+ this.rpcUrls = options.rpcUrls;
3283
3474
  this.transactionExecutor = options.transactionExecutor;
3284
3475
  this.unwindReceiptWaiter = options.unwindReceiptWaiter;
3285
3476
  }
3477
+ getReceiptClient() {
3478
+ this.receiptClient ??= createPaidRpcClient(import_chains3.base, this.rpcUrls);
3479
+ return this.receiptClient;
3480
+ }
3286
3481
  async disconnect() {
3482
+ this.readGeneration += 1;
3287
3483
  this.auth.clear();
3288
3484
  for (const key2 of this.users.keys()) {
3289
3485
  const [walletAddress, chainId] = key2.split(":");
@@ -3292,6 +3488,16 @@ var YieldseekerAgent = class {
3292
3488
  this.users.clear();
3293
3489
  this.agentContexts.clear();
3294
3490
  this.pendingAgents.clear();
3491
+ this.pendingWalletContexts.clear();
3492
+ this.readCache.clear();
3493
+ this.pendingReads.clear();
3494
+ this.portfolioVersions.clear();
3495
+ this.snapshotFailures.clear();
3496
+ this.standardReadFailures.clear();
3497
+ this.reconcileUntil.clear();
3498
+ this.activityRefreshUntil.clear();
3499
+ this.snapshotMovements.clear();
3500
+ this.movementEpochs.clear();
3295
3501
  }
3296
3502
  async activateAgent(state, chainId, asset) {
3297
3503
  this.assertChain(chainId);
@@ -3311,41 +3517,37 @@ var YieldseekerAgent = class {
3311
3517
  );
3312
3518
  }
3313
3519
  const context = await this.ensureAgent(state, chainId, asset);
3520
+ const generation = this.readGeneration;
3521
+ const movement = this.snapshotMovement(state, chainId, context, BigInt(amount));
3314
3522
  let txHash;
3315
- try {
3316
- if (depositCallback) {
3317
- provideDepositVerificationContext(depositCallback, {
3318
- agentId: "yieldseeker",
3319
- signature: await this.auth.getToken(state, chainId),
3320
- userId: context.user.userId,
3321
- yieldseekerAgentId: context.agent.agentId
3322
- });
3323
- txHash = await depositCallback(
3324
- context.wallet.walletAddress,
3325
- chainId,
3326
- amount
3327
- );
3328
- await this.waitForReceipt(state, chainId, txHash);
3329
- } else {
3330
- txHash = await this.submitTransaction(state, chainId, {
3331
- from: (0, import_viem6.getAddress)(state.walletAddress),
3332
- to: YIELDSEEKER_ASSET_METADATA[asset].address,
3333
- data: (0, import_viem6.encodeFunctionData)({
3334
- abi: import_viem6.erc20Abi,
3335
- functionName: "transfer",
3336
- args: [(0, import_viem6.getAddress)(context.wallet.walletAddress), BigInt(amount)]
3337
- }),
3338
- value: "0",
3339
- chainId
3340
- });
3341
- }
3342
- } finally {
3343
- await this.refreshSnapshotAfterMovement(
3344
- state,
3523
+ if (depositCallback) {
3524
+ provideDepositVerificationContext(depositCallback, {
3525
+ agentId: "yieldseeker",
3526
+ signature: await this.auth.getToken(state, chainId),
3527
+ userId: context.user.userId,
3528
+ yieldseekerAgentId: context.agent.agentId
3529
+ });
3530
+ txHash = await depositCallback(
3531
+ context.wallet.walletAddress,
3345
3532
  chainId,
3346
- context,
3347
- "deposit"
3533
+ amount
3348
3534
  );
3535
+ await this.waitForReceipt(state, chainId, txHash);
3536
+ } else {
3537
+ txHash = await this.submitTransaction(state, chainId, {
3538
+ from: (0, import_viem7.getAddress)(state.walletAddress),
3539
+ to: YIELDSEEKER_ASSET_METADATA[asset].address,
3540
+ data: (0, import_viem7.encodeFunctionData)({
3541
+ abi: import_viem7.erc20Abi,
3542
+ functionName: "transfer",
3543
+ args: [(0, import_viem7.getAddress)(context.wallet.walletAddress), BigInt(amount)]
3544
+ }),
3545
+ value: "0",
3546
+ chainId
3547
+ });
3548
+ }
3549
+ if (this.readGeneration === generation) {
3550
+ void this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
3349
3551
  }
3350
3552
  return {
3351
3553
  txHash,
@@ -3373,6 +3575,9 @@ var YieldseekerAgent = class {
3373
3575
  this.id
3374
3576
  );
3375
3577
  }
3578
+ const generation = this.readGeneration;
3579
+ let confirmedMovement = false;
3580
+ let settlement = null;
3376
3581
  try {
3377
3582
  const portfolio = await this.loadPortfolioContext(
3378
3583
  state,
@@ -3390,6 +3595,7 @@ var YieldseekerAgent = class {
3390
3595
  );
3391
3596
  const totalAvailable = idle + deployed;
3392
3597
  const requested = amount === void 0 ? totalAvailable : BigInt(amount);
3598
+ const plannedMovement = this.snapshotMovement(state, chainId, context, -requested);
3393
3599
  if (requested > totalAvailable) {
3394
3600
  throw new OwneyError(
3395
3601
  "WITHDRAW_INSUFFICIENT_BALANCE",
@@ -3425,6 +3631,7 @@ var YieldseekerAgent = class {
3425
3631
  throw this.invalidResponse("position withdrawal");
3426
3632
  }
3427
3633
  await this.waitForReceipt(state, chainId, response.transactionHash);
3634
+ confirmedMovement = true;
3428
3635
  remaining -= assetsRaw;
3429
3636
  }
3430
3637
  if (remaining > 0n) {
@@ -3433,15 +3640,15 @@ var YieldseekerAgent = class {
3433
3640
  remaining: remaining.toString()
3434
3641
  });
3435
3642
  }
3436
- const account = (0, import_viem6.getAddress)(state.walletAddress);
3643
+ const account = (0, import_viem7.getAddress)(state.walletAddress);
3437
3644
  const txHash = await this.submitTransaction(state, chainId, {
3438
3645
  from: account,
3439
- to: (0, import_viem6.getAddress)(context.wallet.walletAddress),
3440
- data: amount === void 0 ? (0, import_viem6.encodeFunctionData)({
3646
+ to: (0, import_viem7.getAddress)(context.wallet.walletAddress),
3647
+ data: amount === void 0 ? (0, import_viem7.encodeFunctionData)({
3441
3648
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
3442
3649
  functionName: "withdrawAllAssetToUser",
3443
3650
  args: [account, metadata.address]
3444
- }) : (0, import_viem6.encodeFunctionData)({
3651
+ }) : (0, import_viem7.encodeFunctionData)({
3445
3652
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
3446
3653
  functionName: "withdrawAssetToUser",
3447
3654
  args: [account, metadata.address, requested]
@@ -3449,18 +3656,23 @@ var YieldseekerAgent = class {
3449
3656
  value: "0",
3450
3657
  chainId
3451
3658
  });
3659
+ confirmedMovement = true;
3660
+ settlement = plannedMovement;
3452
3661
  return {
3453
3662
  txHash,
3454
3663
  type: amount === void 0 ? "full" : "partial",
3455
3664
  amount: requested.toString()
3456
3665
  };
3457
3666
  } finally {
3458
- await this.refreshSnapshotAfterMovement(
3459
- state,
3460
- chainId,
3461
- context,
3462
- "withdrawal"
3463
- );
3667
+ if (confirmedMovement && this.readGeneration === generation) {
3668
+ void this.refreshSnapshotAfterMovement(
3669
+ state,
3670
+ chainId,
3671
+ context,
3672
+ "withdrawal",
3673
+ settlement
3674
+ );
3675
+ }
3464
3676
  }
3465
3677
  }
3466
3678
  async getBalances(state, chainId) {
@@ -3485,7 +3697,7 @@ var YieldseekerAgent = class {
3485
3697
  this.assertChain(chainId);
3486
3698
  const asset = options?.tokenSymbol?.toUpperCase();
3487
3699
  if (asset !== void 0) this.assertAsset(asset);
3488
- const contexts = await this.loadPortfolio(state, chainId, {
3700
+ const contexts = await this.loadActivityPortfolio(state, chainId, {
3489
3701
  ...asset ? { asset } : {},
3490
3702
  historic: true,
3491
3703
  actions: true
@@ -3497,7 +3709,7 @@ var YieldseekerAgent = class {
3497
3709
  );
3498
3710
  const vaultAddresses = new Set(
3499
3711
  catalog.flat().filter(
3500
- (yieldOption) => yieldOption.chainId === chainId && (0, import_viem6.isAddress)(yieldOption.address)
3712
+ (yieldOption) => yieldOption.chainId === chainId && (0, import_viem7.isAddress)(yieldOption.address)
3501
3713
  ).map((yieldOption) => yieldOption.address.toLowerCase())
3502
3714
  );
3503
3715
  return mapYieldseekerHistory(contexts, {
@@ -3512,7 +3724,7 @@ var YieldseekerAgent = class {
3512
3724
  this.assertChain(chainId);
3513
3725
  return mapYieldseekerProfile(
3514
3726
  state.walletAddress,
3515
- await this.loadPortfolio(state, chainId, {})
3727
+ await this.loadActivityPortfolio(state, chainId, {})
3516
3728
  );
3517
3729
  }
3518
3730
  async getAgentApy(days, options) {
@@ -3554,6 +3766,95 @@ var YieldseekerAgent = class {
3554
3766
  contextKey(state, chainId, asset) {
3555
3767
  return `${this.userKey(state, chainId)}:${asset}`;
3556
3768
  }
3769
+ assertSession(generation, chainId, asset) {
3770
+ if (this.readGeneration !== generation) {
3771
+ throw new OwneyError(
3772
+ "NOT_CONNECTED",
3773
+ "Wallet session changed during the Yieldseeker request.",
3774
+ { rpcSource: "agent-api", chainId, ...asset ? { asset } : {} },
3775
+ this.id
3776
+ );
3777
+ }
3778
+ }
3779
+ cachedRead(key2, ttlMs, read2) {
3780
+ const cached = this.readCache.get(key2);
3781
+ if (cached && cached.expiresAt > Date.now()) {
3782
+ return Promise.resolve(cached.value);
3783
+ }
3784
+ const pending = this.pendingReads.get(key2);
3785
+ if (pending) return pending;
3786
+ const generation = this.readGeneration;
3787
+ const request = Promise.resolve().then(read2).then((value) => {
3788
+ if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
3789
+ this.readCache.set(key2, {
3790
+ expiresAt: Date.now() + ttlMs,
3791
+ value
3792
+ });
3793
+ }
3794
+ return value;
3795
+ }).finally(() => {
3796
+ if (this.pendingReads.get(key2) === request) {
3797
+ this.pendingReads.delete(key2);
3798
+ }
3799
+ });
3800
+ this.pendingReads.set(key2, request);
3801
+ return request;
3802
+ }
3803
+ agentListKey(state, chainId) {
3804
+ return `agents:${this.userKey(state, chainId)}`;
3805
+ }
3806
+ async listAgents(state, chainId, user) {
3807
+ const response = await this.cachedRead(
3808
+ this.agentListKey(state, chainId),
3809
+ YIELDSEEKER_AGENT_LIST_CACHE_MS,
3810
+ async () => {
3811
+ const response2 = await this.walletRequest(
3812
+ state,
3813
+ chainId,
3814
+ `/users/${user.userId}/agents`
3815
+ );
3816
+ if (!Array.isArray(response2?.agents)) {
3817
+ throw this.invalidResponse("agent list");
3818
+ }
3819
+ return response2;
3820
+ }
3821
+ );
3822
+ return response.agents;
3823
+ }
3824
+ async contextForAgent(state, chainId, user, agent, asset) {
3825
+ const key2 = this.contextKey(state, chainId, asset);
3826
+ const cached = this.agentContexts.get(key2);
3827
+ if (cached?.agent.agentId === agent.agentId) return cached;
3828
+ const pending = this.pendingWalletContexts.get(key2);
3829
+ if (pending) return pending;
3830
+ const generation = this.readGeneration;
3831
+ const request = this.walletRequest(
3832
+ state,
3833
+ chainId,
3834
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3835
+ ).then((walletResponse) => {
3836
+ this.assertSession(generation, chainId, asset);
3837
+ if (!walletResponse?.agentWallet || !(0, import_viem7.isAddress)(walletResponse.agentWallet.walletAddress)) {
3838
+ throw this.invalidResponse("agent wallet");
3839
+ }
3840
+ const context = {
3841
+ user,
3842
+ agent,
3843
+ wallet: walletResponse.agentWallet,
3844
+ asset
3845
+ };
3846
+ this.agentContexts.set(key2, context);
3847
+ return context;
3848
+ });
3849
+ this.pendingWalletContexts.set(key2, request);
3850
+ try {
3851
+ return await request;
3852
+ } finally {
3853
+ if (this.pendingWalletContexts.get(key2) === request) {
3854
+ this.pendingWalletContexts.delete(key2);
3855
+ }
3856
+ }
3857
+ }
3557
3858
  async resolveUser(state, chainId) {
3558
3859
  const key2 = this.userKey(state, chainId);
3559
3860
  const inMemory = this.users.get(key2);
@@ -3563,7 +3864,8 @@ var YieldseekerAgent = class {
3563
3864
  this.users.set(key2, persisted);
3564
3865
  return persisted;
3565
3866
  }
3566
- const walletAddress = (0, import_viem6.getAddress)(state.walletAddress);
3867
+ const walletAddress = (0, import_viem7.getAddress)(state.walletAddress);
3868
+ const generation = this.readGeneration;
3567
3869
  let user = null;
3568
3870
  try {
3569
3871
  const login = await this.providerRequest(
@@ -3610,6 +3912,7 @@ var YieldseekerAgent = class {
3610
3912
  if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
3611
3913
  throw this.invalidResponse("wallet identity");
3612
3914
  }
3915
+ this.assertSession(generation, chainId);
3613
3916
  const resolved = { userId: user.userId };
3614
3917
  this.users.set(key2, resolved);
3615
3918
  writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
@@ -3625,10 +3928,13 @@ var YieldseekerAgent = class {
3625
3928
  if (cached) return cached;
3626
3929
  const pending = this.pendingAgents.get(key2);
3627
3930
  if (pending) return pending;
3931
+ const generation = this.readGeneration;
3628
3932
  const request = this.resolveAgent(state, chainId, asset, true).then(
3629
3933
  async (context) => {
3934
+ this.assertSession(generation, chainId, asset);
3630
3935
  if (!context) throw this.invalidResponse("agent creation");
3631
3936
  await this.deployAgent(state, chainId, context);
3937
+ this.assertSession(generation, chainId, asset);
3632
3938
  this.agentContexts.set(key2, context);
3633
3939
  return context;
3634
3940
  }
@@ -3637,29 +3943,27 @@ var YieldseekerAgent = class {
3637
3943
  try {
3638
3944
  return await request;
3639
3945
  } finally {
3640
- this.pendingAgents.delete(key2);
3946
+ if (this.pendingAgents.get(key2) === request) this.pendingAgents.delete(key2);
3641
3947
  }
3642
3948
  }
3643
3949
  async findAgent(state, chainId, asset) {
3644
3950
  const key2 = this.contextKey(state, chainId, asset);
3645
3951
  const cached = this.agentContexts.get(key2);
3646
3952
  if (cached) return cached;
3953
+ const generation = this.readGeneration;
3647
3954
  const context = await this.resolveAgent(state, chainId, asset, false);
3955
+ this.assertSession(generation, chainId, asset);
3648
3956
  if (context) this.agentContexts.set(key2, context);
3649
3957
  return context;
3650
3958
  }
3651
3959
  async resolveAgent(state, chainId, asset, createIfMissing) {
3960
+ const generation = this.readGeneration;
3652
3961
  const user = await this.resolveUser(state, chainId);
3653
- const response = await this.walletRequest(
3654
- state,
3655
- chainId,
3656
- `/users/${user.userId}/agents`
3657
- );
3658
- if (!Array.isArray(response?.agents)) {
3659
- throw this.invalidResponse("agent list");
3660
- }
3962
+ this.assertSession(generation, chainId, asset);
3963
+ const agents = await this.listAgents(state, chainId, user);
3964
+ this.assertSession(generation, chainId, asset);
3661
3965
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3662
- let agent = response.agents.find(
3966
+ let agent = agents.find(
3663
3967
  (candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
3664
3968
  );
3665
3969
  if (!agent && createIfMissing) {
@@ -3675,99 +3979,254 @@ var YieldseekerAgent = class {
3675
3979
  chainId,
3676
3980
  assetAddress: metadata.address,
3677
3981
  type: "vault",
3678
- rulePreset: null
3982
+ rulePreset: OWNEY_AGENT_RULE_PRESET
3679
3983
  }
3680
3984
  }
3681
3985
  );
3986
+ this.assertSession(generation, chainId, asset);
3682
3987
  agent = created?.agent;
3988
+ if (agent) {
3989
+ this.readCache.set(this.agentListKey(state, chainId), {
3990
+ expiresAt: Date.now() + YIELDSEEKER_AGENT_LIST_CACHE_MS,
3991
+ value: {
3992
+ agents: [...agents, agent]
3993
+ }
3994
+ });
3995
+ }
3683
3996
  }
3684
3997
  if (!agent) return null;
3685
3998
  this.assertAgent(agent);
3686
- const walletResponse = await this.walletRequest(
3687
- state,
3688
- chainId,
3689
- `/users/${user.userId}/agents/${agent.agentId}/wallet`
3690
- );
3691
- if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
3692
- throw this.invalidResponse("agent wallet");
3693
- }
3694
- return { user, agent, wallet: walletResponse.agentWallet, asset };
3999
+ return this.contextForAgent(state, chainId, user, agent, asset);
3695
4000
  }
3696
4001
  async loadPortfolio(state, chainId, options) {
3697
- const user = await this.resolveUser(state, chainId);
3698
- const response = await this.walletRequest(
3699
- state,
3700
- chainId,
3701
- `/users/${user.userId}/agents`
4002
+ const contexts = await this.resolvePortfolioContexts(state, chainId, options.asset);
4003
+ return Promise.all(
4004
+ contexts.map(
4005
+ (context) => this.loadPortfolioContext(state, chainId, context, options)
4006
+ )
3702
4007
  );
3703
- if (!Array.isArray(response?.agents)) {
3704
- throw this.invalidResponse("agent list");
3705
- }
4008
+ }
4009
+ async loadActivityPortfolio(state, chainId, options) {
4010
+ const contexts = await this.resolvePortfolioContexts(state, chainId, options.asset);
4011
+ return Promise.all(
4012
+ contexts.map(
4013
+ (context) => this.loadActivityContext(state, chainId, context, options)
4014
+ )
4015
+ );
4016
+ }
4017
+ async resolvePortfolioContexts(state, chainId, assetFilter) {
4018
+ const generation = this.readGeneration;
4019
+ const user = await this.resolveUser(state, chainId);
4020
+ this.assertSession(generation, chainId);
4021
+ const agents = await this.listAgents(state, chainId, user);
4022
+ this.assertSession(generation, chainId);
3706
4023
  const contexts = [];
3707
- for (const agent of response.agents) {
4024
+ for (const agent of agents) {
3708
4025
  const asset = this.assetForAgent(agent);
3709
- if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
4026
+ if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || assetFilter && assetFilter !== asset) {
3710
4027
  continue;
3711
4028
  }
3712
4029
  this.assertAgent(agent);
3713
- const walletResponse = await this.walletRequest(
3714
- state,
3715
- chainId,
3716
- `/users/${user.userId}/agents/${agent.agentId}/wallet`
3717
- );
3718
- if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
3719
- throw this.invalidResponse("agent wallet");
3720
- }
3721
- const context = {
3722
- user,
3723
- agent,
3724
- wallet: walletResponse.agentWallet,
3725
- asset
4030
+ contexts.push(this.contextForAgent(state, chainId, user, agent, asset));
4031
+ }
4032
+ const resolvedContexts = await Promise.all(contexts);
4033
+ this.assertSession(generation, chainId);
4034
+ return resolvedContexts;
4035
+ }
4036
+ portfolioVersion(contextKey) {
4037
+ return this.portfolioVersions.get(contextKey) ?? 0;
4038
+ }
4039
+ portfolioCacheMs(contextKey) {
4040
+ return (this.reconcileUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_CACHE_MS : YIELDSEEKER_PORTFOLIO_CACHE_MS;
4041
+ }
4042
+ snapshotMovement(state, chainId, context, delta) {
4043
+ const key2 = this.contextKey(state, chainId, context.asset);
4044
+ const previous = this.snapshotMovements.get(key2);
4045
+ if (this.snapshotMovements.has(key2)) {
4046
+ if (!previous || previous.delta > 0n !== delta > 0n) return null;
4047
+ return {
4048
+ ...previous,
4049
+ epoch: this.movementEpochs.get(key2) ?? 0,
4050
+ delta: previous.delta + delta,
4051
+ expectedBalance: previous.expectedBalance + delta,
4052
+ expectedNetDeposits: previous.expectedNetDeposits + delta
3726
4053
  };
3727
- this.agentContexts.set(this.contextKey(state, chainId, asset), context);
3728
- contexts.push(context);
3729
4054
  }
3730
- return Promise.all(
3731
- contexts.map(
3732
- (context) => this.loadPortfolioContext(state, chainId, context, options)
3733
- )
3734
- );
4055
+ const cached = this.readCache.get(`snapshot:${key2}`);
4056
+ if (!cached || cached.expiresAt <= Date.now()) return null;
4057
+ const snapshot = cached.value.agentSnapshot;
4058
+ if (snapshot.baseAssetDecimals !== YIELDSEEKER_ASSET_METADATA[context.asset].decimals) return null;
4059
+ if (!/^-?\d+$/.test(snapshot.netDepositsBase) || !/^\d+$/.test(snapshot.totalValueBase)) return null;
4060
+ return {
4061
+ epoch: this.movementEpochs.get(key2) ?? 0,
4062
+ delta,
4063
+ decimals: snapshot.baseAssetDecimals,
4064
+ expectedBalance: BigInt(snapshot.totalValueBase) + delta,
4065
+ expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
4066
+ };
4067
+ }
4068
+ advancePortfolioVersion(contextKey) {
4069
+ const version = this.portfolioVersion(contextKey) + 1;
4070
+ this.portfolioVersions.set(contextKey, version);
4071
+ this.snapshotFailures.delete(contextKey);
4072
+ for (const kind of ["snapshot", "positions", "historic", "actions"]) {
4073
+ const key2 = `${kind}:${contextKey}`;
4074
+ this.readCache.delete(key2);
4075
+ this.pendingReads.delete(key2);
4076
+ }
4077
+ return version;
4078
+ }
4079
+ requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, forceRefresh = false) {
4080
+ return this.walletRequest(
4081
+ state,
4082
+ chainId,
4083
+ `${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`
4084
+ ).then((response) => {
4085
+ if (!response?.agentSnapshot) {
4086
+ throw this.invalidResponse("agent snapshot");
4087
+ }
4088
+ if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
4089
+ this.snapshotFailures.delete(contextKey);
4090
+ const movement = this.snapshotMovements.get(contextKey);
4091
+ const snapshot = response.agentSnapshot;
4092
+ if (movement && snapshot.baseAssetDecimals === movement.decimals && /^-?\d+$/.test(snapshot.netDepositsBase) && /^\d+$/.test(snapshot.totalValueBase)) {
4093
+ const balance = BigInt(snapshot.totalValueBase);
4094
+ const netDeposits = BigInt(snapshot.netDepositsBase);
4095
+ const reconciled = movement.delta > 0n ? balance >= movement.expectedBalance && netDeposits >= movement.expectedNetDeposits : balance <= movement.expectedBalance && netDeposits <= movement.expectedNetDeposits;
4096
+ if (reconciled) {
4097
+ this.snapshotMovements.delete(contextKey);
4098
+ this.reconcileUntil.delete(contextKey);
4099
+ }
4100
+ }
4101
+ }
4102
+ return response;
4103
+ }).catch((error) => {
4104
+ if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
4105
+ this.snapshotFailures.set(contextKey, {
4106
+ retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, rateLimitDelay(error) ?? 0),
4107
+ error
4108
+ });
4109
+ }
4110
+ throw error;
4111
+ });
4112
+ }
4113
+ portfolioSnapshot(state, chainId, context, contextKey) {
4114
+ const key2 = `snapshot:${contextKey}`;
4115
+ const cached = this.readCache.get(key2);
4116
+ if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
4117
+ const failure = this.snapshotFailures.get(contextKey);
4118
+ if (failure && failure.retryAt > Date.now()) throw failure.error;
4119
+ this.advancePortfolioVersion(contextKey);
4120
+ }
4121
+ const version = this.portfolioVersion(contextKey);
4122
+ const generation = this.readGeneration;
4123
+ return {
4124
+ version,
4125
+ read: this.cachedRead(key2, this.portfolioCacheMs(contextKey), () => (
4126
+ // Force only during the bounded post-transaction window. Keep unknown
4127
+ // or unreconciled movements for verification, but do not let them turn
4128
+ // routine polling into forced refreshes indefinitely.
4129
+ this.requestPortfolioSnapshot(
4130
+ state,
4131
+ chainId,
4132
+ context,
4133
+ contextKey,
4134
+ version,
4135
+ generation,
4136
+ this.snapshotMovements.has(contextKey) && (this.reconcileUntil.get(contextKey) ?? 0) > Date.now()
4137
+ )
4138
+ ))
4139
+ };
3735
4140
  }
3736
4141
  async loadPortfolioContext(state, chainId, context, options = {}) {
3737
- const [snapshot, positions, historic, actions] = await Promise.all([
3738
- this.walletRequest(
3739
- state,
3740
- chainId,
3741
- `${this.agentPath(context, "snapshot")}${query({
3742
- shouldOnlyUseRecentValue: true,
3743
- shouldAllowStaleOnError: true
3744
- })}`
3745
- ),
3746
- this.walletRequest(
3747
- state,
3748
- chainId,
3749
- this.agentPath(context, "yield-positions")
4142
+ const contextKey = this.contextKey(state, chainId, context.asset);
4143
+ const generation = this.readGeneration;
4144
+ const assertSession = () => this.assertSession(generation, chainId, context.asset);
4145
+ for (let attempt = 0; attempt < 2; attempt++) {
4146
+ const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
4147
+ const parts = this.loadPortfolioParts(state, chainId, context, options, contextKey);
4148
+ try {
4149
+ const [snapshot, portfolioParts] = await Promise.all([read2, parts]);
4150
+ assertSession();
4151
+ if (this.portfolioVersion(contextKey) !== version) continue;
4152
+ return {
4153
+ ...context,
4154
+ snapshot: snapshot.agentSnapshot,
4155
+ ...portfolioParts
4156
+ };
4157
+ } catch (error) {
4158
+ assertSession();
4159
+ if (this.portfolioVersion(contextKey) !== version) continue;
4160
+ throw error;
4161
+ }
4162
+ }
4163
+ throw new OwneyError(
4164
+ "AGENT_API_ERROR",
4165
+ "Yieldseeker portfolio changed during the read. Please retry.",
4166
+ { rpcSource: "agent-api", chainId, asset: context.asset },
4167
+ this.id
4168
+ );
4169
+ }
4170
+ async loadActivityContext(state, chainId, context, options) {
4171
+ const contextKey = this.contextKey(state, chainId, context.asset);
4172
+ const generation = this.readGeneration;
4173
+ for (let attempt = 0; attempt < 2; attempt++) {
4174
+ const version = this.portfolioVersion(contextKey);
4175
+ try {
4176
+ const parts = await this.loadPortfolioParts(state, chainId, context, options, contextKey);
4177
+ this.assertSession(generation, chainId, context.asset);
4178
+ if (this.portfolioVersion(contextKey) !== version) continue;
4179
+ return { ...context, ...parts };
4180
+ } catch (error) {
4181
+ this.assertSession(generation, chainId, context.asset);
4182
+ if (this.portfolioVersion(contextKey) !== version) continue;
4183
+ throw error;
4184
+ }
4185
+ }
4186
+ throw new OwneyError(
4187
+ "AGENT_API_ERROR",
4188
+ "Yieldseeker activity changed during the read. Please retry.",
4189
+ { rpcSource: "agent-api", chainId, asset: context.asset },
4190
+ this.id
4191
+ );
4192
+ }
4193
+ async loadPortfolioParts(state, chainId, context, options, contextKey) {
4194
+ const [positions, historic, actions] = await Promise.all([
4195
+ this.cachedRead(
4196
+ `positions:${contextKey}`,
4197
+ this.portfolioCacheMs(contextKey),
4198
+ async () => {
4199
+ const response = await this.walletRequest(
4200
+ state,
4201
+ chainId,
4202
+ this.agentPath(context, "yield-positions")
4203
+ );
4204
+ if (!Array.isArray(response?.yieldPositions)) {
4205
+ throw this.invalidResponse("yield positions");
4206
+ }
4207
+ return response;
4208
+ }
3750
4209
  ),
3751
- options.historic ? this.walletRequest(
3752
- state,
3753
- chainId,
3754
- this.agentPath(context, "wallet/historic-position")
4210
+ options.historic ? this.cachedRead(
4211
+ `historic:${contextKey}`,
4212
+ (this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
4213
+ () => this.walletRequest(
4214
+ state,
4215
+ chainId,
4216
+ this.agentPath(context, "wallet/historic-position")
4217
+ )
3755
4218
  ) : Promise.resolve(void 0),
3756
- options.actions ? this.walletRequest(
3757
- state,
3758
- chainId,
3759
- this.agentPath(context, "actions")
4219
+ options.actions ? this.cachedRead(
4220
+ `actions:${contextKey}`,
4221
+ (this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
4222
+ () => this.walletRequest(
4223
+ state,
4224
+ chainId,
4225
+ this.agentPath(context, "actions")
4226
+ )
3760
4227
  ) : Promise.resolve(void 0)
3761
4228
  ]);
3762
- if (!snapshot?.agentSnapshot) {
3763
- throw this.invalidResponse("agent snapshot");
3764
- }
3765
- if (!Array.isArray(positions?.yieldPositions)) {
3766
- throw this.invalidResponse("yield positions");
3767
- }
3768
4229
  return {
3769
- ...context,
3770
- snapshot: snapshot.agentSnapshot,
3771
4230
  positions: positions.yieldPositions,
3772
4231
  ...historic?.position ? { historic: historic.position } : {},
3773
4232
  ...actions?.actions ? { actions: actions.actions } : {}
@@ -3782,25 +4241,28 @@ var YieldseekerAgent = class {
3782
4241
  this.agentPath(context, "deploy"),
3783
4242
  { method: "POST", body: {} }
3784
4243
  );
3785
- if (!deployed?.agentWallet || !(0, import_viem6.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
4244
+ if (!deployed?.agentWallet || !(0, import_viem7.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
3786
4245
  throw this.invalidResponse("agent deployment", {
3787
4246
  reason: "Deploy did not return the expected Agent Wallet."
3788
4247
  });
3789
4248
  }
3790
4249
  context.wallet = deployed.agentWallet;
3791
4250
  }
3792
- async refreshSnapshotAfterMovement(state, chainId, context, movement) {
4251
+ async refreshSnapshotAfterMovement(state, chainId, context, movement, settlement) {
4252
+ const contextKey = this.contextKey(state, chainId, context.asset);
4253
+ this.reconcileUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
4254
+ this.activityRefreshUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
4255
+ const epoch = this.movementEpochs.get(contextKey) ?? 0;
4256
+ this.snapshotMovements.set(contextKey, settlement?.epoch === epoch ? settlement : null);
4257
+ this.movementEpochs.set(contextKey, epoch + 1);
4258
+ const version = this.advancePortfolioVersion(contextKey);
4259
+ const generation = this.readGeneration;
3793
4260
  try {
3794
- const response = await this.walletRequest(
3795
- state,
3796
- chainId,
3797
- `${this.agentPath(context, "snapshot")}${query({
3798
- shouldForceRefresh: true
3799
- })}`
4261
+ await this.cachedRead(
4262
+ `snapshot:${contextKey}`,
4263
+ YIELDSEEKER_SETTLEMENT_CACHE_MS,
4264
+ () => this.requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, true)
3800
4265
  );
3801
- if (!response?.agentSnapshot) {
3802
- throw this.invalidResponse("agent snapshot refresh");
3803
- }
3804
4266
  } catch (error) {
3805
4267
  console.warn(
3806
4268
  `[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
@@ -3812,10 +4274,26 @@ var YieldseekerAgent = class {
3812
4274
  return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
3813
4275
  }
3814
4276
  async walletRequest(state, chainId, path, options = {}) {
4277
+ const read2 = (options.method ?? "GET") === "GET";
4278
+ const userKey = this.userKey(state, chainId);
4279
+ if (read2) {
4280
+ const failure = this.standardReadFailures.get(userKey);
4281
+ if (failure && failure.retryAt > Date.now()) throw failure.error;
4282
+ if (failure) this.standardReadFailures.delete(userKey);
4283
+ }
4284
+ const generation = this.readGeneration;
3815
4285
  try {
3816
4286
  return await this.providerRequest(state, chainId, path, options);
3817
4287
  } catch (error) {
3818
- throw this.mapApiError(error);
4288
+ const mapped = this.mapApiError(error);
4289
+ const delay = read2 ? rateLimitDelay(mapped) : void 0;
4290
+ if (delay !== void 0 && this.readGeneration === generation) {
4291
+ this.standardReadFailures.set(userKey, {
4292
+ retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, delay),
4293
+ error: mapped
4294
+ });
4295
+ }
4296
+ throw mapped;
3819
4297
  }
3820
4298
  }
3821
4299
  async providerRequest(state, chainId, path, options = {}) {
@@ -3856,6 +4334,7 @@ var YieldseekerAgent = class {
3856
4334
  code,
3857
4335
  `Yieldseeker request failed: ${error.providerCode}.`,
3858
4336
  {
4337
+ rpcSource: "agent-api",
3859
4338
  statusCode: error.status,
3860
4339
  providerCode: error.providerCode,
3861
4340
  ...error.responseFields ? { fields: error.responseFields } : {}
@@ -3868,32 +4347,37 @@ var YieldseekerAgent = class {
3868
4347
  return this.transactionExecutor(state, chainId, transaction);
3869
4348
  }
3870
4349
  this.assertTransaction(transaction, state, chainId);
3871
- const account = (0, import_viem6.getAddress)(state.walletAddress);
3872
- const walletClient = (0, import_viem6.createWalletClient)({
4350
+ const account = (0, import_viem7.getAddress)(state.walletAddress);
4351
+ const walletClient = (0, import_viem7.createWalletClient)({
3873
4352
  account,
3874
4353
  chain: import_chains3.base,
3875
- transport: (0, import_viem6.custom)(state.provider)
4354
+ transport: (0, import_viem7.custom)(state.provider)
3876
4355
  });
3877
- const publicClient = (0, import_viem6.createPublicClient)({
4356
+ const walletChainClient = (0, import_viem7.createPublicClient)({
3878
4357
  chain: import_chains3.base,
3879
- transport: (0, import_viem6.custom)(state.provider)
4358
+ transport: (0, import_viem7.custom)(state.provider)
3880
4359
  });
3881
4360
  await ensureWalletOnChain(
3882
- publicClient,
4361
+ walletChainClient,
3883
4362
  walletClient,
3884
4363
  8453
3885
4364
  );
3886
4365
  const hash = await walletClient.sendTransaction({
3887
4366
  account,
3888
4367
  chain: import_chains3.base,
3889
- to: (0, import_viem6.getAddress)(transaction.to),
4368
+ to: (0, import_viem7.getAddress)(transaction.to),
3890
4369
  data: transaction.data,
3891
4370
  value: BigInt(transaction.value)
3892
4371
  });
3893
- const receipt = await publicClient.waitForTransactionReceipt({
3894
- hash,
3895
- confirmations: 1
3896
- });
4372
+ const receipt = await withPaidRpcDiagnostics(
4373
+ () => this.getReceiptClient().waitForTransactionReceipt({
4374
+ hash,
4375
+ confirmations: 1
4376
+ }),
4377
+ 8453,
4378
+ "eth_getTransactionReceipt",
4379
+ this.id
4380
+ );
3897
4381
  if (receipt.status !== "success") {
3898
4382
  throw new OwneyError(
3899
4383
  "AGENT_TRANSACTION_REVERTED",
@@ -3909,14 +4393,15 @@ var YieldseekerAgent = class {
3909
4393
  await this.unwindReceiptWaiter(state, chainId, transactionHash);
3910
4394
  return;
3911
4395
  }
3912
- const publicClient = (0, import_viem6.createPublicClient)({
3913
- chain: import_chains3.base,
3914
- transport: (0, import_viem6.custom)(state.provider)
3915
- });
3916
- const receipt = await publicClient.waitForTransactionReceipt({
3917
- hash: transactionHash,
3918
- confirmations: 1
3919
- });
4396
+ const receipt = await withPaidRpcDiagnostics(
4397
+ () => this.getReceiptClient().waitForTransactionReceipt({
4398
+ hash: transactionHash,
4399
+ confirmations: 1
4400
+ }),
4401
+ 8453,
4402
+ "eth_getTransactionReceipt",
4403
+ this.id
4404
+ );
3920
4405
  if (receipt.status !== "success") {
3921
4406
  throw new OwneyError(
3922
4407
  "AGENT_TRANSACTION_REVERTED",
@@ -3927,12 +4412,12 @@ var YieldseekerAgent = class {
3927
4412
  }
3928
4413
  }
3929
4414
  assertTransaction(transaction, state, chainId) {
3930
- if (!transaction || typeof transaction.from !== "string" || !(0, import_viem6.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem6.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_viem6.getAddress)(transaction.from) !== (0, import_viem6.getAddress)(state.walletAddress)) {
4415
+ if (!transaction || typeof transaction.from !== "string" || !(0, import_viem7.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem7.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_viem7.getAddress)(transaction.from) !== (0, import_viem7.getAddress)(state.walletAddress)) {
3931
4416
  throw this.invalidResponse("transaction");
3932
4417
  }
3933
4418
  }
3934
4419
  assertAgent(agent) {
3935
- if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem6.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
4420
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem7.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
3936
4421
  throw this.invalidResponse("agent");
3937
4422
  }
3938
4423
  }
@@ -4085,7 +4570,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
4085
4570
  }
4086
4571
 
4087
4572
  // src/lib/helpers/withdraw-helper.ts
4088
- var import_viem7 = require("viem");
4573
+ var import_viem8 = require("viem");
4089
4574
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
4090
4575
  const target = asset.toUpperCase();
4091
4576
  return agents.map((agent) => {
@@ -4093,7 +4578,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
4093
4578
  const tokenBalance = agentBalance?.tokens.find(
4094
4579
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
4095
4580
  );
4096
- let balance = tokenBalance ? (0, import_viem7.parseUnits)(tokenBalance.amount, decimals) : 0n;
4581
+ let balance = tokenBalance ? (0, import_viem8.parseUnits)(tokenBalance.amount, decimals) : 0n;
4097
4582
  if (agent.balanceComposition === "tokens-plus-positions") {
4098
4583
  const chainNameById = {
4099
4584
  1: "ETHEREUM",
@@ -4105,16 +4590,16 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
4105
4590
  const positionChain = position2.chain.trim().toUpperCase();
4106
4591
  const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
4107
4592
  if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
4108
- if (position2.amountRaw !== void 0) {
4109
- try {
4110
- balance += BigInt(position2.amountRaw);
4111
- continue;
4112
- } catch {
4113
- }
4114
- }
4115
- balance += (0, import_viem7.parseUnits)(position2.amount, decimals);
4593
+ balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : (0, import_viem8.parseUnits)(position2.amount, decimals);
4116
4594
  }
4117
4595
  }
4596
+ const assetTotal = agentBalance?.assetBalances?.find(
4597
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
4598
+ );
4599
+ if (assetTotal) {
4600
+ const total = (0, import_viem8.parseUnits)(assetTotal.amount, decimals);
4601
+ if (total < balance) balance = total > 0n ? total : 0n;
4602
+ }
4118
4603
  return { agent, balance };
4119
4604
  });
4120
4605
  }
@@ -4271,15 +4756,50 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
4271
4756
  }
4272
4757
 
4273
4758
  // src/client.ts
4274
- var import_viem11 = require("viem");
4759
+ var import_viem12 = require("viem");
4275
4760
  var import_chains4 = require("viem/chains");
4276
4761
 
4277
4762
  // src/lib/sponsored-token-batch.ts
4278
- var import_viem9 = require("viem");
4763
+ var import_viem10 = require("viem");
4279
4764
 
4280
4765
  // src/lib/permit2-batch.ts
4281
- var import_viem8 = require("viem");
4766
+ var import_viem9 = require("viem");
4282
4767
  var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
4768
+ var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
4769
+ var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
4770
+ var ERC2612_PERMIT_TYPES = {
4771
+ Permit: [
4772
+ { name: "owner", type: "address" },
4773
+ { name: "spender", type: "address" },
4774
+ { name: "value", type: "uint256" },
4775
+ { name: "nonce", type: "uint256" },
4776
+ { name: "deadline", type: "uint256" }
4777
+ ]
4778
+ };
4779
+ var ERC2612_READ_ABI = (0, import_viem9.parseAbi)([
4780
+ "function name() view returns (string)",
4781
+ "function version() view returns (string)",
4782
+ "function nonces(address owner) view returns (uint256)"
4783
+ ]);
4784
+ function erc2612TypedData(input) {
4785
+ return {
4786
+ domain: {
4787
+ name: input.permit.tokenName,
4788
+ version: input.permit.tokenVersion,
4789
+ chainId: input.chainId,
4790
+ verifyingContract: input.token
4791
+ },
4792
+ types: ERC2612_PERMIT_TYPES,
4793
+ primaryType: "Permit",
4794
+ message: {
4795
+ owner: input.owner,
4796
+ spender: BATCH_PERMIT2_ADDRESS,
4797
+ value: BigInt(input.permit.value),
4798
+ nonce: BigInt(input.permit.nonce),
4799
+ deadline: BigInt(input.permit.deadline)
4800
+ }
4801
+ };
4802
+ }
4283
4803
  var PERMIT_BATCH_TYPES = {
4284
4804
  PermitBatchWitnessTransferFrom: [
4285
4805
  { name: "permitted", type: "TokenPermissions[]" },
@@ -4294,7 +4814,7 @@ var PERMIT_BATCH_TYPES = {
4294
4814
  { name: "amount", type: "uint256" }
4295
4815
  ]
4296
4816
  };
4297
- var PERMIT2_BATCH_ABI = (0, import_viem8.parseAbi)([
4817
+ var PERMIT2_BATCH_ABI = (0, import_viem9.parseAbi)([
4298
4818
  "struct TokenPermissions { address token; uint256 amount; }",
4299
4819
  "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
4300
4820
  "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
@@ -4353,7 +4873,7 @@ function clear(key2) {
4353
4873
  else window.localStorage.removeItem(key2);
4354
4874
  }
4355
4875
  function sponsorTokenBatch(i) {
4356
- const key2 = `owney.token-batch.v1:${(0, import_viem9.keccak256)((0, import_viem9.toBytes)(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
4876
+ const key2 = `owney.token-batch.v1:${(0, import_viem10.keccak256)((0, import_viem10.toBytes)(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
4357
4877
  const plan = planOf(i.transfers);
4358
4878
  const active = inflight.get(key2);
4359
4879
  if (active) {
@@ -4384,7 +4904,7 @@ async function execute(i, key2, plan) {
4384
4904
  baseUrl: i.baseUrl,
4385
4905
  body
4386
4906
  });
4387
- if (!prepared.serializedTransaction || (0, import_viem9.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
4907
+ if (!prepared.serializedTransaction || (0, import_viem10.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
4388
4908
  throw new Error(
4389
4909
  "Sponsorship API did not return a valid prepared transaction."
4390
4910
  );
@@ -4399,7 +4919,7 @@ async function execute(i, key2, plan) {
4399
4919
  baseUrl: i.baseUrl,
4400
4920
  body
4401
4921
  });
4402
- if (result.txHash !== (0, import_viem9.keccak256)(body.serializedTransaction))
4922
+ if (result.txHash !== (0, import_viem10.keccak256)(body.serializedTransaction))
4403
4923
  throw new Error(
4404
4924
  "Sponsorship receipt does not match the pending transaction."
4405
4925
  );
@@ -4414,7 +4934,7 @@ async function execute(i, key2, plan) {
4414
4934
  const saved = read(key2);
4415
4935
  if (saved) {
4416
4936
  const previous = JSON.parse(saved);
4417
- if (previous.chainId !== i.chainId || !(0, import_viem9.isAddressEqual)(previous.from, i.owner) || !(0, import_viem9.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
4937
+ if (previous.chainId !== i.chainId || !(0, import_viem10.isAddressEqual)(previous.from, i.owner) || !(0, import_viem10.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
4418
4938
  throw new Error(
4419
4939
  "Retry the previous token deposit and agent split first to reconcile its status."
4420
4940
  );
@@ -4431,28 +4951,67 @@ async function execute(i, key2, plan) {
4431
4951
  "DEPOSIT_INSUFFICIENT_BALANCE",
4432
4952
  "Insufficient token balance for this deposit."
4433
4953
  );
4434
- if (allowance < total)
4954
+ if (allowance < total && (i.chainId !== 8453 || !(0, import_viem10.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
4435
4955
  throw new OwneyError(
4436
4956
  "PERMIT2_APPROVAL_REQUIRED",
4437
4957
  "token deposits need a one-time Permit2 approval."
4438
4958
  );
4439
- const relayer = await getSponsorRelayerAddress({
4959
+ const now = (await i.pub.getBlock()).timestamp;
4960
+ let erc2612Permit;
4961
+ if (allowance < total) {
4962
+ const [tokenName, tokenVersion, permitNonce] = await Promise.all([
4963
+ i.pub.readContract({
4964
+ address: i.token,
4965
+ abi: ERC2612_READ_ABI,
4966
+ functionName: "name"
4967
+ }),
4968
+ i.pub.readContract({
4969
+ address: i.token,
4970
+ abi: ERC2612_READ_ABI,
4971
+ functionName: "version"
4972
+ }),
4973
+ i.pub.readContract({
4974
+ address: i.token,
4975
+ abi: ERC2612_READ_ABI,
4976
+ functionName: "nonces",
4977
+ args: [i.owner]
4978
+ })
4979
+ ]);
4980
+ const unsignedPermit = {
4981
+ value: MAX_UINT256.toString(),
4982
+ nonce: permitNonce.toString(),
4983
+ deadline: (now + 900n).toString(),
4984
+ tokenName,
4985
+ tokenVersion
4986
+ };
4987
+ const signature2 = await i.wallet.signTypedData({
4988
+ account: i.owner,
4989
+ ...erc2612TypedData({
4990
+ chainId: 8453,
4991
+ token: i.token,
4992
+ owner: i.owner,
4993
+ permit: unsignedPermit
4994
+ })
4995
+ });
4996
+ erc2612Permit = { ...unsignedPermit, signature: signature2 };
4997
+ }
4998
+ const spender = erc2612Permit ? MULTICALL3_ADDRESS : await getSponsorRelayerAddress({
4440
4999
  apiKey: i.apiKey,
4441
5000
  baseUrl: i.baseUrl,
4442
5001
  chainId: i.chainId
4443
5002
  });
4444
- const now = (await i.pub.getBlock()).timestamp;
4445
5003
  const unsigned = {
4446
5004
  chainId: i.chainId,
4447
5005
  token: i.token,
4448
5006
  from: i.owner,
4449
5007
  transfers: i.transfers,
4450
5008
  nonce: randomPermit2Nonce().toString(),
4451
- deadline: (now + 900n).toString()
5009
+ deadline: (now + 900n).toString(),
5010
+ ...erc2612Permit ? { erc2612Permit } : {}
4452
5011
  };
4453
5012
  const signature = await i.wallet.signTypedData({
4454
5013
  account: i.owner,
4455
- ...batchTypedData(unsigned, relayer)
5014
+ ...batchTypedData(unsigned, spender)
4456
5015
  });
4457
5016
  i.onApproved?.();
4458
5017
  return send({ ...unsigned, signature });
@@ -4472,8 +5031,8 @@ function makeSponsoredTokenCallback(deps) {
4472
5031
  "CHAIN_UNSUPPORTED",
4473
5032
  `No sponsored token configured for chain ${chainId}`
4474
5033
  );
4475
- const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
4476
- await ensureWalletOnChain(pub, wallet, chainId);
5034
+ const pub = deps.getPublicClient(chainId), walletChain = deps.getWalletChainClient?.(chainId) ?? pub, wallet = deps.getWalletClient(chainId);
5035
+ await ensureWalletOnChain(walletChain, wallet, chainId);
4477
5036
  return sponsorTokenBatch({
4478
5037
  apiKey: deps.apiKey,
4479
5038
  baseUrl: deps.baseUrl,
@@ -4569,7 +5128,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
4569
5128
  }
4570
5129
 
4571
5130
  // src/lib/sponsored-calls-deposit.ts
4572
- var import_viem10 = require("viem");
5131
+ var import_viem11 = require("viem");
4573
5132
  var DEFAULT_POLL_INTERVAL_MS = 1500;
4574
5133
  var DEFAULT_MAX_POLLS = 30;
4575
5134
  async function paymasterSupported(provider, owner, chainId) {
@@ -4577,7 +5136,7 @@ async function paymasterSupported(provider, owner, chainId) {
4577
5136
  method: "wallet_getCapabilities",
4578
5137
  params: [owner]
4579
5138
  });
4580
- const forChain = caps?.[(0, import_viem10.toHex)(chainId)] ?? caps?.[String(chainId)];
5139
+ const forChain = caps?.[(0, import_viem11.toHex)(chainId)] ?? caps?.[String(chainId)];
4581
5140
  return Boolean(forChain?.paymasterService?.supported);
4582
5141
  }
4583
5142
  function makeSponsoredCallsCallback(deps) {
@@ -4614,8 +5173,8 @@ function makeSponsoredCallsCallback(deps) {
4614
5173
  const calls = transfers.map((transfer) => ({
4615
5174
  to: token,
4616
5175
  value: "0x0",
4617
- data: (0, import_viem10.encodeFunctionData)({
4618
- abi: import_viem10.erc20Abi,
5176
+ data: (0, import_viem11.encodeFunctionData)({
5177
+ abi: import_viem11.erc20Abi,
4619
5178
  functionName: "transfer",
4620
5179
  args: [transfer.to, BigInt(transfer.amount)]
4621
5180
  })
@@ -4653,7 +5212,7 @@ function makeSponsoredCallsCallback(deps) {
4653
5212
  {
4654
5213
  version: "2.0.0",
4655
5214
  from: deps.ownerAddress,
4656
- chainId: (0, import_viem10.toHex)(chainId),
5215
+ chainId: (0, import_viem11.toHex)(chainId),
4657
5216
  atomicRequired: transfers.length > 1,
4658
5217
  calls,
4659
5218
  capabilities: {
@@ -4713,6 +5272,8 @@ function makeSponsoredCallsCallback(deps) {
4713
5272
  }
4714
5273
 
4715
5274
  // src/client.ts
5275
+ var PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS = 6;
5276
+ var PERMIT2_ALLOWANCE_VERIFY_DELAY_MS = 250;
4716
5277
  function encodeMultiAgentCursor(map) {
4717
5278
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
4718
5279
  }
@@ -4773,6 +5334,7 @@ var OwneySDK = class {
4773
5334
  // leave every user's agent profile alone.
4774
5335
  orgAgentConfig;
4775
5336
  orgAgentConfigPromise = null;
5337
+ rpcUrls;
4776
5338
  zyfaiRpcUrls;
4777
5339
  yieldseekerApiBaseUrl;
4778
5340
  yieldseekerSiweOrigin;
@@ -4797,7 +5359,12 @@ var OwneySDK = class {
4797
5359
  constructor(config) {
4798
5360
  this.apiKey = config.apiKey;
4799
5361
  if (config.debug) setOwneyDebug(true);
4800
- this.zyfaiRpcUrls = config.zyfaiRpcUrls;
5362
+ this.rpcUrls = resolveRpcUrls(
5363
+ config.rpcUrls,
5364
+ config.apiKey,
5365
+ config.routingApiBaseUrl
5366
+ );
5367
+ this.zyfaiRpcUrls = config.rpcUrls ? void 0 : config.zyfaiRpcUrls;
4801
5368
  this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
4802
5369
  this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
4803
5370
  this.routingApiBaseUrl = config.routingApiBaseUrl;
@@ -4887,6 +5454,9 @@ var OwneySDK = class {
4887
5454
  }
4888
5455
  return this.state.provider;
4889
5456
  }
5457
+ getPaidRpcClient(chainId) {
5458
+ return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
5459
+ }
4890
5460
  /** Builds the default USDC batch callback for the connected wallet. */
4891
5461
  getDefaultSponsoredCallback(onApproved) {
4892
5462
  if (!onApproved && this.cachedSponsoredCallback)
@@ -4902,14 +5472,15 @@ var OwneySDK = class {
4902
5472
  // Casts work around viem's chain-narrowed Client vs the generic
4903
5473
  // PublicClient/WalletClient param types — structurally identical at
4904
5474
  // runtime, but the two share a name TS treats as unrelated.
4905
- getPublicClient: (cid) => (0, import_viem11.createPublicClient)({
5475
+ getPublicClient: (cid) => this.getPaidRpcClient(cid),
5476
+ getWalletChainClient: (cid) => (0, import_viem12.createPublicClient)({
4906
5477
  chain: VIEM_CHAIN2[cid],
4907
- transport: (0, import_viem11.custom)(provider)
5478
+ transport: (0, import_viem12.custom)(provider)
4908
5479
  }),
4909
- getWalletClient: (cid) => (0, import_viem11.createWalletClient)({
5480
+ getWalletClient: (cid) => (0, import_viem12.createWalletClient)({
4910
5481
  account: owner,
4911
5482
  chain: VIEM_CHAIN2[cid],
4912
- transport: (0, import_viem11.custom)(provider)
5483
+ transport: (0, import_viem12.custom)(provider)
4913
5484
  })
4914
5485
  });
4915
5486
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -4952,14 +5523,15 @@ var OwneySDK = class {
4952
5523
  // Casts work around viem's chain-narrowed Client vs the generic
4953
5524
  // PublicClient/WalletClient param types — structurally identical at
4954
5525
  // runtime, but the two share a name TS treats as unrelated.
4955
- getPublicClient: (cid) => (0, import_viem11.createPublicClient)({
5526
+ getPublicClient: (cid) => this.getPaidRpcClient(cid),
5527
+ getWalletChainClient: (cid) => (0, import_viem12.createPublicClient)({
4956
5528
  chain: VIEM_CHAIN2[cid],
4957
- transport: (0, import_viem11.custom)(provider)
5529
+ transport: (0, import_viem12.custom)(provider)
4958
5530
  }),
4959
- getWalletClient: (cid) => (0, import_viem11.createWalletClient)({
5531
+ getWalletClient: (cid) => (0, import_viem12.createWalletClient)({
4960
5532
  account: owner,
4961
5533
  chain: VIEM_CHAIN2[cid],
4962
- transport: (0, import_viem11.custom)(provider)
5534
+ transport: (0, import_viem12.custom)(provider)
4963
5535
  })
4964
5536
  });
4965
5537
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -5065,12 +5637,21 @@ var OwneySDK = class {
5065
5637
  createAgent(agentId, key2) {
5066
5638
  if (agentId === "zyfai") {
5067
5639
  if (!key2) return null;
5068
- return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
5640
+ return new ZyfaiAgent(
5641
+ key2,
5642
+ this.zyfaiRpcUrls ? resolveRpcUrls(
5643
+ this.zyfaiRpcUrls,
5644
+ this.apiKey,
5645
+ this.routingApiBaseUrl
5646
+ ) : this.rpcUrls,
5647
+ this.referralSource
5648
+ );
5069
5649
  }
5070
5650
  if (agentId === "yieldseeker") {
5071
5651
  return new YieldseekerAgent(this.apiKey, {
5072
5652
  auth: { origin: this.yieldseekerSiweOrigin },
5073
- baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
5653
+ baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl),
5654
+ rpcUrls: this.rpcUrls
5074
5655
  });
5075
5656
  }
5076
5657
  return null;
@@ -5361,7 +5942,8 @@ var OwneySDK = class {
5361
5942
  );
5362
5943
  await this.approvePermit2(
5363
5944
  asset,
5364
- requiredAmount
5945
+ requiredAmount,
5946
+ cid
5365
5947
  );
5366
5948
  return batchTransfer(cid, transfers);
5367
5949
  }
@@ -5405,10 +5987,12 @@ var OwneySDK = class {
5405
5987
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
5406
5988
  * two independent auto-recovery mechanisms:
5407
5989
  *
5408
- * 1. Missing Permit2 allowance: when the app did not supply its own
5409
- * callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
5410
- * token deposit, this is the wallet's first Permit2 deposit for that token. We send
5411
- * the one-time (user-paid) Permit2 approval via `approvePermit2()` and
5990
+ * 1. Missing Permit2 allowance outside the atomic Base-USDC path: when the
5991
+ * app did not supply its own callback and the attempt fails with
5992
+ * `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
5993
+ * for that token. Base USDC bundles a gasless ERC-2612 approval inside
5994
+ * its sponsored deposit and never reaches this branch. Other tokens send
5995
+ * the one-time user-paid Permit2 approval via `approvePermit2()` and
5412
5996
  * retry the SAME sponsored attempt once. Bounded to one approval attempt
5413
5997
  * per call so a wallet/agent that keeps reporting the allowance as
5414
5998
  * missing can't loop forever. If `approvePermit2()` itself throws (e.g.
@@ -5451,7 +6035,8 @@ var OwneySDK = class {
5451
6035
  );
5452
6036
  await this.approvePermit2(
5453
6037
  asset,
5454
- BigInt(amount)
6038
+ BigInt(amount),
6039
+ chainId
5455
6040
  );
5456
6041
  continue;
5457
6042
  }
@@ -5598,6 +6183,32 @@ var OwneySDK = class {
5598
6183
  }
5599
6184
  return eligible;
5600
6185
  }
6186
+ /**
6187
+ * Run owner-approved withdrawals before relayer-only withdrawals. Wallet
6188
+ * approval is the only point at which the user can cancel the aggregate
6189
+ * operation, so no relayer leg should commit before it has completed.
6190
+ */
6191
+ orderAgentsForWithdrawal(agents) {
6192
+ return agents.map((agent, index) => ({ agent, index })).sort((left, right) => {
6193
+ const approvalOrder = Number(Boolean(right.agent.withdrawalRequiresWalletApproval)) - Number(Boolean(left.agent.withdrawalRequiresWalletApproval));
6194
+ return approvalOrder || left.index - right.index;
6195
+ }).map(({ agent }) => agent);
6196
+ }
6197
+ isUserRejectedWithdrawal(error) {
6198
+ let current = error;
6199
+ const seen = /* @__PURE__ */ new Set();
6200
+ while (current && typeof current === "object" && !seen.has(current)) {
6201
+ seen.add(current);
6202
+ const candidate = current;
6203
+ if (candidate.code === 4001 || candidate.code === "4001") return true;
6204
+ const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
6205
+ if (/user (?:rejected|denied)|rejected by user/i.test(message)) {
6206
+ return true;
6207
+ }
6208
+ current = candidate.cause;
6209
+ }
6210
+ return false;
6211
+ }
5601
6212
  // --- Fund operations ---
5602
6213
  /**
5603
6214
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -5609,7 +6220,14 @@ var OwneySDK = class {
5609
6220
  * @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
5610
6221
  */
5611
6222
  async withdraw(options) {
5612
- const { asset, amount, agentId } = options;
6223
+ const { asset, amount, agentId, onAgentResult } = options;
6224
+ const notifyAgentResult = (id, result) => {
6225
+ try {
6226
+ onAgentResult?.(id, result);
6227
+ } catch (error) {
6228
+ console.warn("Withdrawal result observer failed:", error);
6229
+ }
6230
+ };
5613
6231
  const state = this.requireState();
5614
6232
  const chainId = this.requireChainId();
5615
6233
  const token = asset;
@@ -5625,21 +6243,26 @@ var OwneySDK = class {
5625
6243
  if (agentId) {
5626
6244
  const agent = this.getAgent(agentId);
5627
6245
  this.validateAssetSupport(agent, chainId, asset);
5628
- return withFailureReporting(
6246
+ const result = await withFailureReporting(
5629
6247
  this.apiKey,
5630
6248
  agent.id,
5631
6249
  () => agent.withdraw(state, chainId, token, amount),
5632
6250
  this.routingApiBaseUrl
5633
6251
  );
6252
+ notifyAgentResult(agent.id, result);
6253
+ return result;
5634
6254
  }
5635
6255
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
6256
+ const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
5636
6257
  if (!amount) {
5637
6258
  const results2 = {};
5638
6259
  const agentErrors2 = {};
5639
- for (const agent of eligibleAgents) {
6260
+ for (const agent of withdrawalAgents) {
5640
6261
  try {
5641
6262
  results2[agent.id] = await agent.withdraw(state, chainId, token);
6263
+ notifyAgentResult(agent.id, results2[agent.id]);
5642
6264
  } catch (err) {
6265
+ if (this.isUserRejectedWithdrawal(err)) throw err;
5643
6266
  console.error(`withdraw failed for agent "${agent.id}":`, err);
5644
6267
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
5645
6268
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
@@ -5720,12 +6343,13 @@ var OwneySDK = class {
5720
6343
  planned: 0n
5721
6344
  }));
5722
6345
  const plans = [...disabledPlans, ...enabledPlans];
6346
+ const orderedPlans = this.orderAgentsForWithdrawal(plans.map((p) => p.agent)).map((agent) => plans.find((plan) => plan.agent === agent));
5723
6347
  const results = {};
5724
6348
  const agentErrors = {
5725
6349
  ...aggregated.agentErrors ?? {}
5726
6350
  };
5727
- for (let i = 0; i < plans.length; i++) {
5728
- const p = plans[i];
6351
+ for (let i = 0; i < orderedPlans.length; i++) {
6352
+ const p = orderedPlans[i];
5729
6353
  if (p.planned === 0n) continue;
5730
6354
  try {
5731
6355
  results[p.agent.id] = await p.agent.withdraw(
@@ -5734,7 +6358,9 @@ var OwneySDK = class {
5734
6358
  token,
5735
6359
  p.planned.toString()
5736
6360
  );
6361
+ notifyAgentResult(p.agent.id, results[p.agent.id]);
5737
6362
  } catch (err) {
6363
+ if (this.isUserRejectedWithdrawal(err)) throw err;
5738
6364
  console.error(`withdraw failed for agent "${p.agent.id}":`, err);
5739
6365
  agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
5740
6366
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
@@ -5746,7 +6372,7 @@ var OwneySDK = class {
5746
6372
  );
5747
6373
  const failedAmount = p.planned;
5748
6374
  p.planned = 0n;
5749
- redistributeShare(plans, i, failedAmount);
6375
+ redistributeShare(orderedPlans, i, failedAmount);
5750
6376
  }
5751
6377
  }
5752
6378
  if (Object.keys(results).length === 0) {
@@ -5856,23 +6482,11 @@ var OwneySDK = class {
5856
6482
  const agent = this.getAgent(agentId);
5857
6483
  return this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
5858
6484
  }
5859
- let totalEarnings = 0;
5860
- const results = {};
5861
6485
  const entries = [...this.getActiveAgents().entries()];
5862
- const earningsResults = await Promise.all(
5863
- entries.map(async ([id, agent]) => {
5864
- const e = await this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
5865
- return [id, e];
5866
- })
6486
+ return this.aggregateEarnings(
6487
+ entries,
6488
+ (agent) => this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId))
5867
6489
  );
5868
- for (const [id, e] of earningsResults) {
5869
- results[id] = e;
5870
- totalEarnings += Number(e.lifetimeEarnings);
5871
- }
5872
- return {
5873
- totalEarnings: String(totalEarnings),
5874
- agentEarnings: results
5875
- };
5876
6490
  }
5877
6491
  /**
5878
6492
  * Return refreshed earnings while letting each agent own its refresh policy.
@@ -5887,18 +6501,42 @@ var OwneySDK = class {
5887
6501
  () => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId)
5888
6502
  );
5889
6503
  if (agentId) return refreshAgent(this.getAgent(agentId));
5890
- let totalEarnings = 0;
6504
+ return this.aggregateEarnings([...this.getActiveAgents().entries()], refreshAgent);
6505
+ }
6506
+ async aggregateEarnings(entries, read2) {
6507
+ const settled = await Promise.allSettled(entries.map(([, agent]) => read2(agent)));
5891
6508
  const agentEarnings = {};
5892
- const refreshed = await Promise.all(
5893
- [...this.getActiveAgents().entries()].map(
5894
- async ([id, agent]) => [id, await refreshAgent(agent)]
5895
- )
5896
- );
5897
- for (const [id, earnings] of refreshed) {
5898
- agentEarnings[id] = earnings;
5899
- totalEarnings += Number(earnings.lifetimeEarnings);
6509
+ const agentErrors = {};
6510
+ const agentRetryAt = {};
6511
+ const failures = [];
6512
+ let totalEarnings = 0;
6513
+ for (let i = 0; i < settled.length; i++) {
6514
+ const [id] = entries[i];
6515
+ const result = settled[i];
6516
+ if (result.status === "fulfilled") {
6517
+ agentEarnings[id] = result.value;
6518
+ totalEarnings += Number(result.value.lifetimeEarnings);
6519
+ continue;
6520
+ }
6521
+ const reason = result.reason;
6522
+ failures.push(reason);
6523
+ const retryDelay = rateLimitDelay(reason);
6524
+ if (retryDelay !== void 0) agentRetryAt[id] = Date.now() + retryDelay;
6525
+ agentErrors[id] = reason instanceof Error ? reason.message : String(reason);
6526
+ console.error(`[owney-sdk] Earnings fetch failed for agent "${id}":`, reason);
6527
+ }
6528
+ if (entries.length > 0 && Object.keys(agentEarnings).length === 0) {
6529
+ throw new OwneyError(
6530
+ "EARNINGS_ALL_FAILED",
6531
+ "Failed to fetch earnings for all active agents.",
6532
+ { agentErrors, failures }
6533
+ );
5900
6534
  }
5901
- return { totalEarnings: String(totalEarnings), agentEarnings };
6535
+ return {
6536
+ totalEarnings: String(totalEarnings),
6537
+ agentEarnings,
6538
+ ...Object.keys(agentErrors).length > 0 ? { agentErrors, agentRetryAt } : {}
6539
+ };
5902
6540
  }
5903
6541
  /**
5904
6542
  * Get the weighted APY for the user's account over a time period.
@@ -6172,16 +6810,18 @@ var OwneySDK = class {
6172
6810
  }
6173
6811
  /**
6174
6812
  * User-paid approval of Permit2 on the selected token for the active chain.
6175
- * Approves exactly the pending deposit amount. Another approval is required
6176
- * for a later deposit once this allowance has been consumed. Resolves after
6177
- * one confirmation so the subsequent deposit attempt sees the new allowance.
6813
+ * Grants the maximum ERC20 allowance so later deposits do not require another
6814
+ * approval. Resolves after one confirmation so the subsequent deposit attempt
6815
+ * sees the new allowance. Deposit retries pass their captured chain id so a
6816
+ * concurrent activation cannot redirect the approval to another network.
6178
6817
  *
6179
6818
  * @param requiredAmount Raw base-unit amount the pending deposit must cover.
6819
+ * @param expectedChainId Chain captured by the deposit that requested approval.
6180
6820
  * @returns the approval transaction hash.
6181
6821
  */
6182
- async approvePermit2(asset = "WETH", requiredAmount = 0n) {
6822
+ async approvePermit2(asset = "WETH", requiredAmount = 0n, expectedChainId) {
6183
6823
  const state = this.requireState();
6184
- const chainId = this.requireChainId();
6824
+ const chainId = expectedChainId ?? this.requireChainId();
6185
6825
  this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
6186
6826
  const token = sponsoredTokensFor(asset)[chainId];
6187
6827
  if (!token) {
@@ -6191,16 +6831,18 @@ var OwneySDK = class {
6191
6831
  );
6192
6832
  }
6193
6833
  const provider = this.requireConnectedProvider();
6194
- const publicClient = (0, import_viem11.createPublicClient)({
6834
+ const walletChainClient = (0, import_viem12.createPublicClient)({
6195
6835
  chain: VIEM_CHAIN2[chainId],
6196
- transport: (0, import_viem11.custom)(provider)
6836
+ transport: (0, import_viem12.custom)(provider)
6197
6837
  });
6838
+ const publicClient = this.getPaidRpcClient(chainId);
6198
6839
  const approvalAmount = permit2ApprovalAmount(requiredAmount);
6199
- const wallet = (0, import_viem11.createWalletClient)({
6840
+ const wallet = (0, import_viem12.createWalletClient)({
6200
6841
  account: state.walletAddress,
6201
6842
  chain: VIEM_CHAIN2[chainId],
6202
- transport: (0, import_viem11.custom)(provider)
6843
+ transport: (0, import_viem12.custom)(provider)
6203
6844
  });
6845
+ await ensureWalletOnChain(walletChainClient, wallet, chainId);
6204
6846
  const hash = await wallet.writeContract({
6205
6847
  address: token,
6206
6848
  abi: ERC20_ALLOWANCE_ABI,
@@ -6209,14 +6851,53 @@ var OwneySDK = class {
6209
6851
  account: state.walletAddress,
6210
6852
  chain: VIEM_CHAIN2[chainId]
6211
6853
  });
6212
- const receipt = await publicClient.waitForTransactionReceipt({
6213
- hash,
6214
- confirmations: 1
6215
- });
6854
+ const receipt = await withPaidRpcDiagnostics(
6855
+ () => publicClient.waitForTransactionReceipt({
6856
+ hash,
6857
+ confirmations: 1
6858
+ }),
6859
+ chainId,
6860
+ "eth_getTransactionReceipt"
6861
+ );
6216
6862
  if (receipt.status !== "success") {
6217
6863
  throw new Error(`Permit2 approval reverted (tx ${hash})`);
6218
6864
  }
6219
- return hash;
6865
+ let observedAllowance = 0n;
6866
+ let verificationError;
6867
+ for (let attempt = 0; attempt < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS; attempt += 1) {
6868
+ try {
6869
+ observedAllowance = await readPermit2Allowance(
6870
+ publicClient,
6871
+ token,
6872
+ state.walletAddress,
6873
+ attempt === 0 ? receipt.blockNumber : void 0
6874
+ );
6875
+ verificationError = void 0;
6876
+ if (observedAllowance >= requiredAmount) return hash;
6877
+ } catch (error) {
6878
+ verificationError = error;
6879
+ }
6880
+ if (attempt + 1 < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS) {
6881
+ await new Promise(
6882
+ (resolve) => setTimeout(resolve, PERMIT2_ALLOWANCE_VERIFY_DELAY_MS)
6883
+ );
6884
+ }
6885
+ }
6886
+ throw new OwneyError(
6887
+ "PERMIT2_APPROVAL_REQUIRED",
6888
+ "Permit2 approval was confirmed, but the required token allowance was not observable.",
6889
+ {
6890
+ approvalConfirmed: true,
6891
+ approvalTxHash: hash,
6892
+ owner: state.walletAddress,
6893
+ token,
6894
+ spender: PERMIT2_ADDRESS,
6895
+ chainId,
6896
+ requiredAmount: requiredAmount.toString(),
6897
+ observedAllowance: observedAllowance.toString(),
6898
+ ...verificationError instanceof Error ? { verificationError: verificationError.message } : {}
6899
+ }
6900
+ );
6220
6901
  }
6221
6902
  // --- Discovery (no wallet required) ---
6222
6903
  /**
@@ -6321,7 +7002,7 @@ var OwneySDK = class {
6321
7002
  };
6322
7003
 
6323
7004
  // src/agents/zyfai/zyfai.siwx.ts
6324
- var import_viem12 = require("viem");
7005
+ var import_viem13 = require("viem");
6325
7006
  var import_siwe2 = require("siwe");
6326
7007
  var import_sdk2 = require("@zyfai/sdk");
6327
7008
 
@@ -6448,7 +7129,7 @@ function buildSIWXConfig(deps) {
6448
7129
  issuedAt,
6449
7130
  toString() {
6450
7131
  return new import_siwe2.SiweMessage({
6451
- address: (0, import_viem12.getAddress)(accountAddress),
7132
+ address: (0, import_viem13.getAddress)(accountAddress),
6452
7133
  chainId: numericChainId(chainId),
6453
7134
  domain,
6454
7135
  uri,