@owney/sdk 0.7.16-beta.0 → 0.7.16-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -50,23 +50,6 @@ var SupportedAssets = [
50
50
  }
51
51
  ];
52
52
 
53
- // src/lib/debug.ts
54
- var configuredDebug = false;
55
- function setOwneyDebug(enabled) {
56
- configuredDebug = enabled;
57
- }
58
- function isOwneyDebug() {
59
- return globalThis.__OWNEY_DEBUG__ === true || configuredDebug;
60
- }
61
- function debugLog(scope, message, data) {
62
- if (!isOwneyDebug()) return;
63
- if (data === void 0) {
64
- console.log(`[${scope}] ${message}`);
65
- } else {
66
- console.log(`[${scope}] ${message}`, data);
67
- }
68
- }
69
-
70
53
  // src/errors.ts
71
54
  var OwneyError = class extends Error {
72
55
  code;
@@ -164,18 +147,18 @@ function tokenDecimals(symbol, explicit) {
164
147
  return explicit;
165
148
  return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
166
149
  }
167
- function mapDeposit(raw) {
150
+ function mapDeposit(raw2) {
168
151
  return {
169
- txHash: raw.txHash,
170
- smartWallet: raw.smartWallet,
171
- amount: raw.amount
152
+ txHash: raw2.txHash,
153
+ smartWallet: raw2.smartWallet,
154
+ amount: raw2.amount
172
155
  };
173
156
  }
174
- function mapWithdraw(raw) {
157
+ function mapWithdraw(raw2) {
175
158
  return {
176
- txHash: raw.txHash,
177
- type: raw.type,
178
- amount: raw.amount
159
+ txHash: raw2.txHash,
160
+ type: raw2.type,
161
+ amount: raw2.amount
179
162
  };
180
163
  }
181
164
  var CHAIN_ID_TO_NAME = {
@@ -194,10 +177,10 @@ function resolveChainId(chain) {
194
177
  if (Number.isFinite(asNum) && asNum > 0) return asNum;
195
178
  return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
196
179
  }
197
- function mapPendingAllocations(raw) {
198
- if (!Array.isArray(raw)) return void 0;
180
+ function mapPendingAllocations(raw2) {
181
+ if (!Array.isArray(raw2)) return void 0;
199
182
  const pending = [];
200
- for (const entry of raw) {
183
+ for (const entry of raw2) {
201
184
  if (typeof entry !== "object" || entry === null) continue;
202
185
  const e = entry;
203
186
  if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
@@ -220,8 +203,8 @@ function mapPendingAllocations(raw) {
220
203
  }
221
204
  return pending.length > 0 ? pending : void 0;
222
205
  }
223
- function mapBalances(raw, _chainId, smartWallet) {
224
- const portfolio = raw.portfolio;
206
+ function mapBalances(raw2, _chainId, smartWallet) {
207
+ const portfolio = raw2.portfolio;
225
208
  const portfolioByChain = portfolio.portfolioByChain ?? {};
226
209
  let totalBalance = 0;
227
210
  const tokens = [];
@@ -266,7 +249,7 @@ function mapBalances(raw, _chainId, smartWallet) {
266
249
  )
267
250
  )
268
251
  ),
269
- apy: netApy(p.pool_apy_withFee, p.pool_apy, "pool_apy_withFee"),
252
+ apy: p.pool_apy,
270
253
  tvl: p.pool_tvl,
271
254
  // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
272
255
  // structural read keeps it undefined today and auto-populates later.
@@ -290,8 +273,8 @@ function sumTokenValues(tokens) {
290
273
  function sumTokenEarnings(tokens) {
291
274
  return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
292
275
  }
293
- function mapEarnings(raw, smartWallet) {
294
- const totalEarningsByChain = raw.data.totalEarningsByChainWithFee ?? raw.data.totalEarningsByChain ?? {};
276
+ function mapEarnings(raw2, smartWallet) {
277
+ const totalEarningsByChain = raw2.data.totalEarningsByChainWithFee ?? raw2.data.totalEarningsByChain ?? {};
295
278
  const tokens = [];
296
279
  for (const [chainIdKey, tokensBySymbol] of Object.entries(
297
280
  totalEarningsByChain
@@ -310,15 +293,15 @@ function mapEarnings(raw, smartWallet) {
310
293
  return {
311
294
  smartWallet,
312
295
  lifetimeEarnings: sumTokenEarnings(
313
- raw.data.totalEarningsByTokenWithFee ?? raw.data.totalEarningsByToken
296
+ raw2.data.totalEarningsByTokenWithFee ?? raw2.data.totalEarningsByToken
314
297
  ),
315
298
  tokens
316
299
  };
317
300
  }
318
- function mapWeightedApyByChain(raw) {
319
- if (!raw) return void 0;
301
+ function mapWeightedApyByChain(raw2) {
302
+ if (!raw2) return void 0;
320
303
  const out = {};
321
- for (const [chainKey, tokenApy] of Object.entries(raw)) {
304
+ for (const [chainKey, tokenApy] of Object.entries(raw2)) {
322
305
  const chainId = Number(chainKey);
323
306
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
324
307
  const perAsset = {};
@@ -331,19 +314,6 @@ function mapWeightedApyByChain(raw) {
331
314
  }
332
315
  return Object.keys(out).length > 0 ? out : void 0;
333
316
  }
334
- var warnedGrossApyFallbacks = /* @__PURE__ */ new Set();
335
- function netApy(net, gross, field) {
336
- if (net !== void 0 && net !== null) return net;
337
- if (gross === void 0 || gross === null) return void 0;
338
- if (!warnedGrossApyFallbacks.has(field)) {
339
- warnedGrossApyFallbacks.add(field);
340
- console.warn(
341
- `[owney] @zyfai/sdk omitted "${field}"; falling back to the gross APY, which does not deduct Zyfai's performance fee and so reads high.`
342
- );
343
- }
344
- debugLog("zyfai:apy", `gross fallback for ${field}`, { gross });
345
- return gross;
346
- }
347
317
  function rawPoolApyForChain(entry, chainId, tokenSymbol) {
348
318
  let weightedSum = 0;
349
319
  let totalBalance = 0;
@@ -351,22 +321,21 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
351
321
  if (p.chainId !== chainId) continue;
352
322
  if (tokenSymbol && p.tokenSymbol !== tokenSymbol) continue;
353
323
  const balance = p.balance ?? 0;
354
- const apy = netApy(p.apy_withFee, p.apy, "apy_withFee");
355
- if (balance <= 0 || typeof apy !== "number") continue;
356
- weightedSum += apy * balance;
324
+ if (balance <= 0 || typeof p.apy !== "number") continue;
325
+ weightedSum += p.apy * balance;
357
326
  totalBalance += balance;
358
327
  }
359
328
  return totalBalance > 0 ? weightedSum / totalBalance : null;
360
329
  }
361
- function mapApyHistory(raw, chainId, tokenSymbol) {
362
- const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
330
+ function mapApyHistory(raw2, chainId, tokenSymbol) {
331
+ const history = Object.entries(raw2.history ?? {}).map(([date, entry]) => ({
363
332
  date,
364
333
  apy: rawPoolApyForChain(entry, chainId, tokenSymbol)
365
334
  })).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
366
335
  return {
367
- walletAddress: raw.walletAddress,
368
- weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
369
- apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain),
336
+ walletAddress: raw2.walletAddress,
337
+ weightedApyAfterFee: raw2.weightedApyAfterFee ? sumTokenValues(raw2.weightedApyAfterFee) : void 0,
338
+ apyByChainAndAsset: mapWeightedApyByChain(raw2.weightedApyAfterFeeByChain),
370
339
  history
371
340
  };
372
341
  }
@@ -401,12 +370,8 @@ function mapEntries(rawEntries, chainId) {
401
370
  const matched = positions.find(
402
371
  (p) => p.protocol_name ? rawLog.newOpportunity.includes(`${p.protocol_name} (${p.pool})`) : false
403
372
  );
404
- oldApy = Number(
405
- netApy(rawLog.oldApy_withFee, rawLog.oldApy, "oldApy_withFee")
406
- );
407
- newApy = Number(
408
- netApy(rawLog.newApy_withFee, rawLog.newApy, "newApy_withFee")
409
- );
373
+ oldApy = Number(rawLog.oldApy);
374
+ newApy = Number(rawLog.newApy);
410
375
  const from = splitOpportunity(rawLog.oldOpportunity);
411
376
  const to = splitOpportunity(rawLog.newOpportunity);
412
377
  rebalanceLog = [
@@ -462,23 +427,23 @@ function mapEntries(rawEntries, chainId) {
462
427
  };
463
428
  });
464
429
  }
465
- function mapUserProfile(raw, userAddress) {
430
+ function mapUserProfile(raw2, userAddress) {
466
431
  return {
467
432
  address: userAddress,
468
- smartWallet: raw.smartWallet || "",
469
- chains: raw.chains || [],
470
- strategy: raw.strategy,
471
- hasActiveSessionKey: raw.hasActiveSessionKey || false,
472
- protocols: raw.protocols || [],
473
- splitting: raw.splitting,
474
- minSplits: raw.minSplits
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
475
440
  };
476
441
  }
477
- function mapApyByStrategy(raw) {
442
+ function mapApyByStrategy(raw2) {
478
443
  const apyPerAsset = {};
479
444
  let apySum = 0;
480
445
  let apyCount = 0;
481
- for (const entry of raw.data) {
446
+ for (const entry of raw2.data) {
482
447
  const supported = SupportedAssets.find(
483
448
  (asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
484
449
  );
@@ -486,11 +451,7 @@ function mapApyByStrategy(raw) {
486
451
  const chainKey = supported.chainId;
487
452
  const symbolKey = supported.symbol;
488
453
  const bucket = apyPerAsset[chainKey] ?? {};
489
- const apy = netApy(
490
- entry.average_apy_withFee,
491
- entry.average_apy,
492
- "average_apy_withFee"
493
- ) ?? entry.average_apy;
454
+ const apy = entry.average_apy_with_fee ?? entry.average_apy;
494
455
  bucket[symbolKey] = apy;
495
456
  apyPerAsset[chainKey] = bucket;
496
457
  apySum += apy;
@@ -649,15 +610,15 @@ var readSession = (address, _chainId) => {
649
610
  if (typeof window === "undefined") return null;
650
611
  const key2 = buildKey(address);
651
612
  const store = storage();
652
- let raw = null;
613
+ let raw2 = null;
653
614
  try {
654
- raw = store?.getItem(key2) ?? null;
615
+ raw2 = store?.getItem(key2) ?? null;
655
616
  } catch {
656
- raw = null;
617
+ raw2 = null;
657
618
  }
658
- if (raw) {
619
+ if (raw2) {
659
620
  try {
660
- const parsed = JSON.parse(raw);
621
+ const parsed = JSON.parse(raw2);
661
622
  if (isFreshSession(parsed)) return parsed;
662
623
  } catch {
663
624
  }
@@ -825,8 +786,8 @@ function buildChainsByProtocol(protocols, dashboardChainIds) {
825
786
  }
826
787
  return result;
827
788
  }
828
- function flattenAvailablePools(raw) {
829
- const byChain = raw ?? {};
789
+ function flattenAvailablePools(raw2) {
790
+ const byChain = raw2 ?? {};
830
791
  const names = [];
831
792
  for (const byToken of Object.values(byChain ?? {})) {
832
793
  for (const entry of Object.values(byToken ?? {})) {
@@ -850,6 +811,23 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
850
811
  return !protocolListsEqual(current.protocols, desiredProtocols);
851
812
  }
852
813
 
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
+
853
831
  // src/agents/zyfai/zyfai.agent.ts
854
832
  var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi([
855
833
  "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
@@ -1320,8 +1298,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
1320
1298
  const poolResults = await Promise.all(
1321
1299
  universe.map(async (protocol) => {
1322
1300
  try {
1323
- const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
1324
- return [protocol.id, flattenAvailablePools(raw)];
1301
+ const raw2 = await this.sdk.getAvailablePools(protocol.id, strategy);
1302
+ return [protocol.id, flattenAvailablePools(raw2)];
1325
1303
  } catch (error) {
1326
1304
  console.warn(
1327
1305
  `[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
@@ -1387,14 +1365,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1387
1365
  async readWalletState(ownerAddress) {
1388
1366
  try {
1389
1367
  const { portfolio } = await this.sdk.getPositions(ownerAddress);
1390
- const raw = portfolio;
1368
+ const raw2 = portfolio;
1391
1369
  debugLog("zyfai:onboard", "wallet state from getPositions", {
1392
- predeployed: raw?.predeployed,
1393
- hasActiveSessionKey: raw?.hasActiveSessionKey
1370
+ predeployed: raw2?.predeployed,
1371
+ hasActiveSessionKey: raw2?.hasActiveSessionKey
1394
1372
  });
1395
1373
  return {
1396
- predeployed: raw?.predeployed,
1397
- hasActiveSessionKey: raw?.hasActiveSessionKey
1374
+ predeployed: raw2?.predeployed,
1375
+ hasActiveSessionKey: raw2?.hasActiveSessionKey
1398
1376
  };
1399
1377
  } catch (error) {
1400
1378
  console.warn(
@@ -1674,14 +1652,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1674
1652
  return { txHash, smartWallet, amount };
1675
1653
  }
1676
1654
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
1677
- const raw = await this.sdk.depositFunds(
1655
+ const raw2 = await this.sdk.depositFunds(
1678
1656
  this.getAddress(),
1679
1657
  validChainId,
1680
1658
  amount,
1681
1659
  asset,
1682
1660
  "aggressive"
1683
1661
  );
1684
- return mapDeposit(raw);
1662
+ return mapDeposit(raw2);
1685
1663
  } catch (error) {
1686
1664
  throw error;
1687
1665
  }
@@ -1690,32 +1668,32 @@ var ZyfaiAgent = class _ZyfaiAgent {
1690
1668
  async withdraw(state, chainId, token, amount) {
1691
1669
  const validChainId = isValidChainId(chainId);
1692
1670
  await this.ensureConnected(state, validChainId);
1693
- const raw = await this.sdk.withdrawFunds(
1671
+ const raw2 = await this.sdk.withdrawFunds(
1694
1672
  this.getAddress(),
1695
1673
  validChainId,
1696
1674
  amount,
1697
1675
  token
1698
1676
  );
1699
- if (!raw.success) {
1677
+ if (!raw2.success) {
1700
1678
  throw new OwneyError(
1701
1679
  "WITHDRAW_FAILED",
1702
- raw.message || "Zyfai withdraw failed.",
1703
- { chainId: validChainId, token, amount, response: raw },
1680
+ raw2.message || "Zyfai withdraw failed.",
1681
+ { chainId: validChainId, token, amount, response: raw2 },
1704
1682
  this.id
1705
1683
  );
1706
1684
  }
1707
- return mapWithdraw(raw);
1685
+ return mapWithdraw(raw2);
1708
1686
  }
1709
1687
  // --- IAgent: Portfolio reads ---
1710
1688
  async getBalances(state, chainId) {
1711
1689
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
1712
- const raw = await this.sdk.getPortfolio(this.getAddress());
1713
- return mapBalances(raw, validChainId, smartWallet);
1690
+ const raw2 = await this.sdk.getPortfolio(this.getAddress());
1691
+ return mapBalances(raw2, validChainId, smartWallet);
1714
1692
  }
1715
1693
  async getEarnings(state, chainId) {
1716
1694
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1717
- const raw = await this.sdk.getOnchainEarnings(smartWallet);
1718
- return mapEarnings(raw, smartWallet);
1695
+ const raw2 = await this.sdk.getOnchainEarnings(smartWallet);
1696
+ return mapEarnings(raw2, smartWallet);
1719
1697
  }
1720
1698
  async refreshEarnings(state, chainId) {
1721
1699
  if (this.earningsRefreshInFlight) return this.earningsRefreshInFlight;
@@ -1735,8 +1713,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
1735
1713
  }
1736
1714
  async getAccountApy(state, chainId, days, tokenSymbol) {
1737
1715
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1738
- const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
1739
- return mapApyHistory(raw, chainId, tokenSymbol);
1716
+ const raw2 = await this.sdk.getDailyApyHistory(smartWallet, days);
1717
+ return mapApyHistory(raw2, chainId, tokenSymbol);
1740
1718
  }
1741
1719
  /**
1742
1720
  * Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
@@ -1771,7 +1749,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1771
1749
  const matched = [];
1772
1750
  let backendExhausted = false;
1773
1751
  for (let i = 0; i < MAX_BACKEND_CALLS && matched.length < limit; i++) {
1774
- const raw = await this.sdk.getHistory(smartWallet, validChainId, {
1752
+ const raw2 = await this.sdk.getHistory(smartWallet, validChainId, {
1775
1753
  limit: backendPageSize,
1776
1754
  offset,
1777
1755
  fromDate: options?.fromDate,
@@ -1782,13 +1760,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
1782
1760
  // asset's rows and handing back a page that filters to nothing.
1783
1761
  assetType
1784
1762
  });
1785
- raw.data.forEach((entry, idx) => {
1763
+ raw2.data.forEach((entry, idx) => {
1786
1764
  if (entry.chainId === validChainId) {
1787
1765
  matched.push({ entry, rawIdx: offset + idx });
1788
1766
  }
1789
1767
  });
1790
- offset += raw.data.length;
1791
- if (raw.data.length < backendPageSize) {
1768
+ offset += raw2.data.length;
1769
+ if (raw2.data.length < backendPageSize) {
1792
1770
  backendExhausted = true;
1793
1771
  break;
1794
1772
  }
@@ -1810,18 +1788,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
1810
1788
  }
1811
1789
  async getUserProfile(state, chainId) {
1812
1790
  await this.connectAuth(state, chainId);
1813
- const raw = await this.sdk.getUserDetails();
1791
+ const raw2 = await this.sdk.getUserDetails();
1814
1792
  debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
1815
1793
  asset: "USDC (default \u2014 no asset passed)",
1816
- splitting: raw.splitting,
1817
- minSplits: raw.minSplits,
1818
- strategy: raw.strategy,
1819
- chains: raw.chains,
1820
- protocolCount: raw.protocols?.length,
1821
- hasActiveSessionKey: raw.hasActiveSessionKey,
1822
- smartWallet: raw.smartWallet
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
1823
1801
  });
1824
- return mapUserProfile(raw, this.connectedAddress);
1802
+ return mapUserProfile(raw2, this.connectedAddress);
1825
1803
  }
1826
1804
  async ensureAutoSelectProtocols(state, chainId, asset) {
1827
1805
  await this.connectAuth(state, chainId);
@@ -1840,325 +1818,192 @@ var ZyfaiAgent = class _ZyfaiAgent {
1840
1818
  }
1841
1819
  // --- IAgent: Discovery (no wallet required) ---
1842
1820
  async getAgentApy(days, options) {
1843
- const raw = await this.sdk.getAPYPerStrategy(
1821
+ const raw2 = await this.sdk.getAPYPerStrategy(
1844
1822
  false,
1845
1823
  DayFilterMapping[days],
1846
1824
  "aggressive",
1847
1825
  options?.chainId,
1848
1826
  options?.tokenSymbol
1849
1827
  );
1850
- return mapApyByStrategy(raw);
1828
+ return mapApyByStrategy(raw2);
1851
1829
  }
1852
1830
  };
1853
1831
 
1854
- // src/lib/routing-api.ts
1855
- var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1856
- async function fetchOrgAgentConfig(apiKey) {
1857
- const url = `${ROUTING_API_BASE_URL}/api/v1/agent/org-config`;
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;
1858
1856
  try {
1859
- const res = await fetch(url, {
1860
- method: "GET",
1861
- headers: {
1862
- "Content-Type": "application/json",
1863
- "x-owney-api-key": `${apiKey}`
1864
- }
1865
- });
1866
- if (!res.ok) {
1867
- if (res.status !== 404) {
1868
- console.warn(
1869
- `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
1870
- );
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)
1871
1866
  }
1872
- return null;
1873
- }
1874
- const json = await res.json();
1875
- const policy = json.success ? json.data ?? null : null;
1876
- debugLog(
1877
- "owney-sdk",
1878
- policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
1879
- policy ?? void 0
1880
1867
  );
1881
- return policy;
1882
- } catch (error) {
1883
- console.warn(
1884
- "[owney-sdk] Could not read org agent config (non-fatal):",
1885
- error instanceof Error ? error.message : String(error)
1868
+ }
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 }
1886
1875
  );
1887
- return null;
1888
1876
  }
1889
1877
  }
1890
- async function fetchAgentKeys(apiKey) {
1891
- const url = `${ROUTING_API_BASE_URL}/api/v1/agent/keys`;
1892
- const res = await fetch(url, {
1893
- method: "GET",
1894
- headers: {
1895
- "Content-Type": "application/json",
1896
- "x-owney-api-key": `${apiKey}`
1897
- }
1898
- });
1899
- if (!res.ok) {
1900
- const text = await res.text().catch(() => "");
1878
+
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
+ };
1901
+ }
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 };
1908
+ }
1909
+ function randomAuthNonce() {
1910
+ const bytes = new Uint8Array(32);
1911
+ globalThis.crypto.getRandomValues(bytes);
1912
+ return bytesToHex(bytes);
1913
+ }
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) {
1901
1931
  throw new OwneyError(
1902
- "API_ROUTING_ERROR",
1903
- `Routing API error ${res.status}: ${text}`,
1904
- { statusCode: res.status, responseBody: text }
1932
+ "SPONSOR_REQUEST_FAILED",
1933
+ `Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
1934
+ { cause: String(networkError), safeToFallback: true }
1905
1935
  );
1906
1936
  }
1907
- const json = await res.json();
1908
- if (!json.success) {
1937
+ const text = await res.text();
1938
+ let parsed = null;
1939
+ try {
1940
+ parsed = JSON.parse(text);
1941
+ } catch {
1942
+ }
1943
+ if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
1909
1944
  throw new OwneyError(
1910
- "API_ROUTING_FAILED",
1911
- `Routing API request failed: ${json.message}`,
1912
- { message: json.message }
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
1951
+ }
1913
1952
  );
1914
1953
  }
1915
- return json.data;
1954
+ return parsed.data;
1916
1955
  }
1917
-
1918
- // src/lib/health-report.ts
1919
- var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1920
- async function reportAgentFailure(apiKey, agentType, errorCode) {
1956
+ async function postSponsorTransferAuth(input) {
1957
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
1958
+ let res;
1921
1959
  try {
1922
- await fetch(`${ROUTING_API_BASE_URL2}/api/v1/agent/health-report`, {
1960
+ res = await fetch(`${base5}/api/v1/sponsor/erc20-transfer-auth`, {
1923
1961
  method: "POST",
1924
1962
  headers: {
1925
- "Content-Type": "application/json",
1926
- "x-owney-api-key": apiKey
1963
+ "content-type": "application/json",
1964
+ "x-owney-api-key": input.apiKey,
1965
+ ...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
1927
1966
  },
1928
- body: JSON.stringify({
1929
- agent_type: agentType,
1930
- error_code: errorCode,
1931
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1932
- })
1967
+ body: JSON.stringify(input.body)
1933
1968
  });
1934
- } catch (err) {
1935
- console.warn(
1936
- `[owney-sdk] health-report failed for agent "${agentType}":`,
1937
- err instanceof Error ? err.message : err
1969
+ } catch (networkError) {
1970
+ throw new OwneyError(
1971
+ "SPONSOR_REQUEST_FAILED",
1972
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
1973
+ { cause: String(networkError) }
1938
1974
  );
1939
1975
  }
1940
- }
1941
- async function withFailureReporting(apiKey, agentType, fn) {
1976
+ const text = await res.text();
1977
+ let parsed = null;
1942
1978
  try {
1943
- return await fn();
1944
- } catch (err) {
1945
- const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
1946
- void reportAgentFailure(apiKey, agentType, errorCode);
1947
- throw err;
1979
+ parsed = JSON.parse(text);
1980
+ } catch {
1948
1981
  }
1949
- }
1950
-
1951
- // src/lib/helpers/withdraw-helper.ts
1952
- import { parseUnits } from "viem";
1953
- function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
1954
- const target = asset.toUpperCase();
1955
- return agents.map((agent) => {
1956
- const agentBalance = aggregated[agent.id];
1957
- const tokenBalance = agentBalance?.tokens.find(
1958
- (t) => t.chainId === chainId && t.asset.toUpperCase() === target
1982
+ if (!res.ok || !parsed?.success || !parsed.data) {
1983
+ throw new OwneyError(
1984
+ "SPONSOR_REQUEST_FAILED",
1985
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1986
+ {
1987
+ statusCode: res.status,
1988
+ responseBody: text.slice(0, 500),
1989
+ // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
1990
+ // before broadcast, so it is safe to fall back to a user-paid deposit.
1991
+ safeToFallback: res.status === 503
1992
+ }
1959
1993
  );
1960
- if (!tokenBalance) return { agent, balance: 0n };
1961
- return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
1962
- });
1963
- }
1964
- function planProportionalShares(balances, requested, totalAvailable) {
1965
- const plans = balances.map(({ agent, balance }) => ({
1966
- agent,
1967
- balance,
1968
- planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
1969
- }));
1970
- const assigned = plans.reduce((s, p) => s + p.planned, 0n);
1971
- let remainder = requested - assigned;
1972
- const byHeadroom = [...plans].sort((a, b) => {
1973
- const diff = b.balance - b.planned - (a.balance - a.planned);
1974
- return diff > 0n ? 1 : diff < 0n ? -1 : 0;
1975
- });
1976
- for (const p of byHeadroom) {
1977
- if (remainder === 0n) break;
1978
- const headroom = p.balance - p.planned;
1979
- if (headroom <= 0n) continue;
1980
- const take = headroom < remainder ? headroom : remainder;
1981
- p.planned += take;
1982
- remainder -= take;
1983
1994
  }
1984
- return plans;
1985
- }
1986
- function planDisabledDrain(disabled, requested) {
1987
- const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
1988
- const plans = [];
1989
- let remaining = requested;
1990
- for (const { agent, balance } of sorted) {
1991
- if (remaining === 0n) {
1992
- plans.push({ agent, balance, planned: 0n });
1993
- continue;
1994
- }
1995
- const take = balance < remaining ? balance : remaining;
1996
- plans.push({ agent, balance, planned: take });
1997
- remaining -= take;
1998
- }
1999
- return { plans, remaining };
2000
- }
2001
- function redistributeShare(plans, fromIndex, amount, candidatePool) {
2002
- const pool = candidatePool ?? plans.slice(fromIndex + 1);
2003
- const candidates = pool.filter((c) => c.balance - c.planned > 0n);
2004
- const totalHeadroom = candidates.reduce(
2005
- (s, c) => s + (c.balance - c.planned),
2006
- 0n
2007
- );
2008
- if (totalHeadroom === 0n) return;
2009
- let distributed = 0n;
2010
- for (const c of candidates) {
2011
- const headroom = c.balance - c.planned;
2012
- const proportional = headroom * amount / totalHeadroom;
2013
- const give = proportional > headroom ? headroom : proportional;
2014
- c.planned += give;
2015
- distributed += give;
2016
- }
2017
- let leftover = amount - distributed;
2018
- for (const c of candidates) {
2019
- if (leftover === 0n) break;
2020
- const headroom = c.balance - c.planned;
2021
- if (headroom <= 0n) continue;
2022
- const take = headroom < leftover ? headroom : leftover;
2023
- c.planned += take;
2024
- leftover -= take;
2025
- }
2026
- }
2027
- function sumWithdrawnAmount(results) {
2028
- return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
2029
- }
2030
-
2031
- // src/lib/helpers/account-apy-helper.ts
2032
- function aggregateApyByChainAndAsset(agentApys, agentBalances) {
2033
- const sums = {};
2034
- const weights = {};
2035
- for (const id of Object.keys(agentApys)) {
2036
- const cells = agentApys[id].apyByChainAndAsset;
2037
- const balance = agentBalances[id] ?? 0;
2038
- if (!cells || balance <= 0) continue;
2039
- for (const [chainKey, perAsset] of Object.entries(cells)) {
2040
- if (!perAsset) continue;
2041
- const chainId = Number(chainKey);
2042
- for (const [asset, apyValue] of Object.entries(perAsset)) {
2043
- const apy = Number(apyValue ?? 0);
2044
- if (apy === 0) continue;
2045
- sums[chainId] ??= {};
2046
- weights[chainId] ??= {};
2047
- sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
2048
- weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
2049
- }
2050
- }
2051
- }
2052
- const out = {};
2053
- for (const chainKey of Object.keys(sums)) {
2054
- const chainId = Number(chainKey);
2055
- const perAssetOut = {};
2056
- for (const asset of Object.keys(sums[chainId])) {
2057
- const w = weights[chainId][asset];
2058
- if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
2059
- }
2060
- if (Object.keys(perAssetOut).length > 0) {
2061
- out[chainId] = perAssetOut;
2062
- }
2063
- }
2064
- return out;
2065
- }
2066
-
2067
- // src/client.ts
2068
- import {
2069
- createPublicClient as createPublicClient2,
2070
- createWalletClient,
2071
- custom
2072
- } from "viem";
2073
- import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
2074
-
2075
- // src/lib/transfer-auth.ts
2076
- import { bytesToHex } from "viem";
2077
- var ERC20_META_ABI = [
2078
- { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
2079
- { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
2080
- ];
2081
- function buildTransferWithAuthorizationTypedData(input) {
2082
- return {
2083
- domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
2084
- types: {
2085
- TransferWithAuthorization: [
2086
- { name: "from", type: "address" },
2087
- { name: "to", type: "address" },
2088
- { name: "value", type: "uint256" },
2089
- { name: "validAfter", type: "uint256" },
2090
- { name: "validBefore", type: "uint256" },
2091
- { name: "nonce", type: "bytes32" }
2092
- ]
2093
- },
2094
- primaryType: "TransferWithAuthorization",
2095
- message: input.message
2096
- };
2097
- }
2098
- async function readTokenMeta(publicClient, token) {
2099
- const [tokenName, tokenVersion] = await Promise.all([
2100
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
2101
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
2102
- ]);
2103
- return { tokenName, tokenVersion };
2104
- }
2105
- function randomAuthNonce() {
2106
- const bytes = new Uint8Array(32);
2107
- globalThis.crypto.getRandomValues(bytes);
2108
- return bytesToHex(bytes);
2109
- }
2110
-
2111
- // src/lib/sponsor-client.ts
2112
- var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2113
- async function postSponsorTransferAuth(input) {
2114
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2115
- let res;
2116
- try {
2117
- res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
2118
- method: "POST",
2119
- headers: {
2120
- "content-type": "application/json",
2121
- "x-owney-api-key": input.apiKey
2122
- },
2123
- body: JSON.stringify(input.body)
2124
- });
2125
- } catch (networkError) {
2126
- throw new OwneyError(
2127
- "SPONSOR_REQUEST_FAILED",
2128
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2129
- { cause: String(networkError) }
2130
- );
2131
- }
2132
- const text = await res.text();
2133
- let parsed = null;
2134
- try {
2135
- parsed = JSON.parse(text);
2136
- } catch {
2137
- }
2138
- if (!res.ok || !parsed?.success || !parsed.data) {
2139
- throw new OwneyError(
2140
- "SPONSOR_REQUEST_FAILED",
2141
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2142
- {
2143
- statusCode: res.status,
2144
- responseBody: text.slice(0, 500),
2145
- // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
2146
- // before broadcast, so it is safe to fall back to a user-paid deposit.
2147
- safeToFallback: res.status === 503
2148
- }
2149
- );
2150
- }
2151
- return parsed.data;
1995
+ return parsed.data;
2152
1996
  }
2153
1997
  async function postSponsorPermit2Transfer(input) {
2154
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1998
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2155
1999
  let res;
2156
2000
  try {
2157
- res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
2001
+ res = await fetch(`${base5}/api/v1/sponsor/permit2-transfer`, {
2158
2002
  method: "POST",
2159
2003
  headers: {
2160
2004
  "content-type": "application/json",
2161
- "x-owney-api-key": input.apiKey
2005
+ "x-owney-api-key": input.apiKey,
2006
+ ...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
2162
2007
  },
2163
2008
  body: JSON.stringify(input.body)
2164
2009
  });
@@ -2189,11 +2034,11 @@ async function postSponsorPermit2Transfer(input) {
2189
2034
  return parsed.data;
2190
2035
  }
2191
2036
  async function getSponsorRelayerAddress(input) {
2192
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2037
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2193
2038
  let res;
2194
2039
  try {
2195
2040
  res = await fetch(
2196
- `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2041
+ `${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2197
2042
  {
2198
2043
  headers: { "x-owney-api-key": input.apiKey }
2199
2044
  }
@@ -2302,205 +2147,1873 @@ async function readErc20Balance(publicClient, token, owner) {
2302
2147
  args: [owner]
2303
2148
  });
2304
2149
  }
2305
-
2306
- // src/lib/chain-guard.ts
2307
- var CHAIN_NAMES = {
2308
- 1: "Ethereum",
2309
- 8453: "Base",
2310
- 42161: "Arbitrum"
2311
- };
2312
- function chainName(chainId) {
2313
- return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
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
+ return {
2808
+ agent: "yieldseeker",
2809
+ action: actionType(action.actionType),
2810
+ date: action.createdDate,
2811
+ oldApy: null,
2812
+ newApy: null,
2813
+ transactions: [
2814
+ {
2815
+ txHashes: transactionHashes(action.details),
2816
+ chainId: 8453
2817
+ }
2818
+ ],
2819
+ rebalanceLog: []
2820
+ };
2821
+ }
2822
+ function movementEntry(movement, wallet, agent, asset) {
2823
+ const isDeposit = movement.toAddress.toLowerCase() === wallet.walletAddress.toLowerCase();
2824
+ return {
2825
+ agent: "yieldseeker",
2826
+ action: isDeposit ? "Deposit" : "Withdraw",
2827
+ date: movement.blockDate,
2828
+ oldApy: null,
2829
+ newApy: null,
2830
+ transactions: [
2831
+ {
2832
+ txHashes: [movement.transactionHash],
2833
+ chainId: agent.chainId,
2834
+ tokenSymbol: asset,
2835
+ amount: decimal(
2836
+ movement.assetAmount,
2837
+ YIELDSEEKER_ASSET_METADATA[asset].decimals,
2838
+ "historic position"
2839
+ )
2840
+ }
2841
+ ],
2842
+ rebalanceLog: []
2843
+ };
2844
+ }
2845
+ function mapYieldseekerHistory(contexts, options) {
2846
+ const entries = contexts.flatMap((context) => [
2847
+ ...(context.actions ?? []).map(actionEntry),
2848
+ ...(context.historic?.movements ?? []).map(
2849
+ (movement) => movementEntry(movement, context.wallet, context.agent, context.asset)
2850
+ )
2851
+ ]);
2852
+ const filtered = entries.filter(
2853
+ (entry) => (!options.fromDate || entry.date >= options.fromDate) && (!options.toDate || entry.date <= options.toDate)
2854
+ ).filter(
2855
+ (entry, index, all) => all.findIndex(
2856
+ (candidate) => candidate.action === entry.action && candidate.date === entry.date && candidate.transactions[0]?.txHashes[0] === entry.transactions[0]?.txHashes[0]
2857
+ ) === index
2858
+ ).sort((left, right) => right.date.localeCompare(left.date));
2859
+ return {
2860
+ data: filtered.slice(0, options.limit),
2861
+ // v1 returns the whole action/movement collection and defines no cursor.
2862
+ // Report a terminal page so callers never loop over the same prefix.
2863
+ hasMore: false
2864
+ };
2865
+ }
2866
+ function mapYieldseekerProfile(address, contexts) {
2867
+ const protocols = /* @__PURE__ */ new Set();
2868
+ for (const context of contexts) {
2869
+ for (const current of context.positions) {
2870
+ if (current.yieldOption?.provider) {
2871
+ protocols.add(String(current.yieldOption.provider));
2872
+ }
2873
+ }
2874
+ }
2875
+ return {
2876
+ address,
2877
+ smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
2878
+ chains: contexts.length > 0 ? [8453] : [],
2879
+ hasActiveSessionKey: contexts.some(
2880
+ (context) => context.wallet.initializedDate != null
2881
+ ),
2882
+ protocols: [...protocols]
2883
+ };
2884
+ }
2885
+ function mapYieldseekerAgentApy(options, days) {
2886
+ const perAsset = {};
2887
+ const all = [];
2888
+ for (const entry of options) {
2889
+ const apys = entry.yieldOptions.map(
2890
+ (option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
2891
+ ).filter(Number.isFinite);
2892
+ if (apys.length === 0) continue;
2893
+ const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
2894
+ perAsset[entry.asset] = average;
2895
+ all.push(average);
2896
+ }
2897
+ return {
2898
+ averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
2899
+ detailedApys: { apyPerAsset: { 8453: perAsset } }
2900
+ };
2901
+ }
2902
+
2903
+ // src/agents/yieldseeker/yieldseeker.agent.ts
2904
+ var OWNEY_AGENT_NAME = "owney";
2905
+ var YIELDSEEKER_AGENT_WALLET_ABI = [
2906
+ {
2907
+ type: "function",
2908
+ name: "withdrawAssetToUser",
2909
+ stateMutability: "nonpayable",
2910
+ inputs: [
2911
+ { name: "recipient", type: "address" },
2912
+ { name: "asset", type: "address" },
2913
+ { name: "amount", type: "uint256" }
2914
+ ],
2915
+ outputs: []
2916
+ },
2917
+ {
2918
+ type: "function",
2919
+ name: "withdrawAllAssetToUser",
2920
+ stateMutability: "nonpayable",
2921
+ inputs: [
2922
+ { name: "recipient", type: "address" },
2923
+ { name: "asset", type: "address" }
2924
+ ],
2925
+ outputs: []
2926
+ }
2927
+ ];
2928
+ function query(params) {
2929
+ const search = new URLSearchParams();
2930
+ for (const [key2, value] of Object.entries(params)) {
2931
+ if (value !== void 0) search.set(key2, String(value));
2932
+ }
2933
+ const encoded = search.toString();
2934
+ return encoded ? `?${encoded}` : "";
2935
+ }
2936
+ var YieldseekerAgent = class {
2937
+ id = "yieldseeker";
2938
+ balanceComposition = "tokens-plus-positions";
2939
+ supportedChainIds = [8453];
2940
+ supportedAssets = [
2941
+ {
2942
+ chainId: 8453,
2943
+ chain: "BASE",
2944
+ assets: [
2945
+ { symbol: "USDC", minDepositAmount: "10000000" },
2946
+ { symbol: "WETH", minDepositAmount: "1" }
2947
+ ]
2948
+ }
2949
+ ];
2950
+ api;
2951
+ auth;
2952
+ transactionExecutor;
2953
+ unwindReceiptWaiter;
2954
+ agentContexts = /* @__PURE__ */ new Map();
2955
+ users = /* @__PURE__ */ new Map();
2956
+ pendingAgents = /* @__PURE__ */ new Map();
2957
+ constructor(owneyApiKey, options = {}) {
2958
+ this.api = new YieldseekerApiClient(
2959
+ owneyApiKey,
2960
+ options.baseUrl ?? getYieldseekerProxyBaseUrl(),
2961
+ options.fetchFn
2962
+ );
2963
+ this.auth = new YieldseekerAuth(options.auth);
2964
+ this.transactionExecutor = options.transactionExecutor;
2965
+ this.unwindReceiptWaiter = options.unwindReceiptWaiter;
2966
+ }
2967
+ async disconnect() {
2968
+ this.auth.clear();
2969
+ for (const key2 of this.users.keys()) {
2970
+ const [walletAddress, chainId] = key2.split(":");
2971
+ clearYieldseekerIdentity(walletAddress, Number(chainId));
2972
+ }
2973
+ this.users.clear();
2974
+ this.agentContexts.clear();
2975
+ this.pendingAgents.clear();
2976
+ }
2977
+ async activateAgent(state, chainId, asset) {
2978
+ this.assertChain(chainId);
2979
+ const targetAsset = asset ?? "USDC";
2980
+ this.assertAsset(targetAsset);
2981
+ await this.ensureAgent(state, chainId, targetAsset);
2982
+ }
2983
+ async deposit(state, chainId, amount, asset, depositCallback) {
2984
+ this.assertChain(chainId);
2985
+ this.assertAsset(asset);
2986
+ if (BigInt(amount) <= 0n) {
2987
+ throw new OwneyError(
2988
+ "DEPOSIT_AMOUNT_BELOW_MINIMUM",
2989
+ "Yieldseeker deposits must be greater than zero.",
2990
+ { amount, minDepositAmount: "1" },
2991
+ this.id
2992
+ );
2993
+ }
2994
+ const context = await this.ensureAgent(state, chainId, asset);
2995
+ let txHash;
2996
+ try {
2997
+ if (depositCallback) {
2998
+ provideDepositVerificationContext(depositCallback, {
2999
+ agentId: "yieldseeker",
3000
+ signature: await this.auth.getToken(state, chainId),
3001
+ userId: context.user.userId,
3002
+ yieldseekerAgentId: context.agent.agentId
3003
+ });
3004
+ txHash = await depositCallback(
3005
+ context.wallet.walletAddress,
3006
+ chainId,
3007
+ amount
3008
+ );
3009
+ await this.waitForReceipt(state, chainId, txHash);
3010
+ } else {
3011
+ txHash = await this.submitTransaction(state, chainId, {
3012
+ from: getAddress2(state.walletAddress),
3013
+ to: YIELDSEEKER_ASSET_METADATA[asset].address,
3014
+ data: encodeFunctionData({
3015
+ abi: erc20Abi,
3016
+ functionName: "transfer",
3017
+ args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
3018
+ }),
3019
+ value: "0",
3020
+ chainId
3021
+ });
3022
+ }
3023
+ await this.deployAfterFunding(state, chainId, context);
3024
+ } finally {
3025
+ await this.refreshSnapshotAfterMovement(
3026
+ state,
3027
+ chainId,
3028
+ context,
3029
+ "deposit"
3030
+ );
3031
+ }
3032
+ return {
3033
+ txHash,
3034
+ smartWallet: context.wallet.walletAddress,
3035
+ amount
3036
+ };
3037
+ }
3038
+ async withdraw(state, chainId, asset, amount) {
3039
+ this.assertChain(chainId);
3040
+ this.assertAsset(asset);
3041
+ if (amount !== void 0 && BigInt(amount) <= 0n) {
3042
+ throw new OwneyError(
3043
+ "WITHDRAW_FAILED",
3044
+ "Yieldseeker withdrawals must be greater than zero.",
3045
+ { amount },
3046
+ this.id
3047
+ );
3048
+ }
3049
+ const context = await this.findAgent(state, chainId, asset);
3050
+ if (!context) {
3051
+ throw new OwneyError(
3052
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3053
+ `No Yieldseeker ${asset} agent exists for this wallet.`,
3054
+ { asset, available: "0" },
3055
+ this.id
3056
+ );
3057
+ }
3058
+ try {
3059
+ const portfolio = await this.loadPortfolioContext(
3060
+ state,
3061
+ chainId,
3062
+ context
3063
+ );
3064
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3065
+ const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
3066
+ ([address]) => address.toLowerCase() === metadata.address.toLowerCase()
3067
+ );
3068
+ const idle = BigInt(idleEntry?.[1] ?? "0");
3069
+ const deployed = portfolio.positions.reduce(
3070
+ (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
3071
+ 0n
3072
+ );
3073
+ const totalAvailable = idle + deployed;
3074
+ const requested = amount === void 0 ? totalAvailable : BigInt(amount);
3075
+ if (requested > totalAvailable) {
3076
+ throw new OwneyError(
3077
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3078
+ `Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
3079
+ {
3080
+ asset,
3081
+ requested: requested.toString(),
3082
+ available: totalAvailable.toString()
3083
+ },
3084
+ this.id
3085
+ );
3086
+ }
3087
+ let remaining = requested > idle ? requested - idle : 0n;
3088
+ for (const position2 of portfolio.positions) {
3089
+ if (remaining === 0n) break;
3090
+ const available = BigInt(position2.withdrawableAssetsRaw);
3091
+ if (available <= 0n) continue;
3092
+ const assetsRaw = available < remaining ? available : remaining;
3093
+ const response = await this.walletRequest(
3094
+ state,
3095
+ chainId,
3096
+ this.agentPath(context, "withdraw-from-position"),
3097
+ {
3098
+ method: "POST",
3099
+ body: {
3100
+ chainId,
3101
+ vaultAddress: position2.yieldOption.address,
3102
+ assetsRaw: assetsRaw.toString()
3103
+ }
3104
+ }
3105
+ );
3106
+ if (!this.isTransactionHash(response?.transactionHash)) {
3107
+ throw this.invalidResponse("position withdrawal");
3108
+ }
3109
+ await this.waitForReceipt(state, chainId, response.transactionHash);
3110
+ remaining -= assetsRaw;
3111
+ }
3112
+ if (remaining > 0n) {
3113
+ throw this.invalidResponse("yield positions", {
3114
+ reason: "Withdrawable positions could not cover the request.",
3115
+ remaining: remaining.toString()
3116
+ });
3117
+ }
3118
+ const account = getAddress2(state.walletAddress);
3119
+ const txHash = await this.submitTransaction(state, chainId, {
3120
+ from: account,
3121
+ to: getAddress2(context.wallet.walletAddress),
3122
+ data: amount === void 0 ? encodeFunctionData({
3123
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
3124
+ functionName: "withdrawAllAssetToUser",
3125
+ args: [account, metadata.address]
3126
+ }) : encodeFunctionData({
3127
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
3128
+ functionName: "withdrawAssetToUser",
3129
+ args: [account, metadata.address, requested]
3130
+ }),
3131
+ value: "0",
3132
+ chainId
3133
+ });
3134
+ return {
3135
+ txHash,
3136
+ type: amount === void 0 ? "full" : "partial",
3137
+ amount: requested.toString()
3138
+ };
3139
+ } finally {
3140
+ await this.refreshSnapshotAfterMovement(
3141
+ state,
3142
+ chainId,
3143
+ context,
3144
+ "withdrawal"
3145
+ );
3146
+ }
3147
+ }
3148
+ async getBalances(state, chainId) {
3149
+ this.assertChain(chainId);
3150
+ return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
3151
+ }
3152
+ async getEarnings(state, chainId) {
3153
+ this.assertChain(chainId);
3154
+ return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
3155
+ }
3156
+ async getAccountApy(state, chainId, days, tokenSymbol) {
3157
+ this.assertChain(chainId);
3158
+ const asset = tokenSymbol?.toUpperCase();
3159
+ if (asset !== void 0) this.assertAsset(asset);
3160
+ const contexts = await this.loadPortfolio(state, chainId, {
3161
+ ...asset ? { asset } : {},
3162
+ historic: true
3163
+ });
3164
+ return mapYieldseekerApy(state.walletAddress, contexts, days);
3165
+ }
3166
+ async getHistory(state, chainId, options) {
3167
+ this.assertChain(chainId);
3168
+ const asset = options?.tokenSymbol?.toUpperCase();
3169
+ if (asset !== void 0) this.assertAsset(asset);
3170
+ const contexts = await this.loadPortfolio(state, chainId, {
3171
+ ...asset ? { asset } : {},
3172
+ historic: true,
3173
+ actions: true
3174
+ });
3175
+ return mapYieldseekerHistory(contexts, {
3176
+ limit: options?.limit ?? 10,
3177
+ ...options?.fromDate ? { fromDate: options.fromDate } : {},
3178
+ ...options?.toDate ? { toDate: options.toDate } : {}
3179
+ });
3180
+ }
3181
+ async getUserProfile(state, chainId) {
3182
+ this.assertChain(chainId);
3183
+ return mapYieldseekerProfile(
3184
+ state.walletAddress,
3185
+ await this.loadPortfolio(state, chainId, {})
3186
+ );
3187
+ }
3188
+ async getAgentApy(days, options) {
3189
+ this.assertOptionalChain(options?.chainId);
3190
+ const requested = options?.tokenSymbol?.toUpperCase();
3191
+ if (requested !== void 0) this.assertAsset(requested);
3192
+ const assets = requested ? [requested] : ["USDC", "WETH"];
3193
+ const values = await Promise.all(
3194
+ assets.map(async (asset) => {
3195
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3196
+ const response = await this.api.request(
3197
+ `/chains/8453/assets/${metadata.address}/yield-options`
3198
+ );
3199
+ if (!Array.isArray(response?.yieldOptions)) {
3200
+ throw this.invalidResponse("yield options");
3201
+ }
3202
+ return { asset, yieldOptions: response.yieldOptions };
3203
+ })
3204
+ );
3205
+ return mapYieldseekerAgentApy(values, days);
3206
+ }
3207
+ userKey(state, chainId) {
3208
+ return `${state.walletAddress.toLowerCase()}:${chainId}`;
3209
+ }
3210
+ contextKey(state, chainId, asset) {
3211
+ return `${this.userKey(state, chainId)}:${asset}`;
3212
+ }
3213
+ async resolveUser(state, chainId) {
3214
+ const key2 = this.userKey(state, chainId);
3215
+ const inMemory = this.users.get(key2);
3216
+ if (inMemory) return inMemory;
3217
+ const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
3218
+ if (persisted) {
3219
+ this.users.set(key2, persisted);
3220
+ return persisted;
3221
+ }
3222
+ const walletAddress = getAddress2(state.walletAddress);
3223
+ let user = null;
3224
+ try {
3225
+ const login = await this.providerRequest(
3226
+ state,
3227
+ chainId,
3228
+ "/users/login-with-wallet",
3229
+ { method: "POST", body: { walletAddress } }
3230
+ );
3231
+ user = login?.user ?? null;
3232
+ if (!user) {
3233
+ throw this.invalidResponse("wallet login", {
3234
+ reason: "A successful login returned no user."
3235
+ });
3236
+ }
3237
+ } catch (error) {
3238
+ if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
3239
+ if (error instanceof OwneyError) throw error;
3240
+ throw this.mapApiError(error);
3241
+ }
3242
+ let created;
3243
+ try {
3244
+ created = await this.providerRequest(
3245
+ state,
3246
+ chainId,
3247
+ "/users",
3248
+ {
3249
+ method: "POST",
3250
+ body: {
3251
+ walletAddress,
3252
+ username: `owney-${walletAddress.slice(2).toLowerCase()}`
3253
+ }
3254
+ }
3255
+ );
3256
+ } catch (createError) {
3257
+ throw this.mapApiError(createError);
3258
+ }
3259
+ user = created?.user ?? null;
3260
+ }
3261
+ if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
3262
+ throw this.invalidResponse("wallet identity");
3263
+ }
3264
+ const resolved = { userId: user.userId };
3265
+ this.users.set(key2, resolved);
3266
+ writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
3267
+ return resolved;
3268
+ }
3269
+ forgetUser(state, chainId) {
3270
+ this.users.delete(this.userKey(state, chainId));
3271
+ clearYieldseekerIdentity(state.walletAddress, chainId);
3272
+ }
3273
+ async ensureAgent(state, chainId, asset) {
3274
+ const key2 = this.contextKey(state, chainId, asset);
3275
+ const cached = this.agentContexts.get(key2);
3276
+ if (cached) return cached;
3277
+ const pending = this.pendingAgents.get(key2);
3278
+ if (pending) return pending;
3279
+ const request = this.resolveAgent(state, chainId, asset, true).then(
3280
+ (context) => {
3281
+ if (!context) throw this.invalidResponse("agent creation");
3282
+ this.agentContexts.set(key2, context);
3283
+ return context;
3284
+ }
3285
+ );
3286
+ this.pendingAgents.set(key2, request);
3287
+ try {
3288
+ return await request;
3289
+ } finally {
3290
+ this.pendingAgents.delete(key2);
3291
+ }
3292
+ }
3293
+ async findAgent(state, chainId, asset) {
3294
+ const key2 = this.contextKey(state, chainId, asset);
3295
+ const cached = this.agentContexts.get(key2);
3296
+ if (cached) return cached;
3297
+ const context = await this.resolveAgent(state, chainId, asset, false);
3298
+ if (context) this.agentContexts.set(key2, context);
3299
+ return context;
3300
+ }
3301
+ async resolveAgent(state, chainId, asset, createIfMissing) {
3302
+ const user = await this.resolveUser(state, chainId);
3303
+ const response = await this.walletRequest(
3304
+ state,
3305
+ chainId,
3306
+ `/users/${user.userId}/agents`
3307
+ );
3308
+ if (!Array.isArray(response?.agents)) {
3309
+ throw this.invalidResponse("agent list");
3310
+ }
3311
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3312
+ let agent = response.agents.find(
3313
+ (candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
3314
+ );
3315
+ if (!agent && createIfMissing) {
3316
+ const created = await this.walletRequest(
3317
+ state,
3318
+ chainId,
3319
+ `/users/${user.userId}/agents`,
3320
+ {
3321
+ method: "POST",
3322
+ body: {
3323
+ name: OWNEY_AGENT_NAME,
3324
+ emoji: "\u{1F989}",
3325
+ chainId,
3326
+ assetAddress: metadata.address,
3327
+ type: "vault",
3328
+ rulePreset: null
3329
+ }
3330
+ }
3331
+ );
3332
+ agent = created?.agent;
3333
+ }
3334
+ if (!agent) return null;
3335
+ this.assertAgent(agent);
3336
+ const walletResponse = await this.walletRequest(
3337
+ state,
3338
+ chainId,
3339
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3340
+ );
3341
+ if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
3342
+ throw this.invalidResponse("agent wallet");
3343
+ }
3344
+ return { user, agent, wallet: walletResponse.agentWallet, asset };
3345
+ }
3346
+ async loadPortfolio(state, chainId, options) {
3347
+ const user = await this.resolveUser(state, chainId);
3348
+ const response = await this.walletRequest(
3349
+ state,
3350
+ chainId,
3351
+ `/users/${user.userId}/agents`
3352
+ );
3353
+ if (!Array.isArray(response?.agents)) {
3354
+ throw this.invalidResponse("agent list");
3355
+ }
3356
+ const contexts = [];
3357
+ for (const agent of response.agents) {
3358
+ const asset = this.assetForAgent(agent);
3359
+ if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
3360
+ continue;
3361
+ }
3362
+ this.assertAgent(agent);
3363
+ const walletResponse = await this.walletRequest(
3364
+ state,
3365
+ chainId,
3366
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3367
+ );
3368
+ if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
3369
+ throw this.invalidResponse("agent wallet");
3370
+ }
3371
+ const context = {
3372
+ user,
3373
+ agent,
3374
+ wallet: walletResponse.agentWallet,
3375
+ asset
3376
+ };
3377
+ this.agentContexts.set(this.contextKey(state, chainId, asset), context);
3378
+ contexts.push(context);
3379
+ }
3380
+ return Promise.all(
3381
+ contexts.map(
3382
+ (context) => this.loadPortfolioContext(state, chainId, context, options)
3383
+ )
3384
+ );
3385
+ }
3386
+ async loadPortfolioContext(state, chainId, context, options = {}) {
3387
+ const [snapshot, positions, historic, actions] = await Promise.all([
3388
+ this.walletRequest(
3389
+ state,
3390
+ chainId,
3391
+ `${this.agentPath(context, "snapshot")}${query({
3392
+ shouldOnlyUseRecentValue: true,
3393
+ shouldAllowStaleOnError: true
3394
+ })}`
3395
+ ),
3396
+ this.walletRequest(
3397
+ state,
3398
+ chainId,
3399
+ this.agentPath(context, "yield-positions")
3400
+ ),
3401
+ options.historic ? this.walletRequest(
3402
+ state,
3403
+ chainId,
3404
+ this.agentPath(context, "wallet/historic-position")
3405
+ ) : Promise.resolve(void 0),
3406
+ options.actions ? this.walletRequest(
3407
+ state,
3408
+ chainId,
3409
+ this.agentPath(context, "actions")
3410
+ ) : Promise.resolve(void 0)
3411
+ ]);
3412
+ if (!snapshot?.agentSnapshot) {
3413
+ throw this.invalidResponse("agent snapshot");
3414
+ }
3415
+ if (!Array.isArray(positions?.yieldPositions)) {
3416
+ throw this.invalidResponse("yield positions");
3417
+ }
3418
+ return {
3419
+ ...context,
3420
+ snapshot: snapshot.agentSnapshot,
3421
+ positions: positions.yieldPositions,
3422
+ ...historic?.position ? { historic: historic.position } : {},
3423
+ ...actions?.actions ? { actions: actions.actions } : {}
3424
+ };
3425
+ }
3426
+ async deployAfterFunding(state, chainId, context) {
3427
+ if (context.wallet.initializedDate != null) return;
3428
+ try {
3429
+ const deployed = await this.walletRequest(
3430
+ state,
3431
+ chainId,
3432
+ this.agentPath(context, "deploy"),
3433
+ { method: "POST", body: {} }
3434
+ );
3435
+ if (deployed?.agentWallet) {
3436
+ context.wallet = deployed.agentWallet;
3437
+ }
3438
+ } catch (error) {
3439
+ console.warn(
3440
+ "[owney-sdk] Yieldseeker deposit is funded but not deployable yet:",
3441
+ error
3442
+ );
3443
+ }
3444
+ }
3445
+ async refreshSnapshotAfterMovement(state, chainId, context, movement) {
3446
+ try {
3447
+ const response = await this.walletRequest(
3448
+ state,
3449
+ chainId,
3450
+ `${this.agentPath(context, "snapshot")}${query({
3451
+ shouldForceRefresh: true
3452
+ })}`
3453
+ );
3454
+ if (!response?.agentSnapshot) {
3455
+ throw this.invalidResponse("agent snapshot refresh");
3456
+ }
3457
+ } catch (error) {
3458
+ console.warn(
3459
+ `[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
3460
+ error
3461
+ );
3462
+ }
3463
+ }
3464
+ agentPath(context, suffix) {
3465
+ return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
3466
+ }
3467
+ async walletRequest(state, chainId, path, options = {}) {
3468
+ try {
3469
+ return await this.providerRequest(state, chainId, path, options);
3470
+ } catch (error) {
3471
+ throw this.mapApiError(error);
3472
+ }
3473
+ }
3474
+ async providerRequest(state, chainId, path, options = {}) {
3475
+ this.assertChain(chainId);
3476
+ const request = (signature2) => this.api.request(path, {
3477
+ ...options,
3478
+ signature: signature2
3479
+ });
3480
+ let signature = await this.auth.getToken(state, chainId);
3481
+ try {
3482
+ return await request(signature);
3483
+ } catch (error) {
3484
+ if (!(error instanceof YieldseekerApiError)) throw error;
3485
+ if (error.providerCode === "NO_USER") throw error;
3486
+ if (!error.isAuthenticationError) throw error;
3487
+ this.auth.clear(state, chainId);
3488
+ signature = await this.auth.getToken(state, chainId);
3489
+ try {
3490
+ return await request(signature);
3491
+ } catch (retryError) {
3492
+ if (retryError instanceof YieldseekerApiError && retryError.isAuthenticationError) {
3493
+ this.forgetUser(state, chainId);
3494
+ }
3495
+ throw retryError;
3496
+ }
3497
+ }
3498
+ }
3499
+ mapApiError(error) {
3500
+ if (!(error instanceof YieldseekerApiError)) {
3501
+ return new OwneyError(
3502
+ "AGENT_API_ERROR",
3503
+ "Yieldseeker request failed.",
3504
+ { cause: error instanceof Error ? error.message : String(error) },
3505
+ this.id
3506
+ );
3507
+ }
3508
+ const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
3509
+ return new OwneyError(
3510
+ code,
3511
+ `Yieldseeker request failed: ${error.providerCode}.`,
3512
+ {
3513
+ statusCode: error.status,
3514
+ providerCode: error.providerCode,
3515
+ ...error.responseFields ? { fields: error.responseFields } : {}
3516
+ },
3517
+ this.id
3518
+ );
3519
+ }
3520
+ async submitTransaction(state, chainId, transaction) {
3521
+ if (this.transactionExecutor) {
3522
+ return this.transactionExecutor(state, chainId, transaction);
3523
+ }
3524
+ this.assertTransaction(transaction, state, chainId);
3525
+ const account = getAddress2(state.walletAddress);
3526
+ const walletClient = createWalletClient2({
3527
+ account,
3528
+ chain: base3,
3529
+ transport: custom2(state.provider)
3530
+ });
3531
+ const publicClient = createPublicClient3({
3532
+ chain: base3,
3533
+ transport: custom2(state.provider)
3534
+ });
3535
+ await ensureWalletOnChain(
3536
+ publicClient,
3537
+ walletClient,
3538
+ 8453
3539
+ );
3540
+ const hash = await walletClient.sendTransaction({
3541
+ account,
3542
+ chain: base3,
3543
+ to: getAddress2(transaction.to),
3544
+ data: transaction.data,
3545
+ value: BigInt(transaction.value)
3546
+ });
3547
+ const receipt = await publicClient.waitForTransactionReceipt({
3548
+ hash,
3549
+ confirmations: 1
3550
+ });
3551
+ if (receipt.status !== "success") {
3552
+ throw new OwneyError(
3553
+ "AGENT_TRANSACTION_REVERTED",
3554
+ `Yieldseeker transaction reverted (${hash}).`,
3555
+ { transactionHash: hash },
3556
+ this.id
3557
+ );
3558
+ }
3559
+ return hash;
3560
+ }
3561
+ async waitForReceipt(state, chainId, transactionHash) {
3562
+ if (this.unwindReceiptWaiter) {
3563
+ await this.unwindReceiptWaiter(state, chainId, transactionHash);
3564
+ return;
3565
+ }
3566
+ const publicClient = createPublicClient3({
3567
+ chain: base3,
3568
+ transport: custom2(state.provider)
3569
+ });
3570
+ const receipt = await publicClient.waitForTransactionReceipt({
3571
+ hash: transactionHash,
3572
+ confirmations: 1
3573
+ });
3574
+ if (receipt.status !== "success") {
3575
+ throw new OwneyError(
3576
+ "AGENT_TRANSACTION_REVERTED",
3577
+ `Yieldseeker transaction reverted (${transactionHash}).`,
3578
+ { transactionHash },
3579
+ this.id
3580
+ );
3581
+ }
3582
+ }
3583
+ assertTransaction(transaction, state, chainId) {
3584
+ 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)) {
3585
+ throw this.invalidResponse("transaction");
3586
+ }
3587
+ }
3588
+ assertAgent(agent) {
3589
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress2(agent.assetAddress) || agent.chainId !== 8453) {
3590
+ throw this.invalidResponse("agent");
3591
+ }
3592
+ }
3593
+ isOwneyAgent(agent) {
3594
+ return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
3595
+ }
3596
+ assetForAgent(agent) {
3597
+ for (const asset of ["USDC", "WETH"]) {
3598
+ if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
3599
+ return asset;
3600
+ }
3601
+ }
3602
+ return null;
3603
+ }
3604
+ isTransactionHash(value) {
3605
+ return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
3606
+ }
3607
+ assertChain(chainId) {
3608
+ if (chainId !== 8453) {
3609
+ throw new OwneyError(
3610
+ "CHAIN_UNSUPPORTED",
3611
+ `Yieldseeker does not support chain ${chainId}.`,
3612
+ { chainId, supportedChainIds: [8453] },
3613
+ this.id
3614
+ );
3615
+ }
3616
+ }
3617
+ assertOptionalChain(chainId) {
3618
+ if (chainId !== void 0) this.assertChain(chainId);
3619
+ }
3620
+ assertAsset(asset) {
3621
+ if (asset !== "USDC" && asset !== "WETH") {
3622
+ throw new OwneyError(
3623
+ "ASSET_UNSUPPORTED",
3624
+ `Yieldseeker does not support asset ${asset} in the Owney rollout.`,
3625
+ {
3626
+ asset,
3627
+ supportedAssets: ["USDC", "WETH"],
3628
+ providerAlsoAdvertises: ["cbBTC"]
3629
+ },
3630
+ this.id
3631
+ );
3632
+ }
3633
+ }
3634
+ invalidResponse(operation, details = {}) {
3635
+ return new OwneyError(
3636
+ "AGENT_INVALID_RESPONSE",
3637
+ `Yieldseeker returned an invalid ${operation} response.`,
3638
+ details,
3639
+ this.id
3640
+ );
3641
+ }
3642
+ };
3643
+
3644
+ // src/lib/routing-api.ts
3645
+ var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3646
+ async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
3647
+ const url = `${baseUrl}/api/v1/agent/org-config`;
3648
+ try {
3649
+ const res = await fetch(url, {
3650
+ method: "GET",
3651
+ headers: {
3652
+ "Content-Type": "application/json",
3653
+ "x-owney-api-key": `${apiKey}`
3654
+ }
3655
+ });
3656
+ if (!res.ok) {
3657
+ if (res.status !== 404) {
3658
+ console.warn(
3659
+ `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
3660
+ );
3661
+ }
3662
+ return null;
3663
+ }
3664
+ const json = await res.json();
3665
+ const policy = json.success ? json.data ?? null : null;
3666
+ debugLog(
3667
+ "owney-sdk",
3668
+ policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
3669
+ policy ?? void 0
3670
+ );
3671
+ return policy;
3672
+ } catch (error) {
3673
+ console.warn(
3674
+ "[owney-sdk] Could not read org agent config (non-fatal):",
3675
+ error instanceof Error ? error.message : String(error)
3676
+ );
3677
+ return null;
3678
+ }
3679
+ }
3680
+ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
3681
+ const url = `${baseUrl}/api/v1/agent/keys`;
3682
+ const res = await fetch(url, {
3683
+ method: "GET",
3684
+ headers: {
3685
+ "Content-Type": "application/json",
3686
+ "x-owney-api-key": `${apiKey}`
3687
+ }
3688
+ });
3689
+ if (!res.ok) {
3690
+ const text = await res.text().catch(() => "");
3691
+ throw new OwneyError(
3692
+ "API_ROUTING_ERROR",
3693
+ `Routing API error ${res.status}: ${text}`,
3694
+ { statusCode: res.status, responseBody: text }
3695
+ );
3696
+ }
3697
+ const json = await res.json();
3698
+ if (!json.success) {
3699
+ throw new OwneyError(
3700
+ "API_ROUTING_FAILED",
3701
+ `Routing API request failed: ${json.message}`,
3702
+ { message: json.message }
3703
+ );
3704
+ }
3705
+ return json.data;
3706
+ }
3707
+
3708
+ // src/lib/health-report.ts
3709
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3710
+ async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
3711
+ try {
3712
+ await fetch(`${baseUrl}/api/v1/agent/health-report`, {
3713
+ method: "POST",
3714
+ headers: {
3715
+ "Content-Type": "application/json",
3716
+ "x-owney-api-key": apiKey
3717
+ },
3718
+ body: JSON.stringify({
3719
+ agent_type: agentType,
3720
+ error_code: errorCode,
3721
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3722
+ })
3723
+ });
3724
+ } catch (err) {
3725
+ console.warn(
3726
+ `[owney-sdk] health-report failed for agent "${agentType}":`,
3727
+ err instanceof Error ? err.message : err
3728
+ );
3729
+ }
3730
+ }
3731
+ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
3732
+ try {
3733
+ return await fn();
3734
+ } catch (err) {
3735
+ const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
3736
+ void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
3737
+ throw err;
3738
+ }
3739
+ }
3740
+
3741
+ // src/lib/helpers/withdraw-helper.ts
3742
+ import { parseUnits } from "viem";
3743
+ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
3744
+ const target = asset.toUpperCase();
3745
+ return agents.map((agent) => {
3746
+ const agentBalance = aggregated[agent.id];
3747
+ const tokenBalance = agentBalance?.tokens.find(
3748
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
3749
+ );
3750
+ let balance = tokenBalance ? parseUnits(tokenBalance.amount, decimals) : 0n;
3751
+ if (agent.balanceComposition === "tokens-plus-positions") {
3752
+ const chainNameById = {
3753
+ 1: "ETHEREUM",
3754
+ 8453: "BASE",
3755
+ 42161: "ARBITRUM"
3756
+ };
3757
+ const targetChain = chainNameById[chainId];
3758
+ for (const position2 of agentBalance?.positions ?? []) {
3759
+ const positionChain = position2.chain.trim().toUpperCase();
3760
+ const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
3761
+ if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
3762
+ if (position2.amountRaw !== void 0) {
3763
+ try {
3764
+ balance += BigInt(position2.amountRaw);
3765
+ continue;
3766
+ } catch {
3767
+ }
3768
+ }
3769
+ balance += parseUnits(position2.amount, decimals);
3770
+ }
3771
+ }
3772
+ return { agent, balance };
3773
+ });
3774
+ }
3775
+ function planProportionalShares(balances, requested, totalAvailable) {
3776
+ const plans = balances.map(({ agent, balance }) => ({
3777
+ agent,
3778
+ balance,
3779
+ planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
3780
+ }));
3781
+ const assigned = plans.reduce((s, p) => s + p.planned, 0n);
3782
+ let remainder = requested - assigned;
3783
+ const byHeadroom = [...plans].sort((a, b) => {
3784
+ const diff = b.balance - b.planned - (a.balance - a.planned);
3785
+ return diff > 0n ? 1 : diff < 0n ? -1 : 0;
3786
+ });
3787
+ for (const p of byHeadroom) {
3788
+ if (remainder === 0n) break;
3789
+ const headroom = p.balance - p.planned;
3790
+ if (headroom <= 0n) continue;
3791
+ const take = headroom < remainder ? headroom : remainder;
3792
+ p.planned += take;
3793
+ remainder -= take;
3794
+ }
3795
+ return plans;
3796
+ }
3797
+ function planDisabledDrain(disabled, requested) {
3798
+ const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
3799
+ (a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
3800
+ );
3801
+ const plans = [];
3802
+ let remaining = requested;
3803
+ for (const { agent, balance } of sorted) {
3804
+ if (remaining === 0n) {
3805
+ plans.push({ agent, balance, planned: 0n });
3806
+ continue;
3807
+ }
3808
+ const take = balance < remaining ? balance : remaining;
3809
+ plans.push({ agent, balance, planned: take });
3810
+ remaining -= take;
3811
+ }
3812
+ return { plans, remaining };
2314
3813
  }
2315
- async function ensureWalletOnChain(pub, wallet, expected) {
2316
- const actual = await pub.getChainId();
2317
- if (actual === expected) return;
2318
- try {
2319
- await wallet.switchChain({ id: expected });
2320
- } catch (error) {
2321
- throw new OwneyError(
2322
- "CHAIN_MISMATCH",
2323
- `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
2324
- {
2325
- expectedChainId: expected,
2326
- actualChainId: actual,
2327
- cause: error instanceof Error ? error.message : String(error)
2328
- }
2329
- );
3814
+ function redistributeShare(plans, fromIndex, amount, candidatePool) {
3815
+ const pool = candidatePool ?? plans.slice(fromIndex + 1);
3816
+ const candidates = pool.filter((c) => c.balance - c.planned > 0n);
3817
+ const totalHeadroom = candidates.reduce(
3818
+ (s, c) => s + (c.balance - c.planned),
3819
+ 0n
3820
+ );
3821
+ if (totalHeadroom === 0n) return;
3822
+ let distributed = 0n;
3823
+ for (const c of candidates) {
3824
+ const headroom = c.balance - c.planned;
3825
+ const proportional = headroom * amount / totalHeadroom;
3826
+ const give = proportional > headroom ? headroom : proportional;
3827
+ c.planned += give;
3828
+ distributed += give;
2330
3829
  }
2331
- const after = await pub.getChainId();
2332
- if (after !== expected) {
2333
- throw new OwneyError(
2334
- "CHAIN_MISMATCH",
2335
- `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
2336
- { expectedChainId: expected, actualChainId: after }
2337
- );
3830
+ let leftover = amount - distributed;
3831
+ for (const c of candidates) {
3832
+ if (leftover === 0n) break;
3833
+ const headroom = c.balance - c.planned;
3834
+ if (headroom <= 0n) continue;
3835
+ const take = headroom < leftover ? headroom : leftover;
3836
+ c.planned += take;
3837
+ leftover -= take;
2338
3838
  }
2339
3839
  }
3840
+ function sumWithdrawnAmount(results) {
3841
+ return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
3842
+ }
2340
3843
 
2341
- // src/lib/sponsored-deposit.ts
2342
- var AUTH_WINDOW_SECONDS = 15 * 60;
2343
- function makeSponsoredDepositCallback(deps) {
2344
- const post = deps.httpPost ?? postSponsorTransferAuth;
2345
- return async (smartWallet, chainId, amount) => {
2346
- const cid = chainId;
2347
- const token = deps.tokenAddressByChain[cid];
2348
- if (!token) {
2349
- throw new OwneyError(
2350
- "CHAIN_UNSUPPORTED",
2351
- `No sponsored token configured for chain ${chainId}`
2352
- );
3844
+ // src/lib/helpers/account-apy-helper.ts
3845
+ function balanceForApyScope(balance, chainId, tokenSymbol) {
3846
+ if (!tokenSymbol) {
3847
+ const total = Number(balance.totalBalance);
3848
+ return Number.isFinite(total) && total > 0 ? total : 0;
3849
+ }
3850
+ const normalizedToken = tokenSymbol.toUpperCase();
3851
+ return balance.tokens.reduce((total, token) => {
3852
+ if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
3853
+ return total;
2353
3854
  }
2354
- const pub = deps.getPublicClient(cid);
2355
- const wallet = deps.getWalletClient(cid);
2356
- await ensureWalletOnChain(pub, wallet, cid);
2357
- try {
2358
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2359
- if (balance < BigInt(amount)) {
2360
- throw new OwneyError(
2361
- "DEPOSIT_INSUFFICIENT_BALANCE",
2362
- "Insufficient balance for this deposit.",
2363
- { token, chainId: cid, balance: balance.toString(), amount }
2364
- );
2365
- }
2366
- } catch (err) {
2367
- if (err instanceof OwneyError) throw err;
2368
- console.warn(
2369
- "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
2370
- err instanceof Error ? err.message : String(err)
2371
- );
2372
- }
2373
- const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
2374
- const validAfter = 0n;
2375
- const validBefore = BigInt(
2376
- Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
2377
- );
2378
- const nonce = randomAuthNonce();
2379
- const typedData = buildTransferWithAuthorizationTypedData({
2380
- token,
2381
- chainId: cid,
2382
- tokenName,
2383
- tokenVersion,
2384
- message: {
2385
- from: deps.ownerAddress,
2386
- to: smartWallet,
2387
- value: BigInt(amount),
2388
- validAfter,
2389
- validBefore,
2390
- nonce
2391
- }
2392
- });
2393
- const authSignature = await wallet.signTypedData({
2394
- account: deps.ownerAddress,
2395
- ...typedData
2396
- });
2397
- deps.onApproved?.();
2398
- const result = await post({
2399
- baseUrl: deps.baseUrl,
2400
- apiKey: deps.apiKey,
2401
- body: {
2402
- chainId: cid,
2403
- token,
2404
- from: deps.ownerAddress,
2405
- to: smartWallet,
2406
- value: amount,
2407
- validAfter: validAfter.toString(),
2408
- validBefore: validBefore.toString(),
2409
- nonce,
2410
- authSignature,
2411
- tokenName,
2412
- tokenVersion
3855
+ const amount = Number(token.amount);
3856
+ return Number.isFinite(amount) && amount > 0 ? total + amount : total;
3857
+ }, 0);
3858
+ }
3859
+ function aggregateApyHistory(agentApys, agentBalances) {
3860
+ const byDate = /* @__PURE__ */ new Map();
3861
+ for (const [id, accountApy] of Object.entries(agentApys)) {
3862
+ const balance = agentBalances[id] ?? 0;
3863
+ if (!Number.isFinite(balance) || balance <= 0) continue;
3864
+ for (const point of accountApy.history ?? []) {
3865
+ const apy = Number(point.apy);
3866
+ if (!point.date || !Number.isFinite(apy)) continue;
3867
+ const current = byDate.get(point.date) ?? { weightedSum: 0, weight: 0 };
3868
+ current.weightedSum += apy * balance;
3869
+ current.weight += balance;
3870
+ byDate.set(point.date, current);
3871
+ }
3872
+ }
3873
+ return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
3874
+ date,
3875
+ apy: value.weightedSum / value.weight
3876
+ }));
3877
+ }
3878
+ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
3879
+ const sums = {};
3880
+ const weights = {};
3881
+ for (const id of Object.keys(agentApys)) {
3882
+ const cells = agentApys[id].apyByChainAndAsset;
3883
+ const balance = agentBalances[id] ?? 0;
3884
+ if (!cells || balance <= 0) continue;
3885
+ for (const [chainKey, perAsset] of Object.entries(cells)) {
3886
+ if (!perAsset) continue;
3887
+ const chainId = Number(chainKey);
3888
+ for (const [asset, apyValue] of Object.entries(perAsset)) {
3889
+ const apy = Number(apyValue ?? 0);
3890
+ if (apy === 0) continue;
3891
+ sums[chainId] ??= {};
3892
+ weights[chainId] ??= {};
3893
+ sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
3894
+ weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
2413
3895
  }
2414
- });
2415
- return result.txHash;
2416
- };
3896
+ }
3897
+ }
3898
+ const out = {};
3899
+ for (const chainKey of Object.keys(sums)) {
3900
+ const chainId = Number(chainKey);
3901
+ const perAssetOut = {};
3902
+ for (const asset of Object.keys(sums[chainId])) {
3903
+ const w = weights[chainId][asset];
3904
+ if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
3905
+ }
3906
+ if (Object.keys(perAssetOut).length > 0) {
3907
+ out[chainId] = perAssetOut;
3908
+ }
3909
+ }
3910
+ return out;
2417
3911
  }
2418
3912
 
3913
+ // src/client.ts
3914
+ import {
3915
+ createPublicClient as createPublicClient4,
3916
+ createWalletClient as createWalletClient3,
3917
+ custom as custom3
3918
+ } from "viem";
3919
+ import { base as base4, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
3920
+
2419
3921
  // src/lib/sponsored-weth-deposit.ts
2420
3922
  var PERMIT_WINDOW_SECONDS = 15 * 60;
2421
3923
  function makeSponsoredWethCallback(deps) {
2422
3924
  const get = deps.httpGet ?? getSponsorRelayerAddress;
2423
3925
  const post = deps.httpPost ?? postSponsorPermit2Transfer;
2424
- return async (smartWallet, chainId, amount) => {
2425
- const cid = chainId;
2426
- const token = deps.tokenAddressByChain[cid];
2427
- if (!token) {
2428
- throw new OwneyError(
2429
- "CHAIN_UNSUPPORTED",
2430
- `No sponsored WETH configured for chain ${chainId}`
2431
- );
2432
- }
2433
- const amountWei = BigInt(amount);
2434
- const pub = deps.getPublicClient(cid);
2435
- const wallet = deps.getWalletClient(cid);
2436
- await ensureWalletOnChain(pub, wallet, cid);
2437
- try {
2438
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2439
- if (balance < amountWei) {
3926
+ return makeVerificationAwareDepositCallback(
3927
+ async (smartWallet, chainId, amount, verification) => {
3928
+ const cid = chainId;
3929
+ const token = deps.tokenAddressByChain[cid];
3930
+ if (!token) {
2440
3931
  throw new OwneyError(
2441
- "DEPOSIT_INSUFFICIENT_BALANCE",
2442
- "Insufficient WETH balance for this deposit.",
2443
- { token, chainId: cid, balance: balance.toString(), amount }
3932
+ "CHAIN_UNSUPPORTED",
3933
+ `No sponsored WETH configured for chain ${chainId}`
2444
3934
  );
2445
3935
  }
2446
- } catch (err) {
2447
- if (err instanceof OwneyError) throw err;
2448
- console.warn(
2449
- "[owney-sdk] WETH balance pre-check failed (non-fatal):",
2450
- err instanceof Error ? err.message : String(err)
2451
- );
2452
- }
2453
- const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
2454
- if (allowance < amountWei) {
2455
- throw new OwneyError(
2456
- "PERMIT2_APPROVAL_REQUIRED",
2457
- "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
2458
- { token, chainId: cid, allowance: allowance.toString(), amount }
2459
- );
2460
- }
2461
- const relayer = await get({
2462
- baseUrl: deps.baseUrl,
2463
- apiKey: deps.apiKey,
2464
- chainId: cid
2465
- });
2466
- const nonce = randomPermit2Nonce();
2467
- const deadline = BigInt(
2468
- Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
2469
- );
2470
- const typedData = buildPermitTransferFromTypedData({
2471
- chainId: cid,
2472
- message: {
2473
- permitted: { token, amount: amountWei },
2474
- spender: relayer,
2475
- nonce,
2476
- deadline
3936
+ const amountWei = BigInt(amount);
3937
+ const pub = deps.getPublicClient(cid);
3938
+ const wallet = deps.getWalletClient(cid);
3939
+ await ensureWalletOnChain(pub, wallet, cid);
3940
+ try {
3941
+ const balance = await readErc20Balance(pub, token, deps.ownerAddress);
3942
+ if (balance < amountWei) {
3943
+ throw new OwneyError(
3944
+ "DEPOSIT_INSUFFICIENT_BALANCE",
3945
+ "Insufficient WETH balance for this deposit.",
3946
+ { token, chainId: cid, balance: balance.toString(), amount }
3947
+ );
3948
+ }
3949
+ } catch (err) {
3950
+ if (err instanceof OwneyError) throw err;
3951
+ console.warn(
3952
+ "[owney-sdk] WETH balance pre-check failed (non-fatal):",
3953
+ err instanceof Error ? err.message : String(err)
3954
+ );
2477
3955
  }
2478
- });
2479
- const signature = await wallet.signTypedData({
2480
- account: deps.ownerAddress,
2481
- ...typedData
2482
- });
2483
- deps.onApproved?.();
2484
- const result = await post({
2485
- baseUrl: deps.baseUrl,
2486
- apiKey: deps.apiKey,
2487
- body: {
2488
- chainId: cid,
3956
+ const allowance = await readPermit2Allowance(
3957
+ pub,
2489
3958
  token,
2490
- from: deps.ownerAddress,
2491
- to: smartWallet,
2492
- amount,
2493
- nonce: nonce.toString(),
2494
- deadline: deadline.toString(),
2495
- signature
3959
+ deps.ownerAddress
3960
+ );
3961
+ if (allowance < amountWei) {
3962
+ throw new OwneyError(
3963
+ "PERMIT2_APPROVAL_REQUIRED",
3964
+ "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
3965
+ { token, chainId: cid, allowance: allowance.toString(), amount }
3966
+ );
2496
3967
  }
2497
- });
2498
- return result.txHash;
2499
- };
3968
+ const relayer = await get({
3969
+ baseUrl: deps.baseUrl,
3970
+ apiKey: deps.apiKey,
3971
+ chainId: cid
3972
+ });
3973
+ const nonce = randomPermit2Nonce();
3974
+ const deadline = BigInt(
3975
+ Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
3976
+ );
3977
+ const typedData = buildPermitTransferFromTypedData({
3978
+ chainId: cid,
3979
+ message: {
3980
+ permitted: { token, amount: amountWei },
3981
+ spender: relayer,
3982
+ nonce,
3983
+ deadline
3984
+ }
3985
+ });
3986
+ const signature = await wallet.signTypedData({
3987
+ account: deps.ownerAddress,
3988
+ ...typedData
3989
+ });
3990
+ deps.onApproved?.();
3991
+ const result = await post({
3992
+ baseUrl: deps.baseUrl,
3993
+ apiKey: deps.apiKey,
3994
+ ...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
3995
+ body: {
3996
+ chainId: cid,
3997
+ token,
3998
+ from: deps.ownerAddress,
3999
+ to: smartWallet,
4000
+ amount,
4001
+ nonce: nonce.toString(),
4002
+ deadline: deadline.toString(),
4003
+ signature,
4004
+ ...verification?.agentId === "yieldseeker" ? {
4005
+ yieldseekerUserId: verification.userId,
4006
+ yieldseekerAgentId: verification.yieldseekerAgentId
4007
+ } : {}
4008
+ }
4009
+ });
4010
+ return result.txHash;
4011
+ }
4012
+ );
2500
4013
  }
2501
4014
 
2502
4015
  // src/lib/sponsored-calls-deposit.ts
2503
- import { encodeFunctionData, erc20Abi, toHex } from "viem";
4016
+ import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi2, toHex } from "viem";
2504
4017
  var DEFAULT_POLL_INTERVAL_MS = 1500;
2505
4018
  var DEFAULT_MAX_POLLS = 30;
2506
4019
  async function paymasterSupported(provider, owner, chainId) {
@@ -2526,7 +4039,7 @@ function makeSponsoredCallsCallback(deps) {
2526
4039
  }
2527
4040
  return new URL(configured, origin).toString();
2528
4041
  };
2529
- return async (smartWallet, chainId, amount) => {
4042
+ return makeVerificationAwareDepositCallback(async (smartWallet, chainId, amount, verification) => {
2530
4043
  const cid = chainId;
2531
4044
  const token = deps.tokenAddressByChain[cid];
2532
4045
  if (!token) {
@@ -2542,11 +4055,37 @@ function makeSponsoredCallsCallback(deps) {
2542
4055
  { chainId }
2543
4056
  );
2544
4057
  }
2545
- const data = encodeFunctionData({
2546
- abi: erc20Abi,
4058
+ const data = encodeFunctionData2({
4059
+ abi: erc20Abi2,
2547
4060
  functionName: "transfer",
2548
4061
  args: [smartWallet, BigInt(amount)]
2549
4062
  });
4063
+ let paymasterUrl = absolutePaymasterUrl();
4064
+ if (verification?.agentId === "yieldseeker") {
4065
+ if (chainId !== 8453) {
4066
+ throw new OwneyError(
4067
+ "CHAIN_UNSUPPORTED",
4068
+ `Yieldseeker Base Account sponsorship is not available on chain ${chainId}.`
4069
+ );
4070
+ }
4071
+ const { intent } = await postPaymasterIntent({
4072
+ baseUrl: deps.routingApiBaseUrl,
4073
+ apiKey: deps.apiKey,
4074
+ yieldseekerSignature: verification.signature,
4075
+ body: {
4076
+ chainId,
4077
+ token,
4078
+ from: deps.ownerAddress,
4079
+ to: smartWallet,
4080
+ amount,
4081
+ yieldseekerUserId: verification.userId,
4082
+ yieldseekerAgentId: verification.yieldseekerAgentId
4083
+ }
4084
+ });
4085
+ const url = new URL(paymasterUrl);
4086
+ url.searchParams.set("owneyIntent", intent);
4087
+ paymasterUrl = url.toString();
4088
+ }
2550
4089
  const sendResult = await deps.provider.request({
2551
4090
  method: "wallet_sendCalls",
2552
4091
  params: [
@@ -2557,7 +4096,7 @@ function makeSponsoredCallsCallback(deps) {
2557
4096
  atomicRequired: false,
2558
4097
  calls: [{ to: token, value: "0x0", data }],
2559
4098
  capabilities: {
2560
- paymasterService: { url: absolutePaymasterUrl() }
4099
+ paymasterService: { url: paymasterUrl }
2561
4100
  }
2562
4101
  }
2563
4102
  ]
@@ -2587,7 +4126,7 @@ function makeSponsoredCallsCallback(deps) {
2587
4126
  `No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
2588
4127
  { chainId, callsId }
2589
4128
  );
2590
- };
4129
+ });
2591
4130
  }
2592
4131
 
2593
4132
  // src/client.ts
@@ -2617,7 +4156,7 @@ var SPONSORED_USDC_BY_CHAIN = {
2617
4156
  1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
2618
4157
  };
2619
4158
  var VIEM_CHAIN2 = {
2620
- 8453: base2,
4159
+ 8453: base4,
2621
4160
  42161: arbitrum2,
2622
4161
  1: mainnet2
2623
4162
  };
@@ -2647,6 +4186,7 @@ var OwneySDK = class {
2647
4186
  orgAgentConfig;
2648
4187
  orgAgentConfigPromise = null;
2649
4188
  zyfaiRpcUrls;
4189
+ yieldseekerApiBaseUrl;
2650
4190
  routingApiBaseUrl;
2651
4191
  referralSource;
2652
4192
  cachedSponsoredCallback = null;
@@ -2658,6 +4198,7 @@ var OwneySDK = class {
2658
4198
  this.apiKey = config.apiKey;
2659
4199
  if (config.debug) setOwneyDebug(true);
2660
4200
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
4201
+ this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
2661
4202
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2662
4203
  this.paymasterServiceUrl = config.paymasterServiceUrl;
2663
4204
  this.referralSource = config.referralSource;
@@ -2760,14 +4301,14 @@ var OwneySDK = class {
2760
4301
  // Casts work around viem's chain-narrowed Client vs the generic
2761
4302
  // PublicClient/WalletClient param types — structurally identical at
2762
4303
  // runtime, but the two share a name TS treats as unrelated.
2763
- getPublicClient: (cid) => createPublicClient2({
4304
+ getPublicClient: (cid) => createPublicClient4({
2764
4305
  chain: VIEM_CHAIN2[cid],
2765
- transport: custom(provider)
4306
+ transport: custom3(provider)
2766
4307
  }),
2767
- getWalletClient: (cid) => createWalletClient({
4308
+ getWalletClient: (cid) => createWalletClient3({
2768
4309
  account: owner,
2769
4310
  chain: VIEM_CHAIN2[cid],
2770
- transport: custom(provider)
4311
+ transport: custom3(provider)
2771
4312
  })
2772
4313
  });
2773
4314
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -2784,6 +4325,8 @@ var OwneySDK = class {
2784
4325
  if (!onApproved && cached) return cached;
2785
4326
  const provider = this.requireConnectedProvider();
2786
4327
  const callback = makeSponsoredCallsCallback({
4328
+ apiKey: this.apiKey,
4329
+ routingApiBaseUrl: this.routingApiBaseUrl,
2787
4330
  provider,
2788
4331
  ownerAddress: this.state.walletAddress,
2789
4332
  paymasterServiceUrl: this.paymasterServiceUrl,
@@ -2813,14 +4356,14 @@ var OwneySDK = class {
2813
4356
  // Casts work around viem's chain-narrowed Client vs the generic
2814
4357
  // PublicClient/WalletClient param types — structurally identical at
2815
4358
  // runtime, but the two share a name TS treats as unrelated.
2816
- getPublicClient: (cid) => createPublicClient2({
4359
+ getPublicClient: (cid) => createPublicClient4({
2817
4360
  chain: VIEM_CHAIN2[cid],
2818
- transport: custom(provider)
4361
+ transport: custom3(provider)
2819
4362
  }),
2820
- getWalletClient: (cid) => createWalletClient({
4363
+ getWalletClient: (cid) => createWalletClient3({
2821
4364
  account: owner,
2822
4365
  chain: VIEM_CHAIN2[cid],
2823
- transport: custom(provider)
4366
+ transport: custom3(provider)
2824
4367
  })
2825
4368
  });
2826
4369
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -2848,12 +4391,13 @@ var OwneySDK = class {
2848
4391
  async getOrgAgentConfig() {
2849
4392
  if (this.orgAgentConfig !== void 0) return this.orgAgentConfig;
2850
4393
  if (!this.orgAgentConfigPromise) {
2851
- this.orgAgentConfigPromise = fetchOrgAgentConfig(this.apiKey).then(
2852
- (config) => {
2853
- this.orgAgentConfig = config;
2854
- return config;
2855
- }
2856
- );
4394
+ this.orgAgentConfigPromise = fetchOrgAgentConfig(
4395
+ this.apiKey,
4396
+ this.routingApiBaseUrl
4397
+ ).then((config) => {
4398
+ this.orgAgentConfig = config;
4399
+ return config;
4400
+ });
2857
4401
  }
2858
4402
  return this.orgAgentConfigPromise;
2859
4403
  }
@@ -2888,9 +4432,19 @@ var OwneySDK = class {
2888
4432
  if (this.agents.size > 0) return;
2889
4433
  if (!this.initializingAgentsPromise) {
2890
4434
  this.initializingAgentsPromise = (async () => {
2891
- const agentKeys = await fetchAgentKeys(this.apiKey);
4435
+ const agentKeys = await fetchAgentKeys(
4436
+ this.apiKey,
4437
+ this.routingApiBaseUrl
4438
+ );
2892
4439
  this.disabledAgents.clear();
2893
- for (const { key: key2, agent_type, is_enabled } of agentKeys) {
4440
+ for (const {
4441
+ key: key2,
4442
+ agent_type,
4443
+ is_enabled,
4444
+ is_configured
4445
+ } of agentKeys) {
4446
+ const configured = is_configured ?? Boolean(key2);
4447
+ if (!configured) continue;
2894
4448
  const agent = this.createAgent(agent_type, key2);
2895
4449
  if (!agent) continue;
2896
4450
  this.agents.set(agent_type, agent);
@@ -2914,10 +4468,47 @@ var OwneySDK = class {
2914
4468
  }
2915
4469
  createAgent(agentId, key2) {
2916
4470
  if (agentId === "zyfai") {
4471
+ if (!key2) return null;
2917
4472
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
2918
4473
  }
4474
+ if (agentId === "yieldseeker") {
4475
+ return new YieldseekerAgent(this.apiKey, {
4476
+ baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
4477
+ });
4478
+ }
2919
4479
  return null;
2920
4480
  }
4481
+ /**
4482
+ * Discover the agents actually returned by routing for this organization.
4483
+ * Consumers should use this instead of hardcoding a global agent roster.
4484
+ */
4485
+ async getAvailableAgents(options = {}) {
4486
+ await this.ensureAgentsInitialized();
4487
+ const { chainId, asset, includeDisabled = false } = options;
4488
+ const available = [];
4489
+ for (const [id, agent] of this.agents) {
4490
+ const isEnabled = !this.isAgentDisabled(id);
4491
+ if (!includeDisabled && !isEnabled) continue;
4492
+ if (chainId !== void 0 && !agent.supportedChainIds.includes(chainId)) {
4493
+ continue;
4494
+ }
4495
+ if (asset !== void 0) {
4496
+ const supportsAsset = agent.supportedAssets.some(
4497
+ (entry) => (chainId === void 0 || entry.chainId === chainId) && entry.assets.some((candidate) => candidate.symbol === asset)
4498
+ );
4499
+ if (!supportsAsset) {
4500
+ continue;
4501
+ }
4502
+ }
4503
+ available.push({
4504
+ id,
4505
+ isEnabled,
4506
+ supportedChainIds: agent.supportedChainIds,
4507
+ supportedAssets: agent.supportedAssets
4508
+ });
4509
+ }
4510
+ return available;
4511
+ }
2921
4512
  // --- Account lifecycle ---
2922
4513
  /**
2923
4514
  * Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
@@ -2929,7 +4520,7 @@ var OwneySDK = class {
2929
4520
  * If provided, ALL specified agents must support the chainId or the call
2930
4521
  * throws before activating any agent.
2931
4522
  */
2932
- async activateAgent(chainId, agentId) {
4523
+ async activateAgent(chainId, agentId, asset) {
2933
4524
  const state = this.requireState();
2934
4525
  await this.ensureAgentsInitialized();
2935
4526
  if (agentId !== void 0) {
@@ -2965,9 +4556,7 @@ var OwneySDK = class {
2965
4556
  this.activeAgents.add(id);
2966
4557
  }
2967
4558
  state.chainId = chainId;
2968
- this.activateAgentsInTurn(agents, state, chainId).catch((error) => {
2969
- console.error("activateAgent background init failed:", error);
2970
- });
4559
+ await this.activateAgentsInTurn(agents, state, chainId, asset);
2971
4560
  return;
2972
4561
  }
2973
4562
  const compatible = [...this.agents.values()].filter(
@@ -2988,11 +4577,7 @@ var OwneySDK = class {
2988
4577
  const enabledCompatible = compatible.filter(
2989
4578
  (agent) => !this.isAgentDisabled(agent.id)
2990
4579
  );
2991
- this.activateAgentsInTurn(enabledCompatible, state, chainId).catch(
2992
- (error) => {
2993
- console.error("activateAgent background init failed:", error);
2994
- }
2995
- );
4580
+ await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
2996
4581
  }
2997
4582
  /**
2998
4583
  * Activate agents ONE AT A TIME, each followed by its org policy.
@@ -3012,11 +4597,11 @@ var OwneySDK = class {
3012
4597
  * rethrown (matching the previous `Promise.all` rejection) once all agents
3013
4598
  * have had a chance to activate.
3014
4599
  */
3015
- async activateAgentsInTurn(agents, state, chainId) {
4600
+ async activateAgentsInTurn(agents, state, chainId, asset) {
3016
4601
  let firstError = null;
3017
4602
  for (const agent of agents) {
3018
4603
  try {
3019
- await agent.activateAgent(state, chainId);
4604
+ await agent.activateAgent(state, chainId, asset);
3020
4605
  await this.applyOrgPolicyTo(agent, state, chainId);
3021
4606
  } catch (error) {
3022
4607
  if (firstError === null) {
@@ -3144,7 +4729,8 @@ var OwneySDK = class {
3144
4729
  amount,
3145
4730
  asset,
3146
4731
  callback
3147
- )
4732
+ ),
4733
+ this.routingApiBaseUrl
3148
4734
  );
3149
4735
  let approvalAttempted = false;
3150
4736
  for (; ; ) {
@@ -3183,15 +4769,15 @@ var OwneySDK = class {
3183
4769
  agent,
3184
4770
  amount: i === agents.length - 1 ? perAgent + remainder : perAgent
3185
4771
  }));
3186
- const valid = splits.filter(
4772
+ const valid2 = splits.filter(
3187
4773
  (s) => exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset)
3188
4774
  );
3189
- if (valid.length === agents.length) {
4775
+ if (valid2.length === agents.length) {
3190
4776
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
3191
4777
  }
3192
4778
  return this.splitDepositAmount(
3193
4779
  totalAmount,
3194
- valid.map((s) => s.agent),
4780
+ valid2.map((s) => s.agent),
3195
4781
  chainId,
3196
4782
  asset,
3197
4783
  exempt
@@ -3294,7 +4880,8 @@ var OwneySDK = class {
3294
4880
  return withFailureReporting(
3295
4881
  this.apiKey,
3296
4882
  agent.id,
3297
- () => agent.withdraw(state, chainId, token, amount)
4883
+ () => agent.withdraw(state, chainId, token, amount),
4884
+ this.routingApiBaseUrl
3298
4885
  );
3299
4886
  }
3300
4887
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
@@ -3308,7 +4895,12 @@ var OwneySDK = class {
3308
4895
  console.error(`withdraw failed for agent "${agent.id}":`, err);
3309
4896
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
3310
4897
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
3311
- void reportAgentFailure(this.apiKey, agent.id, code);
4898
+ void reportAgentFailure(
4899
+ this.apiKey,
4900
+ agent.id,
4901
+ code,
4902
+ this.routingApiBaseUrl
4903
+ );
3312
4904
  }
3313
4905
  }
3314
4906
  if (Object.keys(results2).length === 0) {
@@ -3325,6 +4917,10 @@ var OwneySDK = class {
3325
4917
  }
3326
4918
  const requested = BigInt(amount);
3327
4919
  const aggregated = await this.getBalances();
4920
+ const unavailableAgents = eligibleAgents.filter(
4921
+ (agent) => !(agent.id in aggregated.agentBalances)
4922
+ );
4923
+ const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
3328
4924
  const balances = projectAgentBalancesForAsset(
3329
4925
  eligibleAgents,
3330
4926
  aggregated.agentBalances,
@@ -3333,7 +4929,18 @@ var OwneySDK = class {
3333
4929
  assetInfo.decimals
3334
4930
  );
3335
4931
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
3336
- if (totalAvailable < requested) {
4932
+ if (totalAvailable === 0n && unavailableAgents.length > 0) {
4933
+ throw new OwneyError(
4934
+ "WITHDRAW_BALANCE_UNAVAILABLE",
4935
+ `Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
4936
+ {
4937
+ asset,
4938
+ unavailableAgents: unavailableAgentIds,
4939
+ agentErrors: aggregated.agentErrors
4940
+ }
4941
+ );
4942
+ }
4943
+ if (totalAvailable < requested && unavailableAgents.length === 0) {
3337
4944
  throw new OwneyError(
3338
4945
  "WITHDRAW_INSUFFICIENT_BALANCE",
3339
4946
  `Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
@@ -3344,6 +4951,7 @@ var OwneySDK = class {
3344
4951
  }
3345
4952
  );
3346
4953
  }
4954
+ const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
3347
4955
  const disabledBalances = balances.filter(
3348
4956
  (b) => this.isAgentDisabled(b.agent.id)
3349
4957
  );
@@ -3352,7 +4960,7 @@ var OwneySDK = class {
3352
4960
  );
3353
4961
  const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
3354
4962
  disabledBalances,
3355
- requested
4963
+ plannedTarget
3356
4964
  );
3357
4965
  const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
3358
4966
  const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
@@ -3362,7 +4970,9 @@ var OwneySDK = class {
3362
4970
  }));
3363
4971
  const plans = [...disabledPlans, ...enabledPlans];
3364
4972
  const results = {};
3365
- const agentErrors = {};
4973
+ const agentErrors = {
4974
+ ...aggregated.agentErrors ?? {}
4975
+ };
3366
4976
  for (let i = 0; i < plans.length; i++) {
3367
4977
  const p = plans[i];
3368
4978
  if (p.planned === 0n) continue;
@@ -3377,7 +4987,12 @@ var OwneySDK = class {
3377
4987
  console.error(`withdraw failed for agent "${p.agent.id}":`, err);
3378
4988
  agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
3379
4989
  const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
3380
- void reportAgentFailure(this.apiKey, p.agent.id, code);
4990
+ void reportAgentFailure(
4991
+ this.apiKey,
4992
+ p.agent.id,
4993
+ code,
4994
+ this.routingApiBaseUrl
4995
+ );
3381
4996
  const failedAmount = p.planned;
3382
4997
  p.planned = 0n;
3383
4998
  redistributeShare(plans, i, failedAmount);
@@ -3404,7 +5019,8 @@ var OwneySDK = class {
3404
5019
  requested: amount,
3405
5020
  withdrawn: withdrawn.toString(),
3406
5021
  partialResults: results,
3407
- agentErrors
5022
+ agentErrors,
5023
+ ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
3408
5024
  }
3409
5025
  );
3410
5026
  }
@@ -3445,7 +5061,12 @@ var OwneySDK = class {
3445
5061
  continue;
3446
5062
  }
3447
5063
  const reason = settledResult.reason;
3448
- agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
5064
+ const message = reason instanceof Error ? reason.message : String(reason);
5065
+ agentErrors[agentId2] = message;
5066
+ console.error(
5067
+ `[owney-sdk] Balance fetch failed for agent "${agentId2}":`,
5068
+ reason
5069
+ );
3449
5070
  }
3450
5071
  if (successCount === 0) {
3451
5072
  throw new OwneyError(
@@ -3457,7 +5078,8 @@ var OwneySDK = class {
3457
5078
  return {
3458
5079
  totalBalance: String(totalBalance),
3459
5080
  totalBalanceAsset: "usdc",
3460
- agentBalances: results
5081
+ agentBalances: results,
5082
+ ...Object.keys(agentErrors).length > 0 ? { agentErrors } : {}
3461
5083
  };
3462
5084
  }
3463
5085
  /**
@@ -3551,7 +5173,10 @@ var OwneySDK = class {
3551
5173
  Promise.all(
3552
5174
  entries.map(async ([id, agent]) => {
3553
5175
  const b = await agent.getBalances(state, chainId);
3554
- return [id, Number(b.totalBalance)];
5176
+ return [
5177
+ id,
5178
+ balanceForApyScope(b, chainId, tokenSymbol)
5179
+ ];
3555
5180
  })
3556
5181
  )
3557
5182
  ]);
@@ -3579,10 +5204,12 @@ var OwneySDK = class {
3579
5204
  }
3580
5205
  }
3581
5206
  const apyByChainAndAsset = aggregateApyByChainAndAsset(results, balances);
5207
+ const history = aggregateApyHistory(results, balances);
3582
5208
  return {
3583
5209
  totalApy: String(totalApy),
3584
5210
  agentApy: results,
3585
- apyByChainAndAsset
5211
+ apyByChainAndAsset,
5212
+ history
3586
5213
  };
3587
5214
  }
3588
5215
  /**
@@ -3712,10 +5339,10 @@ var OwneySDK = class {
3712
5339
  );
3713
5340
  }
3714
5341
  const provider = this.requireConnectedProvider();
3715
- const wallet = createWalletClient({
5342
+ const wallet = createWalletClient3({
3716
5343
  account: state.walletAddress,
3717
5344
  chain: VIEM_CHAIN2[chainId],
3718
- transport: custom(provider)
5345
+ transport: custom3(provider)
3719
5346
  });
3720
5347
  const hash = await wallet.writeContract({
3721
5348
  address: token,
@@ -3725,9 +5352,9 @@ var OwneySDK = class {
3725
5352
  account: state.walletAddress,
3726
5353
  chain: VIEM_CHAIN2[chainId]
3727
5354
  });
3728
- const publicClient = createPublicClient2({
5355
+ const publicClient = createPublicClient4({
3729
5356
  chain: VIEM_CHAIN2[chainId],
3730
- transport: custom(provider)
5357
+ transport: custom3(provider)
3731
5358
  });
3732
5359
  const receipt = await publicClient.waitForTransactionReceipt({
3733
5360
  hash,
@@ -3763,7 +5390,9 @@ var OwneySDK = class {
3763
5390
  return this.getAgent(agentId).getAgentApy(days, agentOptions);
3764
5391
  }
3765
5392
  const results = {};
3766
- const agentEntries = [...this.agents.entries()];
5393
+ const agentEntries = [...this.agents.entries()].filter(
5394
+ ([id]) => !this.isAgentDisabled(id)
5395
+ );
3767
5396
  const apyResults = await Promise.all(
3768
5397
  agentEntries.map(async ([id, agent]) => {
3769
5398
  const apy = await agent.getAgentApy(days, agentOptions);
@@ -3836,13 +5465,13 @@ var OwneySDK = class {
3836
5465
  };
3837
5466
 
3838
5467
  // src/agents/zyfai/zyfai.siwx.ts
3839
- import { getAddress } from "viem";
3840
- import { SiweMessage } from "siwe";
5468
+ import { getAddress as getAddress3 } from "viem";
5469
+ import { SiweMessage as SiweMessage2 } from "siwe";
3841
5470
  import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
3842
5471
 
3843
5472
  // src/agents/zyfai/zyfai.siwx-cache.ts
3844
- var KEY_PREFIX2 = "owney.siwx.session";
3845
- var storage2 = () => {
5473
+ var KEY_PREFIX4 = "owney.siwx.session";
5474
+ var storage4 = () => {
3846
5475
  if (typeof window === "undefined") return null;
3847
5476
  try {
3848
5477
  return window.localStorage;
@@ -3850,8 +5479,8 @@ var storage2 = () => {
3850
5479
  return null;
3851
5480
  }
3852
5481
  };
3853
- var buildKey2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}`;
3854
- var legacyKeyPrefix2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}:`;
5482
+ var buildKey3 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
5483
+ var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
3855
5484
  var memorySiwxSessions = /* @__PURE__ */ new Map();
3856
5485
  var readLegacySiwxSession = (store, address) => {
3857
5486
  if (!store) return null;
@@ -3882,17 +5511,17 @@ var readLegacySiwxSession = (store, address) => {
3882
5511
  };
3883
5512
  var readSiwxSession = (address, chainId) => {
3884
5513
  if (typeof window === "undefined") return null;
3885
- const key2 = buildKey2(address);
3886
- const store = storage2();
3887
- let raw = null;
5514
+ const key2 = buildKey3(address);
5515
+ const store = storage4();
5516
+ let raw2 = null;
3888
5517
  try {
3889
- raw = store?.getItem(key2) ?? null;
5518
+ raw2 = store?.getItem(key2) ?? null;
3890
5519
  } catch {
3891
- raw = null;
5520
+ raw2 = null;
3892
5521
  }
3893
- if (raw) {
5522
+ if (raw2) {
3894
5523
  try {
3895
- return JSON.parse(raw);
5524
+ return JSON.parse(raw2);
3896
5525
  } catch {
3897
5526
  memorySiwxSessions.delete(key2);
3898
5527
  try {
@@ -3911,18 +5540,18 @@ var readSiwxSession = (address, chainId) => {
3911
5540
  };
3912
5541
  var writeSiwxSession = (address, _chainId, session) => {
3913
5542
  if (typeof window === "undefined") return;
3914
- const key2 = buildKey2(address);
5543
+ const key2 = buildKey3(address);
3915
5544
  memorySiwxSessions.set(key2, session);
3916
- const store = storage2();
5545
+ const store = storage4();
3917
5546
  try {
3918
5547
  store?.setItem(key2, JSON.stringify(session));
3919
5548
  } catch {
3920
5549
  }
3921
5550
  };
3922
5551
  var clearSiwxSession = (address, _chainId) => {
3923
- const key2 = buildKey2(address);
5552
+ const key2 = buildKey3(address);
3924
5553
  memorySiwxSessions.delete(key2);
3925
- const store = storage2();
5554
+ const store = storage4();
3926
5555
  try {
3927
5556
  store?.removeItem(key2);
3928
5557
  } catch {
@@ -3962,8 +5591,8 @@ function buildSIWXConfig(deps) {
3962
5591
  statement: STATEMENT,
3963
5592
  issuedAt,
3964
5593
  toString() {
3965
- return new SiweMessage({
3966
- address: getAddress(accountAddress),
5594
+ return new SiweMessage2({
5595
+ address: getAddress3(accountAddress),
3967
5596
  chainId: numericChainId(chainId),
3968
5597
  domain,
3969
5598
  uri,
@@ -4005,7 +5634,7 @@ function buildSIWXConfig(deps) {
4005
5634
  const persistSession = async (session) => {
4006
5635
  const address = session.data.accountAddress;
4007
5636
  const id = numericChainId(session.data.chainId);
4008
- const message = new SiweMessage(session.message);
5637
+ const message = new SiweMessage2(session.message);
4009
5638
  const login = await post("/auth/login", {
4010
5639
  message,
4011
5640
  signature: session.signature,
@@ -4053,6 +5682,7 @@ export {
4053
5682
  NotConnectedError,
4054
5683
  OwneyError,
4055
5684
  OwneySDK,
5685
+ YieldseekerAgent,
4056
5686
  createOwneySIWX,
4057
5687
  setOwneyDebug
4058
5688
  };