@owney/sdk 0.7.16-beta.4 → 0.7.16-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.js CHANGED
@@ -1,3 +1,30 @@
1
+ import {
2
+ debugLog,
3
+ fetchAgentKeys,
4
+ fetchOrgAgentConfig,
5
+ setOwneyDebug
6
+ } from "./chunk-QA7JUSDB.js";
7
+ import {
8
+ buildTransferWithAuthorizationTypedData,
9
+ randomAuthNonce,
10
+ readTokenMeta
11
+ } from "./chunk-5LU2SHO7.js";
12
+ import {
13
+ ERC20_ALLOWANCE_ABI,
14
+ MAX_UINT256,
15
+ PERMIT2_ADDRESS,
16
+ buildPermitTransferFromTypedData,
17
+ randomPermit2Nonce,
18
+ readErc20Balance,
19
+ readPermit2Allowance
20
+ } from "./chunk-DBRXNDSB.js";
21
+ import {
22
+ AgentChainIncompatibleError,
23
+ AgentNotFoundError,
24
+ NotConnectedError,
25
+ OwneyError
26
+ } from "./chunk-GMNQBWUB.js";
27
+
1
28
  // src/agents/zyfai/zyfai.agent.ts
2
29
  import { SMART_SESSIONS_VALIDATOR, ZyfaiSDK } from "@zyfai/sdk";
3
30
  import { createPublicClient, http, parseAbi } from "viem";
@@ -50,55 +77,6 @@ var SupportedAssets = [
50
77
  }
51
78
  ];
52
79
 
53
- // src/errors.ts
54
- var OwneyError = class extends Error {
55
- code;
56
- details;
57
- agentId;
58
- constructor(code, message, details, agentId) {
59
- const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
60
- super(`${prefix} ${message}`);
61
- this.name = "OwneyError";
62
- this.code = code;
63
- this.details = details;
64
- this.agentId = agentId;
65
- }
66
- };
67
- var AgentNotFoundError = class extends OwneyError {
68
- constructor(agentId, available) {
69
- super(
70
- "AGENT_NOT_FOUND",
71
- `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
72
- { agentId, available },
73
- agentId
74
- );
75
- this.name = "AgentNotFoundError";
76
- }
77
- };
78
- var NotConnectedError = class extends OwneyError {
79
- constructor() {
80
- super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
81
- this.name = "NotConnectedError";
82
- }
83
- };
84
- var AgentChainIncompatibleError = class extends OwneyError {
85
- incompatibleAgents;
86
- connectedChainId;
87
- constructor(incompatibleAgents, connectedChainId) {
88
- const details = incompatibleAgents.map(
89
- ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
90
- ).join("; ");
91
- super(
92
- "AGENT_CHAIN_INCOMPATIBLE",
93
- `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
94
- { incompatibleAgents, connectedChainId }
95
- );
96
- this.name = "AgentChainIncompatibleError";
97
- this.incompatibleAgents = incompatibleAgents;
98
- this.connectedChainId = connectedChainId;
99
- }
100
- };
101
-
102
80
  // src/lib/utils.ts
103
81
  var isValidChainId = (chainId) => {
104
82
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
@@ -110,6 +88,12 @@ var isValidChainId = (chainId) => {
110
88
  }
111
89
  return chainId;
112
90
  };
91
+ function isSameAsset(tokenSymbol, asset) {
92
+ const token = tokenSymbol.toLowerCase();
93
+ const target = asset.toLowerCase();
94
+ if (token === target) return true;
95
+ return target === "weth" && token === "eth" || target === "eth" && token === "weth";
96
+ }
113
97
  function hexToDecimal(hex, decimals = 6) {
114
98
  const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
115
99
  const parsed = BigInt(normalized);
@@ -147,18 +131,18 @@ function tokenDecimals(symbol, explicit) {
147
131
  return explicit;
148
132
  return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
149
133
  }
150
- function mapDeposit(raw2) {
134
+ function mapDeposit(raw) {
151
135
  return {
152
- txHash: raw2.txHash,
153
- smartWallet: raw2.smartWallet,
154
- amount: raw2.amount
136
+ txHash: raw.txHash,
137
+ smartWallet: raw.smartWallet,
138
+ amount: raw.amount
155
139
  };
156
140
  }
157
- function mapWithdraw(raw2) {
141
+ function mapWithdraw(raw) {
158
142
  return {
159
- txHash: raw2.txHash,
160
- type: raw2.type,
161
- amount: raw2.amount
143
+ txHash: raw.txHash,
144
+ type: raw.type,
145
+ amount: raw.amount
162
146
  };
163
147
  }
164
148
  var CHAIN_ID_TO_NAME = {
@@ -177,10 +161,10 @@ function resolveChainId(chain) {
177
161
  if (Number.isFinite(asNum) && asNum > 0) return asNum;
178
162
  return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
179
163
  }
180
- function mapPendingAllocations(raw2) {
181
- if (!Array.isArray(raw2)) return void 0;
164
+ function mapPendingAllocations(raw) {
165
+ if (!Array.isArray(raw)) return void 0;
182
166
  const pending = [];
183
- for (const entry of raw2) {
167
+ for (const entry of raw) {
184
168
  if (typeof entry !== "object" || entry === null) continue;
185
169
  const e = entry;
186
170
  if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
@@ -203,8 +187,8 @@ function mapPendingAllocations(raw2) {
203
187
  }
204
188
  return pending.length > 0 ? pending : void 0;
205
189
  }
206
- function mapBalances(raw2, _chainId, smartWallet) {
207
- const portfolio = raw2.portfolio;
190
+ function mapBalances(raw, _chainId, smartWallet) {
191
+ const portfolio = raw.portfolio;
208
192
  const portfolioByChain = portfolio.portfolioByChain ?? {};
209
193
  let totalBalance = 0;
210
194
  const tokens = [];
@@ -249,7 +233,7 @@ function mapBalances(raw2, _chainId, smartWallet) {
249
233
  )
250
234
  )
251
235
  ),
252
- apy: p.pool_apy,
236
+ apy: netApy(p.pool_apy_withFee, p.pool_apy, "pool_apy_withFee"),
253
237
  tvl: p.pool_tvl,
254
238
  // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
255
239
  // structural read keeps it undefined today and auto-populates later.
@@ -273,8 +257,8 @@ function sumTokenValues(tokens) {
273
257
  function sumTokenEarnings(tokens) {
274
258
  return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
275
259
  }
276
- function mapEarnings(raw2, smartWallet) {
277
- const totalEarningsByChain = raw2.data.totalEarningsByChainWithFee ?? raw2.data.totalEarningsByChain ?? {};
260
+ function mapEarnings(raw, smartWallet) {
261
+ const totalEarningsByChain = raw.data.totalEarningsByChainWithFee ?? raw.data.totalEarningsByChain ?? {};
278
262
  const tokens = [];
279
263
  for (const [chainIdKey, tokensBySymbol] of Object.entries(
280
264
  totalEarningsByChain
@@ -293,15 +277,15 @@ function mapEarnings(raw2, smartWallet) {
293
277
  return {
294
278
  smartWallet,
295
279
  lifetimeEarnings: sumTokenEarnings(
296
- raw2.data.totalEarningsByTokenWithFee ?? raw2.data.totalEarningsByToken
280
+ raw.data.totalEarningsByTokenWithFee ?? raw.data.totalEarningsByToken
297
281
  ),
298
282
  tokens
299
283
  };
300
284
  }
301
- function mapWeightedApyByChain(raw2) {
302
- if (!raw2) return void 0;
285
+ function mapWeightedApyByChain(raw) {
286
+ if (!raw) return void 0;
303
287
  const out = {};
304
- for (const [chainKey, tokenApy] of Object.entries(raw2)) {
288
+ for (const [chainKey, tokenApy] of Object.entries(raw)) {
305
289
  const chainId = Number(chainKey);
306
290
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
307
291
  const perAsset = {};
@@ -314,6 +298,19 @@ function mapWeightedApyByChain(raw2) {
314
298
  }
315
299
  return Object.keys(out).length > 0 ? out : void 0;
316
300
  }
301
+ var warnedGrossApyFallbacks = /* @__PURE__ */ new Set();
302
+ function netApy(net, gross, field) {
303
+ if (net !== void 0 && net !== null) return net;
304
+ if (gross === void 0 || gross === null) return void 0;
305
+ if (!warnedGrossApyFallbacks.has(field)) {
306
+ warnedGrossApyFallbacks.add(field);
307
+ console.warn(
308
+ `[owney] @zyfai/sdk omitted "${field}"; falling back to the gross APY, which does not deduct Zyfai's performance fee and so reads high.`
309
+ );
310
+ }
311
+ debugLog("zyfai:apy", `gross fallback for ${field}`, { gross });
312
+ return gross;
313
+ }
317
314
  function rawPoolApyForChain(entry, chainId, tokenSymbol) {
318
315
  let weightedSum = 0;
319
316
  let totalBalance = 0;
@@ -321,21 +318,22 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
321
318
  if (p.chainId !== chainId) continue;
322
319
  if (tokenSymbol && p.tokenSymbol !== tokenSymbol) continue;
323
320
  const balance = p.balance ?? 0;
324
- if (balance <= 0 || typeof p.apy !== "number") continue;
325
- weightedSum += p.apy * balance;
321
+ const apy = netApy(p.apy_withFee, p.apy, "apy_withFee");
322
+ if (balance <= 0 || typeof apy !== "number") continue;
323
+ weightedSum += apy * balance;
326
324
  totalBalance += balance;
327
325
  }
328
326
  return totalBalance > 0 ? weightedSum / totalBalance : null;
329
327
  }
330
- function mapApyHistory(raw2, chainId, tokenSymbol) {
331
- const history = Object.entries(raw2.history ?? {}).map(([date, entry]) => ({
328
+ function mapApyHistory(raw, chainId, tokenSymbol) {
329
+ const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
332
330
  date,
333
331
  apy: rawPoolApyForChain(entry, chainId, tokenSymbol)
334
332
  })).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
335
333
  return {
336
- walletAddress: raw2.walletAddress,
337
- weightedApyAfterFee: raw2.weightedApyAfterFee ? sumTokenValues(raw2.weightedApyAfterFee) : void 0,
338
- apyByChainAndAsset: mapWeightedApyByChain(raw2.weightedApyAfterFeeByChain),
334
+ walletAddress: raw.walletAddress,
335
+ weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
336
+ apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain),
339
337
  history
340
338
  };
341
339
  }
@@ -370,8 +368,12 @@ function mapEntries(rawEntries, chainId) {
370
368
  const matched = positions.find(
371
369
  (p) => p.protocol_name ? rawLog.newOpportunity.includes(`${p.protocol_name} (${p.pool})`) : false
372
370
  );
373
- oldApy = Number(rawLog.oldApy);
374
- newApy = Number(rawLog.newApy);
371
+ oldApy = Number(
372
+ netApy(rawLog.oldApy_withFee, rawLog.oldApy, "oldApy_withFee")
373
+ );
374
+ newApy = Number(
375
+ netApy(rawLog.newApy_withFee, rawLog.newApy, "newApy_withFee")
376
+ );
375
377
  const from = splitOpportunity(rawLog.oldOpportunity);
376
378
  const to = splitOpportunity(rawLog.newOpportunity);
377
379
  rebalanceLog = [
@@ -427,23 +429,23 @@ function mapEntries(rawEntries, chainId) {
427
429
  };
428
430
  });
429
431
  }
430
- function mapUserProfile(raw2, userAddress) {
432
+ function mapUserProfile(raw, userAddress) {
431
433
  return {
432
434
  address: userAddress,
433
- smartWallet: raw2.smartWallet || "",
434
- chains: raw2.chains || [],
435
- strategy: raw2.strategy,
436
- hasActiveSessionKey: raw2.hasActiveSessionKey || false,
437
- protocols: raw2.protocols || [],
438
- splitting: raw2.splitting,
439
- minSplits: raw2.minSplits
435
+ smartWallet: raw.smartWallet || "",
436
+ chains: raw.chains || [],
437
+ strategy: raw.strategy,
438
+ hasActiveSessionKey: raw.hasActiveSessionKey || false,
439
+ protocols: raw.protocols || [],
440
+ splitting: raw.splitting,
441
+ minSplits: raw.minSplits
440
442
  };
441
443
  }
442
- function mapApyByStrategy(raw2) {
444
+ function mapApyByStrategy(raw) {
443
445
  const apyPerAsset = {};
444
446
  let apySum = 0;
445
447
  let apyCount = 0;
446
- for (const entry of raw2.data) {
448
+ for (const entry of raw.data) {
447
449
  const supported = SupportedAssets.find(
448
450
  (asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
449
451
  );
@@ -451,7 +453,11 @@ function mapApyByStrategy(raw2) {
451
453
  const chainKey = supported.chainId;
452
454
  const symbolKey = supported.symbol;
453
455
  const bucket = apyPerAsset[chainKey] ?? {};
454
- const apy = entry.average_apy_with_fee ?? entry.average_apy;
456
+ const apy = netApy(
457
+ entry.average_apy_withFee,
458
+ entry.average_apy,
459
+ "average_apy_withFee"
460
+ ) ?? entry.average_apy;
455
461
  bucket[symbolKey] = apy;
456
462
  apyPerAsset[chainKey] = bucket;
457
463
  apySum += apy;
@@ -610,15 +616,15 @@ var readSession = (address, _chainId) => {
610
616
  if (typeof window === "undefined") return null;
611
617
  const key2 = buildKey(address);
612
618
  const store = storage();
613
- let raw2 = null;
619
+ let raw = null;
614
620
  try {
615
- raw2 = store?.getItem(key2) ?? null;
621
+ raw = store?.getItem(key2) ?? null;
616
622
  } catch {
617
- raw2 = null;
623
+ raw = null;
618
624
  }
619
- if (raw2) {
625
+ if (raw) {
620
626
  try {
621
- const parsed = JSON.parse(raw2);
627
+ const parsed = JSON.parse(raw);
622
628
  if (isFreshSession(parsed)) return parsed;
623
629
  } catch {
624
630
  }
@@ -786,8 +792,8 @@ function buildChainsByProtocol(protocols, dashboardChainIds) {
786
792
  }
787
793
  return result;
788
794
  }
789
- function flattenAvailablePools(raw2) {
790
- const byChain = raw2 ?? {};
795
+ function flattenAvailablePools(raw) {
796
+ const byChain = raw ?? {};
791
797
  const names = [];
792
798
  for (const byToken of Object.values(byChain ?? {})) {
793
799
  for (const entry of Object.values(byToken ?? {})) {
@@ -811,23 +817,6 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
811
817
  return !protocolListsEqual(current.protocols, desiredProtocols);
812
818
  }
813
819
 
814
- // src/lib/debug.ts
815
- var configuredDebug = false;
816
- function setOwneyDebug(enabled) {
817
- configuredDebug = enabled;
818
- }
819
- function isOwneyDebug() {
820
- return globalThis.__OWNEY_DEBUG__ === true || configuredDebug;
821
- }
822
- function debugLog(scope, message, data) {
823
- if (!isOwneyDebug()) return;
824
- if (data === void 0) {
825
- console.log(`[${scope}] ${message}`);
826
- } else {
827
- console.log(`[${scope}] ${message}`, data);
828
- }
829
- }
830
-
831
820
  // src/agents/zyfai/zyfai.agent.ts
832
821
  var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi([
833
822
  "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
@@ -1298,8 +1287,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
1298
1287
  const poolResults = await Promise.all(
1299
1288
  universe.map(async (protocol) => {
1300
1289
  try {
1301
- const raw2 = await this.sdk.getAvailablePools(protocol.id, strategy);
1302
- return [protocol.id, flattenAvailablePools(raw2)];
1290
+ const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
1291
+ return [protocol.id, flattenAvailablePools(raw)];
1303
1292
  } catch (error) {
1304
1293
  console.warn(
1305
1294
  `[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
@@ -1338,6 +1327,37 @@ var ZyfaiAgent = class _ZyfaiAgent {
1338
1327
  );
1339
1328
  }
1340
1329
  }
1330
+ /**
1331
+ * Records the deposit with Zyfai, retrying transient failures.
1332
+ *
1333
+ * This runs AFTER the transfer has already landed on-chain, so it must never
1334
+ * fail the deposit — the funds moved. But it is also the ONLY source of the
1335
+ * "Top up wallet" entry the history is built from: Zyfai auto-deploys the
1336
+ * balance it detects either way, so when this call is lost the user's deposit
1337
+ * never appears in Activity (an earlier withdrawal stays the newest row) and
1338
+ * nothing ever backfills it. Retry, then log loudly enough to be recoverable.
1339
+ */
1340
+ async logDepositWithRetry(chainId, txHash, amount, tokenAddress) {
1341
+ const ATTEMPTS = 3;
1342
+ const RETRY_DELAY_MS = 1e3;
1343
+ for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
1344
+ try {
1345
+ await (tokenAddress ? this.sdk.logDeposit(chainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(chainId, txHash, amount));
1346
+ return;
1347
+ } catch (logError) {
1348
+ if (attempt === ATTEMPTS) {
1349
+ console.error(
1350
+ "[owney-sdk] Deposit landed on-chain but logDeposit failed \u2014 it will be missing from Zyfai history:",
1351
+ { txHash, chainId, amount, tokenAddress, error: logError }
1352
+ );
1353
+ return;
1354
+ }
1355
+ await new Promise(
1356
+ (resolve) => setTimeout(resolve, RETRY_DELAY_MS * attempt)
1357
+ );
1358
+ }
1359
+ }
1360
+ }
1341
1361
  /**
1342
1362
  * True when `smartWallet` is the backend-managed pool wallet Zyfai assigned
1343
1363
  * to this EOA — the "new wallet" kind that is predeployed on the chains it
@@ -1365,14 +1385,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1365
1385
  async readWalletState(ownerAddress) {
1366
1386
  try {
1367
1387
  const { portfolio } = await this.sdk.getPositions(ownerAddress);
1368
- const raw2 = portfolio;
1388
+ const raw = portfolio;
1369
1389
  debugLog("zyfai:onboard", "wallet state from getPositions", {
1370
- predeployed: raw2?.predeployed,
1371
- hasActiveSessionKey: raw2?.hasActiveSessionKey
1390
+ predeployed: raw?.predeployed,
1391
+ hasActiveSessionKey: raw?.hasActiveSessionKey
1372
1392
  });
1373
1393
  return {
1374
- predeployed: raw2?.predeployed,
1375
- hasActiveSessionKey: raw2?.hasActiveSessionKey
1394
+ predeployed: raw?.predeployed,
1395
+ hasActiveSessionKey: raw?.hasActiveSessionKey
1376
1396
  };
1377
1397
  } catch (error) {
1378
1398
  console.warn(
@@ -1641,25 +1661,23 @@ var ZyfaiAgent = class _ZyfaiAgent {
1641
1661
  );
1642
1662
  const txHash = await depositCallback(smartWallet, validChainId, amount);
1643
1663
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
1644
- try {
1645
- await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
1646
- } catch (logError) {
1647
- console.warn(
1648
- "[owney-sdk] Deposit landed on-chain but logDeposit failed (non-fatal):",
1649
- logError
1650
- );
1651
- }
1664
+ await this.logDepositWithRetry(
1665
+ validChainId,
1666
+ txHash,
1667
+ amount,
1668
+ tokenAddress
1669
+ );
1652
1670
  return { txHash, smartWallet, amount };
1653
1671
  }
1654
1672
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
1655
- const raw2 = await this.sdk.depositFunds(
1673
+ const raw = await this.sdk.depositFunds(
1656
1674
  this.getAddress(),
1657
1675
  validChainId,
1658
1676
  amount,
1659
1677
  asset,
1660
1678
  "aggressive"
1661
1679
  );
1662
- return mapDeposit(raw2);
1680
+ return mapDeposit(raw);
1663
1681
  } catch (error) {
1664
1682
  throw error;
1665
1683
  }
@@ -1668,32 +1686,32 @@ var ZyfaiAgent = class _ZyfaiAgent {
1668
1686
  async withdraw(state, chainId, token, amount) {
1669
1687
  const validChainId = isValidChainId(chainId);
1670
1688
  await this.ensureConnected(state, validChainId);
1671
- const raw2 = await this.sdk.withdrawFunds(
1689
+ const raw = await this.sdk.withdrawFunds(
1672
1690
  this.getAddress(),
1673
1691
  validChainId,
1674
1692
  amount,
1675
1693
  token
1676
1694
  );
1677
- if (!raw2.success) {
1695
+ if (!raw.success) {
1678
1696
  throw new OwneyError(
1679
1697
  "WITHDRAW_FAILED",
1680
- raw2.message || "Zyfai withdraw failed.",
1681
- { chainId: validChainId, token, amount, response: raw2 },
1698
+ raw.message || "Zyfai withdraw failed.",
1699
+ { chainId: validChainId, token, amount, response: raw },
1682
1700
  this.id
1683
1701
  );
1684
1702
  }
1685
- return mapWithdraw(raw2);
1703
+ return mapWithdraw(raw);
1686
1704
  }
1687
1705
  // --- IAgent: Portfolio reads ---
1688
1706
  async getBalances(state, chainId) {
1689
1707
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
1690
- const raw2 = await this.sdk.getPortfolio(this.getAddress());
1691
- return mapBalances(raw2, validChainId, smartWallet);
1708
+ const raw = await this.sdk.getPortfolio(this.getAddress());
1709
+ return mapBalances(raw, validChainId, smartWallet);
1692
1710
  }
1693
1711
  async getEarnings(state, chainId) {
1694
1712
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1695
- const raw2 = await this.sdk.getOnchainEarnings(smartWallet);
1696
- return mapEarnings(raw2, smartWallet);
1713
+ const raw = await this.sdk.getOnchainEarnings(smartWallet);
1714
+ return mapEarnings(raw, smartWallet);
1697
1715
  }
1698
1716
  async refreshEarnings(state, chainId) {
1699
1717
  if (this.earningsRefreshInFlight) return this.earningsRefreshInFlight;
@@ -1713,8 +1731,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
1713
1731
  }
1714
1732
  async getAccountApy(state, chainId, days, tokenSymbol) {
1715
1733
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1716
- const raw2 = await this.sdk.getDailyApyHistory(smartWallet, days);
1717
- return mapApyHistory(raw2, chainId, tokenSymbol);
1734
+ const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
1735
+ return mapApyHistory(raw, chainId, tokenSymbol);
1718
1736
  }
1719
1737
  /**
1720
1738
  * Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
@@ -1749,7 +1767,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1749
1767
  const matched = [];
1750
1768
  let backendExhausted = false;
1751
1769
  for (let i = 0; i < MAX_BACKEND_CALLS && matched.length < limit; i++) {
1752
- const raw2 = await this.sdk.getHistory(smartWallet, validChainId, {
1770
+ const raw = await this.sdk.getHistory(smartWallet, validChainId, {
1753
1771
  limit: backendPageSize,
1754
1772
  offset,
1755
1773
  fromDate: options?.fromDate,
@@ -1760,13 +1778,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
1760
1778
  // asset's rows and handing back a page that filters to nothing.
1761
1779
  assetType
1762
1780
  });
1763
- raw2.data.forEach((entry, idx) => {
1781
+ raw.data.forEach((entry, idx) => {
1764
1782
  if (entry.chainId === validChainId) {
1765
1783
  matched.push({ entry, rawIdx: offset + idx });
1766
1784
  }
1767
1785
  });
1768
- offset += raw2.data.length;
1769
- if (raw2.data.length < backendPageSize) {
1786
+ offset += raw.data.length;
1787
+ if (raw.data.length < backendPageSize) {
1770
1788
  backendExhausted = true;
1771
1789
  break;
1772
1790
  }
@@ -1788,18 +1806,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
1788
1806
  }
1789
1807
  async getUserProfile(state, chainId) {
1790
1808
  await this.connectAuth(state, chainId);
1791
- const raw2 = await this.sdk.getUserDetails();
1809
+ const raw = await this.sdk.getUserDetails();
1792
1810
  debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
1793
1811
  asset: "USDC (default \u2014 no asset passed)",
1794
- splitting: raw2.splitting,
1795
- minSplits: raw2.minSplits,
1796
- strategy: raw2.strategy,
1797
- chains: raw2.chains,
1798
- protocolCount: raw2.protocols?.length,
1799
- hasActiveSessionKey: raw2.hasActiveSessionKey,
1800
- smartWallet: raw2.smartWallet
1812
+ splitting: raw.splitting,
1813
+ minSplits: raw.minSplits,
1814
+ strategy: raw.strategy,
1815
+ chains: raw.chains,
1816
+ protocolCount: raw.protocols?.length,
1817
+ hasActiveSessionKey: raw.hasActiveSessionKey,
1818
+ smartWallet: raw.smartWallet
1801
1819
  });
1802
- return mapUserProfile(raw2, this.connectedAddress);
1820
+ return mapUserProfile(raw, this.connectedAddress);
1803
1821
  }
1804
1822
  async ensureAutoSelectProtocols(state, chainId, asset) {
1805
1823
  await this.connectAuth(state, chainId);
@@ -1818,151 +1836,184 @@ var ZyfaiAgent = class _ZyfaiAgent {
1818
1836
  }
1819
1837
  // --- IAgent: Discovery (no wallet required) ---
1820
1838
  async getAgentApy(days, options) {
1821
- const raw2 = await this.sdk.getAPYPerStrategy(
1839
+ const raw = await this.sdk.getAPYPerStrategy(
1822
1840
  false,
1823
1841
  DayFilterMapping[days],
1824
1842
  "aggressive",
1825
1843
  options?.chainId,
1826
1844
  options?.tokenSymbol
1827
1845
  );
1828
- return mapApyByStrategy(raw2);
1846
+ return mapApyByStrategy(raw);
1829
1847
  }
1830
1848
  };
1831
1849
 
1832
- // src/agents/yieldseeker/yieldseeker.agent.ts
1833
- import {
1834
- createPublicClient as createPublicClient3,
1835
- createWalletClient as createWalletClient2,
1836
- custom as custom2,
1837
- encodeFunctionData,
1838
- erc20Abi,
1839
- getAddress as getAddress2,
1840
- isAddress as isAddress2
1841
- } from "viem";
1842
- import { base as base3 } from "viem/chains";
1843
-
1844
- // src/lib/chain-guard.ts
1845
- var CHAIN_NAMES = {
1846
- 1: "Ethereum",
1847
- 8453: "Base",
1848
- 42161: "Arbitrum"
1849
- };
1850
- function chainName(chainId) {
1851
- return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
1852
- }
1853
- async function ensureWalletOnChain(pub, wallet, expected) {
1854
- const actual = await pub.getChainId();
1855
- if (actual === expected) return;
1850
+ // src/lib/health-report.ts
1851
+ var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1852
+ async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL) {
1856
1853
  try {
1857
- await wallet.switchChain({ id: expected });
1858
- } catch (error) {
1859
- throw new OwneyError(
1860
- "CHAIN_MISMATCH",
1861
- `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
1862
- {
1863
- expectedChainId: expected,
1864
- actualChainId: actual,
1865
- cause: error instanceof Error ? error.message : String(error)
1866
- }
1854
+ await fetch(`${baseUrl}/api/v1/agent/health-report`, {
1855
+ method: "POST",
1856
+ headers: {
1857
+ "Content-Type": "application/json",
1858
+ "x-owney-api-key": apiKey
1859
+ },
1860
+ body: JSON.stringify({
1861
+ agent_type: agentType,
1862
+ error_code: errorCode,
1863
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1864
+ })
1865
+ });
1866
+ } catch (err) {
1867
+ console.warn(
1868
+ `[owney-sdk] health-report failed for agent "${agentType}":`,
1869
+ err instanceof Error ? err.message : err
1867
1870
  );
1868
1871
  }
1869
- const after = await pub.getChainId();
1870
- if (after !== expected) {
1871
- throw new OwneyError(
1872
- "CHAIN_MISMATCH",
1873
- `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
1874
- { expectedChainId: expected, actualChainId: after }
1875
- );
1872
+ }
1873
+ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
1874
+ try {
1875
+ return await fn();
1876
+ } catch (err) {
1877
+ const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
1878
+ void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
1879
+ throw err;
1876
1880
  }
1877
1881
  }
1878
1882
 
1879
- // src/lib/transfer-auth.ts
1880
- import { bytesToHex } from "viem";
1881
- var ERC20_META_ABI = [
1882
- { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
1883
- { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
1884
- ];
1885
- function buildTransferWithAuthorizationTypedData(input) {
1886
- return {
1887
- domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
1888
- types: {
1889
- TransferWithAuthorization: [
1890
- { name: "from", type: "address" },
1891
- { name: "to", type: "address" },
1892
- { name: "value", type: "uint256" },
1893
- { name: "validAfter", type: "uint256" },
1894
- { name: "validBefore", type: "uint256" },
1895
- { name: "nonce", type: "bytes32" }
1896
- ]
1897
- },
1898
- primaryType: "TransferWithAuthorization",
1899
- message: input.message
1900
- };
1883
+ // src/lib/helpers/withdraw-helper.ts
1884
+ import { parseUnits } from "viem";
1885
+ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
1886
+ return agents.map((agent) => {
1887
+ const agentBalance = aggregated[agent.id];
1888
+ const tokenBalance = agentBalance?.tokens.find(
1889
+ (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
1890
+ );
1891
+ if (!tokenBalance) return { agent, balance: 0n };
1892
+ return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
1893
+ });
1901
1894
  }
1902
- async function readTokenMeta(publicClient, token) {
1903
- const [tokenName, tokenVersion] = await Promise.all([
1904
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
1905
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
1906
- ]);
1907
- return { tokenName, tokenVersion };
1895
+ function planProportionalShares(balances, requested, totalAvailable) {
1896
+ const plans = balances.map(({ agent, balance }) => ({
1897
+ agent,
1898
+ balance,
1899
+ planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
1900
+ }));
1901
+ const assigned = plans.reduce((s, p) => s + p.planned, 0n);
1902
+ let remainder = requested - assigned;
1903
+ const byHeadroom = [...plans].sort((a, b) => {
1904
+ const diff = b.balance - b.planned - (a.balance - a.planned);
1905
+ return diff > 0n ? 1 : diff < 0n ? -1 : 0;
1906
+ });
1907
+ for (const p of byHeadroom) {
1908
+ if (remainder === 0n) break;
1909
+ const headroom = p.balance - p.planned;
1910
+ if (headroom <= 0n) continue;
1911
+ const take = headroom < remainder ? headroom : remainder;
1912
+ p.planned += take;
1913
+ remainder -= take;
1914
+ }
1915
+ return plans;
1908
1916
  }
1909
- function randomAuthNonce() {
1910
- const bytes = new Uint8Array(32);
1911
- globalThis.crypto.getRandomValues(bytes);
1912
- return bytesToHex(bytes);
1917
+ function planDisabledDrain(disabled, requested) {
1918
+ const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
1919
+ const plans = [];
1920
+ let remaining = requested;
1921
+ for (const { agent, balance } of sorted) {
1922
+ if (remaining === 0n) {
1923
+ plans.push({ agent, balance, planned: 0n });
1924
+ continue;
1925
+ }
1926
+ const take = balance < remaining ? balance : remaining;
1927
+ plans.push({ agent, balance, planned: take });
1928
+ remaining -= take;
1929
+ }
1930
+ return { plans, remaining };
1913
1931
  }
1914
-
1915
- // src/lib/sponsor-client.ts
1916
- var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1917
- async function postPaymasterIntent(input) {
1918
- const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
1919
- let res;
1920
- try {
1921
- res = await fetch(`${base5}/api/v1/sponsor/paymaster-intent`, {
1922
- method: "POST",
1923
- headers: {
1924
- "content-type": "application/json",
1925
- "x-owney-api-key": input.apiKey,
1926
- Authorization: `Signature ${input.yieldseekerSignature}`
1927
- },
1928
- body: JSON.stringify(input.body)
1929
- });
1930
- } catch (networkError) {
1931
- throw new OwneyError(
1932
- "SPONSOR_REQUEST_FAILED",
1933
- `Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
1934
- { cause: String(networkError), safeToFallback: true }
1935
- );
1932
+ function redistributeShare(plans, fromIndex, amount, candidatePool) {
1933
+ const pool = candidatePool ?? plans.slice(fromIndex + 1);
1934
+ const candidates = pool.filter((c) => c.balance - c.planned > 0n);
1935
+ const totalHeadroom = candidates.reduce(
1936
+ (s, c) => s + (c.balance - c.planned),
1937
+ 0n
1938
+ );
1939
+ if (totalHeadroom === 0n) return;
1940
+ let distributed = 0n;
1941
+ for (const c of candidates) {
1942
+ const headroom = c.balance - c.planned;
1943
+ const proportional = headroom * amount / totalHeadroom;
1944
+ const give = proportional > headroom ? headroom : proportional;
1945
+ c.planned += give;
1946
+ distributed += give;
1936
1947
  }
1937
- const text = await res.text();
1938
- let parsed = null;
1939
- try {
1940
- parsed = JSON.parse(text);
1941
- } catch {
1948
+ let leftover = amount - distributed;
1949
+ for (const c of candidates) {
1950
+ if (leftover === 0n) break;
1951
+ const headroom = c.balance - c.planned;
1952
+ if (headroom <= 0n) continue;
1953
+ const take = headroom < leftover ? headroom : leftover;
1954
+ c.planned += take;
1955
+ leftover -= take;
1942
1956
  }
1943
- if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
1944
- throw new OwneyError(
1945
- "SPONSOR_REQUEST_FAILED",
1946
- `Paymaster intent API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1947
- {
1948
- statusCode: res.status,
1949
- responseBody: text.slice(0, 500),
1950
- safeToFallback: true
1957
+ }
1958
+ function sumWithdrawnAmount(results) {
1959
+ return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
1960
+ }
1961
+
1962
+ // src/lib/helpers/account-apy-helper.ts
1963
+ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1964
+ const sums = {};
1965
+ const weights = {};
1966
+ for (const id of Object.keys(agentApys)) {
1967
+ const cells = agentApys[id].apyByChainAndAsset;
1968
+ const balance = agentBalances[id] ?? 0;
1969
+ if (!cells || balance <= 0) continue;
1970
+ for (const [chainKey, perAsset] of Object.entries(cells)) {
1971
+ if (!perAsset) continue;
1972
+ const chainId = Number(chainKey);
1973
+ for (const [asset, apyValue] of Object.entries(perAsset)) {
1974
+ const apy = Number(apyValue ?? 0);
1975
+ if (apy === 0) continue;
1976
+ sums[chainId] ??= {};
1977
+ weights[chainId] ??= {};
1978
+ sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
1979
+ weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
1951
1980
  }
1952
- );
1981
+ }
1953
1982
  }
1954
- return parsed.data;
1983
+ const out = {};
1984
+ for (const chainKey of Object.keys(sums)) {
1985
+ const chainId = Number(chainKey);
1986
+ const perAssetOut = {};
1987
+ for (const asset of Object.keys(sums[chainId])) {
1988
+ const w = weights[chainId][asset];
1989
+ if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
1990
+ }
1991
+ if (Object.keys(perAssetOut).length > 0) {
1992
+ out[chainId] = perAssetOut;
1993
+ }
1994
+ }
1995
+ return out;
1955
1996
  }
1997
+
1998
+ // src/client.ts
1999
+ import {
2000
+ createPublicClient as createPublicClient2,
2001
+ createWalletClient,
2002
+ custom
2003
+ } from "viem";
2004
+ import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
2005
+
2006
+ // src/lib/sponsor-client.ts
2007
+ var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1956
2008
  async function postSponsorTransferAuth(input) {
1957
- const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2009
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL2;
1958
2010
  let res;
1959
2011
  try {
1960
- res = await fetch(`${base5}/api/v1/sponsor/erc20-transfer-auth`, {
2012
+ res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
1961
2013
  method: "POST",
1962
2014
  headers: {
1963
2015
  "content-type": "application/json",
1964
- "x-owney-api-key": input.apiKey,
1965
- ...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
2016
+ "x-owney-api-key": input.apiKey
1966
2017
  },
1967
2018
  body: JSON.stringify(input.body)
1968
2019
  });
@@ -1995,15 +2046,14 @@ async function postSponsorTransferAuth(input) {
1995
2046
  return parsed.data;
1996
2047
  }
1997
2048
  async function postSponsorPermit2Transfer(input) {
1998
- const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2049
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL2;
1999
2050
  let res;
2000
2051
  try {
2001
- res = await fetch(`${base5}/api/v1/sponsor/permit2-transfer`, {
2052
+ res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
2002
2053
  method: "POST",
2003
2054
  headers: {
2004
2055
  "content-type": "application/json",
2005
- "x-owney-api-key": input.apiKey,
2006
- ...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
2056
+ "x-owney-api-key": input.apiKey
2007
2057
  },
2008
2058
  body: JSON.stringify(input.body)
2009
2059
  });
@@ -2034,11 +2084,11 @@ async function postSponsorPermit2Transfer(input) {
2034
2084
  return parsed.data;
2035
2085
  }
2036
2086
  async function getSponsorRelayerAddress(input) {
2037
- const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2087
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL2;
2038
2088
  let res;
2039
2089
  try {
2040
2090
  res = await fetch(
2041
- `${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2091
+ `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2042
2092
  {
2043
2093
  headers: { "x-owney-api-key": input.apiKey }
2044
2094
  }
@@ -2070,2001 +2120,204 @@ async function getSponsorRelayerAddress(input) {
2070
2120
  return parsed.data.relayer;
2071
2121
  }
2072
2122
 
2073
- // src/lib/permit2.ts
2074
- import { bytesToHex as bytesToHex2 } from "viem";
2075
- var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2076
- var MAX_UINT256 = 2n ** 256n - 1n;
2077
- var ERC20_ALLOWANCE_ABI = [
2078
- {
2079
- type: "function",
2080
- name: "allowance",
2081
- stateMutability: "view",
2082
- inputs: [
2083
- { name: "owner", type: "address" },
2084
- { name: "spender", type: "address" }
2085
- ],
2086
- outputs: [{ name: "", type: "uint256" }]
2087
- },
2088
- {
2089
- type: "function",
2090
- name: "approve",
2091
- stateMutability: "nonpayable",
2092
- inputs: [
2093
- { name: "spender", type: "address" },
2094
- { name: "amount", type: "uint256" }
2095
- ],
2096
- outputs: [{ name: "", type: "bool" }]
2097
- },
2098
- {
2099
- type: "function",
2100
- name: "balanceOf",
2101
- stateMutability: "view",
2102
- inputs: [{ name: "account", type: "address" }],
2103
- outputs: [{ name: "", type: "uint256" }]
2104
- }
2105
- ];
2106
- function buildPermitTransferFromTypedData(input) {
2107
- return {
2108
- domain: {
2109
- name: "Permit2",
2110
- chainId: input.chainId,
2111
- verifyingContract: PERMIT2_ADDRESS
2112
- },
2113
- types: {
2114
- PermitTransferFrom: [
2115
- { name: "permitted", type: "TokenPermissions" },
2116
- { name: "spender", type: "address" },
2117
- { name: "nonce", type: "uint256" },
2118
- { name: "deadline", type: "uint256" }
2119
- ],
2120
- TokenPermissions: [
2121
- { name: "token", type: "address" },
2122
- { name: "amount", type: "uint256" }
2123
- ]
2124
- },
2125
- primaryType: "PermitTransferFrom",
2126
- message: input.message
2127
- };
2128
- }
2129
- function randomPermit2Nonce() {
2130
- const bytes = new Uint8Array(32);
2131
- globalThis.crypto.getRandomValues(bytes);
2132
- return BigInt(bytesToHex2(bytes));
2133
- }
2134
- async function readPermit2Allowance(publicClient, token, owner) {
2135
- return publicClient.readContract({
2136
- address: token,
2137
- abi: ERC20_ALLOWANCE_ABI,
2138
- functionName: "allowance",
2139
- args: [owner, PERMIT2_ADDRESS]
2140
- });
2141
- }
2142
- async function readErc20Balance(publicClient, token, owner) {
2143
- return publicClient.readContract({
2144
- address: token,
2145
- abi: ERC20_ALLOWANCE_ABI,
2146
- functionName: "balanceOf",
2147
- args: [owner]
2148
- });
2149
- }
2150
-
2151
- // src/lib/sponsored-deposit.ts
2152
- var AUTH_WINDOW_SECONDS = 15 * 60;
2153
- var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
2154
- function provideDepositVerificationContext(callback, context) {
2155
- callback[verificationSetter]?.(context);
2156
- }
2157
- function makeVerificationAwareDepositCallback(implementation) {
2158
- let nextVerification;
2159
- const callback = async (smartWallet, chainId, amount) => {
2160
- const verification = nextVerification;
2161
- nextVerification = void 0;
2162
- return implementation(smartWallet, chainId, amount, verification);
2163
- };
2164
- Object.defineProperty(callback, verificationSetter, {
2165
- value: (context) => {
2166
- nextVerification = context;
2167
- }
2168
- });
2169
- return callback;
2170
- }
2171
- function makeSponsoredDepositCallback(deps) {
2172
- const post = deps.httpPost ?? postSponsorTransferAuth;
2173
- return makeVerificationAwareDepositCallback(
2174
- async (smartWallet, chainId, amount, verification) => {
2175
- const cid = chainId;
2176
- const token = deps.tokenAddressByChain[cid];
2177
- if (!token) {
2178
- throw new OwneyError(
2179
- "CHAIN_UNSUPPORTED",
2180
- `No sponsored token configured for chain ${chainId}`
2181
- );
2182
- }
2183
- const pub = deps.getPublicClient(cid);
2184
- const wallet = deps.getWalletClient(cid);
2185
- await ensureWalletOnChain(pub, wallet, cid);
2186
- try {
2187
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2188
- if (balance < BigInt(amount)) {
2189
- throw new OwneyError(
2190
- "DEPOSIT_INSUFFICIENT_BALANCE",
2191
- "Insufficient balance for this deposit.",
2192
- { token, chainId: cid, balance: balance.toString(), amount }
2193
- );
2194
- }
2195
- } catch (err) {
2196
- if (err instanceof OwneyError) throw err;
2197
- console.warn(
2198
- "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
2199
- err instanceof Error ? err.message : String(err)
2200
- );
2201
- }
2202
- const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
2203
- const validAfter = 0n;
2204
- const validBefore = BigInt(
2205
- Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
2206
- );
2207
- const nonce = randomAuthNonce();
2208
- const typedData = buildTransferWithAuthorizationTypedData({
2209
- token,
2210
- chainId: cid,
2211
- tokenName,
2212
- tokenVersion,
2213
- message: {
2214
- from: deps.ownerAddress,
2215
- to: smartWallet,
2216
- value: BigInt(amount),
2217
- validAfter,
2218
- validBefore,
2219
- nonce
2220
- }
2221
- });
2222
- const authSignature = await wallet.signTypedData({
2223
- account: deps.ownerAddress,
2224
- ...typedData
2225
- });
2226
- deps.onApproved?.();
2227
- const result = await post({
2228
- baseUrl: deps.baseUrl,
2229
- apiKey: deps.apiKey,
2230
- ...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
2231
- body: {
2232
- chainId: cid,
2233
- token,
2234
- from: deps.ownerAddress,
2235
- to: smartWallet,
2236
- value: amount,
2237
- validAfter: validAfter.toString(),
2238
- validBefore: validBefore.toString(),
2239
- nonce,
2240
- authSignature,
2241
- tokenName,
2242
- tokenVersion,
2243
- ...verification?.agentId === "yieldseeker" ? {
2244
- yieldseekerUserId: verification.userId,
2245
- yieldseekerAgentId: verification.yieldseekerAgentId
2246
- } : {}
2247
- }
2248
- });
2249
- return result.txHash;
2250
- }
2251
- );
2252
- }
2253
-
2254
- // src/agents/yieldseeker/yieldseeker.auth.ts
2255
- import { SiweMessage, generateNonce } from "siwe";
2256
- import {
2257
- createPublicClient as createPublicClient2,
2258
- createWalletClient,
2259
- custom,
2260
- getAddress
2261
- } from "viem";
2262
- import { base as base2 } from "viem/chains";
2263
-
2264
- // src/agents/yieldseeker/yieldseeker.auth-cache.ts
2265
- var KEY_PREFIX2 = "owney.yieldseeker.session.v4";
2266
- var INVALIDATED_KEY_PREFIXES = [
2267
- "owney.yieldseeker.session",
2268
- "owney.yieldseeker.session.v3"
2269
- ];
2270
- var storage2 = () => {
2271
- if (typeof window === "undefined") return null;
2272
- try {
2273
- return window.localStorage;
2274
- } catch {
2275
- return null;
2276
- }
2277
- };
2278
- var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
2279
- var invalidatedKeys = (address, chainId) => INVALIDATED_KEY_PREFIXES.map(
2280
- (prefix) => `${prefix}:${address.toLowerCase()}:${chainId}`
2281
- );
2282
- var clearInvalidatedSessions = (store, address, chainId) => {
2283
- for (const key2 of invalidatedKeys(address, chainId)) {
2284
- memorySessions2.delete(key2);
2285
- try {
2286
- store?.removeItem(key2);
2287
- } catch {
2288
- }
2289
- }
2290
- };
2291
- var memorySessions2 = /* @__PURE__ */ new Map();
2292
- var isValidSession = (session) => {
2293
- if (!session?.token) return false;
2294
- try {
2295
- const parsed = JSON.parse(atob(session.token));
2296
- return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
2297
- } catch {
2298
- return false;
2299
- }
2300
- };
2301
- var readYieldseekerSession = (address, chainId) => {
2302
- if (typeof window === "undefined") return null;
2303
- const key2 = buildKey2(address, chainId);
2304
- const store = storage2();
2305
- clearInvalidatedSessions(store, address, chainId);
2306
- let raw2 = null;
2307
- try {
2308
- raw2 = store?.getItem(key2) ?? null;
2309
- } catch {
2310
- raw2 = null;
2311
- }
2312
- if (raw2) {
2313
- try {
2314
- const parsed = JSON.parse(raw2);
2315
- if (isValidSession(parsed)) return parsed.token;
2316
- } catch {
2317
- }
2318
- memorySessions2.delete(key2);
2319
- try {
2320
- store?.removeItem(key2);
2321
- } catch {
2322
- }
2323
- return null;
2324
- }
2325
- const cached = memorySessions2.get(key2);
2326
- if (isValidSession(cached)) return cached.token;
2327
- if (cached) memorySessions2.delete(key2);
2328
- return null;
2329
- };
2330
- var writeYieldseekerSession = (address, chainId, token) => {
2331
- if (typeof window === "undefined") return;
2332
- const session = { token };
2333
- if (!isValidSession(session)) return;
2334
- const key2 = buildKey2(address, chainId);
2335
- memorySessions2.set(key2, session);
2336
- const store = storage2();
2337
- try {
2338
- store?.setItem(key2, JSON.stringify(session));
2339
- } catch {
2340
- }
2341
- };
2342
- var clearYieldseekerSession = (address, chainId) => {
2343
- const key2 = buildKey2(address, chainId);
2344
- memorySessions2.delete(key2);
2345
- const store = storage2();
2346
- clearInvalidatedSessions(store, address, chainId);
2347
- try {
2348
- store?.removeItem(key2);
2349
- } catch {
2350
- }
2351
- };
2352
-
2353
- // src/agents/yieldseeker/yieldseeker.auth.ts
2354
- function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
2355
- return new SiweMessage({
2356
- domain: "yieldseeker.xyz",
2357
- address: getAddress(address),
2358
- uri: "https://yieldseeker.xyz",
2359
- version: "1",
2360
- chainId,
2361
- nonce: (dependencies.nonce ?? generateNonce)(),
2362
- issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
2363
- }).prepareMessage();
2364
- }
2365
- function encodeYieldseekerAuthToken(token) {
2366
- const bytes = new TextEncoder().encode(JSON.stringify(token));
2367
- let binary = "";
2368
- for (const byte of bytes) binary += String.fromCharCode(byte);
2369
- return btoa(binary);
2370
- }
2371
- var YieldseekerAuth = class {
2372
- constructor(dependencies = {}) {
2373
- this.dependencies = dependencies;
2374
- }
2375
- dependencies;
2376
- tokens = /* @__PURE__ */ new Map();
2377
- pending = /* @__PURE__ */ new Map();
2378
- scopes = /* @__PURE__ */ new Map();
2379
- key(state, chainId) {
2380
- return `${state.walletAddress.toLowerCase()}:${chainId}`;
2381
- }
2382
- async getToken(state, chainId) {
2383
- const key2 = this.key(state, chainId);
2384
- const scope = { address: state.walletAddress, chainId };
2385
- this.scopes.set(key2, scope);
2386
- const cached = this.tokens.get(key2);
2387
- if (cached) return cached;
2388
- const persisted = readYieldseekerSession(scope.address, scope.chainId);
2389
- if (persisted) {
2390
- this.tokens.set(key2, persisted);
2391
- return persisted;
2392
- }
2393
- const inFlight = this.pending.get(key2);
2394
- if (inFlight) return inFlight;
2395
- const request = this.sign(state, chainId).then((token) => {
2396
- this.tokens.set(key2, token);
2397
- writeYieldseekerSession(scope.address, scope.chainId, token);
2398
- return token;
2399
- });
2400
- this.pending.set(key2, request);
2401
- try {
2402
- return await request;
2403
- } finally {
2404
- this.pending.delete(key2);
2405
- }
2406
- }
2407
- clear(state, chainId) {
2408
- if (!state || chainId === void 0) {
2409
- for (const scope of this.scopes.values()) {
2410
- clearYieldseekerSession(scope.address, scope.chainId);
2411
- }
2412
- this.tokens.clear();
2413
- this.pending.clear();
2414
- this.scopes.clear();
2415
- return;
2416
- }
2417
- const key2 = this.key(state, chainId);
2418
- this.tokens.delete(key2);
2419
- this.pending.delete(key2);
2420
- this.scopes.delete(key2);
2421
- clearYieldseekerSession(state.walletAddress, chainId);
2422
- }
2423
- async sign(state, chainId) {
2424
- const account = getAddress(state.walletAddress);
2425
- const publicClient = createPublicClient2({
2426
- chain: base2,
2427
- transport: custom(state.provider)
2428
- });
2429
- const walletClient = createWalletClient({
2430
- account,
2431
- chain: base2,
2432
- transport: custom(state.provider)
2433
- });
2434
- await ensureWalletOnChain(
2435
- publicClient,
2436
- walletClient,
2437
- 8453
2438
- );
2439
- const message = createYieldseekerSiweMessage(
2440
- account,
2441
- chainId,
2442
- this.dependencies
2443
- );
2444
- const signature = await walletClient.signMessage({ account, message });
2445
- return encodeYieldseekerAuthToken({ message, signature });
2446
- }
2447
- };
2448
-
2449
- // src/agents/yieldseeker/yieldseeker.identity-cache.ts
2450
- var KEY_PREFIX3 = "owney.yieldseeker.identity.v1";
2451
- var YIELDSEEKER_IDENTITY_TTL_MS = 24 * 60 * 60 * 1e3;
2452
- var memoryIdentities = /* @__PURE__ */ new Map();
2453
- var storage3 = () => {
2454
- if (typeof window === "undefined") return null;
2455
- try {
2456
- return window.localStorage;
2457
- } catch {
2458
- return null;
2459
- }
2460
- };
2461
- var keyFor = (walletAddress, chainId) => `${KEY_PREFIX3}:${walletAddress.toLowerCase()}:${chainId}`;
2462
- function valid(value, walletAddress, chainId, now) {
2463
- return Boolean(
2464
- value && typeof value.userId === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value.userId) && value.walletAddress.toLowerCase() === walletAddress.toLowerCase() && value.chainId === chainId && Number.isSafeInteger(value.expiresAt) && value.expiresAt > now
2465
- );
2466
- }
2467
- function readYieldseekerIdentity(walletAddress, chainId, now = Date.now()) {
2468
- if (typeof window === "undefined") return null;
2469
- const key2 = keyFor(walletAddress, chainId);
2470
- const store = storage3();
2471
- let parsed = null;
2472
- try {
2473
- const raw2 = store?.getItem(key2);
2474
- parsed = raw2 ? JSON.parse(raw2) : null;
2475
- } catch {
2476
- parsed = null;
2477
- }
2478
- const candidate = parsed ?? memoryIdentities.get(key2);
2479
- if (valid(candidate, walletAddress, chainId, now)) {
2480
- memoryIdentities.set(key2, candidate);
2481
- return { userId: candidate.userId };
2482
- }
2483
- memoryIdentities.delete(key2);
2484
- try {
2485
- store?.removeItem(key2);
2486
- } catch {
2487
- }
2488
- return null;
2489
- }
2490
- function writeYieldseekerIdentity(walletAddress, chainId, userId, now = Date.now()) {
2491
- if (typeof window === "undefined") return;
2492
- const identity = {
2493
- userId,
2494
- walletAddress,
2495
- chainId,
2496
- expiresAt: now + YIELDSEEKER_IDENTITY_TTL_MS
2497
- };
2498
- if (!valid(identity, walletAddress, chainId, now)) return;
2499
- const key2 = keyFor(walletAddress, chainId);
2500
- memoryIdentities.set(key2, identity);
2501
- try {
2502
- storage3()?.setItem(key2, JSON.stringify(identity));
2503
- } catch {
2504
- }
2505
- }
2506
- function clearYieldseekerIdentity(walletAddress, chainId) {
2507
- const key2 = keyFor(walletAddress, chainId);
2508
- memoryIdentities.delete(key2);
2509
- try {
2510
- storage3()?.removeItem(key2);
2511
- } catch {
2512
- }
2513
- }
2514
-
2515
- // src/agents/yieldseeker/yieldseeker.client.ts
2516
- var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2517
- function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
2518
- return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
2519
- }
2520
- var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
2521
- var YieldseekerApiError = class extends Error {
2522
- constructor(status, providerCode, responseFields) {
2523
- super(`Yieldseeker request failed (${status}): ${providerCode}`);
2524
- this.status = status;
2525
- this.providerCode = providerCode;
2526
- this.responseFields = responseFields;
2527
- this.name = "YieldseekerApiError";
2528
- }
2529
- status;
2530
- providerCode;
2531
- responseFields;
2532
- get isAuthenticationError() {
2533
- return this.status === 401 || this.status === 403;
2534
- }
2535
- };
2536
- function providerError(body, fallback) {
2537
- if (!body || typeof body !== "object") return { code: fallback };
2538
- const record = body;
2539
- return {
2540
- code: typeof record.message === "string" ? record.message : fallback,
2541
- fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
2542
- };
2543
- }
2544
- var YieldseekerApiClient = class {
2545
- constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch) {
2546
- this.owneyApiKey = owneyApiKey;
2547
- this.baseUrl = baseUrl;
2548
- this.fetchFn = fetchFn;
2549
- }
2550
- owneyApiKey;
2551
- baseUrl;
2552
- fetchFn;
2553
- async request(path, options = {}) {
2554
- const controller = new AbortController();
2555
- const timer = setTimeout(
2556
- () => controller.abort(),
2557
- options.timeoutMs ?? 15e3
2558
- );
2559
- try {
2560
- const response = await this.fetchFn(`${this.baseUrl}${path}`, {
2561
- method: options.method ?? "GET",
2562
- headers: {
2563
- "Content-Type": "application/json",
2564
- "x-owney-api-key": this.owneyApiKey,
2565
- ...options.signature ? { Authorization: `Signature ${options.signature}` } : {}
2566
- },
2567
- body: options.body ? JSON.stringify(options.body) : void 0,
2568
- signal: controller.signal
2569
- });
2570
- const payload = await response.json().catch(() => null);
2571
- if (!response.ok) {
2572
- const error = providerError(payload, `HTTP_${response.status}`);
2573
- throw new YieldseekerApiError(
2574
- response.status,
2575
- error.code,
2576
- error.fields
2577
- );
2578
- }
2579
- if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
2580
- return payload.data;
2581
- }
2582
- return payload;
2583
- } catch (error) {
2584
- if (error instanceof YieldseekerApiError) throw error;
2585
- if (error instanceof DOMException && error.name === "AbortError") {
2586
- throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
2587
- }
2588
- throw new YieldseekerApiError(0, "NETWORK_ERROR", {
2589
- cause: error instanceof Error ? error.message : String(error)
2590
- });
2591
- } finally {
2592
- clearTimeout(timer);
2593
- }
2594
- }
2595
- };
2596
-
2597
- // src/agents/yieldseeker/yieldseeker.mapper.ts
2598
- import { formatUnits, isAddress } from "viem";
2599
-
2600
- // src/agents/yieldseeker/yieldseeker.types.ts
2601
- var YIELDSEEKER_ASSET_METADATA = {
2602
- USDC: {
2603
- address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2604
- decimals: 6
2605
- },
2606
- WETH: {
2607
- address: "0x4200000000000000000000000000000000000006",
2608
- decimals: 18
2609
- }
2610
- };
2611
-
2612
- // src/agents/yieldseeker/yieldseeker.mapper.ts
2613
- function invalid(endpoint, detail) {
2614
- throw new OwneyError(
2615
- "AGENT_INVALID_RESPONSE",
2616
- `Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
2617
- { endpoint, detail },
2618
- "yieldseeker"
2619
- );
2620
- }
2621
- function raw(value, endpoint) {
2622
- if (typeof value !== "string" || !/^-?[0-9]+$/.test(value)) {
2623
- return invalid(endpoint, "expected a base-10 integer string");
2624
- }
2625
- return BigInt(value);
2626
- }
2627
- function decimal(value, decimals, endpoint) {
2628
- return formatUnits(raw(value, endpoint), decimals);
2629
- }
2630
- function usd(rawAmount, decimals, price) {
2631
- return Number(formatUnits(rawAmount, decimals)) * price;
2632
- }
2633
- function percent(value) {
2634
- const result = Number(value);
2635
- return Number.isFinite(result) ? result * 100 : 0;
2636
- }
2637
- var YIELDSEEKER_POSITIVE_YIELD_FEE_RATE = 0.1;
2638
- function publicApyAfterYieldseekerFee(value) {
2639
- const grossPercent = percent(value);
2640
- if (grossPercent <= 0) return grossPercent;
2641
- const netPercent = grossPercent * (1 - YIELDSEEKER_POSITIVE_YIELD_FEE_RATE);
2642
- return Math.round(netPercent * 1e12) / 1e12;
2643
- }
2644
- function riskAdjustedApyForDays(option, days) {
2645
- if (days === "7D") return option.riskAdjustedApy7dAverage;
2646
- if (days === "30D") return option.riskAdjustedApy30dAverage;
2647
- return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
2648
- }
2649
- function assetAddressValue(record, address) {
2650
- const entry = Object.entries(record).find(
2651
- ([key2]) => key2.toLowerCase() === address.toLowerCase()
2652
- );
2653
- return entry?.[1] ?? "0";
2654
- }
2655
- function position(value, asset) {
2656
- const option = value?.yieldOption;
2657
- if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress(option.address)) {
2658
- return invalid("yield positions", "missing vault metadata");
2659
- }
2660
- const decimals = YIELDSEEKER_ASSET_METADATA[asset].decimals;
2661
- return {
2662
- chain: "BASE",
2663
- protocol: option.provider,
2664
- protocolId: option.address,
2665
- pool: option.name,
2666
- asset,
2667
- amount: decimal(value.assetsRaw, decimals, "yield positions"),
2668
- amountRaw: String(value.assetsRaw),
2669
- apy: percent(option.riskAdjustedApy),
2670
- tvl: Number(option.totalDepositsUsd),
2671
- liquidity: Number(option.withdrawableDepositsUsd)
2672
- };
2673
- }
2674
- function mapYieldseekerBalances(contexts) {
2675
- const tokens = [];
2676
- const positions = [];
2677
- let totalUsd = 0;
2678
- for (const context of contexts) {
2679
- const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
2680
- const idle = assetAddressValue(
2681
- context.snapshot.tokenBalances,
2682
- metadata.address
2683
- );
2684
- tokens.push({
2685
- chain: "BASE",
2686
- chainId: 8453,
2687
- asset: context.asset,
2688
- amount: decimal(idle, metadata.decimals, "snapshot")
2689
- });
2690
- positions.push(
2691
- ...context.positions.map((entry) => position(entry, context.asset))
2692
- );
2693
- totalUsd += usd(
2694
- raw(context.snapshot.totalValueBase, "snapshot"),
2695
- context.snapshot.baseAssetDecimals,
2696
- context.snapshot.baseAssetPriceUsd
2697
- );
2698
- }
2699
- return {
2700
- ...contexts.length === 1 ? { smartWallet: contexts[0].wallet.walletAddress } : {},
2701
- totalBalance: String(totalUsd),
2702
- totalBalanceAsset: "usdc",
2703
- tokens,
2704
- positions
2705
- };
2706
- }
2707
- function mapYieldseekerEarnings(contexts) {
2708
- const tokens = [];
2709
- let lifetimeEarnings = 0;
2710
- for (const context of contexts) {
2711
- const amount = raw(context.snapshot.cumulativeYieldBase, "snapshot");
2712
- tokens.push({
2713
- chain: "BASE",
2714
- chainId: 8453,
2715
- asset: context.asset,
2716
- amount: formatUnits(amount, context.snapshot.baseAssetDecimals)
2717
- });
2718
- lifetimeEarnings += usd(
2719
- amount,
2720
- context.snapshot.baseAssetDecimals,
2721
- context.snapshot.baseAssetPriceUsd
2722
- );
2723
- }
2724
- return {
2725
- smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
2726
- lifetimeEarnings,
2727
- tokens
2728
- };
2729
- }
2730
- function apyForDays(snapshot, days) {
2731
- if (days === "7D") return percent(snapshot.apy7d);
2732
- if (days === "30D") return percent(snapshot.apy30d);
2733
- return percent((snapshot.apy7d + snapshot.apy30d) / 2);
2734
- }
2735
- function dailyApy(point) {
2736
- const total = raw(point.totalValueBase, "historic position");
2737
- const earned = raw(point.dailyYieldBase, "historic position");
2738
- const principal = total - earned;
2739
- if (principal <= 0n || earned === 0n) return 0;
2740
- return Number(earned) / Number(principal) * 365 * 100;
2741
- }
2742
- function aggregateHistory(contexts, dayCount) {
2743
- const byDate = /* @__PURE__ */ new Map();
2744
- for (const context of contexts) {
2745
- const points = context.historic?.dailyYieldSnapshots ?? [];
2746
- for (const point of points.slice(-dayCount)) {
2747
- const valueUsd = usd(
2748
- raw(point.totalValueBase, "historic position"),
2749
- point.baseAssetDecimals,
2750
- point.baseAssetPriceUsd
2751
- );
2752
- const current = byDate.get(point.date) ?? { weighted: 0, valueUsd: 0 };
2753
- current.weighted += dailyApy(point) * valueUsd;
2754
- current.valueUsd += valueUsd;
2755
- byDate.set(point.date, current);
2756
- }
2757
- }
2758
- return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
2759
- date,
2760
- apy: value.valueUsd > 0 ? value.weighted / value.valueUsd : 0
2761
- }));
2762
- }
2763
- function mapYieldseekerApy(walletAddress, contexts, days) {
2764
- let weighted = 0;
2765
- let totalUsd = 0;
2766
- const byAsset = {};
2767
- for (const context of contexts) {
2768
- const valueUsd = usd(
2769
- raw(context.snapshot.totalValueBase, "snapshot"),
2770
- context.snapshot.baseAssetDecimals,
2771
- context.snapshot.baseAssetPriceUsd
2772
- );
2773
- const apy = apyForDays(context.snapshot, days);
2774
- weighted += apy * valueUsd;
2775
- totalUsd += valueUsd;
2776
- byAsset[context.asset] = apy;
2777
- }
2778
- const dayCount = Number(days.slice(0, -1));
2779
- return {
2780
- walletAddress,
2781
- weightedApyAfterFee: totalUsd > 0 ? weighted / totalUsd : 0,
2782
- apyByChainAndAsset: { 8453: byAsset },
2783
- history: aggregateHistory(contexts, dayCount)
2784
- };
2785
- }
2786
- function actionType(value) {
2787
- const normalized = value.toLowerCase();
2788
- if (normalized.includes("deposit")) return "Deposit";
2789
- if (normalized.includes("withdraw")) return "Withdraw";
2790
- if (normalized.includes("yield") || normalized.includes("earn"))
2791
- return "Earned";
2792
- return "Rebalance";
2793
- }
2794
- function transactionHashes(details) {
2795
- if (!details) return [];
2796
- const values = [
2797
- details.transactionHash,
2798
- details.txHash,
2799
- ...Array.isArray(details.transactionHashes) ? details.transactionHashes : [],
2800
- ...Array.isArray(details.txHashes) ? details.txHashes : []
2801
- ];
2802
- return values.filter(
2803
- (value) => typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value)
2804
- ).filter((value, index, all) => all.indexOf(value) === index);
2805
- }
2806
- function actionEntry(action) {
2807
- if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
2808
- return {
2809
- agent: "yieldseeker",
2810
- action: actionType(action.actionType),
2811
- date: action.createdDate,
2812
- oldApy: null,
2813
- newApy: null,
2814
- transactions: [
2815
- {
2816
- txHashes: transactionHashes(action.details),
2817
- chainId: 8453
2818
- }
2819
- ],
2820
- rebalanceLog: []
2821
- };
2822
- }
2823
- function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses) {
2824
- const from = movement.fromAddress.toLowerCase();
2825
- const to = movement.toAddress.toLowerCase();
2826
- const owner = ownerAddress.toLowerCase();
2827
- const agentWallet = wallet.walletAddress.toLowerCase();
2828
- const baseAsset = agent.assetAddress.toLowerCase();
2829
- if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
2830
- return void 0;
2831
- }
2832
- let action;
2833
- if (from === owner && to === agentWallet) {
2834
- action = "Top up";
2835
- } else if (from === agentWallet && to === owner) {
2836
- action = "Withdraw";
2837
- } else if (from === agentWallet && vaultAddresses.has(to)) {
2838
- action = "Deposit";
2839
- }
2840
- if (!action) return void 0;
2841
- return {
2842
- agent: "yieldseeker",
2843
- action,
2844
- date: movement.blockDate,
2845
- oldApy: null,
2846
- newApy: null,
2847
- transactions: [
2848
- {
2849
- txHashes: [movement.transactionHash],
2850
- chainId: agent.chainId,
2851
- tokenSymbol: asset,
2852
- amount: decimal(
2853
- movement.assetAmount,
2854
- YIELDSEEKER_ASSET_METADATA[asset].decimals,
2855
- "historic position"
2856
- )
2857
- }
2858
- ],
2859
- rebalanceLog: []
2860
- };
2861
- }
2862
- function mapYieldseekerHistory(contexts, options) {
2863
- const entries = contexts.flatMap((context) => {
2864
- const vaultAddresses = new Set(
2865
- context.positions.map(
2866
- (position2) => position2.yieldOption.address.toLowerCase()
2867
- )
2868
- );
2869
- return [
2870
- ...(context.historic?.movements ?? []).map(
2871
- (movement) => movementEntry(
2872
- movement,
2873
- context.wallet,
2874
- context.agent,
2875
- context.asset,
2876
- options.ownerAddress,
2877
- vaultAddresses
2878
- )
2879
- ),
2880
- ...(context.actions ?? []).map(actionEntry)
2881
- ].filter((entry) => entry !== void 0);
2882
- });
2883
- const filtered = entries.filter(
2884
- (entry) => (!options.fromDate || entry.date >= options.fromDate) && (!options.toDate || entry.date <= options.toDate)
2885
- ).filter((entry, index, all) => {
2886
- const transactionHash = entry.transactions[0]?.txHashes[0];
2887
- if (!transactionHash) return true;
2888
- return all.findIndex(
2889
- (candidate) => candidate.action === entry.action && candidate.transactions[0]?.txHashes[0] === transactionHash
2890
- ) === index;
2891
- }).sort((left, right) => right.date.localeCompare(left.date));
2892
- return {
2893
- data: filtered.slice(0, options.limit),
2894
- // v1 returns the whole action/movement collection and defines no cursor.
2895
- // Report a terminal page so callers never loop over the same prefix.
2896
- hasMore: false
2897
- };
2898
- }
2899
- function mapYieldseekerProfile(address, contexts) {
2900
- const protocols = /* @__PURE__ */ new Set();
2901
- for (const context of contexts) {
2902
- for (const current of context.positions) {
2903
- if (current.yieldOption?.provider) {
2904
- protocols.add(String(current.yieldOption.provider));
2905
- }
2906
- }
2907
- }
2908
- return {
2909
- address,
2910
- smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
2911
- chains: contexts.length > 0 ? [8453] : [],
2912
- hasActiveSessionKey: contexts.some(
2913
- (context) => context.wallet.initializedDate != null
2914
- ),
2915
- protocols: [...protocols]
2916
- };
2917
- }
2918
- function mapYieldseekerAgentApy(options, days) {
2919
- const perAsset = {};
2920
- const all = [];
2921
- for (const entry of options) {
2922
- const apys = entry.yieldOptions.map(
2923
- (option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
2924
- ).filter(Number.isFinite);
2925
- if (apys.length === 0) continue;
2926
- const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
2927
- perAsset[entry.asset] = average;
2928
- all.push(average);
2929
- }
2930
- return {
2931
- averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
2932
- detailedApys: { apyPerAsset: { 8453: perAsset } }
2933
- };
2934
- }
2935
-
2936
- // src/agents/yieldseeker/yieldseeker.agent.ts
2937
- var OWNEY_AGENT_NAME = "owney";
2938
- var YIELDSEEKER_USERNAME_PREFIX = "owney_";
2939
- var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
2940
- var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
2941
- function generateYieldseekerUsername() {
2942
- const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
2943
- return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
2944
- }
2945
- function isUsernameConflict(error) {
2946
- if (!(error instanceof YieldseekerApiError)) return false;
2947
- const code = error.providerCode.toUpperCase();
2948
- return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
2949
- }
2950
- var YIELDSEEKER_AGENT_WALLET_ABI = [
2951
- {
2952
- type: "function",
2953
- name: "withdrawAssetToUser",
2954
- stateMutability: "nonpayable",
2955
- inputs: [
2956
- { name: "recipient", type: "address" },
2957
- { name: "asset", type: "address" },
2958
- { name: "amount", type: "uint256" }
2959
- ],
2960
- outputs: []
2961
- },
2962
- {
2963
- type: "function",
2964
- name: "withdrawAllAssetToUser",
2965
- stateMutability: "nonpayable",
2966
- inputs: [
2967
- { name: "recipient", type: "address" },
2968
- { name: "asset", type: "address" }
2969
- ],
2970
- outputs: []
2971
- }
2972
- ];
2973
- function query(params) {
2974
- const search = new URLSearchParams();
2975
- for (const [key2, value] of Object.entries(params)) {
2976
- if (value !== void 0) search.set(key2, String(value));
2977
- }
2978
- const encoded = search.toString();
2979
- return encoded ? `?${encoded}` : "";
2980
- }
2981
- var YieldseekerAgent = class {
2982
- id = "yieldseeker";
2983
- balanceComposition = "tokens-plus-positions";
2984
- supportedChainIds = [8453];
2985
- supportedAssets = [
2986
- {
2987
- chainId: 8453,
2988
- chain: "BASE",
2989
- assets: [
2990
- { symbol: "USDC", minDepositAmount: "10000000" },
2991
- { symbol: "WETH", minDepositAmount: "1" }
2992
- ]
2993
- }
2994
- ];
2995
- api;
2996
- auth;
2997
- transactionExecutor;
2998
- unwindReceiptWaiter;
2999
- agentContexts = /* @__PURE__ */ new Map();
3000
- users = /* @__PURE__ */ new Map();
3001
- pendingAgents = /* @__PURE__ */ new Map();
3002
- constructor(owneyApiKey, options = {}) {
3003
- this.api = new YieldseekerApiClient(
3004
- owneyApiKey,
3005
- options.baseUrl ?? getYieldseekerProxyBaseUrl(),
3006
- options.fetchFn
3007
- );
3008
- this.auth = new YieldseekerAuth(options.auth);
3009
- this.transactionExecutor = options.transactionExecutor;
3010
- this.unwindReceiptWaiter = options.unwindReceiptWaiter;
3011
- }
3012
- async disconnect() {
3013
- this.auth.clear();
3014
- for (const key2 of this.users.keys()) {
3015
- const [walletAddress, chainId] = key2.split(":");
3016
- clearYieldseekerIdentity(walletAddress, Number(chainId));
3017
- }
3018
- this.users.clear();
3019
- this.agentContexts.clear();
3020
- this.pendingAgents.clear();
3021
- }
3022
- async activateAgent(state, chainId, asset) {
3023
- this.assertChain(chainId);
3024
- const targetAsset = asset ?? "USDC";
3025
- this.assertAsset(targetAsset);
3026
- await this.ensureAgent(state, chainId, targetAsset);
3027
- }
3028
- async deposit(state, chainId, amount, asset, depositCallback) {
3029
- this.assertChain(chainId);
3030
- this.assertAsset(asset);
3031
- if (BigInt(amount) <= 0n) {
3032
- throw new OwneyError(
3033
- "DEPOSIT_AMOUNT_BELOW_MINIMUM",
3034
- "Yieldseeker deposits must be greater than zero.",
3035
- { amount, minDepositAmount: "1" },
3036
- this.id
3037
- );
3038
- }
3039
- const context = await this.ensureAgent(state, chainId, asset);
3040
- let txHash;
3041
- try {
3042
- if (depositCallback) {
3043
- provideDepositVerificationContext(depositCallback, {
3044
- agentId: "yieldseeker",
3045
- signature: await this.auth.getToken(state, chainId),
3046
- userId: context.user.userId,
3047
- yieldseekerAgentId: context.agent.agentId
3048
- });
3049
- txHash = await depositCallback(
3050
- context.wallet.walletAddress,
3051
- chainId,
3052
- amount
3053
- );
3054
- await this.waitForReceipt(state, chainId, txHash);
3055
- } else {
3056
- txHash = await this.submitTransaction(state, chainId, {
3057
- from: getAddress2(state.walletAddress),
3058
- to: YIELDSEEKER_ASSET_METADATA[asset].address,
3059
- data: encodeFunctionData({
3060
- abi: erc20Abi,
3061
- functionName: "transfer",
3062
- args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
3063
- }),
3064
- value: "0",
3065
- chainId
3066
- });
3067
- }
3068
- await this.deployAfterFunding(state, chainId, context);
3069
- } finally {
3070
- await this.refreshSnapshotAfterMovement(
3071
- state,
3072
- chainId,
3073
- context,
3074
- "deposit"
3075
- );
3076
- }
3077
- return {
3078
- txHash,
3079
- smartWallet: context.wallet.walletAddress,
3080
- amount
3081
- };
3082
- }
3083
- async withdraw(state, chainId, asset, amount) {
3084
- this.assertChain(chainId);
3085
- this.assertAsset(asset);
3086
- if (amount !== void 0 && BigInt(amount) <= 0n) {
3087
- throw new OwneyError(
3088
- "WITHDRAW_FAILED",
3089
- "Yieldseeker withdrawals must be greater than zero.",
3090
- { amount },
3091
- this.id
3092
- );
3093
- }
3094
- const context = await this.findAgent(state, chainId, asset);
3095
- if (!context) {
3096
- throw new OwneyError(
3097
- "WITHDRAW_INSUFFICIENT_BALANCE",
3098
- `No Yieldseeker ${asset} agent exists for this wallet.`,
3099
- { asset, available: "0" },
3100
- this.id
3101
- );
3102
- }
3103
- try {
3104
- const portfolio = await this.loadPortfolioContext(
3105
- state,
3106
- chainId,
3107
- context
3108
- );
3109
- const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3110
- const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
3111
- ([address]) => address.toLowerCase() === metadata.address.toLowerCase()
3112
- );
3113
- const idle = BigInt(idleEntry?.[1] ?? "0");
3114
- const deployed = portfolio.positions.reduce(
3115
- (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
3116
- 0n
3117
- );
3118
- const totalAvailable = idle + deployed;
3119
- const requested = amount === void 0 ? totalAvailable : BigInt(amount);
3120
- if (requested > totalAvailable) {
3121
- throw new OwneyError(
3122
- "WITHDRAW_INSUFFICIENT_BALANCE",
3123
- `Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
3124
- {
3125
- asset,
3126
- requested: requested.toString(),
3127
- available: totalAvailable.toString()
3128
- },
3129
- this.id
3130
- );
3131
- }
3132
- let remaining = requested > idle ? requested - idle : 0n;
3133
- for (const position2 of portfolio.positions) {
3134
- if (remaining === 0n) break;
3135
- const available = BigInt(position2.withdrawableAssetsRaw);
3136
- if (available <= 0n) continue;
3137
- const assetsRaw = available < remaining ? available : remaining;
3138
- const response = await this.walletRequest(
3139
- state,
3140
- chainId,
3141
- this.agentPath(context, "withdraw-from-position"),
3142
- {
3143
- method: "POST",
3144
- body: {
3145
- chainId,
3146
- vaultAddress: position2.yieldOption.address,
3147
- assetsRaw: assetsRaw.toString()
3148
- }
3149
- }
3150
- );
3151
- if (!this.isTransactionHash(response?.transactionHash)) {
3152
- throw this.invalidResponse("position withdrawal");
3153
- }
3154
- await this.waitForReceipt(state, chainId, response.transactionHash);
3155
- remaining -= assetsRaw;
3156
- }
3157
- if (remaining > 0n) {
3158
- throw this.invalidResponse("yield positions", {
3159
- reason: "Withdrawable positions could not cover the request.",
3160
- remaining: remaining.toString()
3161
- });
3162
- }
3163
- const account = getAddress2(state.walletAddress);
3164
- const txHash = await this.submitTransaction(state, chainId, {
3165
- from: account,
3166
- to: getAddress2(context.wallet.walletAddress),
3167
- data: amount === void 0 ? encodeFunctionData({
3168
- abi: YIELDSEEKER_AGENT_WALLET_ABI,
3169
- functionName: "withdrawAllAssetToUser",
3170
- args: [account, metadata.address]
3171
- }) : encodeFunctionData({
3172
- abi: YIELDSEEKER_AGENT_WALLET_ABI,
3173
- functionName: "withdrawAssetToUser",
3174
- args: [account, metadata.address, requested]
3175
- }),
3176
- value: "0",
3177
- chainId
3178
- });
3179
- return {
3180
- txHash,
3181
- type: amount === void 0 ? "full" : "partial",
3182
- amount: requested.toString()
3183
- };
3184
- } finally {
3185
- await this.refreshSnapshotAfterMovement(
3186
- state,
3187
- chainId,
3188
- context,
3189
- "withdrawal"
3190
- );
3191
- }
3192
- }
3193
- async getBalances(state, chainId) {
3194
- this.assertChain(chainId);
3195
- return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
3196
- }
3197
- async getEarnings(state, chainId) {
3198
- this.assertChain(chainId);
3199
- return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
3200
- }
3201
- async getAccountApy(state, chainId, days, tokenSymbol) {
3202
- this.assertChain(chainId);
3203
- const asset = tokenSymbol?.toUpperCase();
3204
- if (asset !== void 0) this.assertAsset(asset);
3205
- const contexts = await this.loadPortfolio(state, chainId, {
3206
- ...asset ? { asset } : {},
3207
- historic: true
3208
- });
3209
- return mapYieldseekerApy(state.walletAddress, contexts, days);
3210
- }
3211
- async getHistory(state, chainId, options) {
3212
- this.assertChain(chainId);
3213
- const asset = options?.tokenSymbol?.toUpperCase();
3214
- if (asset !== void 0) this.assertAsset(asset);
3215
- const contexts = await this.loadPortfolio(state, chainId, {
3216
- ...asset ? { asset } : {},
3217
- historic: true,
3218
- actions: true
3219
- });
3220
- return mapYieldseekerHistory(contexts, {
3221
- limit: options?.limit ?? 10,
3222
- ownerAddress: state.walletAddress,
3223
- ...options?.fromDate ? { fromDate: options.fromDate } : {},
3224
- ...options?.toDate ? { toDate: options.toDate } : {}
3225
- });
3226
- }
3227
- async getUserProfile(state, chainId) {
3228
- this.assertChain(chainId);
3229
- return mapYieldseekerProfile(
3230
- state.walletAddress,
3231
- await this.loadPortfolio(state, chainId, {})
3232
- );
3233
- }
3234
- async getAgentApy(days, options) {
3235
- this.assertOptionalChain(options?.chainId);
3236
- const requested = options?.tokenSymbol?.toUpperCase();
3237
- if (requested !== void 0) this.assertAsset(requested);
3238
- const assets = requested ? [requested] : ["USDC", "WETH"];
3239
- const values = await Promise.all(
3240
- assets.map(async (asset) => {
3241
- const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3242
- const response = await this.api.request(
3243
- `/chains/8453/assets/${metadata.address}/yield-options`
3244
- );
3245
- if (!Array.isArray(response?.yieldOptions)) {
3246
- throw this.invalidResponse("yield options");
3247
- }
3248
- return { asset, yieldOptions: response.yieldOptions };
3249
- })
3250
- );
3251
- return mapYieldseekerAgentApy(values, days);
3252
- }
3253
- userKey(state, chainId) {
3254
- return `${state.walletAddress.toLowerCase()}:${chainId}`;
3255
- }
3256
- contextKey(state, chainId, asset) {
3257
- return `${this.userKey(state, chainId)}:${asset}`;
3258
- }
3259
- async resolveUser(state, chainId) {
3260
- const key2 = this.userKey(state, chainId);
3261
- const inMemory = this.users.get(key2);
3262
- if (inMemory) return inMemory;
3263
- const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
3264
- if (persisted) {
3265
- this.users.set(key2, persisted);
3266
- return persisted;
3267
- }
3268
- const walletAddress = getAddress2(state.walletAddress);
3269
- let user = null;
3270
- try {
3271
- const login = await this.providerRequest(
3272
- state,
3273
- chainId,
3274
- "/users/login-with-wallet",
3275
- { method: "POST", body: { walletAddress } }
3276
- );
3277
- user = login?.user ?? null;
3278
- if (!user) {
3279
- throw this.invalidResponse("wallet login", {
3280
- reason: "A successful login returned no user."
3281
- });
3282
- }
3283
- } catch (error) {
3284
- if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
3285
- if (error instanceof OwneyError) throw error;
3286
- throw this.mapApiError(error);
3287
- }
3288
- let created;
3289
- for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
3290
- try {
3291
- created = await this.providerRequest(
3292
- state,
3293
- chainId,
3294
- "/users",
3295
- {
3296
- method: "POST",
3297
- body: {
3298
- walletAddress,
3299
- username: generateYieldseekerUsername()
3300
- }
3301
- }
3302
- );
3303
- break;
3304
- } catch (createError) {
3305
- const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
3306
- if (canRetry) continue;
3307
- throw this.mapApiError(createError);
3308
- }
3309
- }
3310
- user = created?.user ?? null;
3311
- }
3312
- if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
3313
- throw this.invalidResponse("wallet identity");
3314
- }
3315
- const resolved = { userId: user.userId };
3316
- this.users.set(key2, resolved);
3317
- writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
3318
- return resolved;
3319
- }
3320
- forgetUser(state, chainId) {
3321
- this.users.delete(this.userKey(state, chainId));
3322
- clearYieldseekerIdentity(state.walletAddress, chainId);
3323
- }
3324
- async ensureAgent(state, chainId, asset) {
3325
- const key2 = this.contextKey(state, chainId, asset);
3326
- const cached = this.agentContexts.get(key2);
3327
- if (cached) return cached;
3328
- const pending = this.pendingAgents.get(key2);
3329
- if (pending) return pending;
3330
- const request = this.resolveAgent(state, chainId, asset, true).then(
3331
- (context) => {
3332
- if (!context) throw this.invalidResponse("agent creation");
3333
- this.agentContexts.set(key2, context);
3334
- return context;
3335
- }
3336
- );
3337
- this.pendingAgents.set(key2, request);
3338
- try {
3339
- return await request;
3340
- } finally {
3341
- this.pendingAgents.delete(key2);
3342
- }
3343
- }
3344
- async findAgent(state, chainId, asset) {
3345
- const key2 = this.contextKey(state, chainId, asset);
3346
- const cached = this.agentContexts.get(key2);
3347
- if (cached) return cached;
3348
- const context = await this.resolveAgent(state, chainId, asset, false);
3349
- if (context) this.agentContexts.set(key2, context);
3350
- return context;
3351
- }
3352
- async resolveAgent(state, chainId, asset, createIfMissing) {
3353
- const user = await this.resolveUser(state, chainId);
3354
- const response = await this.walletRequest(
3355
- state,
3356
- chainId,
3357
- `/users/${user.userId}/agents`
3358
- );
3359
- if (!Array.isArray(response?.agents)) {
3360
- throw this.invalidResponse("agent list");
3361
- }
3362
- const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3363
- let agent = response.agents.find(
3364
- (candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
3365
- );
3366
- if (!agent && createIfMissing) {
3367
- const created = await this.walletRequest(
3368
- state,
3369
- chainId,
3370
- `/users/${user.userId}/agents`,
3371
- {
3372
- method: "POST",
3373
- body: {
3374
- name: OWNEY_AGENT_NAME,
3375
- emoji: "\u{1F989}",
3376
- chainId,
3377
- assetAddress: metadata.address,
3378
- type: "vault",
3379
- rulePreset: null
3380
- }
3381
- }
3382
- );
3383
- agent = created?.agent;
3384
- }
3385
- if (!agent) return null;
3386
- this.assertAgent(agent);
3387
- const walletResponse = await this.walletRequest(
3388
- state,
3389
- chainId,
3390
- `/users/${user.userId}/agents/${agent.agentId}/wallet`
3391
- );
3392
- if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
3393
- throw this.invalidResponse("agent wallet");
3394
- }
3395
- return { user, agent, wallet: walletResponse.agentWallet, asset };
3396
- }
3397
- async loadPortfolio(state, chainId, options) {
3398
- const user = await this.resolveUser(state, chainId);
3399
- const response = await this.walletRequest(
3400
- state,
3401
- chainId,
3402
- `/users/${user.userId}/agents`
3403
- );
3404
- if (!Array.isArray(response?.agents)) {
3405
- throw this.invalidResponse("agent list");
3406
- }
3407
- const contexts = [];
3408
- for (const agent of response.agents) {
3409
- const asset = this.assetForAgent(agent);
3410
- if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
3411
- continue;
3412
- }
3413
- this.assertAgent(agent);
3414
- const walletResponse = await this.walletRequest(
3415
- state,
3416
- chainId,
3417
- `/users/${user.userId}/agents/${agent.agentId}/wallet`
3418
- );
3419
- if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
3420
- throw this.invalidResponse("agent wallet");
3421
- }
3422
- const context = {
3423
- user,
3424
- agent,
3425
- wallet: walletResponse.agentWallet,
3426
- asset
3427
- };
3428
- this.agentContexts.set(this.contextKey(state, chainId, asset), context);
3429
- contexts.push(context);
3430
- }
3431
- return Promise.all(
3432
- contexts.map(
3433
- (context) => this.loadPortfolioContext(state, chainId, context, options)
3434
- )
3435
- );
3436
- }
3437
- async loadPortfolioContext(state, chainId, context, options = {}) {
3438
- const [snapshot, positions, historic, actions] = await Promise.all([
3439
- this.walletRequest(
3440
- state,
3441
- chainId,
3442
- `${this.agentPath(context, "snapshot")}${query({
3443
- shouldOnlyUseRecentValue: true,
3444
- shouldAllowStaleOnError: true
3445
- })}`
3446
- ),
3447
- this.walletRequest(
3448
- state,
3449
- chainId,
3450
- this.agentPath(context, "yield-positions")
3451
- ),
3452
- options.historic ? this.walletRequest(
3453
- state,
3454
- chainId,
3455
- this.agentPath(context, "wallet/historic-position")
3456
- ) : Promise.resolve(void 0),
3457
- options.actions ? this.walletRequest(
3458
- state,
3459
- chainId,
3460
- this.agentPath(context, "actions")
3461
- ) : Promise.resolve(void 0)
3462
- ]);
3463
- if (!snapshot?.agentSnapshot) {
3464
- throw this.invalidResponse("agent snapshot");
3465
- }
3466
- if (!Array.isArray(positions?.yieldPositions)) {
3467
- throw this.invalidResponse("yield positions");
3468
- }
3469
- return {
3470
- ...context,
3471
- snapshot: snapshot.agentSnapshot,
3472
- positions: positions.yieldPositions,
3473
- ...historic?.position ? { historic: historic.position } : {},
3474
- ...actions?.actions ? { actions: actions.actions } : {}
3475
- };
3476
- }
3477
- async deployAfterFunding(state, chainId, context) {
3478
- if (context.wallet.initializedDate != null) return;
3479
- try {
3480
- const deployed = await this.walletRequest(
3481
- state,
3482
- chainId,
3483
- this.agentPath(context, "deploy"),
3484
- { method: "POST", body: {} }
3485
- );
3486
- if (deployed?.agentWallet) {
3487
- context.wallet = deployed.agentWallet;
3488
- }
3489
- } catch (error) {
3490
- console.warn(
3491
- "[owney-sdk] Yieldseeker deposit is funded but not deployable yet:",
3492
- error
3493
- );
3494
- }
3495
- }
3496
- async refreshSnapshotAfterMovement(state, chainId, context, movement) {
3497
- try {
3498
- const response = await this.walletRequest(
3499
- state,
3500
- chainId,
3501
- `${this.agentPath(context, "snapshot")}${query({
3502
- shouldForceRefresh: true
3503
- })}`
3504
- );
3505
- if (!response?.agentSnapshot) {
3506
- throw this.invalidResponse("agent snapshot refresh");
3507
- }
3508
- } catch (error) {
3509
- console.warn(
3510
- `[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
3511
- error
3512
- );
3513
- }
3514
- }
3515
- agentPath(context, suffix) {
3516
- return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
3517
- }
3518
- async walletRequest(state, chainId, path, options = {}) {
3519
- try {
3520
- return await this.providerRequest(state, chainId, path, options);
3521
- } catch (error) {
3522
- throw this.mapApiError(error);
3523
- }
3524
- }
3525
- async providerRequest(state, chainId, path, options = {}) {
3526
- this.assertChain(chainId);
3527
- const request = (signature2) => this.api.request(path, {
3528
- ...options,
3529
- signature: signature2
3530
- });
3531
- let signature = await this.auth.getToken(state, chainId);
3532
- try {
3533
- return await request(signature);
3534
- } catch (error) {
3535
- if (!(error instanceof YieldseekerApiError)) throw error;
3536
- if (error.providerCode === "NO_USER") throw error;
3537
- if (!error.isAuthenticationError) throw error;
3538
- this.auth.clear(state, chainId);
3539
- signature = await this.auth.getToken(state, chainId);
3540
- try {
3541
- return await request(signature);
3542
- } catch (retryError) {
3543
- if (retryError instanceof YieldseekerApiError && retryError.isAuthenticationError) {
3544
- this.forgetUser(state, chainId);
3545
- }
3546
- throw retryError;
3547
- }
3548
- }
3549
- }
3550
- mapApiError(error) {
3551
- if (!(error instanceof YieldseekerApiError)) {
3552
- return new OwneyError(
3553
- "AGENT_API_ERROR",
3554
- "Yieldseeker request failed.",
3555
- { cause: error instanceof Error ? error.message : String(error) },
3556
- this.id
3557
- );
3558
- }
3559
- const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
3560
- return new OwneyError(
3561
- code,
3562
- `Yieldseeker request failed: ${error.providerCode}.`,
3563
- {
3564
- statusCode: error.status,
3565
- providerCode: error.providerCode,
3566
- ...error.responseFields ? { fields: error.responseFields } : {}
3567
- },
3568
- this.id
3569
- );
3570
- }
3571
- async submitTransaction(state, chainId, transaction) {
3572
- if (this.transactionExecutor) {
3573
- return this.transactionExecutor(state, chainId, transaction);
3574
- }
3575
- this.assertTransaction(transaction, state, chainId);
3576
- const account = getAddress2(state.walletAddress);
3577
- const walletClient = createWalletClient2({
3578
- account,
3579
- chain: base3,
3580
- transport: custom2(state.provider)
3581
- });
3582
- const publicClient = createPublicClient3({
3583
- chain: base3,
3584
- transport: custom2(state.provider)
3585
- });
3586
- await ensureWalletOnChain(
3587
- publicClient,
3588
- walletClient,
3589
- 8453
3590
- );
3591
- const hash = await walletClient.sendTransaction({
3592
- account,
3593
- chain: base3,
3594
- to: getAddress2(transaction.to),
3595
- data: transaction.data,
3596
- value: BigInt(transaction.value)
3597
- });
3598
- const receipt = await publicClient.waitForTransactionReceipt({
3599
- hash,
3600
- confirmations: 1
3601
- });
3602
- if (receipt.status !== "success") {
3603
- throw new OwneyError(
3604
- "AGENT_TRANSACTION_REVERTED",
3605
- `Yieldseeker transaction reverted (${hash}).`,
3606
- { transactionHash: hash },
3607
- this.id
3608
- );
3609
- }
3610
- return hash;
3611
- }
3612
- async waitForReceipt(state, chainId, transactionHash) {
3613
- if (this.unwindReceiptWaiter) {
3614
- await this.unwindReceiptWaiter(state, chainId, transactionHash);
3615
- return;
3616
- }
3617
- const publicClient = createPublicClient3({
3618
- chain: base3,
3619
- transport: custom2(state.provider)
3620
- });
3621
- const receipt = await publicClient.waitForTransactionReceipt({
3622
- hash: transactionHash,
3623
- confirmations: 1
3624
- });
3625
- if (receipt.status !== "success") {
3626
- throw new OwneyError(
3627
- "AGENT_TRANSACTION_REVERTED",
3628
- `Yieldseeker transaction reverted (${transactionHash}).`,
3629
- { transactionHash },
3630
- this.id
3631
- );
3632
- }
3633
- }
3634
- assertTransaction(transaction, state, chainId) {
3635
- if (!transaction || typeof transaction.from !== "string" || !isAddress2(transaction.from) || typeof transaction.to !== "string" || !isAddress2(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 || getAddress2(transaction.from) !== getAddress2(state.walletAddress)) {
3636
- throw this.invalidResponse("transaction");
3637
- }
3638
- }
3639
- assertAgent(agent) {
3640
- if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress2(agent.assetAddress) || agent.chainId !== 8453) {
3641
- throw this.invalidResponse("agent");
3642
- }
3643
- }
3644
- isOwneyAgent(agent) {
3645
- return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
3646
- }
3647
- assetForAgent(agent) {
3648
- for (const asset of ["USDC", "WETH"]) {
3649
- if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
3650
- return asset;
3651
- }
3652
- }
3653
- return null;
3654
- }
3655
- isTransactionHash(value) {
3656
- return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
3657
- }
3658
- assertChain(chainId) {
3659
- if (chainId !== 8453) {
3660
- throw new OwneyError(
3661
- "CHAIN_UNSUPPORTED",
3662
- `Yieldseeker does not support chain ${chainId}.`,
3663
- { chainId, supportedChainIds: [8453] },
3664
- this.id
3665
- );
3666
- }
3667
- }
3668
- assertOptionalChain(chainId) {
3669
- if (chainId !== void 0) this.assertChain(chainId);
3670
- }
3671
- assertAsset(asset) {
3672
- if (asset !== "USDC" && asset !== "WETH") {
3673
- throw new OwneyError(
3674
- "ASSET_UNSUPPORTED",
3675
- `Yieldseeker does not support asset ${asset} in the Owney rollout.`,
3676
- {
3677
- asset,
3678
- supportedAssets: ["USDC", "WETH"],
3679
- providerAlsoAdvertises: ["cbBTC"]
3680
- },
3681
- this.id
3682
- );
3683
- }
3684
- }
3685
- invalidResponse(operation, details = {}) {
3686
- return new OwneyError(
3687
- "AGENT_INVALID_RESPONSE",
3688
- `Yieldseeker returned an invalid ${operation} response.`,
3689
- details,
3690
- this.id
3691
- );
3692
- }
3693
- };
3694
-
3695
- // src/lib/routing-api.ts
3696
- var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3697
- async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
3698
- const url = `${baseUrl}/api/v1/agent/org-config`;
3699
- try {
3700
- const res = await fetch(url, {
3701
- method: "GET",
3702
- headers: {
3703
- "Content-Type": "application/json",
3704
- "x-owney-api-key": `${apiKey}`
3705
- }
3706
- });
3707
- if (!res.ok) {
3708
- if (res.status !== 404) {
3709
- console.warn(
3710
- `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
3711
- );
3712
- }
3713
- return null;
3714
- }
3715
- const json = await res.json();
3716
- const policy = json.success ? json.data ?? null : null;
3717
- debugLog(
3718
- "owney-sdk",
3719
- policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
3720
- policy ?? void 0
3721
- );
3722
- return policy;
3723
- } catch (error) {
3724
- console.warn(
3725
- "[owney-sdk] Could not read org agent config (non-fatal):",
3726
- error instanceof Error ? error.message : String(error)
3727
- );
3728
- return null;
3729
- }
3730
- }
3731
- async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
3732
- const url = `${baseUrl}/api/v1/agent/keys`;
3733
- const res = await fetch(url, {
3734
- method: "GET",
3735
- headers: {
3736
- "Content-Type": "application/json",
3737
- "x-owney-api-key": `${apiKey}`
3738
- }
3739
- });
3740
- if (!res.ok) {
3741
- const text = await res.text().catch(() => "");
3742
- throw new OwneyError(
3743
- "API_ROUTING_ERROR",
3744
- `Routing API error ${res.status}: ${text}`,
3745
- { statusCode: res.status, responseBody: text }
3746
- );
3747
- }
3748
- const json = await res.json();
3749
- if (!json.success) {
3750
- throw new OwneyError(
3751
- "API_ROUTING_FAILED",
3752
- `Routing API request failed: ${json.message}`,
3753
- { message: json.message }
3754
- );
3755
- }
3756
- return json.data;
3757
- }
3758
-
3759
- // src/lib/health-report.ts
3760
- var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3761
- async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
3762
- try {
3763
- await fetch(`${baseUrl}/api/v1/agent/health-report`, {
3764
- method: "POST",
3765
- headers: {
3766
- "Content-Type": "application/json",
3767
- "x-owney-api-key": apiKey
3768
- },
3769
- body: JSON.stringify({
3770
- agent_type: agentType,
3771
- error_code: errorCode,
3772
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3773
- })
3774
- });
3775
- } catch (err) {
3776
- console.warn(
3777
- `[owney-sdk] health-report failed for agent "${agentType}":`,
3778
- err instanceof Error ? err.message : err
3779
- );
3780
- }
3781
- }
3782
- async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
3783
- try {
3784
- return await fn();
3785
- } catch (err) {
3786
- const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
3787
- void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
3788
- throw err;
3789
- }
3790
- }
3791
-
3792
- // src/lib/helpers/withdraw-helper.ts
3793
- import { parseUnits } from "viem";
3794
- function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
3795
- const target = asset.toUpperCase();
3796
- return agents.map((agent) => {
3797
- const agentBalance = aggregated[agent.id];
3798
- const tokenBalance = agentBalance?.tokens.find(
3799
- (t) => t.chainId === chainId && t.asset.toUpperCase() === target
3800
- );
3801
- let balance = tokenBalance ? parseUnits(tokenBalance.amount, decimals) : 0n;
3802
- if (agent.balanceComposition === "tokens-plus-positions") {
3803
- const chainNameById = {
3804
- 1: "ETHEREUM",
3805
- 8453: "BASE",
3806
- 42161: "ARBITRUM"
3807
- };
3808
- const targetChain = chainNameById[chainId];
3809
- for (const position2 of agentBalance?.positions ?? []) {
3810
- const positionChain = position2.chain.trim().toUpperCase();
3811
- const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
3812
- if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
3813
- if (position2.amountRaw !== void 0) {
3814
- try {
3815
- balance += BigInt(position2.amountRaw);
3816
- continue;
3817
- } catch {
3818
- }
3819
- }
3820
- balance += parseUnits(position2.amount, decimals);
3821
- }
3822
- }
3823
- return { agent, balance };
3824
- });
3825
- }
3826
- function planProportionalShares(balances, requested, totalAvailable) {
3827
- const plans = balances.map(({ agent, balance }) => ({
3828
- agent,
3829
- balance,
3830
- planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
3831
- }));
3832
- const assigned = plans.reduce((s, p) => s + p.planned, 0n);
3833
- let remainder = requested - assigned;
3834
- const byHeadroom = [...plans].sort((a, b) => {
3835
- const diff = b.balance - b.planned - (a.balance - a.planned);
3836
- return diff > 0n ? 1 : diff < 0n ? -1 : 0;
3837
- });
3838
- for (const p of byHeadroom) {
3839
- if (remainder === 0n) break;
3840
- const headroom = p.balance - p.planned;
3841
- if (headroom <= 0n) continue;
3842
- const take = headroom < remainder ? headroom : remainder;
3843
- p.planned += take;
3844
- remainder -= take;
3845
- }
3846
- return plans;
3847
- }
3848
- function planDisabledDrain(disabled, requested) {
3849
- const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
3850
- (a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
3851
- );
3852
- const plans = [];
3853
- let remaining = requested;
3854
- for (const { agent, balance } of sorted) {
3855
- if (remaining === 0n) {
3856
- plans.push({ agent, balance, planned: 0n });
3857
- continue;
3858
- }
3859
- const take = balance < remaining ? balance : remaining;
3860
- plans.push({ agent, balance, planned: take });
3861
- remaining -= take;
3862
- }
3863
- return { plans, remaining };
2123
+ // src/lib/chain-guard.ts
2124
+ var CHAIN_NAMES = {
2125
+ 1: "Ethereum",
2126
+ 8453: "Base",
2127
+ 42161: "Arbitrum"
2128
+ };
2129
+ function chainName(chainId) {
2130
+ return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
3864
2131
  }
3865
- function redistributeShare(plans, fromIndex, amount, candidatePool) {
3866
- const pool = candidatePool ?? plans.slice(fromIndex + 1);
3867
- const candidates = pool.filter((c) => c.balance - c.planned > 0n);
3868
- const totalHeadroom = candidates.reduce(
3869
- (s, c) => s + (c.balance - c.planned),
3870
- 0n
3871
- );
3872
- if (totalHeadroom === 0n) return;
3873
- let distributed = 0n;
3874
- for (const c of candidates) {
3875
- const headroom = c.balance - c.planned;
3876
- const proportional = headroom * amount / totalHeadroom;
3877
- const give = proportional > headroom ? headroom : proportional;
3878
- c.planned += give;
3879
- distributed += give;
2132
+ async function ensureWalletOnChain(pub, wallet, expected) {
2133
+ const actual = await pub.getChainId();
2134
+ if (actual === expected) return;
2135
+ try {
2136
+ await wallet.switchChain({ id: expected });
2137
+ } catch (error) {
2138
+ throw new OwneyError(
2139
+ "CHAIN_MISMATCH",
2140
+ `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
2141
+ {
2142
+ expectedChainId: expected,
2143
+ actualChainId: actual,
2144
+ cause: error instanceof Error ? error.message : String(error)
2145
+ }
2146
+ );
3880
2147
  }
3881
- let leftover = amount - distributed;
3882
- for (const c of candidates) {
3883
- if (leftover === 0n) break;
3884
- const headroom = c.balance - c.planned;
3885
- if (headroom <= 0n) continue;
3886
- const take = headroom < leftover ? headroom : leftover;
3887
- c.planned += take;
3888
- leftover -= take;
2148
+ const after = await pub.getChainId();
2149
+ if (after !== expected) {
2150
+ throw new OwneyError(
2151
+ "CHAIN_MISMATCH",
2152
+ `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
2153
+ { expectedChainId: expected, actualChainId: after }
2154
+ );
3889
2155
  }
3890
2156
  }
3891
- function sumWithdrawnAmount(results) {
3892
- return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
3893
- }
3894
2157
 
3895
- // src/lib/helpers/account-apy-helper.ts
3896
- function balanceForApyScope(balance, chainId, tokenSymbol) {
3897
- if (!tokenSymbol) {
3898
- const total = Number(balance.totalBalance);
3899
- return Number.isFinite(total) && total > 0 ? total : 0;
3900
- }
3901
- const normalizedToken = tokenSymbol.toUpperCase();
3902
- return balance.tokens.reduce((total, token) => {
3903
- if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
3904
- return total;
3905
- }
3906
- const amount = Number(token.amount);
3907
- return Number.isFinite(amount) && amount > 0 ? total + amount : total;
3908
- }, 0);
3909
- }
3910
- function aggregateApyHistory(agentApys, agentBalances) {
3911
- const byDate = /* @__PURE__ */ new Map();
3912
- for (const [id, accountApy] of Object.entries(agentApys)) {
3913
- const balance = agentBalances[id] ?? 0;
3914
- if (!Number.isFinite(balance) || balance <= 0) continue;
3915
- for (const point of accountApy.history ?? []) {
3916
- const apy = Number(point.apy);
3917
- if (!point.date || !Number.isFinite(apy)) continue;
3918
- const current = byDate.get(point.date) ?? { weightedSum: 0, weight: 0 };
3919
- current.weightedSum += apy * balance;
3920
- current.weight += balance;
3921
- byDate.set(point.date, current);
2158
+ // src/lib/sponsored-deposit.ts
2159
+ var AUTH_WINDOW_SECONDS = 15 * 60;
2160
+ function makeSponsoredDepositCallback(deps) {
2161
+ const post = deps.httpPost ?? postSponsorTransferAuth;
2162
+ return async (smartWallet, chainId, amount) => {
2163
+ const cid = chainId;
2164
+ const token = deps.tokenAddressByChain[cid];
2165
+ if (!token) {
2166
+ throw new OwneyError(
2167
+ "CHAIN_UNSUPPORTED",
2168
+ `No sponsored token configured for chain ${chainId}`
2169
+ );
3922
2170
  }
3923
- }
3924
- return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
3925
- date,
3926
- apy: value.weightedSum / value.weight
3927
- }));
3928
- }
3929
- function aggregateApyByChainAndAsset(agentApys, agentBalances) {
3930
- const sums = {};
3931
- const weights = {};
3932
- for (const id of Object.keys(agentApys)) {
3933
- const cells = agentApys[id].apyByChainAndAsset;
3934
- const balance = agentBalances[id] ?? 0;
3935
- if (!cells || balance <= 0) continue;
3936
- for (const [chainKey, perAsset] of Object.entries(cells)) {
3937
- if (!perAsset) continue;
3938
- const chainId = Number(chainKey);
3939
- for (const [asset, apyValue] of Object.entries(perAsset)) {
3940
- const apy = Number(apyValue ?? 0);
3941
- if (apy === 0) continue;
3942
- sums[chainId] ??= {};
3943
- weights[chainId] ??= {};
3944
- sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
3945
- weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
2171
+ const pub = deps.getPublicClient(cid);
2172
+ const wallet = deps.getWalletClient(cid);
2173
+ await ensureWalletOnChain(pub, wallet, cid);
2174
+ try {
2175
+ const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2176
+ if (balance < BigInt(amount)) {
2177
+ throw new OwneyError(
2178
+ "DEPOSIT_INSUFFICIENT_BALANCE",
2179
+ "Insufficient balance for this deposit.",
2180
+ { token, chainId: cid, balance: balance.toString(), amount }
2181
+ );
3946
2182
  }
3947
- }
3948
- }
3949
- const out = {};
3950
- for (const chainKey of Object.keys(sums)) {
3951
- const chainId = Number(chainKey);
3952
- const perAssetOut = {};
3953
- for (const asset of Object.keys(sums[chainId])) {
3954
- const w = weights[chainId][asset];
3955
- if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
3956
- }
3957
- if (Object.keys(perAssetOut).length > 0) {
3958
- out[chainId] = perAssetOut;
3959
- }
3960
- }
3961
- return out;
2183
+ } catch (err) {
2184
+ if (err instanceof OwneyError) throw err;
2185
+ console.warn(
2186
+ "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
2187
+ err instanceof Error ? err.message : String(err)
2188
+ );
2189
+ }
2190
+ const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
2191
+ const validAfter = 0n;
2192
+ const validBefore = BigInt(
2193
+ Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
2194
+ );
2195
+ const nonce = randomAuthNonce();
2196
+ const typedData = buildTransferWithAuthorizationTypedData({
2197
+ token,
2198
+ chainId: cid,
2199
+ tokenName,
2200
+ tokenVersion,
2201
+ message: {
2202
+ from: deps.ownerAddress,
2203
+ to: smartWallet,
2204
+ value: BigInt(amount),
2205
+ validAfter,
2206
+ validBefore,
2207
+ nonce
2208
+ }
2209
+ });
2210
+ const authSignature = await wallet.signTypedData({
2211
+ account: deps.ownerAddress,
2212
+ ...typedData
2213
+ });
2214
+ deps.onApproved?.();
2215
+ const result = await post({
2216
+ baseUrl: deps.baseUrl,
2217
+ apiKey: deps.apiKey,
2218
+ body: {
2219
+ chainId: cid,
2220
+ token,
2221
+ from: deps.ownerAddress,
2222
+ to: smartWallet,
2223
+ value: amount,
2224
+ validAfter: validAfter.toString(),
2225
+ validBefore: validBefore.toString(),
2226
+ nonce,
2227
+ authSignature,
2228
+ tokenName,
2229
+ tokenVersion
2230
+ }
2231
+ });
2232
+ return result.txHash;
2233
+ };
3962
2234
  }
3963
2235
 
3964
- // src/client.ts
3965
- import {
3966
- createPublicClient as createPublicClient4,
3967
- createWalletClient as createWalletClient3,
3968
- custom as custom3
3969
- } from "viem";
3970
- import { base as base4, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
3971
-
3972
2236
  // src/lib/sponsored-weth-deposit.ts
3973
2237
  var PERMIT_WINDOW_SECONDS = 15 * 60;
3974
2238
  function makeSponsoredWethCallback(deps) {
3975
2239
  const get = deps.httpGet ?? getSponsorRelayerAddress;
3976
2240
  const post = deps.httpPost ?? postSponsorPermit2Transfer;
3977
- return makeVerificationAwareDepositCallback(
3978
- async (smartWallet, chainId, amount, verification) => {
3979
- const cid = chainId;
3980
- const token = deps.tokenAddressByChain[cid];
3981
- if (!token) {
3982
- throw new OwneyError(
3983
- "CHAIN_UNSUPPORTED",
3984
- `No sponsored WETH configured for chain ${chainId}`
3985
- );
3986
- }
3987
- const amountWei = BigInt(amount);
3988
- const pub = deps.getPublicClient(cid);
3989
- const wallet = deps.getWalletClient(cid);
3990
- await ensureWalletOnChain(pub, wallet, cid);
3991
- try {
3992
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
3993
- if (balance < amountWei) {
3994
- throw new OwneyError(
3995
- "DEPOSIT_INSUFFICIENT_BALANCE",
3996
- "Insufficient WETH balance for this deposit.",
3997
- { token, chainId: cid, balance: balance.toString(), amount }
3998
- );
3999
- }
4000
- } catch (err) {
4001
- if (err instanceof OwneyError) throw err;
4002
- console.warn(
4003
- "[owney-sdk] WETH balance pre-check failed (non-fatal):",
4004
- err instanceof Error ? err.message : String(err)
4005
- );
4006
- }
4007
- const allowance = await readPermit2Allowance(
4008
- pub,
4009
- token,
4010
- deps.ownerAddress
2241
+ return async (smartWallet, chainId, amount) => {
2242
+ const cid = chainId;
2243
+ const token = deps.tokenAddressByChain[cid];
2244
+ if (!token) {
2245
+ throw new OwneyError(
2246
+ "CHAIN_UNSUPPORTED",
2247
+ `No sponsored WETH configured for chain ${chainId}`
4011
2248
  );
4012
- if (allowance < amountWei) {
2249
+ }
2250
+ const amountWei = BigInt(amount);
2251
+ const pub = deps.getPublicClient(cid);
2252
+ const wallet = deps.getWalletClient(cid);
2253
+ await ensureWalletOnChain(pub, wallet, cid);
2254
+ try {
2255
+ const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2256
+ if (balance < amountWei) {
4013
2257
  throw new OwneyError(
4014
- "PERMIT2_APPROVAL_REQUIRED",
4015
- "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
4016
- { token, chainId: cid, allowance: allowance.toString(), amount }
2258
+ "DEPOSIT_INSUFFICIENT_BALANCE",
2259
+ "Insufficient WETH balance for this deposit.",
2260
+ { token, chainId: cid, balance: balance.toString(), amount }
4017
2261
  );
4018
2262
  }
4019
- const relayer = await get({
4020
- baseUrl: deps.baseUrl,
4021
- apiKey: deps.apiKey,
4022
- chainId: cid
4023
- });
4024
- const nonce = randomPermit2Nonce();
4025
- const deadline = BigInt(
4026
- Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
2263
+ } catch (err) {
2264
+ if (err instanceof OwneyError) throw err;
2265
+ console.warn(
2266
+ "[owney-sdk] WETH balance pre-check failed (non-fatal):",
2267
+ err instanceof Error ? err.message : String(err)
4027
2268
  );
4028
- const typedData = buildPermitTransferFromTypedData({
4029
- chainId: cid,
4030
- message: {
4031
- permitted: { token, amount: amountWei },
4032
- spender: relayer,
4033
- nonce,
4034
- deadline
4035
- }
4036
- });
4037
- const signature = await wallet.signTypedData({
4038
- account: deps.ownerAddress,
4039
- ...typedData
4040
- });
4041
- deps.onApproved?.();
4042
- const result = await post({
4043
- baseUrl: deps.baseUrl,
4044
- apiKey: deps.apiKey,
4045
- ...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
4046
- body: {
4047
- chainId: cid,
4048
- token,
4049
- from: deps.ownerAddress,
4050
- to: smartWallet,
4051
- amount,
4052
- nonce: nonce.toString(),
4053
- deadline: deadline.toString(),
4054
- signature,
4055
- ...verification?.agentId === "yieldseeker" ? {
4056
- yieldseekerUserId: verification.userId,
4057
- yieldseekerAgentId: verification.yieldseekerAgentId
4058
- } : {}
4059
- }
4060
- });
4061
- return result.txHash;
4062
2269
  }
4063
- );
2270
+ const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
2271
+ if (allowance < amountWei) {
2272
+ throw new OwneyError(
2273
+ "PERMIT2_APPROVAL_REQUIRED",
2274
+ "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
2275
+ { token, chainId: cid, allowance: allowance.toString(), amount }
2276
+ );
2277
+ }
2278
+ const relayer = await get({
2279
+ baseUrl: deps.baseUrl,
2280
+ apiKey: deps.apiKey,
2281
+ chainId: cid
2282
+ });
2283
+ const nonce = randomPermit2Nonce();
2284
+ const deadline = BigInt(
2285
+ Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
2286
+ );
2287
+ const typedData = buildPermitTransferFromTypedData({
2288
+ chainId: cid,
2289
+ message: {
2290
+ permitted: { token, amount: amountWei },
2291
+ spender: relayer,
2292
+ nonce,
2293
+ deadline
2294
+ }
2295
+ });
2296
+ const signature = await wallet.signTypedData({
2297
+ account: deps.ownerAddress,
2298
+ ...typedData
2299
+ });
2300
+ deps.onApproved?.();
2301
+ const result = await post({
2302
+ baseUrl: deps.baseUrl,
2303
+ apiKey: deps.apiKey,
2304
+ body: {
2305
+ chainId: cid,
2306
+ token,
2307
+ from: deps.ownerAddress,
2308
+ to: smartWallet,
2309
+ amount,
2310
+ nonce: nonce.toString(),
2311
+ deadline: deadline.toString(),
2312
+ signature
2313
+ }
2314
+ });
2315
+ return result.txHash;
2316
+ };
4064
2317
  }
4065
2318
 
4066
2319
  // src/lib/sponsored-calls-deposit.ts
4067
- import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi2, toHex } from "viem";
2320
+ import { encodeFunctionData, erc20Abi, toHex } from "viem";
4068
2321
  var DEFAULT_POLL_INTERVAL_MS = 1500;
4069
2322
  var DEFAULT_MAX_POLLS = 30;
4070
2323
  async function paymasterSupported(provider, owner, chainId) {
@@ -4090,7 +2343,7 @@ function makeSponsoredCallsCallback(deps) {
4090
2343
  }
4091
2344
  return new URL(configured, origin).toString();
4092
2345
  };
4093
- return makeVerificationAwareDepositCallback(async (smartWallet, chainId, amount, verification) => {
2346
+ return async (smartWallet, chainId, amount) => {
4094
2347
  const cid = chainId;
4095
2348
  const token = deps.tokenAddressByChain[cid];
4096
2349
  if (!token) {
@@ -4106,37 +2359,11 @@ function makeSponsoredCallsCallback(deps) {
4106
2359
  { chainId }
4107
2360
  );
4108
2361
  }
4109
- const data = encodeFunctionData2({
4110
- abi: erc20Abi2,
2362
+ const data = encodeFunctionData({
2363
+ abi: erc20Abi,
4111
2364
  functionName: "transfer",
4112
2365
  args: [smartWallet, BigInt(amount)]
4113
2366
  });
4114
- let paymasterUrl = absolutePaymasterUrl();
4115
- if (verification?.agentId === "yieldseeker") {
4116
- if (chainId !== 8453) {
4117
- throw new OwneyError(
4118
- "CHAIN_UNSUPPORTED",
4119
- `Yieldseeker Base Account sponsorship is not available on chain ${chainId}.`
4120
- );
4121
- }
4122
- const { intent } = await postPaymasterIntent({
4123
- baseUrl: deps.routingApiBaseUrl,
4124
- apiKey: deps.apiKey,
4125
- yieldseekerSignature: verification.signature,
4126
- body: {
4127
- chainId,
4128
- token,
4129
- from: deps.ownerAddress,
4130
- to: smartWallet,
4131
- amount,
4132
- yieldseekerUserId: verification.userId,
4133
- yieldseekerAgentId: verification.yieldseekerAgentId
4134
- }
4135
- });
4136
- const url = new URL(paymasterUrl);
4137
- url.searchParams.set("owneyIntent", intent);
4138
- paymasterUrl = url.toString();
4139
- }
4140
2367
  const sendResult = await deps.provider.request({
4141
2368
  method: "wallet_sendCalls",
4142
2369
  params: [
@@ -4147,7 +2374,7 @@ function makeSponsoredCallsCallback(deps) {
4147
2374
  atomicRequired: false,
4148
2375
  calls: [{ to: token, value: "0x0", data }],
4149
2376
  capabilities: {
4150
- paymasterService: { url: paymasterUrl }
2377
+ paymasterService: { url: absolutePaymasterUrl() }
4151
2378
  }
4152
2379
  }
4153
2380
  ]
@@ -4177,7 +2404,7 @@ function makeSponsoredCallsCallback(deps) {
4177
2404
  `No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
4178
2405
  { chainId, callsId }
4179
2406
  );
4180
- });
2407
+ };
4181
2408
  }
4182
2409
 
4183
2410
  // src/client.ts
@@ -4207,10 +2434,11 @@ var SPONSORED_USDC_BY_CHAIN = {
4207
2434
  1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
4208
2435
  };
4209
2436
  var VIEM_CHAIN2 = {
4210
- 8453: base4,
2437
+ 8453: base2,
4211
2438
  42161: arbitrum2,
4212
2439
  1: mainnet2
4213
2440
  };
2441
+ var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai"];
4214
2442
  var SPONSORED_WETH_BY_CHAIN = {
4215
2443
  8453: "0x4200000000000000000000000000000000000006",
4216
2444
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -4237,9 +2465,9 @@ var OwneySDK = class {
4237
2465
  orgAgentConfig;
4238
2466
  orgAgentConfigPromise = null;
4239
2467
  zyfaiRpcUrls;
4240
- yieldseekerApiBaseUrl;
4241
2468
  routingApiBaseUrl;
4242
2469
  referralSource;
2470
+ surfliquidApiBaseUrl;
4243
2471
  cachedSponsoredCallback = null;
4244
2472
  cachedWethSponsoredCallback = null;
4245
2473
  paymasterServiceUrl;
@@ -4249,10 +2477,10 @@ var OwneySDK = class {
4249
2477
  this.apiKey = config.apiKey;
4250
2478
  if (config.debug) setOwneyDebug(true);
4251
2479
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
4252
- this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
4253
2480
  this.routingApiBaseUrl = config.routingApiBaseUrl;
4254
2481
  this.paymasterServiceUrl = config.paymasterServiceUrl;
4255
2482
  this.referralSource = config.referralSource;
2483
+ this.surfliquidApiBaseUrl = config.surfliquidApiBaseUrl;
4256
2484
  }
4257
2485
  /**
4258
2486
  * Establish connection state for all subsequent SDK calls.
@@ -4352,14 +2580,14 @@ var OwneySDK = class {
4352
2580
  // Casts work around viem's chain-narrowed Client vs the generic
4353
2581
  // PublicClient/WalletClient param types — structurally identical at
4354
2582
  // runtime, but the two share a name TS treats as unrelated.
4355
- getPublicClient: (cid) => createPublicClient4({
2583
+ getPublicClient: (cid) => createPublicClient2({
4356
2584
  chain: VIEM_CHAIN2[cid],
4357
- transport: custom3(provider)
2585
+ transport: custom(provider)
4358
2586
  }),
4359
- getWalletClient: (cid) => createWalletClient3({
2587
+ getWalletClient: (cid) => createWalletClient({
4360
2588
  account: owner,
4361
2589
  chain: VIEM_CHAIN2[cid],
4362
- transport: custom3(provider)
2590
+ transport: custom(provider)
4363
2591
  })
4364
2592
  });
4365
2593
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -4376,8 +2604,6 @@ var OwneySDK = class {
4376
2604
  if (!onApproved && cached) return cached;
4377
2605
  const provider = this.requireConnectedProvider();
4378
2606
  const callback = makeSponsoredCallsCallback({
4379
- apiKey: this.apiKey,
4380
- routingApiBaseUrl: this.routingApiBaseUrl,
4381
2607
  provider,
4382
2608
  ownerAddress: this.state.walletAddress,
4383
2609
  paymasterServiceUrl: this.paymasterServiceUrl,
@@ -4407,14 +2633,14 @@ var OwneySDK = class {
4407
2633
  // Casts work around viem's chain-narrowed Client vs the generic
4408
2634
  // PublicClient/WalletClient param types — structurally identical at
4409
2635
  // runtime, but the two share a name TS treats as unrelated.
4410
- getPublicClient: (cid) => createPublicClient4({
2636
+ getPublicClient: (cid) => createPublicClient2({
4411
2637
  chain: VIEM_CHAIN2[cid],
4412
- transport: custom3(provider)
2638
+ transport: custom(provider)
4413
2639
  }),
4414
- getWalletClient: (cid) => createWalletClient3({
2640
+ getWalletClient: (cid) => createWalletClient({
4415
2641
  account: owner,
4416
2642
  chain: VIEM_CHAIN2[cid],
4417
- transport: custom3(provider)
2643
+ transport: custom(provider)
4418
2644
  })
4419
2645
  });
4420
2646
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -4445,10 +2671,12 @@ var OwneySDK = class {
4445
2671
  this.orgAgentConfigPromise = fetchOrgAgentConfig(
4446
2672
  this.apiKey,
4447
2673
  this.routingApiBaseUrl
4448
- ).then((config) => {
4449
- this.orgAgentConfig = config;
4450
- return config;
4451
- });
2674
+ ).then(
2675
+ (config) => {
2676
+ this.orgAgentConfig = config;
2677
+ return config;
2678
+ }
2679
+ );
4452
2680
  }
4453
2681
  return this.orgAgentConfigPromise;
4454
2682
  }
@@ -4488,15 +2716,8 @@ var OwneySDK = class {
4488
2716
  this.routingApiBaseUrl
4489
2717
  );
4490
2718
  this.disabledAgents.clear();
4491
- for (const {
4492
- key: key2,
4493
- agent_type,
4494
- is_enabled,
4495
- is_configured
4496
- } of agentKeys) {
4497
- const configured = is_configured ?? Boolean(key2);
4498
- if (!configured) continue;
4499
- const agent = this.createAgent(agent_type, key2);
2719
+ for (const { key: key2, agent_type, project_name, is_enabled } of agentKeys) {
2720
+ const agent = await this.createAgent(agent_type, key2, project_name);
4500
2721
  if (!agent) continue;
4501
2722
  this.agents.set(agent_type, agent);
4502
2723
  if (is_enabled === false) {
@@ -4517,49 +2738,26 @@ var OwneySDK = class {
4517
2738
  this.initializingAgentsPromise = null;
4518
2739
  }
4519
2740
  }
4520
- createAgent(agentId, key2) {
2741
+ async createAgent(agentId, key2, projectName) {
4521
2742
  if (agentId === "zyfai") {
4522
- if (!key2) return null;
4523
2743
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
4524
2744
  }
4525
- if (agentId === "yieldseeker") {
4526
- return new YieldseekerAgent(this.apiKey, {
4527
- baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
2745
+ if (agentId === "surfliquid") {
2746
+ if (!projectName) return null;
2747
+ const { SurfLiquidAgent } = await import("./surfliquid.agent-5CWKYMRL.js");
2748
+ return new SurfLiquidAgent({
2749
+ projectName,
2750
+ appId: key2,
2751
+ // Same-origin proxy override — SurfLiquid auth is an httpOnly cookie,
2752
+ // which WebKit (iOS) discards when it is third-party. See
2753
+ // OwneySDKConfig.surfliquidApiBaseUrl.
2754
+ apiBaseUrl: this.surfliquidApiBaseUrl,
2755
+ apiKey: this.apiKey,
2756
+ routingApiBaseUrl: this.routingApiBaseUrl
4528
2757
  });
4529
2758
  }
4530
2759
  return null;
4531
2760
  }
4532
- /**
4533
- * Discover the agents actually returned by routing for this organization.
4534
- * Consumers should use this instead of hardcoding a global agent roster.
4535
- */
4536
- async getAvailableAgents(options = {}) {
4537
- await this.ensureAgentsInitialized();
4538
- const { chainId, asset, includeDisabled = false } = options;
4539
- const available = [];
4540
- for (const [id, agent] of this.agents) {
4541
- const isEnabled = !this.isAgentDisabled(id);
4542
- if (!includeDisabled && !isEnabled) continue;
4543
- if (chainId !== void 0 && !agent.supportedChainIds.includes(chainId)) {
4544
- continue;
4545
- }
4546
- if (asset !== void 0) {
4547
- const supportsAsset = agent.supportedAssets.some(
4548
- (entry) => (chainId === void 0 || entry.chainId === chainId) && entry.assets.some((candidate) => candidate.symbol === asset)
4549
- );
4550
- if (!supportsAsset) {
4551
- continue;
4552
- }
4553
- }
4554
- available.push({
4555
- id,
4556
- isEnabled,
4557
- supportedChainIds: agent.supportedChainIds,
4558
- supportedAssets: agent.supportedAssets
4559
- });
4560
- }
4561
- return available;
4562
- }
4563
2761
  // --- Account lifecycle ---
4564
2762
  /**
4565
2763
  * Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
@@ -4571,7 +2769,7 @@ var OwneySDK = class {
4571
2769
  * If provided, ALL specified agents must support the chainId or the call
4572
2770
  * throws before activating any agent.
4573
2771
  */
4574
- async activateAgent(chainId, agentId, asset) {
2772
+ async activateAgent(chainId, agentId) {
4575
2773
  const state = this.requireState();
4576
2774
  await this.ensureAgentsInitialized();
4577
2775
  if (agentId !== void 0) {
@@ -4607,7 +2805,9 @@ var OwneySDK = class {
4607
2805
  this.activeAgents.add(id);
4608
2806
  }
4609
2807
  state.chainId = chainId;
4610
- await this.activateAgentsInTurn(agents, state, chainId, asset);
2808
+ this.activateAgentsInTurn(agents, state, chainId).catch((error) => {
2809
+ console.error("activateAgent background init failed:", error);
2810
+ });
4611
2811
  return;
4612
2812
  }
4613
2813
  const compatible = [...this.agents.values()].filter(
@@ -4628,7 +2828,11 @@ var OwneySDK = class {
4628
2828
  const enabledCompatible = compatible.filter(
4629
2829
  (agent) => !this.isAgentDisabled(agent.id)
4630
2830
  );
4631
- await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
2831
+ this.activateAgentsInTurn(enabledCompatible, state, chainId).catch(
2832
+ (error) => {
2833
+ console.error("activateAgent background init failed:", error);
2834
+ }
2835
+ );
4632
2836
  }
4633
2837
  /**
4634
2838
  * Activate agents ONE AT A TIME, each followed by its org policy.
@@ -4648,11 +2852,11 @@ var OwneySDK = class {
4648
2852
  * rethrown (matching the previous `Promise.all` rejection) once all agents
4649
2853
  * have had a chance to activate.
4650
2854
  */
4651
- async activateAgentsInTurn(agents, state, chainId, asset) {
2855
+ async activateAgentsInTurn(agents, state, chainId) {
4652
2856
  let firstError = null;
4653
2857
  for (const agent of agents) {
4654
2858
  try {
4655
- await agent.activateAgent(state, chainId, asset);
2859
+ await agent.activateAgent(state, chainId);
4656
2860
  await this.applyOrgPolicyTo(agent, state, chainId);
4657
2861
  } catch (error) {
4658
2862
  if (firstError === null) {
@@ -4732,18 +2936,30 @@ var OwneySDK = class {
4732
2936
  );
4733
2937
  }
4734
2938
  const agentResults = {};
2939
+ const agentErrors = {};
4735
2940
  for (const { agent, amount: agentAmount } of agentAmounts) {
4736
- agentResults[agent.id] = await this.depositWithFallback(
4737
- agent,
4738
- state,
4739
- chainId,
4740
- agentAmount,
4741
- asset,
4742
- effectiveCallback,
4743
- depositCallback
2941
+ try {
2942
+ agentResults[agent.id] = await this.depositWithFallback(
2943
+ agent,
2944
+ state,
2945
+ chainId,
2946
+ agentAmount,
2947
+ asset,
2948
+ effectiveCallback,
2949
+ depositCallback
2950
+ );
2951
+ } catch (error) {
2952
+ agentErrors[agent.id] = error instanceof Error ? error.message : String(error);
2953
+ }
2954
+ }
2955
+ if (Object.keys(agentResults).length === 0) {
2956
+ throw new OwneyError(
2957
+ "DEPOSIT_ALL_FAILED",
2958
+ `All ${agentAmounts.length} agent deposit(s) failed.`,
2959
+ { agentErrors }
4744
2960
  );
4745
2961
  }
4746
- return { agentResults };
2962
+ return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
4747
2963
  }
4748
2964
  /**
4749
2965
  * Invokes `agent.deposit` with the resolved sponsored callback, composing
@@ -4820,15 +3036,15 @@ var OwneySDK = class {
4820
3036
  agent,
4821
3037
  amount: i === agents.length - 1 ? perAgent + remainder : perAgent
4822
3038
  }));
4823
- const valid2 = splits.filter(
3039
+ const valid = splits.filter(
4824
3040
  (s) => exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset)
4825
3041
  );
4826
- if (valid2.length === agents.length) {
3042
+ if (valid.length === agents.length) {
4827
3043
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
4828
3044
  }
4829
3045
  return this.splitDepositAmount(
4830
3046
  totalAmount,
4831
- valid2.map((s) => s.agent),
3047
+ valid.map((s) => s.agent),
4832
3048
  chainId,
4833
3049
  asset,
4834
3050
  exempt
@@ -4860,9 +3076,8 @@ var OwneySDK = class {
4860
3076
  async hasExistingBalance(agent, state, chainId, asset) {
4861
3077
  try {
4862
3078
  const balance = await agent.getBalances(state, chainId);
4863
- const target = asset.toLowerCase();
4864
3079
  const token = balance.tokens.find(
4865
- (t) => t.chainId === chainId && t.asset.toLowerCase() === target
3080
+ (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
4866
3081
  );
4867
3082
  return !!token && Number(token.amount) > 0;
4868
3083
  } catch {
@@ -4901,6 +3116,34 @@ var OwneySDK = class {
4901
3116
  }
4902
3117
  return eligible;
4903
3118
  }
3119
+ /**
3120
+ * Agent ids the routing API provisioned for this org that support the given
3121
+ * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
3122
+ * surfliquid first). Returns `[]` when the org has no compatible agent — never
3123
+ * throws on an empty org. Loads agent keys on first call (apiKey only, no
3124
+ * wallet), so the UI can resolve which agent to use before the user connects.
3125
+ *
3126
+ * This is the source of truth for agent availability: an agent appears here
3127
+ * iff the routing API returned its key. No per-app feature flags.
3128
+ */
3129
+ async getEligibleAgentIds(chainId, asset) {
3130
+ try {
3131
+ await this.ensureAgentsInitialized();
3132
+ } catch (error) {
3133
+ if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
3134
+ return [];
3135
+ }
3136
+ throw error;
3137
+ }
3138
+ return [...this.agents.values()].filter((agent) => {
3139
+ const chainAssets = agent.supportedAssets.find(
3140
+ (sa) => sa.chainId === chainId
3141
+ );
3142
+ return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
3143
+ }).map((agent) => agent.id).sort(
3144
+ (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
3145
+ );
3146
+ }
4904
3147
  // --- Fund operations ---
4905
3148
  /**
4906
3149
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -4937,9 +3180,17 @@ var OwneySDK = class {
4937
3180
  }
4938
3181
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
4939
3182
  if (!amount) {
3183
+ const aggregated2 = await this.getBalances();
3184
+ const funded = projectAgentBalancesForAsset(
3185
+ eligibleAgents,
3186
+ aggregated2.agentBalances,
3187
+ chainId,
3188
+ asset,
3189
+ assetInfo.decimals
3190
+ ).filter((b) => b.balance > 0n);
4940
3191
  const results2 = {};
4941
3192
  const agentErrors2 = {};
4942
- for (const agent of eligibleAgents) {
3193
+ for (const { agent } of funded) {
4943
3194
  try {
4944
3195
  results2[agent.id] = await agent.withdraw(state, chainId, token);
4945
3196
  } catch (err) {
@@ -4961,17 +3212,14 @@ var OwneySDK = class {
4961
3212
  { asset, agentErrors: agentErrors2 }
4962
3213
  );
4963
3214
  }
3215
+ const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
4964
3216
  return {
4965
3217
  agentResult: results2,
4966
- totalWithdrawn: sumWithdrawnAmount(results2).toString()
3218
+ totalWithdrawn: totalWithdrawn.toString()
4967
3219
  };
4968
3220
  }
4969
3221
  const requested = BigInt(amount);
4970
3222
  const aggregated = await this.getBalances();
4971
- const unavailableAgents = eligibleAgents.filter(
4972
- (agent) => !(agent.id in aggregated.agentBalances)
4973
- );
4974
- const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
4975
3223
  const balances = projectAgentBalancesForAsset(
4976
3224
  eligibleAgents,
4977
3225
  aggregated.agentBalances,
@@ -4980,18 +3228,7 @@ var OwneySDK = class {
4980
3228
  assetInfo.decimals
4981
3229
  );
4982
3230
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
4983
- if (totalAvailable === 0n && unavailableAgents.length > 0) {
4984
- throw new OwneyError(
4985
- "WITHDRAW_BALANCE_UNAVAILABLE",
4986
- `Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
4987
- {
4988
- asset,
4989
- unavailableAgents: unavailableAgentIds,
4990
- agentErrors: aggregated.agentErrors
4991
- }
4992
- );
4993
- }
4994
- if (totalAvailable < requested && unavailableAgents.length === 0) {
3231
+ if (totalAvailable < requested) {
4995
3232
  throw new OwneyError(
4996
3233
  "WITHDRAW_INSUFFICIENT_BALANCE",
4997
3234
  `Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
@@ -5002,7 +3239,6 @@ var OwneySDK = class {
5002
3239
  }
5003
3240
  );
5004
3241
  }
5005
- const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
5006
3242
  const disabledBalances = balances.filter(
5007
3243
  (b) => this.isAgentDisabled(b.agent.id)
5008
3244
  );
@@ -5011,7 +3247,7 @@ var OwneySDK = class {
5011
3247
  );
5012
3248
  const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
5013
3249
  disabledBalances,
5014
- plannedTarget
3250
+ requested
5015
3251
  );
5016
3252
  const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
5017
3253
  const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
@@ -5021,9 +3257,7 @@ var OwneySDK = class {
5021
3257
  }));
5022
3258
  const plans = [...disabledPlans, ...enabledPlans];
5023
3259
  const results = {};
5024
- const agentErrors = {
5025
- ...aggregated.agentErrors ?? {}
5026
- };
3260
+ const agentErrors = {};
5027
3261
  for (let i = 0; i < plans.length; i++) {
5028
3262
  const p = plans[i];
5029
3263
  if (p.planned === 0n) continue;
@@ -5070,8 +3304,7 @@ var OwneySDK = class {
5070
3304
  requested: amount,
5071
3305
  withdrawn: withdrawn.toString(),
5072
3306
  partialResults: results,
5073
- agentErrors,
5074
- ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
3307
+ agentErrors
5075
3308
  }
5076
3309
  );
5077
3310
  }
@@ -5112,12 +3345,7 @@ var OwneySDK = class {
5112
3345
  continue;
5113
3346
  }
5114
3347
  const reason = settledResult.reason;
5115
- const message = reason instanceof Error ? reason.message : String(reason);
5116
- agentErrors[agentId2] = message;
5117
- console.error(
5118
- `[owney-sdk] Balance fetch failed for agent "${agentId2}":`,
5119
- reason
5120
- );
3348
+ agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
5121
3349
  }
5122
3350
  if (successCount === 0) {
5123
3351
  throw new OwneyError(
@@ -5129,8 +3357,7 @@ var OwneySDK = class {
5129
3357
  return {
5130
3358
  totalBalance: String(totalBalance),
5131
3359
  totalBalanceAsset: "usdc",
5132
- agentBalances: results,
5133
- ...Object.keys(agentErrors).length > 0 ? { agentErrors } : {}
3360
+ agentBalances: results
5134
3361
  };
5135
3362
  }
5136
3363
  /**
@@ -5224,10 +3451,7 @@ var OwneySDK = class {
5224
3451
  Promise.all(
5225
3452
  entries.map(async ([id, agent]) => {
5226
3453
  const b = await agent.getBalances(state, chainId);
5227
- return [
5228
- id,
5229
- balanceForApyScope(b, chainId, tokenSymbol)
5230
- ];
3454
+ return [id, Number(b.totalBalance)];
5231
3455
  })
5232
3456
  )
5233
3457
  ]);
@@ -5255,12 +3479,10 @@ var OwneySDK = class {
5255
3479
  }
5256
3480
  }
5257
3481
  const apyByChainAndAsset = aggregateApyByChainAndAsset(results, balances);
5258
- const history = aggregateApyHistory(results, balances);
5259
3482
  return {
5260
3483
  totalApy: String(totalApy),
5261
3484
  agentApy: results,
5262
- apyByChainAndAsset,
5263
- history
3485
+ apyByChainAndAsset
5264
3486
  };
5265
3487
  }
5266
3488
  /**
@@ -5390,10 +3612,10 @@ var OwneySDK = class {
5390
3612
  );
5391
3613
  }
5392
3614
  const provider = this.requireConnectedProvider();
5393
- const wallet = createWalletClient3({
3615
+ const wallet = createWalletClient({
5394
3616
  account: state.walletAddress,
5395
3617
  chain: VIEM_CHAIN2[chainId],
5396
- transport: custom3(provider)
3618
+ transport: custom(provider)
5397
3619
  });
5398
3620
  const hash = await wallet.writeContract({
5399
3621
  address: token,
@@ -5403,9 +3625,9 @@ var OwneySDK = class {
5403
3625
  account: state.walletAddress,
5404
3626
  chain: VIEM_CHAIN2[chainId]
5405
3627
  });
5406
- const publicClient = createPublicClient4({
3628
+ const publicClient = createPublicClient2({
5407
3629
  chain: VIEM_CHAIN2[chainId],
5408
- transport: custom3(provider)
3630
+ transport: custom(provider)
5409
3631
  });
5410
3632
  const receipt = await publicClient.waitForTransactionReceipt({
5411
3633
  hash,
@@ -5441,9 +3663,7 @@ var OwneySDK = class {
5441
3663
  return this.getAgent(agentId).getAgentApy(days, agentOptions);
5442
3664
  }
5443
3665
  const results = {};
5444
- const agentEntries = [...this.agents.entries()].filter(
5445
- ([id]) => !this.isAgentDisabled(id)
5446
- );
3666
+ const agentEntries = [...this.agents.entries()];
5447
3667
  const apyResults = await Promise.all(
5448
3668
  agentEntries.map(async ([id, agent]) => {
5449
3669
  const apy = await agent.getAgentApy(days, agentOptions);
@@ -5516,13 +3736,13 @@ var OwneySDK = class {
5516
3736
  };
5517
3737
 
5518
3738
  // src/agents/zyfai/zyfai.siwx.ts
5519
- import { getAddress as getAddress3 } from "viem";
5520
- import { SiweMessage as SiweMessage2 } from "siwe";
3739
+ import { getAddress } from "viem";
3740
+ import { SiweMessage } from "siwe";
5521
3741
  import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
5522
3742
 
5523
3743
  // src/agents/zyfai/zyfai.siwx-cache.ts
5524
- var KEY_PREFIX4 = "owney.siwx.session";
5525
- var storage4 = () => {
3744
+ var KEY_PREFIX2 = "owney.siwx.session";
3745
+ var storage2 = () => {
5526
3746
  if (typeof window === "undefined") return null;
5527
3747
  try {
5528
3748
  return window.localStorage;
@@ -5530,8 +3750,8 @@ var storage4 = () => {
5530
3750
  return null;
5531
3751
  }
5532
3752
  };
5533
- var buildKey3 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
5534
- var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
3753
+ var buildKey2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}`;
3754
+ var legacyKeyPrefix2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}:`;
5535
3755
  var memorySiwxSessions = /* @__PURE__ */ new Map();
5536
3756
  var readLegacySiwxSession = (store, address) => {
5537
3757
  if (!store) return null;
@@ -5562,17 +3782,17 @@ var readLegacySiwxSession = (store, address) => {
5562
3782
  };
5563
3783
  var readSiwxSession = (address, chainId) => {
5564
3784
  if (typeof window === "undefined") return null;
5565
- const key2 = buildKey3(address);
5566
- const store = storage4();
5567
- let raw2 = null;
3785
+ const key2 = buildKey2(address);
3786
+ const store = storage2();
3787
+ let raw = null;
5568
3788
  try {
5569
- raw2 = store?.getItem(key2) ?? null;
3789
+ raw = store?.getItem(key2) ?? null;
5570
3790
  } catch {
5571
- raw2 = null;
3791
+ raw = null;
5572
3792
  }
5573
- if (raw2) {
3793
+ if (raw) {
5574
3794
  try {
5575
- return JSON.parse(raw2);
3795
+ return JSON.parse(raw);
5576
3796
  } catch {
5577
3797
  memorySiwxSessions.delete(key2);
5578
3798
  try {
@@ -5591,18 +3811,18 @@ var readSiwxSession = (address, chainId) => {
5591
3811
  };
5592
3812
  var writeSiwxSession = (address, _chainId, session) => {
5593
3813
  if (typeof window === "undefined") return;
5594
- const key2 = buildKey3(address);
3814
+ const key2 = buildKey2(address);
5595
3815
  memorySiwxSessions.set(key2, session);
5596
- const store = storage4();
3816
+ const store = storage2();
5597
3817
  try {
5598
3818
  store?.setItem(key2, JSON.stringify(session));
5599
3819
  } catch {
5600
3820
  }
5601
3821
  };
5602
3822
  var clearSiwxSession = (address, _chainId) => {
5603
- const key2 = buildKey3(address);
3823
+ const key2 = buildKey2(address);
5604
3824
  memorySiwxSessions.delete(key2);
5605
- const store = storage4();
3825
+ const store = storage2();
5606
3826
  try {
5607
3827
  store?.removeItem(key2);
5608
3828
  } catch {
@@ -5642,8 +3862,8 @@ function buildSIWXConfig(deps) {
5642
3862
  statement: STATEMENT,
5643
3863
  issuedAt,
5644
3864
  toString() {
5645
- return new SiweMessage2({
5646
- address: getAddress3(accountAddress),
3865
+ return new SiweMessage({
3866
+ address: getAddress(accountAddress),
5647
3867
  chainId: numericChainId(chainId),
5648
3868
  domain,
5649
3869
  uri,
@@ -5685,7 +3905,7 @@ function buildSIWXConfig(deps) {
5685
3905
  const persistSession = async (session) => {
5686
3906
  const address = session.data.accountAddress;
5687
3907
  const id = numericChainId(session.data.chainId);
5688
- const message = new SiweMessage2(session.message);
3908
+ const message = new SiweMessage(session.message);
5689
3909
  const login = await post("/auth/login", {
5690
3910
  message,
5691
3911
  signature: session.signature,
@@ -5733,7 +3953,6 @@ export {
5733
3953
  NotConnectedError,
5734
3954
  OwneyError,
5735
3955
  OwneySDK,
5736
- YieldseekerAgent,
5737
3956
  createOwneySIWX,
5738
3957
  setOwneyDebug
5739
3958
  };