@owney/sdk 0.7.25-beta.4 → 0.7.25-beta.6

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,128 @@ 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
+ var DEFAULT_RPC_URLS = {
1137
+ 1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
1138
+ 8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
1139
+ 42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
1140
+ };
1141
+ function resolveRpcUrl(rpcUrls, chainId) {
1142
+ const url = rpcUrls?.[chainId]?.trim();
1143
+ if (url) return url;
1144
+ return DEFAULT_RPC_URLS[chainId];
1145
+ }
1146
+ function resolveRpcUrls(rpcUrls) {
1147
+ return {
1148
+ 1: resolveRpcUrl(rpcUrls, 1),
1149
+ 8453: resolveRpcUrl(rpcUrls, 8453),
1150
+ 42161: resolveRpcUrl(rpcUrls, 42161)
1151
+ };
1152
+ }
1153
+ function createPaidRpcClient(chain, rpcUrls) {
1154
+ const url = resolveRpcUrl(
1155
+ rpcUrls,
1156
+ chain.id
1157
+ );
1158
+ return (0, import_viem.createPublicClient)({
1159
+ chain,
1160
+ transport: (0, import_viem.http)(url, {
1161
+ retryCount: PAID_RPC_RETRY_COUNT,
1162
+ retryDelay: PAID_RPC_RETRY_DELAY_MS
1163
+ })
1164
+ });
1165
+ }
1166
+ function headerValue(error, name) {
1167
+ const seen = /* @__PURE__ */ new Set();
1168
+ let value;
1169
+ function visit(candidate, depth = 0) {
1170
+ if (value || depth > 6 || !candidate || typeof candidate !== "object")
1171
+ return;
1172
+ if (seen.has(candidate)) return;
1173
+ seen.add(candidate);
1174
+ const record = candidate;
1175
+ const headers = record.headers;
1176
+ const found = typeof headers?.get === "function" ? headers.get(name) : headers?.[name] ?? headers?.[name.toLowerCase()];
1177
+ if (typeof found === "string" && found) {
1178
+ value = found;
1179
+ return;
1180
+ }
1181
+ for (const key2 of ["cause", "details", "response", "error"])
1182
+ visit(record[key2], depth + 1);
1183
+ }
1184
+ visit(error);
1185
+ return value;
1186
+ }
1187
+ function statusCode(error) {
1188
+ const seen = /* @__PURE__ */ new Set();
1189
+ let status;
1190
+ function visit(candidate, depth = 0) {
1191
+ if (status || depth > 6 || !candidate || typeof candidate !== "object")
1192
+ return;
1193
+ if (seen.has(candidate)) return;
1194
+ seen.add(candidate);
1195
+ const record = candidate;
1196
+ for (const key2 of ["status", "statusCode"]) {
1197
+ const parsed = Number(record[key2]);
1198
+ if (Number.isInteger(parsed) && parsed >= 100 && parsed <= 599) {
1199
+ status = parsed;
1200
+ return;
1201
+ }
1202
+ }
1203
+ for (const key2 of ["cause", "details", "response", "error"])
1204
+ visit(record[key2], depth + 1);
1205
+ }
1206
+ visit(error);
1207
+ return status;
1208
+ }
1209
+ function paidRpcError(error, chainId, rpcMethod, agentId) {
1210
+ const delay = rateLimitDelay(error);
1211
+ const providerRequestId = headerValue(error, "x-alchemy-request-id") ?? headerValue(error, "x-request-id");
1212
+ if (delay === void 0) {
1213
+ return new OwneyError(
1214
+ "AGENT_API_ERROR",
1215
+ "The blockchain RPC request failed.",
1216
+ {
1217
+ rpcSource: "paid-rpc",
1218
+ chainId,
1219
+ rpcMethod,
1220
+ ...statusCode(error) ? { statusCode: statusCode(error) } : {},
1221
+ ...providerRequestId ? { providerRequestId } : {}
1222
+ },
1223
+ agentId
1224
+ );
1225
+ }
1226
+ const retryAt = Date.now() + delay;
1227
+ return new OwneyError(
1228
+ "AGENT_RATE_LIMITED",
1229
+ "The blockchain RPC is rate limited. Please wait before trying again.",
1230
+ {
1231
+ rpcSource: "paid-rpc",
1232
+ chainId,
1233
+ rpcMethod,
1234
+ statusCode: 429,
1235
+ retryAt,
1236
+ retryAfterSeconds: Math.max(0, Math.ceil(delay / 1e3)),
1237
+ ...providerRequestId ? { providerRequestId } : {}
1238
+ },
1239
+ agentId
1240
+ );
1241
+ }
1242
+ async function withPaidRpcDiagnostics(operation, chainId, rpcMethod, agentId) {
1243
+ try {
1244
+ return await operation();
1245
+ } catch (error) {
1246
+ throw paidRpcError(error, chainId, rpcMethod, agentId);
1247
+ }
1248
+ }
1249
+
1100
1250
  // src/agents/zyfai/zyfai.agent.ts
1101
- var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem.parseAbi)([
1251
+ var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem2.parseAbi)([
1102
1252
  "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
1103
1253
  ]);
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
1254
  var WETH_ADDRESS_BY_CHAIN = {
1110
1255
  8453: "0x4200000000000000000000000000000000000006",
1111
1256
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -1171,7 +1316,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1171
1316
  earningsSnapshot = null;
1172
1317
  earningsGeneration = 0;
1173
1318
  constructor(apiKey, rpcUrls, referralSource) {
1174
- this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
1319
+ this.rpcUrls = resolveRpcUrls(rpcUrls);
1175
1320
  this.sdk = new import_sdk.ZyfaiSDK({
1176
1321
  apiKey,
1177
1322
  rpcUrls: this.rpcUrls,
@@ -1185,10 +1330,10 @@ var ZyfaiAgent = class _ZyfaiAgent {
1185
1330
  getPublicClient(chainId) {
1186
1331
  const cached = this.publicClients.get(chainId);
1187
1332
  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
- });
1333
+ const client = createPaidRpcClient(
1334
+ VIEM_CHAIN[chainId],
1335
+ this.rpcUrls
1336
+ );
1192
1337
  this.publicClients.set(chainId, client);
1193
1338
  return client;
1194
1339
  }
@@ -2156,7 +2301,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2156
2301
  };
2157
2302
 
2158
2303
  // src/agents/yieldseeker/yieldseeker.agent.ts
2159
- var import_viem6 = require("viem");
2304
+ var import_viem7 = require("viem");
2160
2305
  var import_chains3 = require("viem/chains");
2161
2306
 
2162
2307
  // src/lib/chain-guard.ts
@@ -2195,7 +2340,7 @@ async function ensureWalletOnChain(pub, wallet, expected) {
2195
2340
  }
2196
2341
 
2197
2342
  // src/lib/transfer-auth.ts
2198
- var import_viem2 = require("viem");
2343
+ var import_viem3 = require("viem");
2199
2344
 
2200
2345
  // src/lib/sponsor-client.ts
2201
2346
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
@@ -2311,7 +2456,7 @@ async function postSponsorBatchTransfer(input) {
2311
2456
  }
2312
2457
 
2313
2458
  // src/lib/permit2.ts
2314
- var import_viem3 = require("viem");
2459
+ var import_viem4 = require("viem");
2315
2460
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2316
2461
  var MAX_UINT256 = 2n ** 256n - 1n;
2317
2462
  function permit2ApprovalAmount(requiredAmount) {
@@ -2352,7 +2497,7 @@ var ERC20_ALLOWANCE_ABI = [
2352
2497
  function randomPermit2Nonce() {
2353
2498
  const bytes = new Uint8Array(32);
2354
2499
  globalThis.crypto.getRandomValues(bytes);
2355
- return BigInt((0, import_viem3.bytesToHex)(bytes));
2500
+ return BigInt((0, import_viem4.bytesToHex)(bytes));
2356
2501
  }
2357
2502
  async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
2358
2503
  return publicClient.readContract({
@@ -2395,7 +2540,7 @@ function makeVerificationAwareDepositCallback(implementation) {
2395
2540
 
2396
2541
  // src/agents/yieldseeker/yieldseeker.auth.ts
2397
2542
  var import_siwe = require("siwe");
2398
- var import_viem4 = require("viem");
2543
+ var import_viem5 = require("viem");
2399
2544
  var import_chains2 = require("viem/chains");
2400
2545
 
2401
2546
  // src/agents/yieldseeker/yieldseeker.auth-cache.ts
@@ -2508,7 +2653,7 @@ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
2508
2653
  return new import_siwe.SiweMessage({
2509
2654
  scheme: url.protocol.slice(0, -1),
2510
2655
  domain: url.host,
2511
- address: (0, import_viem4.getAddress)(address),
2656
+ address: (0, import_viem5.getAddress)(address),
2512
2657
  uri: url.origin,
2513
2658
  version: "1",
2514
2659
  chainId,
@@ -2594,15 +2739,15 @@ var YieldseekerAuth = class {
2594
2739
  clearYieldseekerSession(state.walletAddress, chainId);
2595
2740
  }
2596
2741
  async sign(state, chainId) {
2597
- const account = (0, import_viem4.getAddress)(state.walletAddress);
2598
- const publicClient = (0, import_viem4.createPublicClient)({
2742
+ const account = (0, import_viem5.getAddress)(state.walletAddress);
2743
+ const publicClient = (0, import_viem5.createPublicClient)({
2599
2744
  chain: import_chains2.base,
2600
- transport: (0, import_viem4.custom)(state.provider)
2745
+ transport: (0, import_viem5.custom)(state.provider)
2601
2746
  });
2602
- const walletClient = (0, import_viem4.createWalletClient)({
2747
+ const walletClient = (0, import_viem5.createWalletClient)({
2603
2748
  account,
2604
2749
  chain: import_chains2.base,
2605
- transport: (0, import_viem4.custom)(state.provider)
2750
+ transport: (0, import_viem5.custom)(state.provider)
2606
2751
  });
2607
2752
  await ensureWalletOnChain(
2608
2753
  publicClient,
@@ -2768,7 +2913,7 @@ var YieldseekerApiClient = class {
2768
2913
  };
2769
2914
 
2770
2915
  // src/agents/yieldseeker/yieldseeker.mapper.ts
2771
- var import_viem5 = require("viem");
2916
+ var import_viem6 = require("viem");
2772
2917
 
2773
2918
  // src/lib/helpers/snapshot-apy.ts
2774
2919
  var DAY_MS = 864e5;
@@ -2835,10 +2980,10 @@ function raw(value, endpoint) {
2835
2980
  return BigInt(value);
2836
2981
  }
2837
2982
  function decimal(value, decimals, endpoint) {
2838
- return (0, import_viem5.formatUnits)(raw(value, endpoint), decimals);
2983
+ return (0, import_viem6.formatUnits)(raw(value, endpoint), decimals);
2839
2984
  }
2840
2985
  function usd(rawAmount, decimals, price) {
2841
- return Number((0, import_viem5.formatUnits)(rawAmount, decimals)) * price;
2986
+ return Number((0, import_viem6.formatUnits)(rawAmount, decimals)) * price;
2842
2987
  }
2843
2988
  function percent(value) {
2844
2989
  const result = Number(value);
@@ -2864,7 +3009,7 @@ function assetAddressValue(record, address) {
2864
3009
  }
2865
3010
  function position(value, asset, baseAssetDecimals) {
2866
3011
  const option = value?.yieldOption;
2867
- if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem5.isAddress)(option.address)) {
3012
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem6.isAddress)(option.address)) {
2868
3013
  return invalid("yield positions", "missing vault metadata");
2869
3014
  }
2870
3015
  return {
@@ -2945,7 +3090,7 @@ function mapYieldseekerEarnings(contexts) {
2945
3090
  chain: "BASE",
2946
3091
  chainId: 8453,
2947
3092
  asset: context.asset,
2948
- amount: (0, import_viem5.formatUnits)(amount, context.snapshot.baseAssetDecimals)
3093
+ amount: (0, import_viem6.formatUnits)(amount, context.snapshot.baseAssetDecimals)
2949
3094
  });
2950
3095
  lifetimeEarnings += usd(
2951
3096
  amount,
@@ -3212,6 +3357,9 @@ var YIELDSEEKER_USERNAME_PREFIX = "owney_";
3212
3357
  var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
3213
3358
  var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
3214
3359
  var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
3360
+ var YIELDSEEKER_AGENT_LIST_CACHE_MS = 6e4;
3361
+ var YIELDSEEKER_PORTFOLIO_CACHE_MS = 3e4;
3362
+ var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
3215
3363
  function generateYieldseekerUsername() {
3216
3364
  const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
3217
3365
  return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
@@ -3255,6 +3403,7 @@ function query(params) {
3255
3403
  var YieldseekerAgent = class {
3256
3404
  id = "yieldseeker";
3257
3405
  balanceComposition = "tokens-plus-positions";
3406
+ withdrawalRequiresWalletApproval = true;
3258
3407
  supportedChainIds = [8453];
3259
3408
  supportedAssets = [
3260
3409
  {
@@ -3268,11 +3417,17 @@ var YieldseekerAgent = class {
3268
3417
  ];
3269
3418
  api;
3270
3419
  auth;
3420
+ rpcUrls;
3421
+ receiptClient;
3271
3422
  transactionExecutor;
3272
3423
  unwindReceiptWaiter;
3273
3424
  agentContexts = /* @__PURE__ */ new Map();
3274
3425
  users = /* @__PURE__ */ new Map();
3275
3426
  pendingAgents = /* @__PURE__ */ new Map();
3427
+ pendingWalletContexts = /* @__PURE__ */ new Map();
3428
+ readCache = /* @__PURE__ */ new Map();
3429
+ pendingReads = /* @__PURE__ */ new Map();
3430
+ readGeneration = 0;
3276
3431
  yieldOptions = /* @__PURE__ */ new Map();
3277
3432
  pendingYieldOptions = /* @__PURE__ */ new Map();
3278
3433
  constructor(owneyApiKey, options = {}) {
@@ -3282,10 +3437,16 @@ var YieldseekerAgent = class {
3282
3437
  options.fetchFn
3283
3438
  );
3284
3439
  this.auth = new YieldseekerAuth(options.auth);
3440
+ this.rpcUrls = options.rpcUrls;
3285
3441
  this.transactionExecutor = options.transactionExecutor;
3286
3442
  this.unwindReceiptWaiter = options.unwindReceiptWaiter;
3287
3443
  }
3444
+ getReceiptClient() {
3445
+ this.receiptClient ??= createPaidRpcClient(import_chains3.base, this.rpcUrls);
3446
+ return this.receiptClient;
3447
+ }
3288
3448
  async disconnect() {
3449
+ this.readGeneration += 1;
3289
3450
  this.auth.clear();
3290
3451
  for (const key2 of this.users.keys()) {
3291
3452
  const [walletAddress, chainId] = key2.split(":");
@@ -3294,6 +3455,9 @@ var YieldseekerAgent = class {
3294
3455
  this.users.clear();
3295
3456
  this.agentContexts.clear();
3296
3457
  this.pendingAgents.clear();
3458
+ this.pendingWalletContexts.clear();
3459
+ this.readCache.clear();
3460
+ this.pendingReads.clear();
3297
3461
  }
3298
3462
  async activateAgent(state, chainId, asset) {
3299
3463
  this.assertChain(chainId);
@@ -3330,12 +3494,12 @@ var YieldseekerAgent = class {
3330
3494
  await this.waitForReceipt(state, chainId, txHash);
3331
3495
  } else {
3332
3496
  txHash = await this.submitTransaction(state, chainId, {
3333
- from: (0, import_viem6.getAddress)(state.walletAddress),
3497
+ from: (0, import_viem7.getAddress)(state.walletAddress),
3334
3498
  to: YIELDSEEKER_ASSET_METADATA[asset].address,
3335
- data: (0, import_viem6.encodeFunctionData)({
3336
- abi: import_viem6.erc20Abi,
3499
+ data: (0, import_viem7.encodeFunctionData)({
3500
+ abi: import_viem7.erc20Abi,
3337
3501
  functionName: "transfer",
3338
- args: [(0, import_viem6.getAddress)(context.wallet.walletAddress), BigInt(amount)]
3502
+ args: [(0, import_viem7.getAddress)(context.wallet.walletAddress), BigInt(amount)]
3339
3503
  }),
3340
3504
  value: "0",
3341
3505
  chainId
@@ -3435,15 +3599,15 @@ var YieldseekerAgent = class {
3435
3599
  remaining: remaining.toString()
3436
3600
  });
3437
3601
  }
3438
- const account = (0, import_viem6.getAddress)(state.walletAddress);
3602
+ const account = (0, import_viem7.getAddress)(state.walletAddress);
3439
3603
  const txHash = await this.submitTransaction(state, chainId, {
3440
3604
  from: account,
3441
- to: (0, import_viem6.getAddress)(context.wallet.walletAddress),
3442
- data: amount === void 0 ? (0, import_viem6.encodeFunctionData)({
3605
+ to: (0, import_viem7.getAddress)(context.wallet.walletAddress),
3606
+ data: amount === void 0 ? (0, import_viem7.encodeFunctionData)({
3443
3607
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
3444
3608
  functionName: "withdrawAllAssetToUser",
3445
3609
  args: [account, metadata.address]
3446
- }) : (0, import_viem6.encodeFunctionData)({
3610
+ }) : (0, import_viem7.encodeFunctionData)({
3447
3611
  abi: YIELDSEEKER_AGENT_WALLET_ABI,
3448
3612
  functionName: "withdrawAssetToUser",
3449
3613
  args: [account, metadata.address, requested]
@@ -3499,7 +3663,7 @@ var YieldseekerAgent = class {
3499
3663
  );
3500
3664
  const vaultAddresses = new Set(
3501
3665
  catalog.flat().filter(
3502
- (yieldOption) => yieldOption.chainId === chainId && (0, import_viem6.isAddress)(yieldOption.address)
3666
+ (yieldOption) => yieldOption.chainId === chainId && (0, import_viem7.isAddress)(yieldOption.address)
3503
3667
  ).map((yieldOption) => yieldOption.address.toLowerCase())
3504
3668
  );
3505
3669
  return mapYieldseekerHistory(contexts, {
@@ -3556,6 +3720,86 @@ var YieldseekerAgent = class {
3556
3720
  contextKey(state, chainId, asset) {
3557
3721
  return `${this.userKey(state, chainId)}:${asset}`;
3558
3722
  }
3723
+ cachedRead(key2, ttlMs, read2) {
3724
+ const cached = this.readCache.get(key2);
3725
+ if (cached && cached.expiresAt > Date.now()) {
3726
+ return Promise.resolve(cached.value);
3727
+ }
3728
+ const pending = this.pendingReads.get(key2);
3729
+ if (pending) return pending;
3730
+ const generation = this.readGeneration;
3731
+ const request = Promise.resolve().then(read2).then((value) => {
3732
+ if (this.readGeneration === generation) {
3733
+ this.readCache.set(key2, {
3734
+ expiresAt: Date.now() + ttlMs,
3735
+ value
3736
+ });
3737
+ }
3738
+ return value;
3739
+ }).finally(() => {
3740
+ if (this.pendingReads.get(key2) === request) {
3741
+ this.pendingReads.delete(key2);
3742
+ }
3743
+ });
3744
+ this.pendingReads.set(key2, request);
3745
+ return request;
3746
+ }
3747
+ agentListKey(state, chainId) {
3748
+ return `agents:${this.userKey(state, chainId)}`;
3749
+ }
3750
+ async listAgents(state, chainId, user) {
3751
+ const response = await this.cachedRead(
3752
+ this.agentListKey(state, chainId),
3753
+ YIELDSEEKER_AGENT_LIST_CACHE_MS,
3754
+ async () => {
3755
+ const response2 = await this.walletRequest(
3756
+ state,
3757
+ chainId,
3758
+ `/users/${user.userId}/agents`
3759
+ );
3760
+ if (!Array.isArray(response2?.agents)) {
3761
+ throw this.invalidResponse("agent list");
3762
+ }
3763
+ return response2;
3764
+ }
3765
+ );
3766
+ return response.agents;
3767
+ }
3768
+ async contextForAgent(state, chainId, user, agent, asset) {
3769
+ const key2 = this.contextKey(state, chainId, asset);
3770
+ const cached = this.agentContexts.get(key2);
3771
+ if (cached?.agent.agentId === agent.agentId) return cached;
3772
+ const pending = this.pendingWalletContexts.get(key2);
3773
+ if (pending) return pending;
3774
+ const generation = this.readGeneration;
3775
+ const request = this.walletRequest(
3776
+ state,
3777
+ chainId,
3778
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3779
+ ).then((walletResponse) => {
3780
+ if (!walletResponse?.agentWallet || !(0, import_viem7.isAddress)(walletResponse.agentWallet.walletAddress)) {
3781
+ throw this.invalidResponse("agent wallet");
3782
+ }
3783
+ const context = {
3784
+ user,
3785
+ agent,
3786
+ wallet: walletResponse.agentWallet,
3787
+ asset
3788
+ };
3789
+ if (this.readGeneration === generation) {
3790
+ this.agentContexts.set(key2, context);
3791
+ }
3792
+ return context;
3793
+ });
3794
+ this.pendingWalletContexts.set(key2, request);
3795
+ try {
3796
+ return await request;
3797
+ } finally {
3798
+ if (this.pendingWalletContexts.get(key2) === request) {
3799
+ this.pendingWalletContexts.delete(key2);
3800
+ }
3801
+ }
3802
+ }
3559
3803
  async resolveUser(state, chainId) {
3560
3804
  const key2 = this.userKey(state, chainId);
3561
3805
  const inMemory = this.users.get(key2);
@@ -3565,7 +3809,7 @@ var YieldseekerAgent = class {
3565
3809
  this.users.set(key2, persisted);
3566
3810
  return persisted;
3567
3811
  }
3568
- const walletAddress = (0, import_viem6.getAddress)(state.walletAddress);
3812
+ const walletAddress = (0, import_viem7.getAddress)(state.walletAddress);
3569
3813
  let user = null;
3570
3814
  try {
3571
3815
  const login = await this.providerRequest(
@@ -3652,16 +3896,9 @@ var YieldseekerAgent = class {
3652
3896
  }
3653
3897
  async resolveAgent(state, chainId, asset, createIfMissing) {
3654
3898
  const user = await this.resolveUser(state, chainId);
3655
- const response = await this.walletRequest(
3656
- state,
3657
- chainId,
3658
- `/users/${user.userId}/agents`
3659
- );
3660
- if (!Array.isArray(response?.agents)) {
3661
- throw this.invalidResponse("agent list");
3662
- }
3899
+ const agents = await this.listAgents(state, chainId, user);
3663
3900
  const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3664
- let agent = response.agents.find(
3901
+ let agent = agents.find(
3665
3902
  (candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
3666
3903
  );
3667
3904
  if (!agent && createIfMissing) {
@@ -3682,83 +3919,91 @@ var YieldseekerAgent = class {
3682
3919
  }
3683
3920
  );
3684
3921
  agent = created?.agent;
3922
+ if (agent) {
3923
+ this.readCache.set(this.agentListKey(state, chainId), {
3924
+ expiresAt: Date.now() + YIELDSEEKER_AGENT_LIST_CACHE_MS,
3925
+ value: {
3926
+ agents: [...agents, agent]
3927
+ }
3928
+ });
3929
+ }
3685
3930
  }
3686
3931
  if (!agent) return null;
3687
3932
  this.assertAgent(agent);
3688
- const walletResponse = await this.walletRequest(
3689
- state,
3690
- chainId,
3691
- `/users/${user.userId}/agents/${agent.agentId}/wallet`
3692
- );
3693
- if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
3694
- throw this.invalidResponse("agent wallet");
3695
- }
3696
- return { user, agent, wallet: walletResponse.agentWallet, asset };
3933
+ return this.contextForAgent(state, chainId, user, agent, asset);
3697
3934
  }
3698
3935
  async loadPortfolio(state, chainId, options) {
3699
3936
  const user = await this.resolveUser(state, chainId);
3700
- const response = await this.walletRequest(
3701
- state,
3702
- chainId,
3703
- `/users/${user.userId}/agents`
3704
- );
3705
- if (!Array.isArray(response?.agents)) {
3706
- throw this.invalidResponse("agent list");
3707
- }
3937
+ const agents = await this.listAgents(state, chainId, user);
3708
3938
  const contexts = [];
3709
- for (const agent of response.agents) {
3939
+ for (const agent of agents) {
3710
3940
  const asset = this.assetForAgent(agent);
3711
3941
  if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
3712
3942
  continue;
3713
3943
  }
3714
3944
  this.assertAgent(agent);
3715
- const walletResponse = await this.walletRequest(
3716
- state,
3717
- chainId,
3718
- `/users/${user.userId}/agents/${agent.agentId}/wallet`
3719
- );
3720
- if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
3721
- throw this.invalidResponse("agent wallet");
3722
- }
3723
- const context = {
3724
- user,
3725
- agent,
3726
- wallet: walletResponse.agentWallet,
3727
- asset
3728
- };
3729
- this.agentContexts.set(this.contextKey(state, chainId, asset), context);
3730
- contexts.push(context);
3945
+ contexts.push(this.contextForAgent(state, chainId, user, agent, asset));
3731
3946
  }
3947
+ const resolvedContexts = await Promise.all(contexts);
3732
3948
  return Promise.all(
3733
- contexts.map(
3949
+ resolvedContexts.map(
3734
3950
  (context) => this.loadPortfolioContext(state, chainId, context, options)
3735
3951
  )
3736
3952
  );
3737
3953
  }
3738
3954
  async loadPortfolioContext(state, chainId, context, options = {}) {
3955
+ const contextKey = this.contextKey(state, chainId, context.asset);
3739
3956
  const [snapshot, positions, historic, actions] = await Promise.all([
3740
- this.walletRequest(
3741
- state,
3742
- chainId,
3743
- `${this.agentPath(context, "snapshot")}${query({
3744
- shouldOnlyUseRecentValue: true,
3745
- shouldAllowStaleOnError: true
3746
- })}`
3957
+ this.cachedRead(
3958
+ `snapshot:${contextKey}`,
3959
+ YIELDSEEKER_PORTFOLIO_CACHE_MS,
3960
+ async () => {
3961
+ const response = await this.walletRequest(
3962
+ state,
3963
+ chainId,
3964
+ `${this.agentPath(context, "snapshot")}${query({
3965
+ shouldOnlyUseRecentValue: true,
3966
+ shouldAllowStaleOnError: true
3967
+ })}`
3968
+ );
3969
+ if (!response?.agentSnapshot) {
3970
+ throw this.invalidResponse("agent snapshot");
3971
+ }
3972
+ return response;
3973
+ }
3747
3974
  ),
3748
- this.walletRequest(
3749
- state,
3750
- chainId,
3751
- this.agentPath(context, "yield-positions")
3975
+ this.cachedRead(
3976
+ `positions:${contextKey}`,
3977
+ YIELDSEEKER_PORTFOLIO_CACHE_MS,
3978
+ async () => {
3979
+ const response = await this.walletRequest(
3980
+ state,
3981
+ chainId,
3982
+ this.agentPath(context, "yield-positions")
3983
+ );
3984
+ if (!Array.isArray(response?.yieldPositions)) {
3985
+ throw this.invalidResponse("yield positions");
3986
+ }
3987
+ return response;
3988
+ }
3752
3989
  ),
3753
- options.historic ? this.walletRequest(
3754
- state,
3755
- chainId,
3756
- this.agentPath(context, "wallet/historic-position")
3990
+ options.historic ? this.cachedRead(
3991
+ `historic:${contextKey}`,
3992
+ YIELDSEEKER_ACTIVITY_CACHE_MS,
3993
+ () => this.walletRequest(
3994
+ state,
3995
+ chainId,
3996
+ this.agentPath(context, "wallet/historic-position")
3997
+ )
3757
3998
  ) : Promise.resolve(void 0),
3758
- options.actions ? this.walletRequest(
3759
- state,
3760
- chainId,
3761
- this.agentPath(context, "actions")
3999
+ options.actions ? this.cachedRead(
4000
+ `actions:${contextKey}`,
4001
+ YIELDSEEKER_ACTIVITY_CACHE_MS,
4002
+ () => this.walletRequest(
4003
+ state,
4004
+ chainId,
4005
+ this.agentPath(context, "actions")
4006
+ )
3762
4007
  ) : Promise.resolve(void 0)
3763
4008
  ]);
3764
4009
  if (!snapshot?.agentSnapshot) {
@@ -3784,7 +4029,7 @@ var YieldseekerAgent = class {
3784
4029
  this.agentPath(context, "deploy"),
3785
4030
  { method: "POST", body: {} }
3786
4031
  );
3787
- if (!deployed?.agentWallet || !(0, import_viem6.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
4032
+ if (!deployed?.agentWallet || !(0, import_viem7.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
3788
4033
  throw this.invalidResponse("agent deployment", {
3789
4034
  reason: "Deploy did not return the expected Agent Wallet."
3790
4035
  });
@@ -3792,6 +4037,13 @@ var YieldseekerAgent = class {
3792
4037
  context.wallet = deployed.agentWallet;
3793
4038
  }
3794
4039
  async refreshSnapshotAfterMovement(state, chainId, context, movement) {
4040
+ const contextKey = this.contextKey(state, chainId, context.asset);
4041
+ const invalidatePortfolio = () => {
4042
+ for (const kind of ["snapshot", "positions", "historic", "actions"]) {
4043
+ this.readCache.delete(`${kind}:${contextKey}`);
4044
+ }
4045
+ };
4046
+ invalidatePortfolio();
3795
4047
  try {
3796
4048
  const response = await this.walletRequest(
3797
4049
  state,
@@ -3808,6 +4060,8 @@ var YieldseekerAgent = class {
3808
4060
  `[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
3809
4061
  error
3810
4062
  );
4063
+ } finally {
4064
+ invalidatePortfolio();
3811
4065
  }
3812
4066
  }
3813
4067
  agentPath(context, suffix) {
@@ -3858,6 +4112,7 @@ var YieldseekerAgent = class {
3858
4112
  code,
3859
4113
  `Yieldseeker request failed: ${error.providerCode}.`,
3860
4114
  {
4115
+ rpcSource: "agent-api",
3861
4116
  statusCode: error.status,
3862
4117
  providerCode: error.providerCode,
3863
4118
  ...error.responseFields ? { fields: error.responseFields } : {}
@@ -3870,32 +4125,37 @@ var YieldseekerAgent = class {
3870
4125
  return this.transactionExecutor(state, chainId, transaction);
3871
4126
  }
3872
4127
  this.assertTransaction(transaction, state, chainId);
3873
- const account = (0, import_viem6.getAddress)(state.walletAddress);
3874
- const walletClient = (0, import_viem6.createWalletClient)({
4128
+ const account = (0, import_viem7.getAddress)(state.walletAddress);
4129
+ const walletClient = (0, import_viem7.createWalletClient)({
3875
4130
  account,
3876
4131
  chain: import_chains3.base,
3877
- transport: (0, import_viem6.custom)(state.provider)
4132
+ transport: (0, import_viem7.custom)(state.provider)
3878
4133
  });
3879
- const publicClient = (0, import_viem6.createPublicClient)({
4134
+ const walletChainClient = (0, import_viem7.createPublicClient)({
3880
4135
  chain: import_chains3.base,
3881
- transport: (0, import_viem6.custom)(state.provider)
4136
+ transport: (0, import_viem7.custom)(state.provider)
3882
4137
  });
3883
4138
  await ensureWalletOnChain(
3884
- publicClient,
4139
+ walletChainClient,
3885
4140
  walletClient,
3886
4141
  8453
3887
4142
  );
3888
4143
  const hash = await walletClient.sendTransaction({
3889
4144
  account,
3890
4145
  chain: import_chains3.base,
3891
- to: (0, import_viem6.getAddress)(transaction.to),
4146
+ to: (0, import_viem7.getAddress)(transaction.to),
3892
4147
  data: transaction.data,
3893
4148
  value: BigInt(transaction.value)
3894
4149
  });
3895
- const receipt = await publicClient.waitForTransactionReceipt({
3896
- hash,
3897
- confirmations: 1
3898
- });
4150
+ const receipt = await withPaidRpcDiagnostics(
4151
+ () => this.getReceiptClient().waitForTransactionReceipt({
4152
+ hash,
4153
+ confirmations: 1
4154
+ }),
4155
+ 8453,
4156
+ "eth_getTransactionReceipt",
4157
+ this.id
4158
+ );
3899
4159
  if (receipt.status !== "success") {
3900
4160
  throw new OwneyError(
3901
4161
  "AGENT_TRANSACTION_REVERTED",
@@ -3911,14 +4171,15 @@ var YieldseekerAgent = class {
3911
4171
  await this.unwindReceiptWaiter(state, chainId, transactionHash);
3912
4172
  return;
3913
4173
  }
3914
- const publicClient = (0, import_viem6.createPublicClient)({
3915
- chain: import_chains3.base,
3916
- transport: (0, import_viem6.custom)(state.provider)
3917
- });
3918
- const receipt = await publicClient.waitForTransactionReceipt({
3919
- hash: transactionHash,
3920
- confirmations: 1
3921
- });
4174
+ const receipt = await withPaidRpcDiagnostics(
4175
+ () => this.getReceiptClient().waitForTransactionReceipt({
4176
+ hash: transactionHash,
4177
+ confirmations: 1
4178
+ }),
4179
+ 8453,
4180
+ "eth_getTransactionReceipt",
4181
+ this.id
4182
+ );
3922
4183
  if (receipt.status !== "success") {
3923
4184
  throw new OwneyError(
3924
4185
  "AGENT_TRANSACTION_REVERTED",
@@ -3929,12 +4190,12 @@ var YieldseekerAgent = class {
3929
4190
  }
3930
4191
  }
3931
4192
  assertTransaction(transaction, state, chainId) {
3932
- 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)) {
4193
+ 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)) {
3933
4194
  throw this.invalidResponse("transaction");
3934
4195
  }
3935
4196
  }
3936
4197
  assertAgent(agent) {
3937
- if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem6.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
4198
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem7.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
3938
4199
  throw this.invalidResponse("agent");
3939
4200
  }
3940
4201
  }
@@ -4087,7 +4348,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
4087
4348
  }
4088
4349
 
4089
4350
  // src/lib/helpers/withdraw-helper.ts
4090
- var import_viem7 = require("viem");
4351
+ var import_viem8 = require("viem");
4091
4352
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
4092
4353
  const target = asset.toUpperCase();
4093
4354
  return agents.map((agent) => {
@@ -4095,7 +4356,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
4095
4356
  const tokenBalance = agentBalance?.tokens.find(
4096
4357
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
4097
4358
  );
4098
- let balance = tokenBalance ? (0, import_viem7.parseUnits)(tokenBalance.amount, decimals) : 0n;
4359
+ let balance = tokenBalance ? (0, import_viem8.parseUnits)(tokenBalance.amount, decimals) : 0n;
4099
4360
  if (agent.balanceComposition === "tokens-plus-positions") {
4100
4361
  const chainNameById = {
4101
4362
  1: "ETHEREUM",
@@ -4114,7 +4375,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
4114
4375
  } catch {
4115
4376
  }
4116
4377
  }
4117
- balance += (0, import_viem7.parseUnits)(position2.amount, decimals);
4378
+ balance += (0, import_viem8.parseUnits)(position2.amount, decimals);
4118
4379
  }
4119
4380
  }
4120
4381
  return { agent, balance };
@@ -4273,15 +4534,50 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
4273
4534
  }
4274
4535
 
4275
4536
  // src/client.ts
4276
- var import_viem11 = require("viem");
4537
+ var import_viem12 = require("viem");
4277
4538
  var import_chains4 = require("viem/chains");
4278
4539
 
4279
4540
  // src/lib/sponsored-token-batch.ts
4280
- var import_viem9 = require("viem");
4541
+ var import_viem10 = require("viem");
4281
4542
 
4282
4543
  // src/lib/permit2-batch.ts
4283
- var import_viem8 = require("viem");
4544
+ var import_viem9 = require("viem");
4284
4545
  var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
4546
+ var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
4547
+ var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
4548
+ var ERC2612_PERMIT_TYPES = {
4549
+ Permit: [
4550
+ { name: "owner", type: "address" },
4551
+ { name: "spender", type: "address" },
4552
+ { name: "value", type: "uint256" },
4553
+ { name: "nonce", type: "uint256" },
4554
+ { name: "deadline", type: "uint256" }
4555
+ ]
4556
+ };
4557
+ var ERC2612_READ_ABI = (0, import_viem9.parseAbi)([
4558
+ "function name() view returns (string)",
4559
+ "function version() view returns (string)",
4560
+ "function nonces(address owner) view returns (uint256)"
4561
+ ]);
4562
+ function erc2612TypedData(input) {
4563
+ return {
4564
+ domain: {
4565
+ name: input.permit.tokenName,
4566
+ version: input.permit.tokenVersion,
4567
+ chainId: input.chainId,
4568
+ verifyingContract: input.token
4569
+ },
4570
+ types: ERC2612_PERMIT_TYPES,
4571
+ primaryType: "Permit",
4572
+ message: {
4573
+ owner: input.owner,
4574
+ spender: BATCH_PERMIT2_ADDRESS,
4575
+ value: BigInt(input.permit.value),
4576
+ nonce: BigInt(input.permit.nonce),
4577
+ deadline: BigInt(input.permit.deadline)
4578
+ }
4579
+ };
4580
+ }
4285
4581
  var PERMIT_BATCH_TYPES = {
4286
4582
  PermitBatchWitnessTransferFrom: [
4287
4583
  { name: "permitted", type: "TokenPermissions[]" },
@@ -4296,7 +4592,7 @@ var PERMIT_BATCH_TYPES = {
4296
4592
  { name: "amount", type: "uint256" }
4297
4593
  ]
4298
4594
  };
4299
- var PERMIT2_BATCH_ABI = (0, import_viem8.parseAbi)([
4595
+ var PERMIT2_BATCH_ABI = (0, import_viem9.parseAbi)([
4300
4596
  "struct TokenPermissions { address token; uint256 amount; }",
4301
4597
  "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
4302
4598
  "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
@@ -4355,7 +4651,7 @@ function clear(key2) {
4355
4651
  else window.localStorage.removeItem(key2);
4356
4652
  }
4357
4653
  function sponsorTokenBatch(i) {
4358
- 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()}`;
4654
+ 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()}`;
4359
4655
  const plan = planOf(i.transfers);
4360
4656
  const active = inflight.get(key2);
4361
4657
  if (active) {
@@ -4386,7 +4682,7 @@ async function execute(i, key2, plan) {
4386
4682
  baseUrl: i.baseUrl,
4387
4683
  body
4388
4684
  });
4389
- if (!prepared.serializedTransaction || (0, import_viem9.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
4685
+ if (!prepared.serializedTransaction || (0, import_viem10.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
4390
4686
  throw new Error(
4391
4687
  "Sponsorship API did not return a valid prepared transaction."
4392
4688
  );
@@ -4401,7 +4697,7 @@ async function execute(i, key2, plan) {
4401
4697
  baseUrl: i.baseUrl,
4402
4698
  body
4403
4699
  });
4404
- if (result.txHash !== (0, import_viem9.keccak256)(body.serializedTransaction))
4700
+ if (result.txHash !== (0, import_viem10.keccak256)(body.serializedTransaction))
4405
4701
  throw new Error(
4406
4702
  "Sponsorship receipt does not match the pending transaction."
4407
4703
  );
@@ -4416,7 +4712,7 @@ async function execute(i, key2, plan) {
4416
4712
  const saved = read(key2);
4417
4713
  if (saved) {
4418
4714
  const previous = JSON.parse(saved);
4419
- 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)
4715
+ 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)
4420
4716
  throw new Error(
4421
4717
  "Retry the previous token deposit and agent split first to reconcile its status."
4422
4718
  );
@@ -4433,28 +4729,67 @@ async function execute(i, key2, plan) {
4433
4729
  "DEPOSIT_INSUFFICIENT_BALANCE",
4434
4730
  "Insufficient token balance for this deposit."
4435
4731
  );
4436
- if (allowance < total)
4732
+ if (allowance < total && (i.chainId !== 8453 || !(0, import_viem10.isAddressEqual)(i.token, BASE_USDC_ADDRESS)))
4437
4733
  throw new OwneyError(
4438
4734
  "PERMIT2_APPROVAL_REQUIRED",
4439
4735
  "token deposits need a one-time Permit2 approval."
4440
4736
  );
4441
- const relayer = await getSponsorRelayerAddress({
4737
+ const now = (await i.pub.getBlock()).timestamp;
4738
+ let erc2612Permit;
4739
+ if (allowance < total) {
4740
+ const [tokenName, tokenVersion, permitNonce] = await Promise.all([
4741
+ i.pub.readContract({
4742
+ address: i.token,
4743
+ abi: ERC2612_READ_ABI,
4744
+ functionName: "name"
4745
+ }),
4746
+ i.pub.readContract({
4747
+ address: i.token,
4748
+ abi: ERC2612_READ_ABI,
4749
+ functionName: "version"
4750
+ }),
4751
+ i.pub.readContract({
4752
+ address: i.token,
4753
+ abi: ERC2612_READ_ABI,
4754
+ functionName: "nonces",
4755
+ args: [i.owner]
4756
+ })
4757
+ ]);
4758
+ const unsignedPermit = {
4759
+ value: MAX_UINT256.toString(),
4760
+ nonce: permitNonce.toString(),
4761
+ deadline: (now + 900n).toString(),
4762
+ tokenName,
4763
+ tokenVersion
4764
+ };
4765
+ const signature2 = await i.wallet.signTypedData({
4766
+ account: i.owner,
4767
+ ...erc2612TypedData({
4768
+ chainId: 8453,
4769
+ token: i.token,
4770
+ owner: i.owner,
4771
+ permit: unsignedPermit
4772
+ })
4773
+ });
4774
+ erc2612Permit = { ...unsignedPermit, signature: signature2 };
4775
+ }
4776
+ const spender = erc2612Permit ? MULTICALL3_ADDRESS : await getSponsorRelayerAddress({
4442
4777
  apiKey: i.apiKey,
4443
4778
  baseUrl: i.baseUrl,
4444
4779
  chainId: i.chainId
4445
4780
  });
4446
- const now = (await i.pub.getBlock()).timestamp;
4447
4781
  const unsigned = {
4448
4782
  chainId: i.chainId,
4449
4783
  token: i.token,
4450
4784
  from: i.owner,
4451
4785
  transfers: i.transfers,
4452
4786
  nonce: randomPermit2Nonce().toString(),
4453
- deadline: (now + 900n).toString()
4787
+ deadline: (now + 900n).toString(),
4788
+ ...erc2612Permit ? { erc2612Permit } : {}
4454
4789
  };
4455
4790
  const signature = await i.wallet.signTypedData({
4456
4791
  account: i.owner,
4457
- ...batchTypedData(unsigned, relayer)
4792
+ ...batchTypedData(unsigned, spender)
4458
4793
  });
4459
4794
  i.onApproved?.();
4460
4795
  return send({ ...unsigned, signature });
@@ -4474,8 +4809,8 @@ function makeSponsoredTokenCallback(deps) {
4474
4809
  "CHAIN_UNSUPPORTED",
4475
4810
  `No sponsored token configured for chain ${chainId}`
4476
4811
  );
4477
- const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
4478
- await ensureWalletOnChain(pub, wallet, chainId);
4812
+ const pub = deps.getPublicClient(chainId), walletChain = deps.getWalletChainClient?.(chainId) ?? pub, wallet = deps.getWalletClient(chainId);
4813
+ await ensureWalletOnChain(walletChain, wallet, chainId);
4479
4814
  return sponsorTokenBatch({
4480
4815
  apiKey: deps.apiKey,
4481
4816
  baseUrl: deps.baseUrl,
@@ -4571,7 +4906,7 @@ async function runAgentDepositBatch(chainId, legs, transfer) {
4571
4906
  }
4572
4907
 
4573
4908
  // src/lib/sponsored-calls-deposit.ts
4574
- var import_viem10 = require("viem");
4909
+ var import_viem11 = require("viem");
4575
4910
  var DEFAULT_POLL_INTERVAL_MS = 1500;
4576
4911
  var DEFAULT_MAX_POLLS = 30;
4577
4912
  async function paymasterSupported(provider, owner, chainId) {
@@ -4579,7 +4914,7 @@ async function paymasterSupported(provider, owner, chainId) {
4579
4914
  method: "wallet_getCapabilities",
4580
4915
  params: [owner]
4581
4916
  });
4582
- const forChain = caps?.[(0, import_viem10.toHex)(chainId)] ?? caps?.[String(chainId)];
4917
+ const forChain = caps?.[(0, import_viem11.toHex)(chainId)] ?? caps?.[String(chainId)];
4583
4918
  return Boolean(forChain?.paymasterService?.supported);
4584
4919
  }
4585
4920
  function makeSponsoredCallsCallback(deps) {
@@ -4616,8 +4951,8 @@ function makeSponsoredCallsCallback(deps) {
4616
4951
  const calls = transfers.map((transfer) => ({
4617
4952
  to: token,
4618
4953
  value: "0x0",
4619
- data: (0, import_viem10.encodeFunctionData)({
4620
- abi: import_viem10.erc20Abi,
4954
+ data: (0, import_viem11.encodeFunctionData)({
4955
+ abi: import_viem11.erc20Abi,
4621
4956
  functionName: "transfer",
4622
4957
  args: [transfer.to, BigInt(transfer.amount)]
4623
4958
  })
@@ -4655,7 +4990,7 @@ function makeSponsoredCallsCallback(deps) {
4655
4990
  {
4656
4991
  version: "2.0.0",
4657
4992
  from: deps.ownerAddress,
4658
- chainId: (0, import_viem10.toHex)(chainId),
4993
+ chainId: (0, import_viem11.toHex)(chainId),
4659
4994
  atomicRequired: transfers.length > 1,
4660
4995
  calls,
4661
4996
  capabilities: {
@@ -4777,6 +5112,7 @@ var OwneySDK = class {
4777
5112
  // leave every user's agent profile alone.
4778
5113
  orgAgentConfig;
4779
5114
  orgAgentConfigPromise = null;
5115
+ rpcUrls;
4780
5116
  zyfaiRpcUrls;
4781
5117
  yieldseekerApiBaseUrl;
4782
5118
  yieldseekerSiweOrigin;
@@ -4801,6 +5137,7 @@ var OwneySDK = class {
4801
5137
  constructor(config) {
4802
5138
  this.apiKey = config.apiKey;
4803
5139
  if (config.debug) setOwneyDebug(true);
5140
+ this.rpcUrls = config.rpcUrls;
4804
5141
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
4805
5142
  this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
4806
5143
  this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
@@ -4891,6 +5228,9 @@ var OwneySDK = class {
4891
5228
  }
4892
5229
  return this.state.provider;
4893
5230
  }
5231
+ getPaidRpcClient(chainId) {
5232
+ return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
5233
+ }
4894
5234
  /** Builds the default USDC batch callback for the connected wallet. */
4895
5235
  getDefaultSponsoredCallback(onApproved) {
4896
5236
  if (!onApproved && this.cachedSponsoredCallback)
@@ -4906,14 +5246,15 @@ var OwneySDK = class {
4906
5246
  // Casts work around viem's chain-narrowed Client vs the generic
4907
5247
  // PublicClient/WalletClient param types — structurally identical at
4908
5248
  // runtime, but the two share a name TS treats as unrelated.
4909
- getPublicClient: (cid) => (0, import_viem11.createPublicClient)({
5249
+ getPublicClient: (cid) => this.getPaidRpcClient(cid),
5250
+ getWalletChainClient: (cid) => (0, import_viem12.createPublicClient)({
4910
5251
  chain: VIEM_CHAIN2[cid],
4911
- transport: (0, import_viem11.custom)(provider)
5252
+ transport: (0, import_viem12.custom)(provider)
4912
5253
  }),
4913
- getWalletClient: (cid) => (0, import_viem11.createWalletClient)({
5254
+ getWalletClient: (cid) => (0, import_viem12.createWalletClient)({
4914
5255
  account: owner,
4915
5256
  chain: VIEM_CHAIN2[cid],
4916
- transport: (0, import_viem11.custom)(provider)
5257
+ transport: (0, import_viem12.custom)(provider)
4917
5258
  })
4918
5259
  });
4919
5260
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -4956,14 +5297,15 @@ var OwneySDK = class {
4956
5297
  // Casts work around viem's chain-narrowed Client vs the generic
4957
5298
  // PublicClient/WalletClient param types — structurally identical at
4958
5299
  // runtime, but the two share a name TS treats as unrelated.
4959
- getPublicClient: (cid) => (0, import_viem11.createPublicClient)({
5300
+ getPublicClient: (cid) => this.getPaidRpcClient(cid),
5301
+ getWalletChainClient: (cid) => (0, import_viem12.createPublicClient)({
4960
5302
  chain: VIEM_CHAIN2[cid],
4961
- transport: (0, import_viem11.custom)(provider)
5303
+ transport: (0, import_viem12.custom)(provider)
4962
5304
  }),
4963
- getWalletClient: (cid) => (0, import_viem11.createWalletClient)({
5305
+ getWalletClient: (cid) => (0, import_viem12.createWalletClient)({
4964
5306
  account: owner,
4965
5307
  chain: VIEM_CHAIN2[cid],
4966
- transport: (0, import_viem11.custom)(provider)
5308
+ transport: (0, import_viem12.custom)(provider)
4967
5309
  })
4968
5310
  });
4969
5311
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -5069,12 +5411,17 @@ var OwneySDK = class {
5069
5411
  createAgent(agentId, key2) {
5070
5412
  if (agentId === "zyfai") {
5071
5413
  if (!key2) return null;
5072
- return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
5414
+ return new ZyfaiAgent(
5415
+ key2,
5416
+ this.rpcUrls ?? this.zyfaiRpcUrls,
5417
+ this.referralSource
5418
+ );
5073
5419
  }
5074
5420
  if (agentId === "yieldseeker") {
5075
5421
  return new YieldseekerAgent(this.apiKey, {
5076
5422
  auth: { origin: this.yieldseekerSiweOrigin },
5077
- baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
5423
+ baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl),
5424
+ rpcUrls: this.rpcUrls
5078
5425
  });
5079
5426
  }
5080
5427
  return null;
@@ -5410,10 +5757,12 @@ var OwneySDK = class {
5410
5757
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
5411
5758
  * two independent auto-recovery mechanisms:
5412
5759
  *
5413
- * 1. Missing Permit2 allowance: when the app did not supply its own
5414
- * callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
5415
- * token deposit, this is the wallet's first Permit2 deposit for that token. We send
5416
- * the one-time (user-paid) Permit2 approval via `approvePermit2()` and
5760
+ * 1. Missing Permit2 allowance outside the atomic Base-USDC path: when the
5761
+ * app did not supply its own callback and the attempt fails with
5762
+ * `PERMIT2_APPROVAL_REQUIRED`, this is the wallet's first Permit2 deposit
5763
+ * for that token. Base USDC bundles a gasless ERC-2612 approval inside
5764
+ * its sponsored deposit and never reaches this branch. Other tokens send
5765
+ * the one-time user-paid Permit2 approval via `approvePermit2()` and
5417
5766
  * retry the SAME sponsored attempt once. Bounded to one approval attempt
5418
5767
  * per call so a wallet/agent that keeps reporting the allowance as
5419
5768
  * missing can't loop forever. If `approvePermit2()` itself throws (e.g.
@@ -5604,6 +5953,32 @@ var OwneySDK = class {
5604
5953
  }
5605
5954
  return eligible;
5606
5955
  }
5956
+ /**
5957
+ * Run owner-approved withdrawals before relayer-only withdrawals. Wallet
5958
+ * approval is the only point at which the user can cancel the aggregate
5959
+ * operation, so no relayer leg should commit before it has completed.
5960
+ */
5961
+ orderAgentsForWithdrawal(agents) {
5962
+ return agents.map((agent, index) => ({ agent, index })).sort((left, right) => {
5963
+ const approvalOrder = Number(Boolean(right.agent.withdrawalRequiresWalletApproval)) - Number(Boolean(left.agent.withdrawalRequiresWalletApproval));
5964
+ return approvalOrder || left.index - right.index;
5965
+ }).map(({ agent }) => agent);
5966
+ }
5967
+ isUserRejectedWithdrawal(error) {
5968
+ let current = error;
5969
+ const seen = /* @__PURE__ */ new Set();
5970
+ while (current && typeof current === "object" && !seen.has(current)) {
5971
+ seen.add(current);
5972
+ const candidate = current;
5973
+ if (candidate.code === 4001 || candidate.code === "4001") return true;
5974
+ const message = [candidate.message, candidate.shortMessage].filter((value) => typeof value === "string").join(" ");
5975
+ if (/user (?:rejected|denied)|rejected by user/i.test(message)) {
5976
+ return true;
5977
+ }
5978
+ current = candidate.cause;
5979
+ }
5980
+ return false;
5981
+ }
5607
5982
  // --- Fund operations ---
5608
5983
  /**
5609
5984
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -5639,13 +6014,15 @@ var OwneySDK = class {
5639
6014
  );
5640
6015
  }
5641
6016
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
6017
+ const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
5642
6018
  if (!amount) {
5643
6019
  const results2 = {};
5644
6020
  const agentErrors2 = {};
5645
- for (const agent of eligibleAgents) {
6021
+ for (const agent of withdrawalAgents) {
5646
6022
  try {
5647
6023
  results2[agent.id] = await agent.withdraw(state, chainId, token);
5648
6024
  } catch (err) {
6025
+ if (this.isUserRejectedWithdrawal(err)) throw err;
5649
6026
  console.error(`withdraw failed for agent "${agent.id}":`, err);
5650
6027
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
5651
6028
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
@@ -5726,12 +6103,13 @@ var OwneySDK = class {
5726
6103
  planned: 0n
5727
6104
  }));
5728
6105
  const plans = [...disabledPlans, ...enabledPlans];
6106
+ const orderedPlans = this.orderAgentsForWithdrawal(plans.map((p) => p.agent)).map((agent) => plans.find((plan) => plan.agent === agent));
5729
6107
  const results = {};
5730
6108
  const agentErrors = {
5731
6109
  ...aggregated.agentErrors ?? {}
5732
6110
  };
5733
- for (let i = 0; i < plans.length; i++) {
5734
- const p = plans[i];
6111
+ for (let i = 0; i < orderedPlans.length; i++) {
6112
+ const p = orderedPlans[i];
5735
6113
  if (p.planned === 0n) continue;
5736
6114
  try {
5737
6115
  results[p.agent.id] = await p.agent.withdraw(
@@ -5741,6 +6119,7 @@ var OwneySDK = class {
5741
6119
  p.planned.toString()
5742
6120
  );
5743
6121
  } catch (err) {
6122
+ if (this.isUserRejectedWithdrawal(err)) throw err;
5744
6123
  console.error(`withdraw failed for agent "${p.agent.id}":`, err);
5745
6124
  agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
5746
6125
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
@@ -5752,7 +6131,7 @@ var OwneySDK = class {
5752
6131
  );
5753
6132
  const failedAmount = p.planned;
5754
6133
  p.planned = 0n;
5755
- redistributeShare(plans, i, failedAmount);
6134
+ redistributeShare(orderedPlans, i, failedAmount);
5756
6135
  }
5757
6136
  }
5758
6137
  if (Object.keys(results).length === 0) {
@@ -6199,17 +6578,18 @@ var OwneySDK = class {
6199
6578
  );
6200
6579
  }
6201
6580
  const provider = this.requireConnectedProvider();
6202
- const publicClient = (0, import_viem11.createPublicClient)({
6581
+ const walletChainClient = (0, import_viem12.createPublicClient)({
6203
6582
  chain: VIEM_CHAIN2[chainId],
6204
- transport: (0, import_viem11.custom)(provider)
6583
+ transport: (0, import_viem12.custom)(provider)
6205
6584
  });
6585
+ const publicClient = this.getPaidRpcClient(chainId);
6206
6586
  const approvalAmount = permit2ApprovalAmount(requiredAmount);
6207
- const wallet = (0, import_viem11.createWalletClient)({
6587
+ const wallet = (0, import_viem12.createWalletClient)({
6208
6588
  account: state.walletAddress,
6209
6589
  chain: VIEM_CHAIN2[chainId],
6210
- transport: (0, import_viem11.custom)(provider)
6590
+ transport: (0, import_viem12.custom)(provider)
6211
6591
  });
6212
- await ensureWalletOnChain(publicClient, wallet, chainId);
6592
+ await ensureWalletOnChain(walletChainClient, wallet, chainId);
6213
6593
  const hash = await wallet.writeContract({
6214
6594
  address: token,
6215
6595
  abi: ERC20_ALLOWANCE_ABI,
@@ -6218,10 +6598,14 @@ var OwneySDK = class {
6218
6598
  account: state.walletAddress,
6219
6599
  chain: VIEM_CHAIN2[chainId]
6220
6600
  });
6221
- const receipt = await publicClient.waitForTransactionReceipt({
6222
- hash,
6223
- confirmations: 1
6224
- });
6601
+ const receipt = await withPaidRpcDiagnostics(
6602
+ () => publicClient.waitForTransactionReceipt({
6603
+ hash,
6604
+ confirmations: 1
6605
+ }),
6606
+ chainId,
6607
+ "eth_getTransactionReceipt"
6608
+ );
6225
6609
  if (receipt.status !== "success") {
6226
6610
  throw new Error(`Permit2 approval reverted (tx ${hash})`);
6227
6611
  }
@@ -6365,7 +6749,7 @@ var OwneySDK = class {
6365
6749
  };
6366
6750
 
6367
6751
  // src/agents/zyfai/zyfai.siwx.ts
6368
- var import_viem12 = require("viem");
6752
+ var import_viem13 = require("viem");
6369
6753
  var import_siwe2 = require("siwe");
6370
6754
  var import_sdk2 = require("@zyfai/sdk");
6371
6755
 
@@ -6492,7 +6876,7 @@ function buildSIWXConfig(deps) {
6492
6876
  issuedAt,
6493
6877
  toString() {
6494
6878
  return new import_siwe2.SiweMessage({
6495
- address: (0, import_viem12.getAddress)(accountAddress),
6879
+ address: (0, import_viem13.getAddress)(accountAddress),
6496
6880
  chainId: numericChainId(chainId),
6497
6881
  domain,
6498
6882
  uri,