@owney/sdk 0.7.21-beta.0 → 0.7.21-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  NotConnectedError: () => NotConnectedError,
27
27
  OwneyError: () => OwneyError,
28
28
  OwneySDK: () => OwneySDK,
29
+ YieldseekerAgent: () => YieldseekerAgent,
29
30
  createOwneySIWX: () => createOwneySIWX,
30
31
  setOwneyDebug: () => setOwneyDebug
31
32
  });
@@ -310,18 +311,18 @@ function tokenDecimals(symbol, explicit) {
310
311
  return explicit;
311
312
  return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
312
313
  }
313
- function mapDeposit(raw) {
314
+ function mapDeposit(raw2) {
314
315
  return {
315
- txHash: raw.txHash,
316
- smartWallet: raw.smartWallet,
317
- amount: raw.amount
316
+ txHash: raw2.txHash,
317
+ smartWallet: raw2.smartWallet,
318
+ amount: raw2.amount
318
319
  };
319
320
  }
320
- function mapWithdraw(raw) {
321
+ function mapWithdraw(raw2) {
321
322
  return {
322
- txHash: raw.txHash,
323
- type: raw.type,
324
- amount: raw.amount
323
+ txHash: raw2.txHash,
324
+ type: raw2.type,
325
+ amount: raw2.amount
325
326
  };
326
327
  }
327
328
  var CHAIN_ID_TO_NAME = {
@@ -340,10 +341,10 @@ function resolveChainId(chain) {
340
341
  if (Number.isFinite(asNum) && asNum > 0) return asNum;
341
342
  return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
342
343
  }
343
- function mapPendingAllocations(raw) {
344
- if (!Array.isArray(raw)) return void 0;
344
+ function mapPendingAllocations(raw2) {
345
+ if (!Array.isArray(raw2)) return void 0;
345
346
  const pending = [];
346
- for (const entry of raw) {
347
+ for (const entry of raw2) {
347
348
  if (typeof entry !== "object" || entry === null) continue;
348
349
  const e = entry;
349
350
  if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
@@ -366,8 +367,8 @@ function mapPendingAllocations(raw) {
366
367
  }
367
368
  return pending.length > 0 ? pending : void 0;
368
369
  }
369
- function mapBalances(raw, _chainId, smartWallet) {
370
- const portfolio = raw.portfolio;
370
+ function mapBalances(raw2, _chainId, smartWallet) {
371
+ const portfolio = raw2.portfolio;
371
372
  const portfolioByChain = portfolio.portfolioByChain ?? {};
372
373
  let totalBalance = 0;
373
374
  const tokens = [];
@@ -436,8 +437,8 @@ function sumTokenValues(tokens) {
436
437
  function sumTokenEarnings(tokens) {
437
438
  return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
438
439
  }
439
- function mapEarnings(raw, smartWallet) {
440
- const totalEarningsByChain = raw.data.totalEarningsByChainWithFee ?? raw.data.totalEarningsByChain ?? {};
440
+ function mapEarnings(raw2, smartWallet) {
441
+ const totalEarningsByChain = raw2.data.totalEarningsByChainWithFee ?? raw2.data.totalEarningsByChain ?? {};
441
442
  const tokens = [];
442
443
  for (const [chainIdKey, tokensBySymbol] of Object.entries(
443
444
  totalEarningsByChain
@@ -456,15 +457,15 @@ function mapEarnings(raw, smartWallet) {
456
457
  return {
457
458
  smartWallet,
458
459
  lifetimeEarnings: sumTokenEarnings(
459
- raw.data.totalEarningsByTokenWithFee ?? raw.data.totalEarningsByToken
460
+ raw2.data.totalEarningsByTokenWithFee ?? raw2.data.totalEarningsByToken
460
461
  ),
461
462
  tokens
462
463
  };
463
464
  }
464
- function mapWeightedApyByChain(raw) {
465
- if (!raw) return void 0;
465
+ function mapWeightedApyByChain(raw2) {
466
+ if (!raw2) return void 0;
466
467
  const out = {};
467
- for (const [chainKey, tokenApy] of Object.entries(raw)) {
468
+ for (const [chainKey, tokenApy] of Object.entries(raw2)) {
468
469
  const chainId = Number(chainKey);
469
470
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
470
471
  const perAsset = {};
@@ -504,8 +505,8 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
504
505
  }
505
506
  return totalBalance > 0 ? weightedSum / totalBalance : null;
506
507
  }
507
- function mapApyHistory(raw, chainId, tokenSymbol) {
508
- const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
508
+ function mapApyHistory(raw2, chainId, tokenSymbol) {
509
+ const history = Object.entries(raw2.history ?? {}).map(([date, entry]) => ({
509
510
  date,
510
511
  apy: rawPoolApyForChain(entry, chainId, tokenSymbol),
511
512
  // Provider position balances are treated as decimal amounts of the
@@ -521,9 +522,9 @@ function mapApyHistory(raw, chainId, tokenSymbol) {
521
522
  } : {}
522
523
  })).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
523
524
  return {
524
- walletAddress: raw.walletAddress,
525
- weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
526
- apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain),
525
+ walletAddress: raw2.walletAddress,
526
+ weightedApyAfterFee: raw2.weightedApyAfterFee ? sumTokenValues(raw2.weightedApyAfterFee) : void 0,
527
+ apyByChainAndAsset: mapWeightedApyByChain(raw2.weightedApyAfterFeeByChain),
527
528
  history
528
529
  };
529
530
  }
@@ -619,23 +620,23 @@ function mapEntries(rawEntries, chainId) {
619
620
  };
620
621
  });
621
622
  }
622
- function mapUserProfile(raw, userAddress) {
623
+ function mapUserProfile(raw2, userAddress) {
623
624
  return {
624
625
  address: userAddress,
625
- smartWallet: raw.smartWallet || "",
626
- chains: raw.chains || [],
627
- strategy: raw.strategy,
628
- hasActiveSessionKey: raw.hasActiveSessionKey || false,
629
- protocols: raw.protocols || [],
630
- splitting: raw.splitting,
631
- minSplits: raw.minSplits
626
+ smartWallet: raw2.smartWallet || "",
627
+ chains: raw2.chains || [],
628
+ strategy: raw2.strategy,
629
+ hasActiveSessionKey: raw2.hasActiveSessionKey || false,
630
+ protocols: raw2.protocols || [],
631
+ splitting: raw2.splitting,
632
+ minSplits: raw2.minSplits
632
633
  };
633
634
  }
634
- function mapApyByStrategy(raw) {
635
+ function mapApyByStrategy(raw2) {
635
636
  const apyPerAsset = {};
636
637
  let apySum = 0;
637
638
  let apyCount = 0;
638
- for (const entry of raw.data) {
639
+ for (const entry of raw2.data) {
639
640
  const supported = SupportedAssets.find(
640
641
  (asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
641
642
  );
@@ -806,15 +807,15 @@ var readSession = (address, _chainId) => {
806
807
  if (typeof window === "undefined") return null;
807
808
  const key2 = buildKey(address);
808
809
  const store = storage();
809
- let raw = null;
810
+ let raw2 = null;
810
811
  try {
811
- raw = store?.getItem(key2) ?? null;
812
+ raw2 = store?.getItem(key2) ?? null;
812
813
  } catch {
813
- raw = null;
814
+ raw2 = null;
814
815
  }
815
- if (raw) {
816
+ if (raw2) {
816
817
  try {
817
- const parsed = JSON.parse(raw);
818
+ const parsed = JSON.parse(raw2);
818
819
  if (isFreshSession(parsed)) return parsed;
819
820
  } catch {
820
821
  }
@@ -982,8 +983,8 @@ function buildChainsByProtocol(protocols, dashboardChainIds) {
982
983
  }
983
984
  return result;
984
985
  }
985
- function flattenAvailablePools(raw) {
986
- const byChain = raw ?? {};
986
+ function flattenAvailablePools(raw2) {
987
+ const byChain = raw2 ?? {};
987
988
  const names = [];
988
989
  for (const byToken of Object.values(byChain ?? {})) {
989
990
  for (const entry of Object.values(byToken ?? {})) {
@@ -1486,8 +1487,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
1486
1487
  const poolResults = await Promise.all(
1487
1488
  universe.map(async (protocol) => {
1488
1489
  try {
1489
- const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
1490
- return [protocol.id, flattenAvailablePools(raw)];
1490
+ const raw2 = await this.sdk.getAvailablePools(protocol.id, strategy);
1491
+ return [protocol.id, flattenAvailablePools(raw2)];
1491
1492
  } catch (error) {
1492
1493
  console.warn(
1493
1494
  `[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
@@ -1553,14 +1554,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1553
1554
  async readWalletState(ownerAddress) {
1554
1555
  try {
1555
1556
  const { portfolio } = await this.sdk.getPositions(ownerAddress);
1556
- const raw = portfolio;
1557
+ const raw2 = portfolio;
1557
1558
  debugLog("zyfai:onboard", "wallet state from getPositions", {
1558
- predeployed: raw?.predeployed,
1559
- hasActiveSessionKey: raw?.hasActiveSessionKey
1559
+ predeployed: raw2?.predeployed,
1560
+ hasActiveSessionKey: raw2?.hasActiveSessionKey
1560
1561
  });
1561
1562
  return {
1562
- predeployed: raw?.predeployed,
1563
- hasActiveSessionKey: raw?.hasActiveSessionKey
1563
+ predeployed: raw2?.predeployed,
1564
+ hasActiveSessionKey: raw2?.hasActiveSessionKey
1564
1565
  };
1565
1566
  } catch (error) {
1566
1567
  console.warn(
@@ -1846,14 +1847,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1846
1847
  return { txHash, smartWallet, amount };
1847
1848
  }
1848
1849
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
1849
- const raw = await this.sdk.depositFunds(
1850
+ const raw2 = await this.sdk.depositFunds(
1850
1851
  this.getAddress(),
1851
1852
  validChainId,
1852
1853
  amount,
1853
1854
  asset,
1854
1855
  "aggressive"
1855
1856
  );
1856
- return mapDeposit(raw);
1857
+ return mapDeposit(raw2);
1857
1858
  } catch (error) {
1858
1859
  throw error;
1859
1860
  }
@@ -1862,27 +1863,27 @@ var ZyfaiAgent = class _ZyfaiAgent {
1862
1863
  async withdraw(state, chainId, token, amount) {
1863
1864
  const validChainId = isValidChainId(chainId);
1864
1865
  await this.ensureConnected(state, validChainId);
1865
- const raw = await this.sdk.withdrawFunds(
1866
+ const raw2 = await this.sdk.withdrawFunds(
1866
1867
  this.getAddress(),
1867
1868
  validChainId,
1868
1869
  amount,
1869
1870
  token
1870
1871
  );
1871
- if (!raw.success) {
1872
+ if (!raw2.success) {
1872
1873
  throw new OwneyError(
1873
1874
  "WITHDRAW_FAILED",
1874
- raw.message || "Zyfai withdraw failed.",
1875
- { chainId: validChainId, token, amount, response: raw },
1875
+ raw2.message || "Zyfai withdraw failed.",
1876
+ { chainId: validChainId, token, amount, response: raw2 },
1876
1877
  this.id
1877
1878
  );
1878
1879
  }
1879
- return mapWithdraw(raw);
1880
+ return mapWithdraw(raw2);
1880
1881
  }
1881
1882
  // --- IAgent: Portfolio reads ---
1882
1883
  async getBalances(state, chainId) {
1883
1884
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
1884
- const raw = await this.sdk.getPortfolio(this.getAddress());
1885
- return mapBalances(raw, validChainId, smartWallet);
1885
+ const raw2 = await this.sdk.getPortfolio(this.getAddress());
1886
+ return mapBalances(raw2, validChainId, smartWallet);
1886
1887
  }
1887
1888
  earningsKey(state, chainId, smartWallet) {
1888
1889
  return JSON.stringify([
@@ -1895,11 +1896,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
1895
1896
  const existing = this.earningsReads.get(key2);
1896
1897
  if (existing) return existing;
1897
1898
  const generation = this.earningsGeneration;
1898
- const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw) => {
1899
+ const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw2) => {
1899
1900
  if (generation === this.earningsGeneration) {
1900
- this.earningsSnapshot = { key: key2, raw, at: Date.now() };
1901
+ this.earningsSnapshot = { key: key2, raw: raw2, at: Date.now() };
1901
1902
  }
1902
- return raw;
1903
+ return raw2;
1903
1904
  }).finally(() => {
1904
1905
  if (this.earningsReads.get(key2) === pending)
1905
1906
  this.earningsReads.delete(key2);
@@ -1909,11 +1910,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
1909
1910
  }
1910
1911
  async getEarnings(state, chainId) {
1911
1912
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1912
- const raw = await this.readEarnings(
1913
+ const raw2 = await this.readEarnings(
1913
1914
  this.earningsKey(state, chainId, smartWallet),
1914
1915
  smartWallet
1915
1916
  );
1916
- return mapEarnings(raw, smartWallet);
1917
+ return mapEarnings(raw2, smartWallet);
1917
1918
  }
1918
1919
  async refreshEarnings(state, chainId) {
1919
1920
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -1940,8 +1941,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
1940
1941
  }
1941
1942
  async getAccountApy(state, chainId, days, tokenSymbol) {
1942
1943
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1943
- const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
1944
- return mapApyHistory(raw, chainId, tokenSymbol);
1944
+ const raw2 = await this.sdk.getDailyApyHistory(smartWallet, days);
1945
+ return mapApyHistory(raw2, chainId, tokenSymbol);
1945
1946
  }
1946
1947
  /**
1947
1948
  * Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
@@ -1976,7 +1977,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1976
1977
  const matched = [];
1977
1978
  let backendExhausted = false;
1978
1979
  for (let i = 0; i < MAX_BACKEND_CALLS && matched.length < limit; i++) {
1979
- const raw = await this.sdk.getHistory(smartWallet, validChainId, {
1980
+ const raw2 = await this.sdk.getHistory(smartWallet, validChainId, {
1980
1981
  limit: backendPageSize,
1981
1982
  offset,
1982
1983
  fromDate: options?.fromDate,
@@ -1987,13 +1988,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
1987
1988
  // asset's rows and handing back a page that filters to nothing.
1988
1989
  assetType
1989
1990
  });
1990
- raw.data.forEach((entry, idx) => {
1991
+ raw2.data.forEach((entry, idx) => {
1991
1992
  if (entry.chainId === validChainId) {
1992
1993
  matched.push({ entry, rawIdx: offset + idx });
1993
1994
  }
1994
1995
  });
1995
- offset += raw.data.length;
1996
- if (raw.data.length < backendPageSize) {
1996
+ offset += raw2.data.length;
1997
+ if (raw2.data.length < backendPageSize) {
1997
1998
  backendExhausted = true;
1998
1999
  break;
1999
2000
  }
@@ -2015,18 +2016,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
2015
2016
  }
2016
2017
  async getUserProfile(state, chainId) {
2017
2018
  await this.connectAuth(state, chainId);
2018
- const raw = await this.sdk.getUserDetails();
2019
+ const raw2 = await this.sdk.getUserDetails();
2019
2020
  debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
2020
2021
  asset: "USDC (default \u2014 no asset passed)",
2021
- splitting: raw.splitting,
2022
- minSplits: raw.minSplits,
2023
- strategy: raw.strategy,
2024
- chains: raw.chains,
2025
- protocolCount: raw.protocols?.length,
2026
- hasActiveSessionKey: raw.hasActiveSessionKey,
2027
- smartWallet: raw.smartWallet
2022
+ splitting: raw2.splitting,
2023
+ minSplits: raw2.minSplits,
2024
+ strategy: raw2.strategy,
2025
+ chains: raw2.chains,
2026
+ protocolCount: raw2.protocols?.length,
2027
+ hasActiveSessionKey: raw2.hasActiveSessionKey,
2028
+ smartWallet: raw2.smartWallet
2028
2029
  });
2029
- return mapUserProfile(raw, this.connectedAddress);
2030
+ return mapUserProfile(raw2, this.connectedAddress);
2030
2031
  }
2031
2032
  async ensureAutoSelectProtocols(state, chainId, asset) {
2032
2033
  await this.connectAuth(state, chainId);
@@ -2045,396 +2046,219 @@ var ZyfaiAgent = class _ZyfaiAgent {
2045
2046
  }
2046
2047
  // --- IAgent: Discovery (no wallet required) ---
2047
2048
  async getAgentApy(days, options) {
2048
- const raw = await this.sdk.getAPYPerStrategy(
2049
+ const raw2 = await this.sdk.getAPYPerStrategy(
2049
2050
  false,
2050
2051
  DayFilterMapping[days],
2051
2052
  "aggressive",
2052
2053
  options?.chainId,
2053
2054
  options?.tokenSymbol
2054
2055
  );
2055
- return mapApyByStrategy(raw);
2056
+ return mapApyByStrategy(raw2);
2056
2057
  }
2057
2058
  };
2058
2059
 
2059
- // src/lib/routing-api.ts
2060
+ // src/agents/yieldseeker/yieldseeker.agent.ts
2061
+ var import_viem6 = require("viem");
2062
+ var import_chains3 = require("viem/chains");
2063
+
2064
+ // src/lib/chain-guard.ts
2065
+ var CHAIN_NAMES = {
2066
+ 1: "Ethereum",
2067
+ 8453: "Base",
2068
+ 42161: "Arbitrum"
2069
+ };
2070
+ function chainName(chainId) {
2071
+ return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
2072
+ }
2073
+ async function ensureWalletOnChain(pub, wallet, expected) {
2074
+ const actual = await pub.getChainId();
2075
+ if (actual === expected) return;
2076
+ try {
2077
+ await wallet.switchChain({ id: expected });
2078
+ } catch (error) {
2079
+ throw new OwneyError(
2080
+ "CHAIN_MISMATCH",
2081
+ `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
2082
+ {
2083
+ expectedChainId: expected,
2084
+ actualChainId: actual,
2085
+ cause: error instanceof Error ? error.message : String(error)
2086
+ }
2087
+ );
2088
+ }
2089
+ const after = await pub.getChainId();
2090
+ if (after !== expected) {
2091
+ throw new OwneyError(
2092
+ "CHAIN_MISMATCH",
2093
+ `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
2094
+ { expectedChainId: expected, actualChainId: after }
2095
+ );
2096
+ }
2097
+ }
2098
+
2099
+ // src/lib/transfer-auth.ts
2100
+ var import_viem2 = require("viem");
2101
+ var ERC20_META_ABI = [
2102
+ { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
2103
+ { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
2104
+ ];
2105
+ function buildTransferWithAuthorizationTypedData(input) {
2106
+ return {
2107
+ domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
2108
+ types: {
2109
+ TransferWithAuthorization: [
2110
+ { name: "from", type: "address" },
2111
+ { name: "to", type: "address" },
2112
+ { name: "value", type: "uint256" },
2113
+ { name: "validAfter", type: "uint256" },
2114
+ { name: "validBefore", type: "uint256" },
2115
+ { name: "nonce", type: "bytes32" }
2116
+ ]
2117
+ },
2118
+ primaryType: "TransferWithAuthorization",
2119
+ message: input.message
2120
+ };
2121
+ }
2122
+ async function readTokenMeta(publicClient, token) {
2123
+ const [tokenName, tokenVersion] = await Promise.all([
2124
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
2125
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
2126
+ ]);
2127
+ return { tokenName, tokenVersion };
2128
+ }
2129
+ function randomAuthNonce() {
2130
+ const bytes = new Uint8Array(32);
2131
+ globalThis.crypto.getRandomValues(bytes);
2132
+ return (0, import_viem2.bytesToHex)(bytes);
2133
+ }
2134
+
2135
+ // src/lib/sponsor-client.ts
2060
2136
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2061
- async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2062
- const url = `${baseUrl}/api/v1/agent/org-config`;
2137
+ async function postPaymasterIntent(input) {
2138
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2139
+ let res;
2063
2140
  try {
2064
- const res = await fetch(url, {
2065
- method: "GET",
2141
+ res = await fetch(`${base5}/api/v1/sponsor/paymaster-intent`, {
2142
+ method: "POST",
2066
2143
  headers: {
2067
- "Content-Type": "application/json",
2068
- "x-owney-api-key": `${apiKey}`
2069
- }
2144
+ "content-type": "application/json",
2145
+ "x-owney-api-key": input.apiKey,
2146
+ Authorization: `Signature ${input.yieldseekerSignature}`
2147
+ },
2148
+ body: JSON.stringify(input.body)
2070
2149
  });
2071
- if (!res.ok) {
2072
- if (res.status !== 404) {
2073
- console.warn(
2074
- `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
2075
- );
2076
- }
2077
- return null;
2078
- }
2079
- const json = await res.json();
2080
- const policy = json.success ? json.data ?? null : null;
2081
- debugLog(
2082
- "owney-sdk",
2083
- policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
2084
- policy ?? void 0
2150
+ } catch (networkError) {
2151
+ throw new OwneyError(
2152
+ "SPONSOR_REQUEST_FAILED",
2153
+ `Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2154
+ { cause: String(networkError), safeToFallback: true }
2085
2155
  );
2086
- return policy;
2087
- } catch (error) {
2088
- console.warn(
2089
- "[owney-sdk] Could not read org agent config (non-fatal):",
2090
- error instanceof Error ? error.message : String(error)
2156
+ }
2157
+ const text = await res.text();
2158
+ let parsed = null;
2159
+ try {
2160
+ parsed = JSON.parse(text);
2161
+ } catch {
2162
+ }
2163
+ if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
2164
+ throw new OwneyError(
2165
+ "SPONSOR_REQUEST_FAILED",
2166
+ `Paymaster intent API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2167
+ {
2168
+ statusCode: res.status,
2169
+ responseBody: text.slice(0, 500),
2170
+ safeToFallback: true
2171
+ }
2091
2172
  );
2092
- return null;
2093
2173
  }
2174
+ return parsed.data;
2094
2175
  }
2095
- async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2096
- const url = `${baseUrl}/api/v1/agent/keys`;
2097
- const res = await fetch(url, {
2098
- method: "GET",
2099
- headers: {
2100
- "Content-Type": "application/json",
2101
- "x-owney-api-key": `${apiKey}`
2102
- }
2103
- });
2104
- if (!res.ok) {
2105
- const text = await res.text().catch(() => "");
2176
+ async function postSponsorTransferAuth(input) {
2177
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2178
+ let res;
2179
+ try {
2180
+ res = await fetch(`${base5}/api/v1/sponsor/erc20-transfer-auth`, {
2181
+ method: "POST",
2182
+ headers: {
2183
+ "content-type": "application/json",
2184
+ "x-owney-api-key": input.apiKey,
2185
+ ...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
2186
+ },
2187
+ body: JSON.stringify(input.body)
2188
+ });
2189
+ } catch (networkError) {
2106
2190
  throw new OwneyError(
2107
- "API_ROUTING_ERROR",
2108
- `Routing API error ${res.status}: ${text}`,
2109
- { statusCode: res.status, responseBody: text }
2191
+ "SPONSOR_REQUEST_FAILED",
2192
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2193
+ { cause: String(networkError) }
2110
2194
  );
2111
2195
  }
2112
- const json = await res.json();
2113
- if (!json.success) {
2196
+ const text = await res.text();
2197
+ let parsed = null;
2198
+ try {
2199
+ parsed = JSON.parse(text);
2200
+ } catch {
2201
+ }
2202
+ if (!res.ok || !parsed?.success || !parsed.data) {
2114
2203
  throw new OwneyError(
2115
- "API_ROUTING_FAILED",
2116
- `Routing API request failed: ${json.message}`,
2117
- { message: json.message }
2204
+ "SPONSOR_REQUEST_FAILED",
2205
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2206
+ {
2207
+ statusCode: res.status,
2208
+ responseBody: text.slice(0, 500),
2209
+ // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
2210
+ // before broadcast, so it is safe to fall back to a user-paid deposit.
2211
+ safeToFallback: res.status === 503
2212
+ }
2118
2213
  );
2119
2214
  }
2120
- return json.data;
2215
+ return parsed.data;
2121
2216
  }
2122
-
2123
- // src/lib/health-report.ts
2124
- var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2125
- async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
2217
+ async function postSponsorPermit2Transfer(input) {
2218
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2219
+ let res;
2126
2220
  try {
2127
- await fetch(`${baseUrl}/api/v1/agent/health-report`, {
2221
+ res = await fetch(`${base5}/api/v1/sponsor/permit2-transfer`, {
2128
2222
  method: "POST",
2129
2223
  headers: {
2130
- "Content-Type": "application/json",
2131
- "x-owney-api-key": apiKey
2224
+ "content-type": "application/json",
2225
+ "x-owney-api-key": input.apiKey,
2226
+ ...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
2132
2227
  },
2133
- body: JSON.stringify({
2134
- agent_type: agentType,
2135
- error_code: errorCode,
2136
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2137
- })
2228
+ body: JSON.stringify(input.body)
2138
2229
  });
2139
- } catch (err) {
2140
- console.warn(
2141
- `[owney-sdk] health-report failed for agent "${agentType}":`,
2142
- err instanceof Error ? err.message : err
2230
+ } catch (networkError) {
2231
+ throw new OwneyError(
2232
+ "SPONSOR_REQUEST_FAILED",
2233
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2234
+ { cause: String(networkError), safeToFallback: false }
2143
2235
  );
2144
2236
  }
2145
- }
2146
- async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
2237
+ const text = await res.text();
2238
+ let parsed = null;
2147
2239
  try {
2148
- return await fn();
2149
- } catch (err) {
2150
- const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
2151
- void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
2152
- throw err;
2240
+ parsed = JSON.parse(text);
2241
+ } catch {
2153
2242
  }
2154
- }
2155
-
2156
- // src/lib/helpers/withdraw-helper.ts
2157
- var import_viem2 = require("viem");
2158
- function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
2159
- const target = asset.toUpperCase();
2160
- return agents.map((agent) => {
2161
- const agentBalance = aggregated[agent.id];
2162
- const tokenBalance = agentBalance?.tokens.find(
2163
- (t) => t.chainId === chainId && t.asset.toUpperCase() === target
2243
+ if (!res.ok || !parsed?.success || !parsed.data) {
2244
+ throw new OwneyError(
2245
+ "SPONSOR_REQUEST_FAILED",
2246
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2247
+ {
2248
+ statusCode: res.status,
2249
+ responseBody: text.slice(0, 500),
2250
+ safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
2251
+ }
2164
2252
  );
2165
- if (!tokenBalance) return { agent, balance: 0n };
2166
- return { agent, balance: (0, import_viem2.parseUnits)(tokenBalance.amount, decimals) };
2167
- });
2168
- }
2169
- function planProportionalShares(balances, requested, totalAvailable) {
2170
- const plans = balances.map(({ agent, balance }) => ({
2171
- agent,
2172
- balance,
2173
- planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
2174
- }));
2175
- const assigned = plans.reduce((s, p) => s + p.planned, 0n);
2176
- let remainder = requested - assigned;
2177
- const byHeadroom = [...plans].sort((a, b) => {
2178
- const diff = b.balance - b.planned - (a.balance - a.planned);
2179
- return diff > 0n ? 1 : diff < 0n ? -1 : 0;
2180
- });
2181
- for (const p of byHeadroom) {
2182
- if (remainder === 0n) break;
2183
- const headroom = p.balance - p.planned;
2184
- if (headroom <= 0n) continue;
2185
- const take = headroom < remainder ? headroom : remainder;
2186
- p.planned += take;
2187
- remainder -= take;
2188
2253
  }
2189
- return plans;
2190
- }
2191
- function planDisabledDrain(disabled, requested) {
2192
- const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
2193
- const plans = [];
2194
- let remaining = requested;
2195
- for (const { agent, balance } of sorted) {
2196
- if (remaining === 0n) {
2197
- plans.push({ agent, balance, planned: 0n });
2198
- continue;
2199
- }
2200
- const take = balance < remaining ? balance : remaining;
2201
- plans.push({ agent, balance, planned: take });
2202
- remaining -= take;
2203
- }
2204
- return { plans, remaining };
2205
- }
2206
- function redistributeShare(plans, fromIndex, amount, candidatePool) {
2207
- const pool = candidatePool ?? plans.slice(fromIndex + 1);
2208
- const candidates = pool.filter((c) => c.balance - c.planned > 0n);
2209
- const totalHeadroom = candidates.reduce(
2210
- (s, c) => s + (c.balance - c.planned),
2211
- 0n
2212
- );
2213
- if (totalHeadroom === 0n) return;
2214
- let distributed = 0n;
2215
- for (const c of candidates) {
2216
- const headroom = c.balance - c.planned;
2217
- const proportional = headroom * amount / totalHeadroom;
2218
- const give = proportional > headroom ? headroom : proportional;
2219
- c.planned += give;
2220
- distributed += give;
2221
- }
2222
- let leftover = amount - distributed;
2223
- for (const c of candidates) {
2224
- if (leftover === 0n) break;
2225
- const headroom = c.balance - c.planned;
2226
- if (headroom <= 0n) continue;
2227
- const take = headroom < leftover ? headroom : leftover;
2228
- c.planned += take;
2229
- leftover -= take;
2230
- }
2231
- }
2232
- function sumWithdrawnAmount(results) {
2233
- return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
2234
- }
2235
-
2236
- // src/lib/helpers/account-apy-helper.ts
2237
- function balanceForApyScope(balance, chainId, tokenSymbol) {
2238
- if (!tokenSymbol) {
2239
- const total = Number(balance.totalBalance);
2240
- return Number.isFinite(total) && total > 0 ? total : 0;
2241
- }
2242
- const normalizedToken = tokenSymbol.toUpperCase();
2243
- return balance.tokens.reduce((total, token) => {
2244
- if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
2245
- return total;
2246
- }
2247
- const amount = Number(token.amount);
2248
- return Number.isFinite(amount) && amount > 0 ? total + amount : total;
2249
- }, 0);
2250
- }
2251
- function aggregateApyHistory(agentApys) {
2252
- const byDate = /* @__PURE__ */ new Map();
2253
- for (const accountApy of Object.values(agentApys)) {
2254
- const seen = /* @__PURE__ */ new Set();
2255
- for (const point of accountApy.history ?? []) {
2256
- if (!point.date || seen.has(point.date) || !Number.isFinite(point.apy)) continue;
2257
- seen.add(point.date);
2258
- const points = byDate.get(point.date) ?? [];
2259
- points.push(point);
2260
- byDate.set(point.date, points);
2261
- }
2262
- }
2263
- return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).flatMap(([date, points]) => {
2264
- if (points.length === 1 && !points[0].historicalBalance) {
2265
- return [{ date, apy: points[0].apy }];
2266
- }
2267
- const unit = points[0].historicalBalance?.unit;
2268
- if (!unit || points.some(
2269
- ({ historicalBalance: balance }) => !balance || balance.unit !== unit || !Number.isFinite(balance.amount) || balance.amount < 0
2270
- )) return [];
2271
- const total = points.reduce((sum, p) => sum + p.historicalBalance.amount, 0);
2272
- if (total <= 0 || !Number.isFinite(total)) return [];
2273
- const apy = points.reduce((sum, p) => sum + p.apy * (p.historicalBalance.amount / total), 0);
2274
- return Number.isFinite(apy) ? [{ date, apy }] : [];
2275
- });
2276
- }
2277
- function aggregateApyByChainAndAsset(agentApys, agentBalances) {
2278
- const sums = {};
2279
- const weights = {};
2280
- for (const id of Object.keys(agentApys)) {
2281
- const cells = agentApys[id].apyByChainAndAsset;
2282
- const balance = agentBalances[id] ?? 0;
2283
- if (!cells || balance <= 0) continue;
2284
- for (const [chainKey, perAsset] of Object.entries(cells)) {
2285
- if (!perAsset) continue;
2286
- const chainId = Number(chainKey);
2287
- for (const [asset, apyValue] of Object.entries(perAsset)) {
2288
- const apy = Number(apyValue ?? 0);
2289
- if (apy === 0) continue;
2290
- sums[chainId] ??= {};
2291
- weights[chainId] ??= {};
2292
- sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
2293
- weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
2294
- }
2295
- }
2296
- }
2297
- const out = {};
2298
- for (const chainKey of Object.keys(sums)) {
2299
- const chainId = Number(chainKey);
2300
- const perAssetOut = {};
2301
- for (const asset of Object.keys(sums[chainId])) {
2302
- const w = weights[chainId][asset];
2303
- if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
2304
- }
2305
- if (Object.keys(perAssetOut).length > 0) {
2306
- out[chainId] = perAssetOut;
2307
- }
2308
- }
2309
- return out;
2310
- }
2311
-
2312
- // src/client.ts
2313
- var import_viem6 = require("viem");
2314
- var import_chains2 = require("viem/chains");
2315
-
2316
- // src/lib/transfer-auth.ts
2317
- var import_viem3 = require("viem");
2318
- var ERC20_META_ABI = [
2319
- { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
2320
- { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
2321
- ];
2322
- function buildTransferWithAuthorizationTypedData(input) {
2323
- return {
2324
- domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
2325
- types: {
2326
- TransferWithAuthorization: [
2327
- { name: "from", type: "address" },
2328
- { name: "to", type: "address" },
2329
- { name: "value", type: "uint256" },
2330
- { name: "validAfter", type: "uint256" },
2331
- { name: "validBefore", type: "uint256" },
2332
- { name: "nonce", type: "bytes32" }
2333
- ]
2334
- },
2335
- primaryType: "TransferWithAuthorization",
2336
- message: input.message
2337
- };
2338
- }
2339
- async function readTokenMeta(publicClient, token) {
2340
- const [tokenName, tokenVersion] = await Promise.all([
2341
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
2342
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
2343
- ]);
2344
- return { tokenName, tokenVersion };
2345
- }
2346
- function randomAuthNonce() {
2347
- const bytes = new Uint8Array(32);
2348
- globalThis.crypto.getRandomValues(bytes);
2349
- return (0, import_viem3.bytesToHex)(bytes);
2350
- }
2351
-
2352
- // src/lib/sponsor-client.ts
2353
- var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2354
- async function postSponsorTransferAuth(input) {
2355
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2356
- let res;
2357
- try {
2358
- res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
2359
- method: "POST",
2360
- headers: {
2361
- "content-type": "application/json",
2362
- "x-owney-api-key": input.apiKey
2363
- },
2364
- body: JSON.stringify(input.body)
2365
- });
2366
- } catch (networkError) {
2367
- throw new OwneyError(
2368
- "SPONSOR_REQUEST_FAILED",
2369
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2370
- { cause: String(networkError) }
2371
- );
2372
- }
2373
- const text = await res.text();
2374
- let parsed = null;
2375
- try {
2376
- parsed = JSON.parse(text);
2377
- } catch {
2378
- }
2379
- if (!res.ok || !parsed?.success || !parsed.data) {
2380
- throw new OwneyError(
2381
- "SPONSOR_REQUEST_FAILED",
2382
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2383
- {
2384
- statusCode: res.status,
2385
- responseBody: text.slice(0, 500),
2386
- // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
2387
- // before broadcast, so it is safe to fall back to a user-paid deposit.
2388
- safeToFallback: res.status === 503
2389
- }
2390
- );
2391
- }
2392
- return parsed.data;
2393
- }
2394
- async function postSponsorPermit2Transfer(input) {
2395
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2396
- let res;
2397
- try {
2398
- res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
2399
- method: "POST",
2400
- headers: {
2401
- "content-type": "application/json",
2402
- "x-owney-api-key": input.apiKey
2403
- },
2404
- body: JSON.stringify(input.body)
2405
- });
2406
- } catch (networkError) {
2407
- throw new OwneyError(
2408
- "SPONSOR_REQUEST_FAILED",
2409
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2410
- { cause: String(networkError), safeToFallback: false }
2411
- );
2412
- }
2413
- const text = await res.text();
2414
- let parsed = null;
2415
- try {
2416
- parsed = JSON.parse(text);
2417
- } catch {
2418
- }
2419
- if (!res.ok || !parsed?.success || !parsed.data) {
2420
- throw new OwneyError(
2421
- "SPONSOR_REQUEST_FAILED",
2422
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2423
- {
2424
- statusCode: res.status,
2425
- responseBody: text.slice(0, 500),
2426
- safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
2427
- }
2428
- );
2429
- }
2430
- return parsed.data;
2254
+ return parsed.data;
2431
2255
  }
2432
2256
  async function getSponsorRelayerAddress(input) {
2433
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2257
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2434
2258
  let res;
2435
2259
  try {
2436
2260
  res = await fetch(
2437
- `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2261
+ `${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2438
2262
  {
2439
2263
  headers: { "x-owney-api-key": input.apiKey }
2440
2264
  }
@@ -2467,7 +2291,7 @@ async function getSponsorRelayerAddress(input) {
2467
2291
  }
2468
2292
 
2469
2293
  // src/lib/permit2.ts
2470
- var import_viem4 = require("viem");
2294
+ var import_viem3 = require("viem");
2471
2295
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2472
2296
  var MAX_UINT256 = 2n ** 256n - 1n;
2473
2297
  var ERC20_ALLOWANCE_ABI = [
@@ -2525,7 +2349,7 @@ function buildPermitTransferFromTypedData(input) {
2525
2349
  function randomPermit2Nonce() {
2526
2350
  const bytes = new Uint8Array(32);
2527
2351
  globalThis.crypto.getRandomValues(bytes);
2528
- return BigInt((0, import_viem4.bytesToHex)(bytes));
2352
+ return BigInt((0, import_viem3.bytesToHex)(bytes));
2529
2353
  }
2530
2354
  async function readPermit2Allowance(publicClient, token, owner) {
2531
2355
  return publicClient.readContract({
@@ -2535,213 +2359,2032 @@ async function readPermit2Allowance(publicClient, token, owner) {
2535
2359
  args: [owner, PERMIT2_ADDRESS]
2536
2360
  });
2537
2361
  }
2538
- async function readErc20Balance(publicClient, token, owner) {
2539
- return publicClient.readContract({
2540
- address: token,
2541
- abi: ERC20_ALLOWANCE_ABI,
2542
- functionName: "balanceOf",
2543
- args: [owner]
2544
- });
2362
+ async function readErc20Balance(publicClient, token, owner) {
2363
+ return publicClient.readContract({
2364
+ address: token,
2365
+ abi: ERC20_ALLOWANCE_ABI,
2366
+ functionName: "balanceOf",
2367
+ args: [owner]
2368
+ });
2369
+ }
2370
+
2371
+ // src/lib/sponsored-deposit.ts
2372
+ var AUTH_WINDOW_SECONDS = 15 * 60;
2373
+ var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
2374
+ function provideDepositVerificationContext(callback, context) {
2375
+ callback[verificationSetter]?.(context);
2376
+ }
2377
+ function makeVerificationAwareDepositCallback(implementation) {
2378
+ let nextVerification;
2379
+ const callback = async (smartWallet, chainId, amount) => {
2380
+ const verification = nextVerification;
2381
+ nextVerification = void 0;
2382
+ return implementation(smartWallet, chainId, amount, verification);
2383
+ };
2384
+ Object.defineProperty(callback, verificationSetter, {
2385
+ value: (context) => {
2386
+ nextVerification = context;
2387
+ }
2388
+ });
2389
+ return callback;
2390
+ }
2391
+ function makeSponsoredDepositCallback(deps) {
2392
+ const post = deps.httpPost ?? postSponsorTransferAuth;
2393
+ return makeVerificationAwareDepositCallback(
2394
+ async (smartWallet, chainId, amount, verification) => {
2395
+ const cid = chainId;
2396
+ const token = deps.tokenAddressByChain[cid];
2397
+ if (!token) {
2398
+ throw new OwneyError(
2399
+ "CHAIN_UNSUPPORTED",
2400
+ `No sponsored token configured for chain ${chainId}`
2401
+ );
2402
+ }
2403
+ const pub = deps.getPublicClient(cid);
2404
+ const wallet = deps.getWalletClient(cid);
2405
+ await ensureWalletOnChain(pub, wallet, cid);
2406
+ try {
2407
+ const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2408
+ if (balance < BigInt(amount)) {
2409
+ throw new OwneyError(
2410
+ "DEPOSIT_INSUFFICIENT_BALANCE",
2411
+ "Insufficient balance for this deposit.",
2412
+ { token, chainId: cid, balance: balance.toString(), amount }
2413
+ );
2414
+ }
2415
+ } catch (err) {
2416
+ if (err instanceof OwneyError) throw err;
2417
+ console.warn(
2418
+ "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
2419
+ err instanceof Error ? err.message : String(err)
2420
+ );
2421
+ }
2422
+ const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
2423
+ const validAfter = 0n;
2424
+ const validBefore = BigInt(
2425
+ Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
2426
+ );
2427
+ const nonce = randomAuthNonce();
2428
+ const typedData = buildTransferWithAuthorizationTypedData({
2429
+ token,
2430
+ chainId: cid,
2431
+ tokenName,
2432
+ tokenVersion,
2433
+ message: {
2434
+ from: deps.ownerAddress,
2435
+ to: smartWallet,
2436
+ value: BigInt(amount),
2437
+ validAfter,
2438
+ validBefore,
2439
+ nonce
2440
+ }
2441
+ });
2442
+ const authSignature = await wallet.signTypedData({
2443
+ account: deps.ownerAddress,
2444
+ ...typedData
2445
+ });
2446
+ deps.onApproved?.();
2447
+ const result = await post({
2448
+ baseUrl: deps.baseUrl,
2449
+ apiKey: deps.apiKey,
2450
+ ...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
2451
+ body: {
2452
+ chainId: cid,
2453
+ token,
2454
+ from: deps.ownerAddress,
2455
+ to: smartWallet,
2456
+ value: amount,
2457
+ validAfter: validAfter.toString(),
2458
+ validBefore: validBefore.toString(),
2459
+ nonce,
2460
+ authSignature,
2461
+ tokenName,
2462
+ tokenVersion,
2463
+ ...verification?.agentId === "yieldseeker" ? {
2464
+ yieldseekerUserId: verification.userId,
2465
+ yieldseekerAgentId: verification.yieldseekerAgentId
2466
+ } : {}
2467
+ }
2468
+ });
2469
+ return result.txHash;
2470
+ }
2471
+ );
2472
+ }
2473
+
2474
+ // src/agents/yieldseeker/yieldseeker.auth.ts
2475
+ var import_siwe = require("siwe");
2476
+ var import_viem4 = require("viem");
2477
+ var import_chains2 = require("viem/chains");
2478
+
2479
+ // src/agents/yieldseeker/yieldseeker.auth-cache.ts
2480
+ var KEY_PREFIX2 = "owney.yieldseeker.session.v5";
2481
+ var INVALIDATED_KEY_PREFIXES = [
2482
+ "owney.yieldseeker.session",
2483
+ "owney.yieldseeker.session.v3",
2484
+ "owney.yieldseeker.session.v4"
2485
+ ];
2486
+ var storage2 = () => {
2487
+ if (typeof window === "undefined") return null;
2488
+ try {
2489
+ return window.localStorage;
2490
+ } catch {
2491
+ return null;
2492
+ }
2493
+ };
2494
+ var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
2495
+ var invalidatedKeys = (address, chainId) => INVALIDATED_KEY_PREFIXES.map(
2496
+ (prefix) => `${prefix}:${address.toLowerCase()}:${chainId}`
2497
+ );
2498
+ var clearInvalidatedSessions = (store, address, chainId) => {
2499
+ for (const key2 of invalidatedKeys(address, chainId)) {
2500
+ memorySessions2.delete(key2);
2501
+ try {
2502
+ store?.removeItem(key2);
2503
+ } catch {
2504
+ }
2505
+ }
2506
+ };
2507
+ var memorySessions2 = /* @__PURE__ */ new Map();
2508
+ var isValidSession = (session) => {
2509
+ if (!session?.token) return false;
2510
+ try {
2511
+ const parsed = JSON.parse(atob(session.token));
2512
+ return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
2513
+ } catch {
2514
+ return false;
2515
+ }
2516
+ };
2517
+ var readYieldseekerSession = (address, chainId) => {
2518
+ if (typeof window === "undefined") return null;
2519
+ const key2 = buildKey2(address, chainId);
2520
+ const store = storage2();
2521
+ clearInvalidatedSessions(store, address, chainId);
2522
+ let raw2 = null;
2523
+ try {
2524
+ raw2 = store?.getItem(key2) ?? null;
2525
+ } catch {
2526
+ raw2 = null;
2527
+ }
2528
+ if (raw2) {
2529
+ try {
2530
+ const parsed = JSON.parse(raw2);
2531
+ if (isValidSession(parsed)) return parsed.token;
2532
+ } catch {
2533
+ }
2534
+ memorySessions2.delete(key2);
2535
+ try {
2536
+ store?.removeItem(key2);
2537
+ } catch {
2538
+ }
2539
+ return null;
2540
+ }
2541
+ const cached = memorySessions2.get(key2);
2542
+ if (isValidSession(cached)) return cached.token;
2543
+ if (cached) memorySessions2.delete(key2);
2544
+ return null;
2545
+ };
2546
+ var writeYieldseekerSession = (address, chainId, token) => {
2547
+ if (typeof window === "undefined") return;
2548
+ const session = { token };
2549
+ if (!isValidSession(session)) return;
2550
+ const key2 = buildKey2(address, chainId);
2551
+ memorySessions2.set(key2, session);
2552
+ const store = storage2();
2553
+ try {
2554
+ store?.setItem(key2, JSON.stringify(session));
2555
+ } catch {
2556
+ }
2557
+ };
2558
+ var clearYieldseekerSession = (address, chainId) => {
2559
+ const key2 = buildKey2(address, chainId);
2560
+ memorySessions2.delete(key2);
2561
+ const store = storage2();
2562
+ clearInvalidatedSessions(store, address, chainId);
2563
+ try {
2564
+ store?.removeItem(key2);
2565
+ } catch {
2566
+ }
2567
+ };
2568
+
2569
+ // src/agents/yieldseeker/yieldseeker.auth.ts
2570
+ function resolveSiweOrigin(override) {
2571
+ const origin = override?.trim() || (typeof window !== "undefined" ? window.location?.origin : void 0);
2572
+ if (!origin || origin === "null") {
2573
+ throw new Error("Yieldseeker sign-in requires the requesting app's browser origin.");
2574
+ }
2575
+ const url = new URL(origin);
2576
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
2577
+ throw new Error("Yieldseeker sign-in requires an HTTP or HTTPS app origin.");
2578
+ }
2579
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
2580
+ throw new Error("Yieldseeker SIWE override must be an origin without credentials, path, query, or fragment.");
2581
+ }
2582
+ return url;
2583
+ }
2584
+ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
2585
+ const url = resolveSiweOrigin(dependencies.origin);
2586
+ return new import_siwe.SiweMessage({
2587
+ scheme: url.protocol.slice(0, -1),
2588
+ domain: url.host,
2589
+ address: (0, import_viem4.getAddress)(address),
2590
+ uri: url.origin,
2591
+ version: "1",
2592
+ chainId,
2593
+ nonce: (dependencies.nonce ?? import_siwe.generateNonce)(),
2594
+ issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
2595
+ }).prepareMessage();
2596
+ }
2597
+ function encodeYieldseekerAuthToken(token) {
2598
+ const bytes = new TextEncoder().encode(JSON.stringify(token));
2599
+ let binary = "";
2600
+ for (const byte of bytes) binary += String.fromCharCode(byte);
2601
+ return btoa(binary);
2602
+ }
2603
+ var YieldseekerAuth = class {
2604
+ constructor(dependencies = {}) {
2605
+ this.dependencies = dependencies;
2606
+ }
2607
+ dependencies;
2608
+ tokens = /* @__PURE__ */ new Map();
2609
+ pending = /* @__PURE__ */ new Map();
2610
+ scopes = /* @__PURE__ */ new Map();
2611
+ key(state, chainId) {
2612
+ return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
2613
+ }
2614
+ async getToken(state, chainId) {
2615
+ const key2 = this.key(state, chainId);
2616
+ const scope = { address: state.walletAddress, chainId };
2617
+ this.scopes.set(key2, scope);
2618
+ const cached = this.tokens.get(key2);
2619
+ if (cached) return cached;
2620
+ const persisted = readYieldseekerSession(scope.address, scope.chainId);
2621
+ if (persisted && this.matchesOrigin(persisted)) {
2622
+ this.tokens.set(key2, persisted);
2623
+ return persisted;
2624
+ }
2625
+ if (persisted) clearYieldseekerSession(scope.address, scope.chainId);
2626
+ const inFlight = this.pending.get(key2);
2627
+ if (inFlight) return inFlight;
2628
+ const request = this.sign(state, chainId).then((token) => {
2629
+ this.tokens.set(key2, token);
2630
+ writeYieldseekerSession(scope.address, scope.chainId, token);
2631
+ return token;
2632
+ });
2633
+ this.pending.set(key2, request);
2634
+ try {
2635
+ return await request;
2636
+ } finally {
2637
+ this.pending.delete(key2);
2638
+ }
2639
+ }
2640
+ matchesOrigin(token) {
2641
+ try {
2642
+ const message = new import_siwe.SiweMessage(JSON.parse(atob(token)).message);
2643
+ const url = resolveSiweOrigin(this.dependencies.origin);
2644
+ return message.domain === url.host && message.uri === url.origin && message.scheme === url.protocol.slice(0, -1);
2645
+ } catch {
2646
+ return false;
2647
+ }
2648
+ }
2649
+ clear(state, chainId) {
2650
+ if (!state || chainId === void 0) {
2651
+ for (const scope of this.scopes.values()) {
2652
+ clearYieldseekerSession(scope.address, scope.chainId);
2653
+ }
2654
+ this.tokens.clear();
2655
+ this.pending.clear();
2656
+ this.scopes.clear();
2657
+ return;
2658
+ }
2659
+ const key2 = this.key(state, chainId);
2660
+ this.tokens.delete(key2);
2661
+ this.pending.delete(key2);
2662
+ this.scopes.delete(key2);
2663
+ clearYieldseekerSession(state.walletAddress, chainId);
2664
+ }
2665
+ async sign(state, chainId) {
2666
+ const account = (0, import_viem4.getAddress)(state.walletAddress);
2667
+ const publicClient = (0, import_viem4.createPublicClient)({
2668
+ chain: import_chains2.base,
2669
+ transport: (0, import_viem4.custom)(state.provider)
2670
+ });
2671
+ const walletClient = (0, import_viem4.createWalletClient)({
2672
+ account,
2673
+ chain: import_chains2.base,
2674
+ transport: (0, import_viem4.custom)(state.provider)
2675
+ });
2676
+ await ensureWalletOnChain(
2677
+ publicClient,
2678
+ walletClient,
2679
+ 8453
2680
+ );
2681
+ const message = createYieldseekerSiweMessage(
2682
+ account,
2683
+ chainId,
2684
+ this.dependencies
2685
+ );
2686
+ const signature = await walletClient.signMessage({ account, message });
2687
+ return encodeYieldseekerAuthToken({ message, signature });
2688
+ }
2689
+ };
2690
+
2691
+ // src/agents/yieldseeker/yieldseeker.identity-cache.ts
2692
+ var KEY_PREFIX3 = "owney.yieldseeker.identity.v1";
2693
+ var YIELDSEEKER_IDENTITY_TTL_MS = 24 * 60 * 60 * 1e3;
2694
+ var memoryIdentities = /* @__PURE__ */ new Map();
2695
+ var storage3 = () => {
2696
+ if (typeof window === "undefined") return null;
2697
+ try {
2698
+ return window.localStorage;
2699
+ } catch {
2700
+ return null;
2701
+ }
2702
+ };
2703
+ var keyFor = (walletAddress, chainId) => `${KEY_PREFIX3}:${walletAddress.toLowerCase()}:${chainId}`;
2704
+ function valid(value, walletAddress, chainId, now) {
2705
+ return Boolean(
2706
+ 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
2707
+ );
2708
+ }
2709
+ function readYieldseekerIdentity(walletAddress, chainId, now = Date.now()) {
2710
+ if (typeof window === "undefined") return null;
2711
+ const key2 = keyFor(walletAddress, chainId);
2712
+ const store = storage3();
2713
+ let parsed = null;
2714
+ try {
2715
+ const raw2 = store?.getItem(key2);
2716
+ parsed = raw2 ? JSON.parse(raw2) : null;
2717
+ } catch {
2718
+ parsed = null;
2719
+ }
2720
+ const candidate = parsed ?? memoryIdentities.get(key2);
2721
+ if (valid(candidate, walletAddress, chainId, now)) {
2722
+ memoryIdentities.set(key2, candidate);
2723
+ return { userId: candidate.userId };
2724
+ }
2725
+ memoryIdentities.delete(key2);
2726
+ try {
2727
+ store?.removeItem(key2);
2728
+ } catch {
2729
+ }
2730
+ return null;
2731
+ }
2732
+ function writeYieldseekerIdentity(walletAddress, chainId, userId, now = Date.now()) {
2733
+ if (typeof window === "undefined") return;
2734
+ const identity = {
2735
+ userId,
2736
+ walletAddress,
2737
+ chainId,
2738
+ expiresAt: now + YIELDSEEKER_IDENTITY_TTL_MS
2739
+ };
2740
+ if (!valid(identity, walletAddress, chainId, now)) return;
2741
+ const key2 = keyFor(walletAddress, chainId);
2742
+ memoryIdentities.set(key2, identity);
2743
+ try {
2744
+ storage3()?.setItem(key2, JSON.stringify(identity));
2745
+ } catch {
2746
+ }
2747
+ }
2748
+ function clearYieldseekerIdentity(walletAddress, chainId) {
2749
+ const key2 = keyFor(walletAddress, chainId);
2750
+ memoryIdentities.delete(key2);
2751
+ try {
2752
+ storage3()?.removeItem(key2);
2753
+ } catch {
2754
+ }
2755
+ }
2756
+
2757
+ // src/agents/yieldseeker/yieldseeker.client.ts
2758
+ var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2759
+ function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
2760
+ return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
2761
+ }
2762
+ var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
2763
+ var YieldseekerApiError = class extends Error {
2764
+ constructor(status, providerCode, responseFields) {
2765
+ super(`Yieldseeker request failed (${status}): ${providerCode}`);
2766
+ this.status = status;
2767
+ this.providerCode = providerCode;
2768
+ this.responseFields = responseFields;
2769
+ this.name = "YieldseekerApiError";
2770
+ }
2771
+ status;
2772
+ providerCode;
2773
+ responseFields;
2774
+ get isAuthenticationError() {
2775
+ return this.status === 401 || this.status === 403;
2776
+ }
2777
+ };
2778
+ function providerError(body, fallback) {
2779
+ if (!body || typeof body !== "object") return { code: fallback };
2780
+ const record = body;
2781
+ return {
2782
+ code: typeof record.message === "string" ? record.message : fallback,
2783
+ fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
2784
+ };
2785
+ }
2786
+ var YieldseekerApiClient = class {
2787
+ constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch) {
2788
+ this.owneyApiKey = owneyApiKey;
2789
+ this.baseUrl = baseUrl;
2790
+ this.fetchFn = fetchFn;
2791
+ }
2792
+ owneyApiKey;
2793
+ baseUrl;
2794
+ fetchFn;
2795
+ async request(path, options = {}) {
2796
+ const controller = new AbortController();
2797
+ const timer = setTimeout(
2798
+ () => controller.abort(),
2799
+ options.timeoutMs ?? 15e3
2800
+ );
2801
+ try {
2802
+ const response = await this.fetchFn(`${this.baseUrl}${path}`, {
2803
+ method: options.method ?? "GET",
2804
+ headers: {
2805
+ "Content-Type": "application/json",
2806
+ "x-owney-api-key": this.owneyApiKey,
2807
+ ...options.signature ? { Authorization: `Signature ${options.signature}` } : {}
2808
+ },
2809
+ body: options.body ? JSON.stringify(options.body) : void 0,
2810
+ signal: controller.signal
2811
+ });
2812
+ const payload = await response.json().catch(() => null);
2813
+ if (!response.ok) {
2814
+ const error = providerError(payload, `HTTP_${response.status}`);
2815
+ throw new YieldseekerApiError(
2816
+ response.status,
2817
+ error.code,
2818
+ error.fields
2819
+ );
2820
+ }
2821
+ if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
2822
+ return payload.data;
2823
+ }
2824
+ return payload;
2825
+ } catch (error) {
2826
+ if (error instanceof YieldseekerApiError) throw error;
2827
+ if (error instanceof DOMException && error.name === "AbortError") {
2828
+ throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
2829
+ }
2830
+ throw new YieldseekerApiError(0, "NETWORK_ERROR", {
2831
+ cause: error instanceof Error ? error.message : String(error)
2832
+ });
2833
+ } finally {
2834
+ clearTimeout(timer);
2835
+ }
2836
+ }
2837
+ };
2838
+
2839
+ // src/agents/yieldseeker/yieldseeker.mapper.ts
2840
+ var import_viem5 = require("viem");
2841
+
2842
+ // src/lib/helpers/snapshot-apy.ts
2843
+ var DAY_MS = 864e5;
2844
+ function snapshotTime(date) {
2845
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
2846
+ const time = Date.parse(date);
2847
+ return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === date ? time : void 0;
2848
+ }
2849
+ function returnFactor(value) {
2850
+ if (typeof value !== "number" && typeof value !== "string") return void 0;
2851
+ if (typeof value === "string" && value.trim() === "") return void 0;
2852
+ const factor = Number(value);
2853
+ return Number.isFinite(factor) && factor >= 0 ? factor : void 0;
2854
+ }
2855
+ function calculateSnapshotApy(snapshots, lookbackDays, now = Date.now()) {
2856
+ if (!Number.isFinite(lookbackDays) || lookbackDays <= 0 || !Number.isFinite(now)) {
2857
+ return void 0;
2858
+ }
2859
+ const points = snapshots.flatMap((snapshot) => {
2860
+ const time = snapshotTime(snapshot.date);
2861
+ return time !== void 0 && time <= now ? [{ snapshot, time }] : [];
2862
+ }).sort((a, b) => a.time - b.time);
2863
+ const end = points.at(-1);
2864
+ if (!end) return void 0;
2865
+ const cutoff = end.time - lookbackDays * DAY_MS;
2866
+ const start = points.find((point) => point.time >= cutoff);
2867
+ const actualDays = (end.time - start.time) / DAY_MS;
2868
+ if (actualDays <= 0) return void 0;
2869
+ const startFactor = returnFactor(start.snapshot.cumulativeReturnFactor);
2870
+ const endFactor = returnFactor(end.snapshot.cumulativeReturnFactor);
2871
+ if (startFactor === void 0 || startFactor === 0 || endFactor === void 0) {
2872
+ return void 0;
2873
+ }
2874
+ const periodReturn = endFactor / startFactor - 1;
2875
+ const apy = Math.pow(1 + periodReturn, 365 / actualDays) - 1;
2876
+ return Number.isFinite(apy) ? apy : void 0;
2877
+ }
2878
+
2879
+ // src/agents/yieldseeker/yieldseeker.types.ts
2880
+ var YIELDSEEKER_ASSET_METADATA = {
2881
+ USDC: {
2882
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2883
+ decimals: 6
2884
+ },
2885
+ WETH: {
2886
+ address: "0x4200000000000000000000000000000000000006",
2887
+ decimals: 18
2888
+ }
2889
+ };
2890
+
2891
+ // src/agents/yieldseeker/yieldseeker.mapper.ts
2892
+ function invalid(endpoint, detail) {
2893
+ throw new OwneyError(
2894
+ "AGENT_INVALID_RESPONSE",
2895
+ `Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
2896
+ { endpoint, detail },
2897
+ "yieldseeker"
2898
+ );
2899
+ }
2900
+ function raw(value, endpoint) {
2901
+ if (typeof value !== "string" || !/^-?[0-9]+$/.test(value)) {
2902
+ return invalid(endpoint, "expected a base-10 integer string");
2903
+ }
2904
+ return BigInt(value);
2905
+ }
2906
+ function decimal(value, decimals, endpoint) {
2907
+ return (0, import_viem5.formatUnits)(raw(value, endpoint), decimals);
2908
+ }
2909
+ function usd(rawAmount, decimals, price) {
2910
+ return Number((0, import_viem5.formatUnits)(rawAmount, decimals)) * price;
2911
+ }
2912
+ function percent(value) {
2913
+ const result = Number(value);
2914
+ return Number.isFinite(result) ? result * 100 : 0;
2915
+ }
2916
+ var YIELDSEEKER_POSITIVE_YIELD_FEE_RATE = 0.1;
2917
+ function publicApyAfterYieldseekerFee(value) {
2918
+ const grossPercent = percent(value);
2919
+ if (grossPercent <= 0) return grossPercent;
2920
+ const netPercent = grossPercent * (1 - YIELDSEEKER_POSITIVE_YIELD_FEE_RATE);
2921
+ return Math.round(netPercent * 1e12) / 1e12;
2922
+ }
2923
+ function riskAdjustedApyForDays(option, days) {
2924
+ if (days === "7D") return option.riskAdjustedApy7dAverage;
2925
+ if (days === "30D") return option.riskAdjustedApy30dAverage;
2926
+ return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
2927
+ }
2928
+ function assetAddressValue(record, address) {
2929
+ const entry = Object.entries(record).find(
2930
+ ([key2]) => key2.toLowerCase() === address.toLowerCase()
2931
+ );
2932
+ return entry?.[1] ?? "0";
2933
+ }
2934
+ function position(value, asset, baseAssetDecimals) {
2935
+ const option = value?.yieldOption;
2936
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem5.isAddress)(option.address)) {
2937
+ return invalid("yield positions", "missing vault metadata");
2938
+ }
2939
+ return {
2940
+ chain: "BASE",
2941
+ protocol: option.provider,
2942
+ protocolId: option.address,
2943
+ pool: option.name,
2944
+ asset,
2945
+ // `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
2946
+ // differ from the underlying asset. Yieldseeker already converts it to
2947
+ // underlying base-asset units in `assetsBase`; pair that value with the
2948
+ // snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
2949
+ // share quantity separately because withdraw-from-position expects it.
2950
+ amount: decimal(
2951
+ value.assetsBase,
2952
+ baseAssetDecimals,
2953
+ "yield positions"
2954
+ ),
2955
+ amountRaw: String(value.assetsRaw),
2956
+ apy: percent(option.riskAdjustedApy),
2957
+ tvl: Number(option.totalDepositsUsd),
2958
+ liquidity: Number(option.withdrawableDepositsUsd)
2959
+ };
2960
+ }
2961
+ function mapYieldseekerBalances(contexts) {
2962
+ const tokens = [];
2963
+ const assetBalances = [];
2964
+ const positions = [];
2965
+ let totalUsd = 0;
2966
+ for (const context of contexts) {
2967
+ const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
2968
+ assetBalances.push({
2969
+ chain: "BASE",
2970
+ chainId: 8453,
2971
+ asset: context.asset,
2972
+ amount: decimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
2973
+ });
2974
+ const idle = assetAddressValue(
2975
+ context.snapshot.tokenBalances,
2976
+ metadata.address
2977
+ );
2978
+ tokens.push({
2979
+ chain: "BASE",
2980
+ chainId: 8453,
2981
+ asset: context.asset,
2982
+ amount: decimal(idle, metadata.decimals, "snapshot")
2983
+ });
2984
+ positions.push(
2985
+ ...context.positions.map(
2986
+ (entry) => position(
2987
+ entry,
2988
+ context.asset,
2989
+ context.snapshot.baseAssetDecimals
2990
+ )
2991
+ )
2992
+ );
2993
+ totalUsd += usd(
2994
+ raw(context.snapshot.totalValueBase, "snapshot"),
2995
+ context.snapshot.baseAssetDecimals,
2996
+ context.snapshot.baseAssetPriceUsd
2997
+ );
2998
+ }
2999
+ return {
3000
+ ...contexts.length === 1 ? { smartWallet: contexts[0].wallet.walletAddress } : {},
3001
+ totalBalance: String(totalUsd),
3002
+ totalBalanceAsset: "usdc",
3003
+ assetBalances,
3004
+ tokens,
3005
+ positions
3006
+ };
3007
+ }
3008
+ function mapYieldseekerEarnings(contexts) {
3009
+ const tokens = [];
3010
+ let lifetimeEarnings = 0;
3011
+ for (const context of contexts) {
3012
+ const amount = raw(context.snapshot.cumulativeYieldBase, "snapshot");
3013
+ tokens.push({
3014
+ chain: "BASE",
3015
+ chainId: 8453,
3016
+ asset: context.asset,
3017
+ amount: (0, import_viem5.formatUnits)(amount, context.snapshot.baseAssetDecimals)
3018
+ });
3019
+ lifetimeEarnings += usd(
3020
+ amount,
3021
+ context.snapshot.baseAssetDecimals,
3022
+ context.snapshot.baseAssetPriceUsd
3023
+ );
3024
+ }
3025
+ return {
3026
+ smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
3027
+ lifetimeEarnings,
3028
+ tokens
3029
+ };
3030
+ }
3031
+ function apyForDays(context, days, now) {
3032
+ if (days === "7D") return percent(context.snapshot.apy7d);
3033
+ if (days === "30D") return percent(context.snapshot.apy30d);
3034
+ const apy = calculateSnapshotApy(context.historic?.dailyYieldSnapshots ?? [], 14, now);
3035
+ const apyPercent = apy === void 0 ? void 0 : apy * 100;
3036
+ return apyPercent !== void 0 && Number.isFinite(apyPercent) ? apyPercent : void 0;
3037
+ }
3038
+ function dailyApy(point) {
3039
+ const total = raw(point.totalValueBase, "historic position");
3040
+ const earned = raw(point.dailyYieldBase, "historic position");
3041
+ const principal = total - earned;
3042
+ if (principal <= 0n || earned === 0n) return 0;
3043
+ return Number(earned) / Number(principal) * 365 * 100;
3044
+ }
3045
+ function aggregateHistory(contexts, dayCount, now) {
3046
+ const today = new Date(now).toISOString().slice(0, 10);
3047
+ const cutoff = new Date(Date.parse(today) - (dayCount - 1) * 864e5).toISOString().slice(0, 10);
3048
+ const assets = new Set(contexts.map((context) => context.asset.toUpperCase()));
3049
+ const unit = assets.size === 1 ? [...assets][0] : "USD";
3050
+ const byDate = /* @__PURE__ */ new Map();
3051
+ for (const context of contexts) {
3052
+ const points = context.historic?.dailyYieldSnapshots ?? [];
3053
+ for (const point of points) {
3054
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(point.date) || !Number.isFinite(Date.parse(point.date)) || point.date < cutoff || point.date > today) continue;
3055
+ const tokens = Number(decimal(point.totalValueBase, point.baseAssetDecimals, "historic position"));
3056
+ const amount = assets.size === 1 ? tokens : tokens * point.baseAssetPriceUsd;
3057
+ if (!Number.isFinite(amount) || amount < 0) {
3058
+ invalid("historic position", "expected a finite non-negative balance");
3059
+ }
3060
+ const current = byDate.get(point.date) ?? { weighted: 0, amount: 0 };
3061
+ current.weighted += dailyApy(point) * amount;
3062
+ current.amount += amount;
3063
+ byDate.set(point.date, current);
3064
+ }
3065
+ }
3066
+ return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
3067
+ date,
3068
+ apy: value.amount > 0 ? value.weighted / value.amount : 0,
3069
+ historicalBalance: { amount: value.amount, unit }
3070
+ }));
3071
+ }
3072
+ function mapYieldseekerApy(walletAddress, contexts, days, now = Date.now()) {
3073
+ let weighted = 0;
3074
+ let totalUsd = 0;
3075
+ const byAsset = {};
3076
+ for (const context of contexts) {
3077
+ const valueUsd = usd(
3078
+ raw(context.snapshot.totalValueBase, "snapshot"),
3079
+ context.snapshot.baseAssetDecimals,
3080
+ context.snapshot.baseAssetPriceUsd
3081
+ );
3082
+ const apy = apyForDays(context, days, now);
3083
+ if (apy === void 0) continue;
3084
+ weighted += apy * valueUsd;
3085
+ totalUsd += valueUsd;
3086
+ byAsset[context.asset] = apy;
3087
+ }
3088
+ const dayCount = Number(days.slice(0, -1));
3089
+ return {
3090
+ walletAddress,
3091
+ ...days !== "14D" || Object.keys(byAsset).length > 0 ? { weightedApyAfterFee: totalUsd > 0 ? weighted / totalUsd : 0 } : {},
3092
+ apyByChainAndAsset: { 8453: byAsset },
3093
+ history: aggregateHistory(contexts, dayCount, now)
3094
+ };
3095
+ }
3096
+ function actionType(value) {
3097
+ const normalized = value.toLowerCase();
3098
+ if (normalized.includes("deposit")) return "Deposit";
3099
+ if (normalized.includes("withdraw")) return "Withdraw";
3100
+ if (normalized.includes("yield") || normalized.includes("earn"))
3101
+ return "Earned";
3102
+ return "Rebalance";
3103
+ }
3104
+ function transactionHashes(details) {
3105
+ if (!details) return [];
3106
+ const values = [
3107
+ details.transactionHash,
3108
+ details.txHash,
3109
+ ...Array.isArray(details.transactionHashes) ? details.transactionHashes : [],
3110
+ ...Array.isArray(details.txHashes) ? details.txHashes : []
3111
+ ];
3112
+ return values.filter(
3113
+ (value) => typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value)
3114
+ ).filter((value, index, all) => all.indexOf(value) === index);
3115
+ }
3116
+ function actionEntry(action) {
3117
+ if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
3118
+ return {
3119
+ agent: "yieldseeker",
3120
+ action: actionType(action.actionType),
3121
+ date: action.createdDate,
3122
+ oldApy: null,
3123
+ newApy: null,
3124
+ transactions: [
3125
+ {
3126
+ txHashes: transactionHashes(action.details),
3127
+ chainId: 8453
3128
+ }
3129
+ ],
3130
+ rebalanceLog: []
3131
+ };
3132
+ }
3133
+ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses) {
3134
+ const from = movement.fromAddress.toLowerCase();
3135
+ const to = movement.toAddress.toLowerCase();
3136
+ const owner = ownerAddress.toLowerCase();
3137
+ const agentWallet = wallet.walletAddress.toLowerCase();
3138
+ const baseAsset = agent.assetAddress.toLowerCase();
3139
+ if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
3140
+ return void 0;
3141
+ }
3142
+ let action;
3143
+ if (from === owner && to === agentWallet) {
3144
+ action = "Top up";
3145
+ } else if (from === agentWallet && to === owner) {
3146
+ action = "Withdraw";
3147
+ } else if (from === agentWallet && vaultAddresses.has(to)) {
3148
+ action = "Deposit";
3149
+ }
3150
+ if (!action) return void 0;
3151
+ return {
3152
+ agent: "yieldseeker",
3153
+ action,
3154
+ date: movement.blockDate,
3155
+ oldApy: null,
3156
+ newApy: null,
3157
+ transactions: [
3158
+ {
3159
+ txHashes: [movement.transactionHash],
3160
+ chainId: agent.chainId,
3161
+ tokenSymbol: asset,
3162
+ amount: decimal(
3163
+ movement.assetAmount,
3164
+ YIELDSEEKER_ASSET_METADATA[asset].decimals,
3165
+ "historic position"
3166
+ )
3167
+ }
3168
+ ],
3169
+ rebalanceLog: []
3170
+ };
3171
+ }
3172
+ function mapYieldseekerHistory(contexts, options) {
3173
+ const entries = contexts.flatMap((context) => {
3174
+ const vaultAddresses = new Set(
3175
+ context.positions.map(
3176
+ (position2) => position2.yieldOption.address.toLowerCase()
3177
+ )
3178
+ );
3179
+ return [
3180
+ ...(context.historic?.movements ?? []).map(
3181
+ (movement) => movementEntry(
3182
+ movement,
3183
+ context.wallet,
3184
+ context.agent,
3185
+ context.asset,
3186
+ options.ownerAddress,
3187
+ vaultAddresses
3188
+ )
3189
+ ),
3190
+ ...(context.actions ?? []).map(actionEntry)
3191
+ ].filter((entry) => entry !== void 0);
3192
+ });
3193
+ const filtered = entries.filter(
3194
+ (entry) => (!options.fromDate || entry.date >= options.fromDate) && (!options.toDate || entry.date <= options.toDate)
3195
+ ).filter((entry, index, all) => {
3196
+ const transactionHash = entry.transactions[0]?.txHashes[0];
3197
+ if (!transactionHash) return true;
3198
+ return all.findIndex(
3199
+ (candidate) => candidate.action === entry.action && candidate.transactions[0]?.txHashes[0] === transactionHash
3200
+ ) === index;
3201
+ }).sort((left, right) => right.date.localeCompare(left.date));
3202
+ return {
3203
+ data: filtered.slice(0, options.limit),
3204
+ // v1 returns the whole action/movement collection and defines no cursor.
3205
+ // Report a terminal page so callers never loop over the same prefix.
3206
+ hasMore: false
3207
+ };
3208
+ }
3209
+ function mapYieldseekerProfile(address, contexts) {
3210
+ const protocols = /* @__PURE__ */ new Set();
3211
+ for (const context of contexts) {
3212
+ for (const current of context.positions) {
3213
+ if (current.yieldOption?.provider) {
3214
+ protocols.add(String(current.yieldOption.provider));
3215
+ }
3216
+ }
3217
+ }
3218
+ return {
3219
+ address,
3220
+ smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
3221
+ chains: contexts.length > 0 ? [8453] : [],
3222
+ hasActiveSessionKey: contexts.some(
3223
+ (context) => context.wallet.initializedDate != null
3224
+ ),
3225
+ protocols: [...protocols]
3226
+ };
3227
+ }
3228
+ function mapYieldseekerAgentApy(options, days) {
3229
+ const perAsset = {};
3230
+ const all = [];
3231
+ for (const entry of options) {
3232
+ const apys = entry.yieldOptions.map(
3233
+ (option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
3234
+ ).filter(Number.isFinite);
3235
+ if (apys.length === 0) continue;
3236
+ const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
3237
+ perAsset[entry.asset] = average;
3238
+ all.push(average);
3239
+ }
3240
+ return {
3241
+ averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
3242
+ detailedApys: { apyPerAsset: { 8453: perAsset } }
3243
+ };
3244
+ }
3245
+
3246
+ // src/agents/yieldseeker/yieldseeker.agent.ts
3247
+ var OWNEY_AGENT_NAME = "owney";
3248
+ var YIELDSEEKER_USERNAME_PREFIX = "owney_";
3249
+ var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
3250
+ var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
3251
+ function generateYieldseekerUsername() {
3252
+ const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
3253
+ return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
3254
+ }
3255
+ function isUsernameConflict(error) {
3256
+ if (!(error instanceof YieldseekerApiError)) return false;
3257
+ const code = error.providerCode.toUpperCase();
3258
+ return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
3259
+ }
3260
+ var YIELDSEEKER_AGENT_WALLET_ABI = [
3261
+ {
3262
+ type: "function",
3263
+ name: "withdrawAssetToUser",
3264
+ stateMutability: "nonpayable",
3265
+ inputs: [
3266
+ { name: "recipient", type: "address" },
3267
+ { name: "asset", type: "address" },
3268
+ { name: "amount", type: "uint256" }
3269
+ ],
3270
+ outputs: []
3271
+ },
3272
+ {
3273
+ type: "function",
3274
+ name: "withdrawAllAssetToUser",
3275
+ stateMutability: "nonpayable",
3276
+ inputs: [
3277
+ { name: "recipient", type: "address" },
3278
+ { name: "asset", type: "address" }
3279
+ ],
3280
+ outputs: []
3281
+ }
3282
+ ];
3283
+ function query(params) {
3284
+ const search = new URLSearchParams();
3285
+ for (const [key2, value] of Object.entries(params)) {
3286
+ if (value !== void 0) search.set(key2, String(value));
3287
+ }
3288
+ const encoded = search.toString();
3289
+ return encoded ? `?${encoded}` : "";
3290
+ }
3291
+ var YieldseekerAgent = class {
3292
+ id = "yieldseeker";
3293
+ balanceComposition = "tokens-plus-positions";
3294
+ supportedChainIds = [8453];
3295
+ supportedAssets = [
3296
+ {
3297
+ chainId: 8453,
3298
+ chain: "BASE",
3299
+ assets: [
3300
+ { symbol: "USDC", minDepositAmount: "10000000" },
3301
+ { symbol: "WETH", minDepositAmount: "1" }
3302
+ ]
3303
+ }
3304
+ ];
3305
+ api;
3306
+ auth;
3307
+ transactionExecutor;
3308
+ unwindReceiptWaiter;
3309
+ agentContexts = /* @__PURE__ */ new Map();
3310
+ users = /* @__PURE__ */ new Map();
3311
+ pendingAgents = /* @__PURE__ */ new Map();
3312
+ constructor(owneyApiKey, options = {}) {
3313
+ this.api = new YieldseekerApiClient(
3314
+ owneyApiKey,
3315
+ options.baseUrl ?? getYieldseekerProxyBaseUrl(),
3316
+ options.fetchFn
3317
+ );
3318
+ this.auth = new YieldseekerAuth(options.auth);
3319
+ this.transactionExecutor = options.transactionExecutor;
3320
+ this.unwindReceiptWaiter = options.unwindReceiptWaiter;
3321
+ }
3322
+ async disconnect() {
3323
+ this.auth.clear();
3324
+ for (const key2 of this.users.keys()) {
3325
+ const [walletAddress, chainId] = key2.split(":");
3326
+ clearYieldseekerIdentity(walletAddress, Number(chainId));
3327
+ }
3328
+ this.users.clear();
3329
+ this.agentContexts.clear();
3330
+ this.pendingAgents.clear();
3331
+ }
3332
+ async activateAgent(state, chainId, asset) {
3333
+ this.assertChain(chainId);
3334
+ const targetAsset = asset ?? "USDC";
3335
+ this.assertAsset(targetAsset);
3336
+ await this.ensureAgent(state, chainId, targetAsset);
3337
+ }
3338
+ async deposit(state, chainId, amount, asset, depositCallback) {
3339
+ this.assertChain(chainId);
3340
+ this.assertAsset(asset);
3341
+ if (BigInt(amount) <= 0n) {
3342
+ throw new OwneyError(
3343
+ "DEPOSIT_AMOUNT_BELOW_MINIMUM",
3344
+ "Yieldseeker deposits must be greater than zero.",
3345
+ { amount, minDepositAmount: "1" },
3346
+ this.id
3347
+ );
3348
+ }
3349
+ const context = await this.ensureAgent(state, chainId, asset);
3350
+ let txHash;
3351
+ try {
3352
+ if (depositCallback) {
3353
+ provideDepositVerificationContext(depositCallback, {
3354
+ agentId: "yieldseeker",
3355
+ signature: await this.auth.getToken(state, chainId),
3356
+ userId: context.user.userId,
3357
+ yieldseekerAgentId: context.agent.agentId
3358
+ });
3359
+ txHash = await depositCallback(
3360
+ context.wallet.walletAddress,
3361
+ chainId,
3362
+ amount
3363
+ );
3364
+ await this.waitForReceipt(state, chainId, txHash);
3365
+ } else {
3366
+ txHash = await this.submitTransaction(state, chainId, {
3367
+ from: (0, import_viem6.getAddress)(state.walletAddress),
3368
+ to: YIELDSEEKER_ASSET_METADATA[asset].address,
3369
+ data: (0, import_viem6.encodeFunctionData)({
3370
+ abi: import_viem6.erc20Abi,
3371
+ functionName: "transfer",
3372
+ args: [(0, import_viem6.getAddress)(context.wallet.walletAddress), BigInt(amount)]
3373
+ }),
3374
+ value: "0",
3375
+ chainId
3376
+ });
3377
+ }
3378
+ await this.deployAfterFunding(state, chainId, context);
3379
+ } finally {
3380
+ await this.refreshSnapshotAfterMovement(
3381
+ state,
3382
+ chainId,
3383
+ context,
3384
+ "deposit"
3385
+ );
3386
+ }
3387
+ return {
3388
+ txHash,
3389
+ smartWallet: context.wallet.walletAddress,
3390
+ amount
3391
+ };
3392
+ }
3393
+ async withdraw(state, chainId, asset, amount) {
3394
+ this.assertChain(chainId);
3395
+ this.assertAsset(asset);
3396
+ if (amount !== void 0 && BigInt(amount) <= 0n) {
3397
+ throw new OwneyError(
3398
+ "WITHDRAW_FAILED",
3399
+ "Yieldseeker withdrawals must be greater than zero.",
3400
+ { amount },
3401
+ this.id
3402
+ );
3403
+ }
3404
+ const context = await this.findAgent(state, chainId, asset);
3405
+ if (!context) {
3406
+ throw new OwneyError(
3407
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3408
+ `No Yieldseeker ${asset} agent exists for this wallet.`,
3409
+ { asset, available: "0" },
3410
+ this.id
3411
+ );
3412
+ }
3413
+ try {
3414
+ const portfolio = await this.loadPortfolioContext(
3415
+ state,
3416
+ chainId,
3417
+ context
3418
+ );
3419
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3420
+ const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
3421
+ ([address]) => address.toLowerCase() === metadata.address.toLowerCase()
3422
+ );
3423
+ const idle = BigInt(idleEntry?.[1] ?? "0");
3424
+ const deployed = portfolio.positions.reduce(
3425
+ (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
3426
+ 0n
3427
+ );
3428
+ const totalAvailable = idle + deployed;
3429
+ const requested = amount === void 0 ? totalAvailable : BigInt(amount);
3430
+ if (requested > totalAvailable) {
3431
+ throw new OwneyError(
3432
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3433
+ `Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
3434
+ {
3435
+ asset,
3436
+ requested: requested.toString(),
3437
+ available: totalAvailable.toString()
3438
+ },
3439
+ this.id
3440
+ );
3441
+ }
3442
+ let remaining = requested > idle ? requested - idle : 0n;
3443
+ for (const position2 of portfolio.positions) {
3444
+ if (remaining === 0n) break;
3445
+ const available = BigInt(position2.withdrawableAssetsRaw);
3446
+ if (available <= 0n) continue;
3447
+ const assetsRaw = available < remaining ? available : remaining;
3448
+ const response = await this.walletRequest(
3449
+ state,
3450
+ chainId,
3451
+ this.agentPath(context, "withdraw-from-position"),
3452
+ {
3453
+ method: "POST",
3454
+ body: {
3455
+ chainId,
3456
+ vaultAddress: position2.yieldOption.address,
3457
+ assetsRaw: assetsRaw.toString()
3458
+ }
3459
+ }
3460
+ );
3461
+ if (!this.isTransactionHash(response?.transactionHash)) {
3462
+ throw this.invalidResponse("position withdrawal");
3463
+ }
3464
+ await this.waitForReceipt(state, chainId, response.transactionHash);
3465
+ remaining -= assetsRaw;
3466
+ }
3467
+ if (remaining > 0n) {
3468
+ throw this.invalidResponse("yield positions", {
3469
+ reason: "Withdrawable positions could not cover the request.",
3470
+ remaining: remaining.toString()
3471
+ });
3472
+ }
3473
+ const account = (0, import_viem6.getAddress)(state.walletAddress);
3474
+ const txHash = await this.submitTransaction(state, chainId, {
3475
+ from: account,
3476
+ to: (0, import_viem6.getAddress)(context.wallet.walletAddress),
3477
+ data: amount === void 0 ? (0, import_viem6.encodeFunctionData)({
3478
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
3479
+ functionName: "withdrawAllAssetToUser",
3480
+ args: [account, metadata.address]
3481
+ }) : (0, import_viem6.encodeFunctionData)({
3482
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
3483
+ functionName: "withdrawAssetToUser",
3484
+ args: [account, metadata.address, requested]
3485
+ }),
3486
+ value: "0",
3487
+ chainId
3488
+ });
3489
+ return {
3490
+ txHash,
3491
+ type: amount === void 0 ? "full" : "partial",
3492
+ amount: requested.toString()
3493
+ };
3494
+ } finally {
3495
+ await this.refreshSnapshotAfterMovement(
3496
+ state,
3497
+ chainId,
3498
+ context,
3499
+ "withdrawal"
3500
+ );
3501
+ }
3502
+ }
3503
+ async getBalances(state, chainId) {
3504
+ this.assertChain(chainId);
3505
+ return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
3506
+ }
3507
+ async getEarnings(state, chainId) {
3508
+ this.assertChain(chainId);
3509
+ return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
3510
+ }
3511
+ async getAccountApy(state, chainId, days, tokenSymbol) {
3512
+ this.assertChain(chainId);
3513
+ const asset = tokenSymbol?.toUpperCase();
3514
+ if (asset !== void 0) this.assertAsset(asset);
3515
+ const contexts = await this.loadPortfolio(state, chainId, {
3516
+ ...asset ? { asset } : {},
3517
+ historic: true
3518
+ });
3519
+ return mapYieldseekerApy(state.walletAddress, contexts, days);
3520
+ }
3521
+ async getHistory(state, chainId, options) {
3522
+ this.assertChain(chainId);
3523
+ const asset = options?.tokenSymbol?.toUpperCase();
3524
+ if (asset !== void 0) this.assertAsset(asset);
3525
+ const contexts = await this.loadPortfolio(state, chainId, {
3526
+ ...asset ? { asset } : {},
3527
+ historic: true,
3528
+ actions: true
3529
+ });
3530
+ return mapYieldseekerHistory(contexts, {
3531
+ limit: options?.limit ?? 10,
3532
+ ownerAddress: state.walletAddress,
3533
+ ...options?.fromDate ? { fromDate: options.fromDate } : {},
3534
+ ...options?.toDate ? { toDate: options.toDate } : {}
3535
+ });
3536
+ }
3537
+ async getUserProfile(state, chainId) {
3538
+ this.assertChain(chainId);
3539
+ return mapYieldseekerProfile(
3540
+ state.walletAddress,
3541
+ await this.loadPortfolio(state, chainId, {})
3542
+ );
3543
+ }
3544
+ async getAgentApy(days, options) {
3545
+ this.assertOptionalChain(options?.chainId);
3546
+ const requested = options?.tokenSymbol?.toUpperCase();
3547
+ if (requested !== void 0) this.assertAsset(requested);
3548
+ const assets = requested ? [requested] : ["USDC", "WETH"];
3549
+ const values = await Promise.all(
3550
+ assets.map(async (asset) => {
3551
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3552
+ const response = await this.api.request(
3553
+ `/chains/8453/assets/${metadata.address}/yield-options`
3554
+ );
3555
+ if (!Array.isArray(response?.yieldOptions)) {
3556
+ throw this.invalidResponse("yield options");
3557
+ }
3558
+ return { asset, yieldOptions: response.yieldOptions };
3559
+ })
3560
+ );
3561
+ return mapYieldseekerAgentApy(values, days);
3562
+ }
3563
+ userKey(state, chainId) {
3564
+ return `${state.walletAddress.toLowerCase()}:${chainId}`;
3565
+ }
3566
+ contextKey(state, chainId, asset) {
3567
+ return `${this.userKey(state, chainId)}:${asset}`;
3568
+ }
3569
+ async resolveUser(state, chainId) {
3570
+ const key2 = this.userKey(state, chainId);
3571
+ const inMemory = this.users.get(key2);
3572
+ if (inMemory) return inMemory;
3573
+ const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
3574
+ if (persisted) {
3575
+ this.users.set(key2, persisted);
3576
+ return persisted;
3577
+ }
3578
+ const walletAddress = (0, import_viem6.getAddress)(state.walletAddress);
3579
+ let user = null;
3580
+ try {
3581
+ const login = await this.providerRequest(
3582
+ state,
3583
+ chainId,
3584
+ "/users/login-with-wallet",
3585
+ { method: "POST", body: { walletAddress } }
3586
+ );
3587
+ user = login?.user ?? null;
3588
+ if (!user) {
3589
+ throw this.invalidResponse("wallet login", {
3590
+ reason: "A successful login returned no user."
3591
+ });
3592
+ }
3593
+ } catch (error) {
3594
+ if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
3595
+ if (error instanceof OwneyError) throw error;
3596
+ throw this.mapApiError(error);
3597
+ }
3598
+ let created;
3599
+ for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
3600
+ try {
3601
+ created = await this.providerRequest(
3602
+ state,
3603
+ chainId,
3604
+ "/users",
3605
+ {
3606
+ method: "POST",
3607
+ body: {
3608
+ walletAddress,
3609
+ username: generateYieldseekerUsername()
3610
+ }
3611
+ }
3612
+ );
3613
+ break;
3614
+ } catch (createError) {
3615
+ const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
3616
+ if (canRetry) continue;
3617
+ throw this.mapApiError(createError);
3618
+ }
3619
+ }
3620
+ user = created?.user ?? null;
3621
+ }
3622
+ if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
3623
+ throw this.invalidResponse("wallet identity");
3624
+ }
3625
+ const resolved = { userId: user.userId };
3626
+ this.users.set(key2, resolved);
3627
+ writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
3628
+ return resolved;
3629
+ }
3630
+ forgetUser(state, chainId) {
3631
+ this.users.delete(this.userKey(state, chainId));
3632
+ clearYieldseekerIdentity(state.walletAddress, chainId);
3633
+ }
3634
+ async ensureAgent(state, chainId, asset) {
3635
+ const key2 = this.contextKey(state, chainId, asset);
3636
+ const cached = this.agentContexts.get(key2);
3637
+ if (cached) return cached;
3638
+ const pending = this.pendingAgents.get(key2);
3639
+ if (pending) return pending;
3640
+ const request = this.resolveAgent(state, chainId, asset, true).then(
3641
+ (context) => {
3642
+ if (!context) throw this.invalidResponse("agent creation");
3643
+ this.agentContexts.set(key2, context);
3644
+ return context;
3645
+ }
3646
+ );
3647
+ this.pendingAgents.set(key2, request);
3648
+ try {
3649
+ return await request;
3650
+ } finally {
3651
+ this.pendingAgents.delete(key2);
3652
+ }
3653
+ }
3654
+ async findAgent(state, chainId, asset) {
3655
+ const key2 = this.contextKey(state, chainId, asset);
3656
+ const cached = this.agentContexts.get(key2);
3657
+ if (cached) return cached;
3658
+ const context = await this.resolveAgent(state, chainId, asset, false);
3659
+ if (context) this.agentContexts.set(key2, context);
3660
+ return context;
3661
+ }
3662
+ async resolveAgent(state, chainId, asset, createIfMissing) {
3663
+ const user = await this.resolveUser(state, chainId);
3664
+ const response = await this.walletRequest(
3665
+ state,
3666
+ chainId,
3667
+ `/users/${user.userId}/agents`
3668
+ );
3669
+ if (!Array.isArray(response?.agents)) {
3670
+ throw this.invalidResponse("agent list");
3671
+ }
3672
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3673
+ let agent = response.agents.find(
3674
+ (candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
3675
+ );
3676
+ if (!agent && createIfMissing) {
3677
+ const created = await this.walletRequest(
3678
+ state,
3679
+ chainId,
3680
+ `/users/${user.userId}/agents`,
3681
+ {
3682
+ method: "POST",
3683
+ body: {
3684
+ name: OWNEY_AGENT_NAME,
3685
+ emoji: "\u{1F989}",
3686
+ chainId,
3687
+ assetAddress: metadata.address,
3688
+ type: "vault",
3689
+ rulePreset: null
3690
+ }
3691
+ }
3692
+ );
3693
+ agent = created?.agent;
3694
+ }
3695
+ if (!agent) return null;
3696
+ this.assertAgent(agent);
3697
+ const walletResponse = await this.walletRequest(
3698
+ state,
3699
+ chainId,
3700
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3701
+ );
3702
+ if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
3703
+ throw this.invalidResponse("agent wallet");
3704
+ }
3705
+ return { user, agent, wallet: walletResponse.agentWallet, asset };
3706
+ }
3707
+ async loadPortfolio(state, chainId, options) {
3708
+ const user = await this.resolveUser(state, chainId);
3709
+ const response = await this.walletRequest(
3710
+ state,
3711
+ chainId,
3712
+ `/users/${user.userId}/agents`
3713
+ );
3714
+ if (!Array.isArray(response?.agents)) {
3715
+ throw this.invalidResponse("agent list");
3716
+ }
3717
+ const contexts = [];
3718
+ for (const agent of response.agents) {
3719
+ const asset = this.assetForAgent(agent);
3720
+ if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
3721
+ continue;
3722
+ }
3723
+ this.assertAgent(agent);
3724
+ const walletResponse = await this.walletRequest(
3725
+ state,
3726
+ chainId,
3727
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3728
+ );
3729
+ if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
3730
+ throw this.invalidResponse("agent wallet");
3731
+ }
3732
+ const context = {
3733
+ user,
3734
+ agent,
3735
+ wallet: walletResponse.agentWallet,
3736
+ asset
3737
+ };
3738
+ this.agentContexts.set(this.contextKey(state, chainId, asset), context);
3739
+ contexts.push(context);
3740
+ }
3741
+ return Promise.all(
3742
+ contexts.map(
3743
+ (context) => this.loadPortfolioContext(state, chainId, context, options)
3744
+ )
3745
+ );
3746
+ }
3747
+ async loadPortfolioContext(state, chainId, context, options = {}) {
3748
+ const [snapshot, positions, historic, actions] = await Promise.all([
3749
+ this.walletRequest(
3750
+ state,
3751
+ chainId,
3752
+ `${this.agentPath(context, "snapshot")}${query({
3753
+ shouldOnlyUseRecentValue: true,
3754
+ shouldAllowStaleOnError: true
3755
+ })}`
3756
+ ),
3757
+ this.walletRequest(
3758
+ state,
3759
+ chainId,
3760
+ this.agentPath(context, "yield-positions")
3761
+ ),
3762
+ options.historic ? this.walletRequest(
3763
+ state,
3764
+ chainId,
3765
+ this.agentPath(context, "wallet/historic-position")
3766
+ ) : Promise.resolve(void 0),
3767
+ options.actions ? this.walletRequest(
3768
+ state,
3769
+ chainId,
3770
+ this.agentPath(context, "actions")
3771
+ ) : Promise.resolve(void 0)
3772
+ ]);
3773
+ if (!snapshot?.agentSnapshot) {
3774
+ throw this.invalidResponse("agent snapshot");
3775
+ }
3776
+ if (!Array.isArray(positions?.yieldPositions)) {
3777
+ throw this.invalidResponse("yield positions");
3778
+ }
3779
+ return {
3780
+ ...context,
3781
+ snapshot: snapshot.agentSnapshot,
3782
+ positions: positions.yieldPositions,
3783
+ ...historic?.position ? { historic: historic.position } : {},
3784
+ ...actions?.actions ? { actions: actions.actions } : {}
3785
+ };
3786
+ }
3787
+ async deployAfterFunding(state, chainId, context) {
3788
+ if (context.wallet.initializedDate != null) return;
3789
+ try {
3790
+ const deployed = await this.walletRequest(
3791
+ state,
3792
+ chainId,
3793
+ this.agentPath(context, "deploy"),
3794
+ { method: "POST", body: {} }
3795
+ );
3796
+ if (deployed?.agentWallet) {
3797
+ context.wallet = deployed.agentWallet;
3798
+ }
3799
+ } catch (error) {
3800
+ console.warn(
3801
+ "[owney-sdk] Yieldseeker deposit is funded but not deployable yet:",
3802
+ error
3803
+ );
3804
+ }
3805
+ }
3806
+ async refreshSnapshotAfterMovement(state, chainId, context, movement) {
3807
+ try {
3808
+ const response = await this.walletRequest(
3809
+ state,
3810
+ chainId,
3811
+ `${this.agentPath(context, "snapshot")}${query({
3812
+ shouldForceRefresh: true
3813
+ })}`
3814
+ );
3815
+ if (!response?.agentSnapshot) {
3816
+ throw this.invalidResponse("agent snapshot refresh");
3817
+ }
3818
+ } catch (error) {
3819
+ console.warn(
3820
+ `[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
3821
+ error
3822
+ );
3823
+ }
3824
+ }
3825
+ agentPath(context, suffix) {
3826
+ return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
3827
+ }
3828
+ async walletRequest(state, chainId, path, options = {}) {
3829
+ try {
3830
+ return await this.providerRequest(state, chainId, path, options);
3831
+ } catch (error) {
3832
+ throw this.mapApiError(error);
3833
+ }
3834
+ }
3835
+ async providerRequest(state, chainId, path, options = {}) {
3836
+ this.assertChain(chainId);
3837
+ const request = (signature2) => this.api.request(path, {
3838
+ ...options,
3839
+ signature: signature2
3840
+ });
3841
+ let signature = await this.auth.getToken(state, chainId);
3842
+ try {
3843
+ return await request(signature);
3844
+ } catch (error) {
3845
+ if (!(error instanceof YieldseekerApiError)) throw error;
3846
+ if (error.providerCode === "NO_USER") throw error;
3847
+ if (!error.isAuthenticationError) throw error;
3848
+ this.auth.clear(state, chainId);
3849
+ signature = await this.auth.getToken(state, chainId);
3850
+ try {
3851
+ return await request(signature);
3852
+ } catch (retryError) {
3853
+ if (retryError instanceof YieldseekerApiError && retryError.isAuthenticationError) {
3854
+ this.forgetUser(state, chainId);
3855
+ }
3856
+ throw retryError;
3857
+ }
3858
+ }
3859
+ }
3860
+ mapApiError(error) {
3861
+ if (!(error instanceof YieldseekerApiError)) {
3862
+ return new OwneyError(
3863
+ "AGENT_API_ERROR",
3864
+ "Yieldseeker request failed.",
3865
+ { cause: error instanceof Error ? error.message : String(error) },
3866
+ this.id
3867
+ );
3868
+ }
3869
+ const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
3870
+ return new OwneyError(
3871
+ code,
3872
+ `Yieldseeker request failed: ${error.providerCode}.`,
3873
+ {
3874
+ statusCode: error.status,
3875
+ providerCode: error.providerCode,
3876
+ ...error.responseFields ? { fields: error.responseFields } : {}
3877
+ },
3878
+ this.id
3879
+ );
3880
+ }
3881
+ async submitTransaction(state, chainId, transaction) {
3882
+ if (this.transactionExecutor) {
3883
+ return this.transactionExecutor(state, chainId, transaction);
3884
+ }
3885
+ this.assertTransaction(transaction, state, chainId);
3886
+ const account = (0, import_viem6.getAddress)(state.walletAddress);
3887
+ const walletClient = (0, import_viem6.createWalletClient)({
3888
+ account,
3889
+ chain: import_chains3.base,
3890
+ transport: (0, import_viem6.custom)(state.provider)
3891
+ });
3892
+ const publicClient = (0, import_viem6.createPublicClient)({
3893
+ chain: import_chains3.base,
3894
+ transport: (0, import_viem6.custom)(state.provider)
3895
+ });
3896
+ await ensureWalletOnChain(
3897
+ publicClient,
3898
+ walletClient,
3899
+ 8453
3900
+ );
3901
+ const hash = await walletClient.sendTransaction({
3902
+ account,
3903
+ chain: import_chains3.base,
3904
+ to: (0, import_viem6.getAddress)(transaction.to),
3905
+ data: transaction.data,
3906
+ value: BigInt(transaction.value)
3907
+ });
3908
+ const receipt = await publicClient.waitForTransactionReceipt({
3909
+ hash,
3910
+ confirmations: 1
3911
+ });
3912
+ if (receipt.status !== "success") {
3913
+ throw new OwneyError(
3914
+ "AGENT_TRANSACTION_REVERTED",
3915
+ `Yieldseeker transaction reverted (${hash}).`,
3916
+ { transactionHash: hash },
3917
+ this.id
3918
+ );
3919
+ }
3920
+ return hash;
3921
+ }
3922
+ async waitForReceipt(state, chainId, transactionHash) {
3923
+ if (this.unwindReceiptWaiter) {
3924
+ await this.unwindReceiptWaiter(state, chainId, transactionHash);
3925
+ return;
3926
+ }
3927
+ const publicClient = (0, import_viem6.createPublicClient)({
3928
+ chain: import_chains3.base,
3929
+ transport: (0, import_viem6.custom)(state.provider)
3930
+ });
3931
+ const receipt = await publicClient.waitForTransactionReceipt({
3932
+ hash: transactionHash,
3933
+ confirmations: 1
3934
+ });
3935
+ if (receipt.status !== "success") {
3936
+ throw new OwneyError(
3937
+ "AGENT_TRANSACTION_REVERTED",
3938
+ `Yieldseeker transaction reverted (${transactionHash}).`,
3939
+ { transactionHash },
3940
+ this.id
3941
+ );
3942
+ }
3943
+ }
3944
+ assertTransaction(transaction, state, chainId) {
3945
+ if (!transaction || typeof transaction.from !== "string" || !(0, import_viem6.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem6.isAddress)(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || (0, import_viem6.getAddress)(transaction.from) !== (0, import_viem6.getAddress)(state.walletAddress)) {
3946
+ throw this.invalidResponse("transaction");
3947
+ }
3948
+ }
3949
+ assertAgent(agent) {
3950
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem6.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
3951
+ throw this.invalidResponse("agent");
3952
+ }
3953
+ }
3954
+ isOwneyAgent(agent) {
3955
+ return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
3956
+ }
3957
+ assetForAgent(agent) {
3958
+ for (const asset of ["USDC", "WETH"]) {
3959
+ if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
3960
+ return asset;
3961
+ }
3962
+ }
3963
+ return null;
3964
+ }
3965
+ isTransactionHash(value) {
3966
+ return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
3967
+ }
3968
+ assertChain(chainId) {
3969
+ if (chainId !== 8453) {
3970
+ throw new OwneyError(
3971
+ "CHAIN_UNSUPPORTED",
3972
+ `Yieldseeker does not support chain ${chainId}.`,
3973
+ { chainId, supportedChainIds: [8453] },
3974
+ this.id
3975
+ );
3976
+ }
3977
+ }
3978
+ assertOptionalChain(chainId) {
3979
+ if (chainId !== void 0) this.assertChain(chainId);
3980
+ }
3981
+ assertAsset(asset) {
3982
+ if (asset !== "USDC" && asset !== "WETH") {
3983
+ throw new OwneyError(
3984
+ "ASSET_UNSUPPORTED",
3985
+ `Yieldseeker does not support asset ${asset} in the Owney rollout.`,
3986
+ {
3987
+ asset,
3988
+ supportedAssets: ["USDC", "WETH"],
3989
+ providerAlsoAdvertises: ["cbBTC"]
3990
+ },
3991
+ this.id
3992
+ );
3993
+ }
3994
+ }
3995
+ invalidResponse(operation, details = {}) {
3996
+ return new OwneyError(
3997
+ "AGENT_INVALID_RESPONSE",
3998
+ `Yieldseeker returned an invalid ${operation} response.`,
3999
+ details,
4000
+ this.id
4001
+ );
4002
+ }
4003
+ };
4004
+
4005
+ // src/lib/routing-api.ts
4006
+ var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
4007
+ async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
4008
+ const url = `${baseUrl}/api/v1/agent/org-config`;
4009
+ try {
4010
+ const res = await fetch(url, {
4011
+ method: "GET",
4012
+ headers: {
4013
+ "Content-Type": "application/json",
4014
+ "x-owney-api-key": `${apiKey}`
4015
+ }
4016
+ });
4017
+ if (!res.ok) {
4018
+ if (res.status !== 404) {
4019
+ console.warn(
4020
+ `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
4021
+ );
4022
+ }
4023
+ return null;
4024
+ }
4025
+ const json = await res.json();
4026
+ const policy = json.success ? json.data ?? null : null;
4027
+ debugLog(
4028
+ "owney-sdk",
4029
+ policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
4030
+ policy ?? void 0
4031
+ );
4032
+ return policy;
4033
+ } catch (error) {
4034
+ console.warn(
4035
+ "[owney-sdk] Could not read org agent config (non-fatal):",
4036
+ error instanceof Error ? error.message : String(error)
4037
+ );
4038
+ return null;
4039
+ }
4040
+ }
4041
+ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
4042
+ const url = `${baseUrl}/api/v1/agent/keys`;
4043
+ const res = await fetch(url, {
4044
+ method: "GET",
4045
+ headers: {
4046
+ "Content-Type": "application/json",
4047
+ "x-owney-api-key": `${apiKey}`
4048
+ }
4049
+ });
4050
+ if (!res.ok) {
4051
+ const text = await res.text().catch(() => "");
4052
+ throw new OwneyError(
4053
+ "API_ROUTING_ERROR",
4054
+ `Routing API error ${res.status}: ${text}`,
4055
+ { statusCode: res.status, responseBody: text }
4056
+ );
4057
+ }
4058
+ const json = await res.json();
4059
+ if (!json.success) {
4060
+ throw new OwneyError(
4061
+ "API_ROUTING_FAILED",
4062
+ `Routing API request failed: ${json.message}`,
4063
+ { message: json.message }
4064
+ );
4065
+ }
4066
+ return json.data;
4067
+ }
4068
+
4069
+ // src/lib/health-report.ts
4070
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
4071
+ async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
4072
+ try {
4073
+ await fetch(`${baseUrl}/api/v1/agent/health-report`, {
4074
+ method: "POST",
4075
+ headers: {
4076
+ "Content-Type": "application/json",
4077
+ "x-owney-api-key": apiKey
4078
+ },
4079
+ body: JSON.stringify({
4080
+ agent_type: agentType,
4081
+ error_code: errorCode,
4082
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4083
+ })
4084
+ });
4085
+ } catch (err) {
4086
+ console.warn(
4087
+ `[owney-sdk] health-report failed for agent "${agentType}":`,
4088
+ err instanceof Error ? err.message : err
4089
+ );
4090
+ }
4091
+ }
4092
+ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
4093
+ try {
4094
+ return await fn();
4095
+ } catch (err) {
4096
+ const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
4097
+ void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
4098
+ throw err;
4099
+ }
4100
+ }
4101
+
4102
+ // src/lib/helpers/withdraw-helper.ts
4103
+ var import_viem7 = require("viem");
4104
+ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
4105
+ const target = asset.toUpperCase();
4106
+ return agents.map((agent) => {
4107
+ const agentBalance = aggregated[agent.id];
4108
+ const tokenBalance = agentBalance?.tokens.find(
4109
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
4110
+ );
4111
+ let balance = tokenBalance ? (0, import_viem7.parseUnits)(tokenBalance.amount, decimals) : 0n;
4112
+ if (agent.balanceComposition === "tokens-plus-positions") {
4113
+ const chainNameById = {
4114
+ 1: "ETHEREUM",
4115
+ 8453: "BASE",
4116
+ 42161: "ARBITRUM"
4117
+ };
4118
+ const targetChain = chainNameById[chainId];
4119
+ for (const position2 of agentBalance?.positions ?? []) {
4120
+ const positionChain = position2.chain.trim().toUpperCase();
4121
+ const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
4122
+ if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
4123
+ if (position2.amountRaw !== void 0) {
4124
+ try {
4125
+ balance += BigInt(position2.amountRaw);
4126
+ continue;
4127
+ } catch {
4128
+ }
4129
+ }
4130
+ balance += (0, import_viem7.parseUnits)(position2.amount, decimals);
4131
+ }
4132
+ }
4133
+ return { agent, balance };
4134
+ });
4135
+ }
4136
+ function planProportionalShares(balances, requested, totalAvailable) {
4137
+ const plans = balances.map(({ agent, balance }) => ({
4138
+ agent,
4139
+ balance,
4140
+ planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
4141
+ }));
4142
+ const assigned = plans.reduce((s, p) => s + p.planned, 0n);
4143
+ let remainder = requested - assigned;
4144
+ const byHeadroom = [...plans].sort((a, b) => {
4145
+ const diff = b.balance - b.planned - (a.balance - a.planned);
4146
+ return diff > 0n ? 1 : diff < 0n ? -1 : 0;
4147
+ });
4148
+ for (const p of byHeadroom) {
4149
+ if (remainder === 0n) break;
4150
+ const headroom = p.balance - p.planned;
4151
+ if (headroom <= 0n) continue;
4152
+ const take = headroom < remainder ? headroom : remainder;
4153
+ p.planned += take;
4154
+ remainder -= take;
4155
+ }
4156
+ return plans;
4157
+ }
4158
+ function planDisabledDrain(disabled, requested) {
4159
+ const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
4160
+ (a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
4161
+ );
4162
+ const plans = [];
4163
+ let remaining = requested;
4164
+ for (const { agent, balance } of sorted) {
4165
+ if (remaining === 0n) {
4166
+ plans.push({ agent, balance, planned: 0n });
4167
+ continue;
4168
+ }
4169
+ const take = balance < remaining ? balance : remaining;
4170
+ plans.push({ agent, balance, planned: take });
4171
+ remaining -= take;
4172
+ }
4173
+ return { plans, remaining };
4174
+ }
4175
+ function redistributeShare(plans, fromIndex, amount, candidatePool) {
4176
+ const pool = candidatePool ?? plans.slice(fromIndex + 1);
4177
+ const candidates = pool.filter((c) => c.balance - c.planned > 0n);
4178
+ const totalHeadroom = candidates.reduce(
4179
+ (s, c) => s + (c.balance - c.planned),
4180
+ 0n
4181
+ );
4182
+ if (totalHeadroom === 0n) return;
4183
+ let distributed = 0n;
4184
+ for (const c of candidates) {
4185
+ const headroom = c.balance - c.planned;
4186
+ const proportional = headroom * amount / totalHeadroom;
4187
+ const give = proportional > headroom ? headroom : proportional;
4188
+ c.planned += give;
4189
+ distributed += give;
4190
+ }
4191
+ let leftover = amount - distributed;
4192
+ for (const c of candidates) {
4193
+ if (leftover === 0n) break;
4194
+ const headroom = c.balance - c.planned;
4195
+ if (headroom <= 0n) continue;
4196
+ const take = headroom < leftover ? headroom : leftover;
4197
+ c.planned += take;
4198
+ leftover -= take;
4199
+ }
4200
+ }
4201
+ function sumWithdrawnAmount(results) {
4202
+ return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
2545
4203
  }
2546
4204
 
2547
- // src/lib/chain-guard.ts
2548
- var CHAIN_NAMES = {
2549
- 1: "Ethereum",
2550
- 8453: "Base",
2551
- 42161: "Arbitrum"
2552
- };
2553
- function chainName(chainId) {
2554
- return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
2555
- }
2556
- async function ensureWalletOnChain(pub, wallet, expected) {
2557
- const actual = await pub.getChainId();
2558
- if (actual === expected) return;
2559
- try {
2560
- await wallet.switchChain({ id: expected });
2561
- } catch (error) {
2562
- throw new OwneyError(
2563
- "CHAIN_MISMATCH",
2564
- `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
2565
- {
2566
- expectedChainId: expected,
2567
- actualChainId: actual,
2568
- cause: error instanceof Error ? error.message : String(error)
2569
- }
2570
- );
4205
+ // src/lib/helpers/account-apy-helper.ts
4206
+ function balanceForApyScope(balance, chainId, tokenSymbol) {
4207
+ if (!tokenSymbol) {
4208
+ const total = Number(balance.totalBalance);
4209
+ return Number.isFinite(total) && total > 0 ? total : 0;
2571
4210
  }
2572
- const after = await pub.getChainId();
2573
- if (after !== expected) {
2574
- throw new OwneyError(
2575
- "CHAIN_MISMATCH",
2576
- `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
2577
- { expectedChainId: expected, actualChainId: after }
2578
- );
4211
+ const normalizedToken = tokenSymbol.toUpperCase();
4212
+ const snapshots = balance.assetBalances?.filter(
4213
+ (token) => Number(token.chainId) === chainId && String(token.asset).toUpperCase() === normalizedToken
4214
+ );
4215
+ if (snapshots?.length) {
4216
+ const amount = snapshots.reduce((sum, token) => sum + Number(token.amount), 0);
4217
+ if (Number.isFinite(amount)) return Math.max(0, amount);
2579
4218
  }
4219
+ return balance.tokens.reduce((total, token) => {
4220
+ if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
4221
+ return total;
4222
+ }
4223
+ const amount = Number(token.amount);
4224
+ return Number.isFinite(amount) && amount > 0 ? total + amount : total;
4225
+ }, 0);
2580
4226
  }
2581
-
2582
- // src/lib/sponsored-deposit.ts
2583
- var AUTH_WINDOW_SECONDS = 15 * 60;
2584
- function makeSponsoredDepositCallback(deps) {
2585
- const post = deps.httpPost ?? postSponsorTransferAuth;
2586
- return async (smartWallet, chainId, amount) => {
2587
- const cid = chainId;
2588
- const token = deps.tokenAddressByChain[cid];
2589
- if (!token) {
2590
- throw new OwneyError(
2591
- "CHAIN_UNSUPPORTED",
2592
- `No sponsored token configured for chain ${chainId}`
2593
- );
4227
+ function aggregateApyHistory(agentApys) {
4228
+ const byDate = /* @__PURE__ */ new Map();
4229
+ for (const accountApy of Object.values(agentApys)) {
4230
+ const seen = /* @__PURE__ */ new Set();
4231
+ for (const point of accountApy.history ?? []) {
4232
+ if (!point.date || seen.has(point.date) || !Number.isFinite(point.apy)) continue;
4233
+ seen.add(point.date);
4234
+ const points = byDate.get(point.date) ?? [];
4235
+ points.push(point);
4236
+ byDate.set(point.date, points);
2594
4237
  }
2595
- const pub = deps.getPublicClient(cid);
2596
- const wallet = deps.getWalletClient(cid);
2597
- await ensureWalletOnChain(pub, wallet, cid);
2598
- try {
2599
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2600
- if (balance < BigInt(amount)) {
2601
- throw new OwneyError(
2602
- "DEPOSIT_INSUFFICIENT_BALANCE",
2603
- "Insufficient balance for this deposit.",
2604
- { token, chainId: cid, balance: balance.toString(), amount }
2605
- );
2606
- }
2607
- } catch (err) {
2608
- if (err instanceof OwneyError) throw err;
2609
- console.warn(
2610
- "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
2611
- err instanceof Error ? err.message : String(err)
2612
- );
4238
+ }
4239
+ return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).flatMap(([date, points]) => {
4240
+ if (points.length === 1 && !points[0].historicalBalance) {
4241
+ return [{ date, apy: points[0].apy }];
2613
4242
  }
2614
- const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
2615
- const validAfter = 0n;
2616
- const validBefore = BigInt(
2617
- Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
2618
- );
2619
- const nonce = randomAuthNonce();
2620
- const typedData = buildTransferWithAuthorizationTypedData({
2621
- token,
2622
- chainId: cid,
2623
- tokenName,
2624
- tokenVersion,
2625
- message: {
2626
- from: deps.ownerAddress,
2627
- to: smartWallet,
2628
- value: BigInt(amount),
2629
- validAfter,
2630
- validBefore,
2631
- nonce
2632
- }
2633
- });
2634
- const authSignature = await wallet.signTypedData({
2635
- account: deps.ownerAddress,
2636
- ...typedData
2637
- });
2638
- deps.onApproved?.();
2639
- const result = await post({
2640
- baseUrl: deps.baseUrl,
2641
- apiKey: deps.apiKey,
2642
- body: {
2643
- chainId: cid,
2644
- token,
2645
- from: deps.ownerAddress,
2646
- to: smartWallet,
2647
- value: amount,
2648
- validAfter: validAfter.toString(),
2649
- validBefore: validBefore.toString(),
2650
- nonce,
2651
- authSignature,
2652
- tokenName,
2653
- tokenVersion
4243
+ const unit = points[0].historicalBalance?.unit;
4244
+ if (!unit || points.some(
4245
+ ({ historicalBalance: balance }) => !balance || balance.unit !== unit || !Number.isFinite(balance.amount) || balance.amount < 0
4246
+ )) return [];
4247
+ const total = points.reduce((sum, p) => sum + p.historicalBalance.amount, 0);
4248
+ if (total <= 0 || !Number.isFinite(total)) return [];
4249
+ const apy = points.reduce((sum, p) => sum + p.apy * (p.historicalBalance.amount / total), 0);
4250
+ return Number.isFinite(apy) ? [{ date, apy }] : [];
4251
+ });
4252
+ }
4253
+ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
4254
+ const sums = {};
4255
+ const weights = {};
4256
+ for (const id of Object.keys(agentApys)) {
4257
+ const cells = agentApys[id].apyByChainAndAsset;
4258
+ const balance = agentBalances[id] ?? 0;
4259
+ if (!cells || balance <= 0) continue;
4260
+ for (const [chainKey, perAsset] of Object.entries(cells)) {
4261
+ if (!perAsset) continue;
4262
+ const chainId = Number(chainKey);
4263
+ for (const [asset, apyValue] of Object.entries(perAsset)) {
4264
+ const apy = Number(apyValue ?? 0);
4265
+ if (apy === 0) continue;
4266
+ sums[chainId] ??= {};
4267
+ weights[chainId] ??= {};
4268
+ sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
4269
+ weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
2654
4270
  }
2655
- });
2656
- return result.txHash;
2657
- };
4271
+ }
4272
+ }
4273
+ const out = {};
4274
+ for (const chainKey of Object.keys(sums)) {
4275
+ const chainId = Number(chainKey);
4276
+ const perAssetOut = {};
4277
+ for (const asset of Object.keys(sums[chainId])) {
4278
+ const w = weights[chainId][asset];
4279
+ if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
4280
+ }
4281
+ if (Object.keys(perAssetOut).length > 0) {
4282
+ out[chainId] = perAssetOut;
4283
+ }
4284
+ }
4285
+ return out;
2658
4286
  }
2659
4287
 
4288
+ // src/client.ts
4289
+ var import_viem9 = require("viem");
4290
+ var import_chains4 = require("viem/chains");
4291
+
2660
4292
  // src/lib/sponsored-weth-deposit.ts
2661
4293
  var PERMIT_WINDOW_SECONDS = 15 * 60;
2662
4294
  function makeSponsoredWethCallback(deps) {
2663
4295
  const get = deps.httpGet ?? getSponsorRelayerAddress;
2664
4296
  const post = deps.httpPost ?? postSponsorPermit2Transfer;
2665
- return async (smartWallet, chainId, amount) => {
2666
- const cid = chainId;
2667
- const token = deps.tokenAddressByChain[cid];
2668
- if (!token) {
2669
- throw new OwneyError(
2670
- "CHAIN_UNSUPPORTED",
2671
- `No sponsored WETH configured for chain ${chainId}`
2672
- );
2673
- }
2674
- const amountWei = BigInt(amount);
2675
- const pub = deps.getPublicClient(cid);
2676
- const wallet = deps.getWalletClient(cid);
2677
- await ensureWalletOnChain(pub, wallet, cid);
2678
- try {
2679
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2680
- if (balance < amountWei) {
4297
+ return makeVerificationAwareDepositCallback(
4298
+ async (smartWallet, chainId, amount, verification) => {
4299
+ const cid = chainId;
4300
+ const token = deps.tokenAddressByChain[cid];
4301
+ if (!token) {
2681
4302
  throw new OwneyError(
2682
- "DEPOSIT_INSUFFICIENT_BALANCE",
2683
- "Insufficient WETH balance for this deposit.",
2684
- { token, chainId: cid, balance: balance.toString(), amount }
4303
+ "CHAIN_UNSUPPORTED",
4304
+ `No sponsored WETH configured for chain ${chainId}`
2685
4305
  );
2686
4306
  }
2687
- } catch (err) {
2688
- if (err instanceof OwneyError) throw err;
2689
- console.warn(
2690
- "[owney-sdk] WETH balance pre-check failed (non-fatal):",
2691
- err instanceof Error ? err.message : String(err)
2692
- );
2693
- }
2694
- const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
2695
- if (allowance < amountWei) {
2696
- throw new OwneyError(
2697
- "PERMIT2_APPROVAL_REQUIRED",
2698
- "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
2699
- { token, chainId: cid, allowance: allowance.toString(), amount }
2700
- );
2701
- }
2702
- const relayer = await get({
2703
- baseUrl: deps.baseUrl,
2704
- apiKey: deps.apiKey,
2705
- chainId: cid
2706
- });
2707
- const nonce = randomPermit2Nonce();
2708
- const deadline = BigInt(
2709
- Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
2710
- );
2711
- const typedData = buildPermitTransferFromTypedData({
2712
- chainId: cid,
2713
- message: {
2714
- permitted: { token, amount: amountWei },
2715
- spender: relayer,
2716
- nonce,
2717
- deadline
4307
+ const amountWei = BigInt(amount);
4308
+ const pub = deps.getPublicClient(cid);
4309
+ const wallet = deps.getWalletClient(cid);
4310
+ await ensureWalletOnChain(pub, wallet, cid);
4311
+ try {
4312
+ const balance = await readErc20Balance(pub, token, deps.ownerAddress);
4313
+ if (balance < amountWei) {
4314
+ throw new OwneyError(
4315
+ "DEPOSIT_INSUFFICIENT_BALANCE",
4316
+ "Insufficient WETH balance for this deposit.",
4317
+ { token, chainId: cid, balance: balance.toString(), amount }
4318
+ );
4319
+ }
4320
+ } catch (err) {
4321
+ if (err instanceof OwneyError) throw err;
4322
+ console.warn(
4323
+ "[owney-sdk] WETH balance pre-check failed (non-fatal):",
4324
+ err instanceof Error ? err.message : String(err)
4325
+ );
2718
4326
  }
2719
- });
2720
- const signature = await wallet.signTypedData({
2721
- account: deps.ownerAddress,
2722
- ...typedData
2723
- });
2724
- deps.onApproved?.();
2725
- const result = await post({
2726
- baseUrl: deps.baseUrl,
2727
- apiKey: deps.apiKey,
2728
- body: {
2729
- chainId: cid,
4327
+ const allowance = await readPermit2Allowance(
4328
+ pub,
2730
4329
  token,
2731
- from: deps.ownerAddress,
2732
- to: smartWallet,
2733
- amount,
2734
- nonce: nonce.toString(),
2735
- deadline: deadline.toString(),
2736
- signature
4330
+ deps.ownerAddress
4331
+ );
4332
+ if (allowance < amountWei) {
4333
+ throw new OwneyError(
4334
+ "PERMIT2_APPROVAL_REQUIRED",
4335
+ "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
4336
+ { token, chainId: cid, allowance: allowance.toString(), amount }
4337
+ );
2737
4338
  }
2738
- });
2739
- return result.txHash;
2740
- };
4339
+ const relayer = await get({
4340
+ baseUrl: deps.baseUrl,
4341
+ apiKey: deps.apiKey,
4342
+ chainId: cid
4343
+ });
4344
+ const nonce = randomPermit2Nonce();
4345
+ const deadline = BigInt(
4346
+ Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
4347
+ );
4348
+ const typedData = buildPermitTransferFromTypedData({
4349
+ chainId: cid,
4350
+ message: {
4351
+ permitted: { token, amount: amountWei },
4352
+ spender: relayer,
4353
+ nonce,
4354
+ deadline
4355
+ }
4356
+ });
4357
+ const signature = await wallet.signTypedData({
4358
+ account: deps.ownerAddress,
4359
+ ...typedData
4360
+ });
4361
+ deps.onApproved?.();
4362
+ const result = await post({
4363
+ baseUrl: deps.baseUrl,
4364
+ apiKey: deps.apiKey,
4365
+ ...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
4366
+ body: {
4367
+ chainId: cid,
4368
+ token,
4369
+ from: deps.ownerAddress,
4370
+ to: smartWallet,
4371
+ amount,
4372
+ nonce: nonce.toString(),
4373
+ deadline: deadline.toString(),
4374
+ signature,
4375
+ ...verification?.agentId === "yieldseeker" ? {
4376
+ yieldseekerUserId: verification.userId,
4377
+ yieldseekerAgentId: verification.yieldseekerAgentId
4378
+ } : {}
4379
+ }
4380
+ });
4381
+ return result.txHash;
4382
+ }
4383
+ );
2741
4384
  }
2742
4385
 
2743
4386
  // src/lib/sponsored-calls-deposit.ts
2744
- var import_viem5 = require("viem");
4387
+ var import_viem8 = require("viem");
2745
4388
  var DEFAULT_POLL_INTERVAL_MS = 1500;
2746
4389
  var DEFAULT_MAX_POLLS = 30;
2747
4390
  async function paymasterSupported(provider, owner, chainId) {
@@ -2749,7 +4392,7 @@ async function paymasterSupported(provider, owner, chainId) {
2749
4392
  method: "wallet_getCapabilities",
2750
4393
  params: [owner]
2751
4394
  });
2752
- const forChain = caps?.[(0, import_viem5.toHex)(chainId)] ?? caps?.[String(chainId)];
4395
+ const forChain = caps?.[(0, import_viem8.toHex)(chainId)] ?? caps?.[String(chainId)];
2753
4396
  return Boolean(forChain?.paymasterService?.supported);
2754
4397
  }
2755
4398
  function makeSponsoredCallsCallback(deps) {
@@ -2767,7 +4410,7 @@ function makeSponsoredCallsCallback(deps) {
2767
4410
  }
2768
4411
  return new URL(configured, origin).toString();
2769
4412
  };
2770
- return async (smartWallet, chainId, amount) => {
4413
+ return makeVerificationAwareDepositCallback(async (smartWallet, chainId, amount, verification) => {
2771
4414
  const cid = chainId;
2772
4415
  const token = deps.tokenAddressByChain[cid];
2773
4416
  if (!token) {
@@ -2783,22 +4426,48 @@ function makeSponsoredCallsCallback(deps) {
2783
4426
  { chainId }
2784
4427
  );
2785
4428
  }
2786
- const data = (0, import_viem5.encodeFunctionData)({
2787
- abi: import_viem5.erc20Abi,
4429
+ const data = (0, import_viem8.encodeFunctionData)({
4430
+ abi: import_viem8.erc20Abi,
2788
4431
  functionName: "transfer",
2789
4432
  args: [smartWallet, BigInt(amount)]
2790
4433
  });
4434
+ let paymasterUrl = absolutePaymasterUrl();
4435
+ if (verification?.agentId === "yieldseeker") {
4436
+ if (chainId !== 8453) {
4437
+ throw new OwneyError(
4438
+ "CHAIN_UNSUPPORTED",
4439
+ `Yieldseeker Base Account sponsorship is not available on chain ${chainId}.`
4440
+ );
4441
+ }
4442
+ const { intent } = await postPaymasterIntent({
4443
+ baseUrl: deps.routingApiBaseUrl,
4444
+ apiKey: deps.apiKey,
4445
+ yieldseekerSignature: verification.signature,
4446
+ body: {
4447
+ chainId,
4448
+ token,
4449
+ from: deps.ownerAddress,
4450
+ to: smartWallet,
4451
+ amount,
4452
+ yieldseekerUserId: verification.userId,
4453
+ yieldseekerAgentId: verification.yieldseekerAgentId
4454
+ }
4455
+ });
4456
+ const url = new URL(paymasterUrl);
4457
+ url.searchParams.set("owneyIntent", intent);
4458
+ paymasterUrl = url.toString();
4459
+ }
2791
4460
  const sendResult = await deps.provider.request({
2792
4461
  method: "wallet_sendCalls",
2793
4462
  params: [
2794
4463
  {
2795
4464
  version: "2.0.0",
2796
4465
  from: deps.ownerAddress,
2797
- chainId: (0, import_viem5.toHex)(chainId),
4466
+ chainId: (0, import_viem8.toHex)(chainId),
2798
4467
  atomicRequired: false,
2799
4468
  calls: [{ to: token, value: "0x0", data }],
2800
4469
  capabilities: {
2801
- paymasterService: { url: absolutePaymasterUrl() }
4470
+ paymasterService: { url: paymasterUrl }
2802
4471
  }
2803
4472
  }
2804
4473
  ]
@@ -2828,7 +4497,7 @@ function makeSponsoredCallsCallback(deps) {
2828
4497
  `No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
2829
4498
  { chainId, callsId }
2830
4499
  );
2831
- };
4500
+ });
2832
4501
  }
2833
4502
 
2834
4503
  // src/client.ts
@@ -2858,9 +4527,9 @@ var SPONSORED_USDC_BY_CHAIN = {
2858
4527
  1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
2859
4528
  };
2860
4529
  var VIEM_CHAIN2 = {
2861
- 8453: import_chains2.base,
2862
- 42161: import_chains2.arbitrum,
2863
- 1: import_chains2.mainnet
4530
+ 8453: import_chains4.base,
4531
+ 42161: import_chains4.arbitrum,
4532
+ 1: import_chains4.mainnet
2864
4533
  };
2865
4534
  var SPONSORED_WETH_BY_CHAIN = {
2866
4535
  8453: "0x4200000000000000000000000000000000000006",
@@ -2888,6 +4557,8 @@ var OwneySDK = class {
2888
4557
  orgAgentConfig;
2889
4558
  orgAgentConfigPromise = null;
2890
4559
  zyfaiRpcUrls;
4560
+ yieldseekerApiBaseUrl;
4561
+ yieldseekerSiweOrigin;
2891
4562
  routingApiBaseUrl;
2892
4563
  referralSource;
2893
4564
  cachedSponsoredCallback = null;
@@ -2910,6 +4581,8 @@ var OwneySDK = class {
2910
4581
  this.apiKey = config.apiKey;
2911
4582
  if (config.debug) setOwneyDebug(true);
2912
4583
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
4584
+ this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
4585
+ this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
2913
4586
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2914
4587
  this.paymasterServiceUrl = config.paymasterServiceUrl;
2915
4588
  this.referralSource = config.referralSource;
@@ -3016,14 +4689,14 @@ var OwneySDK = class {
3016
4689
  // Casts work around viem's chain-narrowed Client vs the generic
3017
4690
  // PublicClient/WalletClient param types — structurally identical at
3018
4691
  // runtime, but the two share a name TS treats as unrelated.
3019
- getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
4692
+ getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
3020
4693
  chain: VIEM_CHAIN2[cid],
3021
- transport: (0, import_viem6.custom)(provider)
4694
+ transport: (0, import_viem9.custom)(provider)
3022
4695
  }),
3023
- getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
4696
+ getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
3024
4697
  account: owner,
3025
4698
  chain: VIEM_CHAIN2[cid],
3026
- transport: (0, import_viem6.custom)(provider)
4699
+ transport: (0, import_viem9.custom)(provider)
3027
4700
  })
3028
4701
  });
3029
4702
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -3040,6 +4713,8 @@ var OwneySDK = class {
3040
4713
  if (!onApproved && cached) return cached;
3041
4714
  const provider = this.requireConnectedProvider();
3042
4715
  const callback = makeSponsoredCallsCallback({
4716
+ apiKey: this.apiKey,
4717
+ routingApiBaseUrl: this.routingApiBaseUrl,
3043
4718
  provider,
3044
4719
  ownerAddress: this.state.walletAddress,
3045
4720
  paymasterServiceUrl: this.paymasterServiceUrl,
@@ -3069,14 +4744,14 @@ var OwneySDK = class {
3069
4744
  // Casts work around viem's chain-narrowed Client vs the generic
3070
4745
  // PublicClient/WalletClient param types — structurally identical at
3071
4746
  // runtime, but the two share a name TS treats as unrelated.
3072
- getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
4747
+ getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
3073
4748
  chain: VIEM_CHAIN2[cid],
3074
- transport: (0, import_viem6.custom)(provider)
4749
+ transport: (0, import_viem9.custom)(provider)
3075
4750
  }),
3076
- getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
4751
+ getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
3077
4752
  account: owner,
3078
4753
  chain: VIEM_CHAIN2[cid],
3079
- transport: (0, import_viem6.custom)(provider)
4754
+ transport: (0, import_viem9.custom)(provider)
3080
4755
  })
3081
4756
  });
3082
4757
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -3107,12 +4782,10 @@ var OwneySDK = class {
3107
4782
  this.orgAgentConfigPromise = fetchOrgAgentConfig(
3108
4783
  this.apiKey,
3109
4784
  this.routingApiBaseUrl
3110
- ).then(
3111
- (config) => {
3112
- this.orgAgentConfig = config;
3113
- return config;
3114
- }
3115
- );
4785
+ ).then((config) => {
4786
+ this.orgAgentConfig = config;
4787
+ return config;
4788
+ });
3116
4789
  }
3117
4790
  return this.orgAgentConfigPromise;
3118
4791
  }
@@ -3152,7 +4825,14 @@ var OwneySDK = class {
3152
4825
  this.routingApiBaseUrl
3153
4826
  );
3154
4827
  this.disabledAgents.clear();
3155
- for (const { key: key2, agent_type, is_enabled } of agentKeys) {
4828
+ for (const {
4829
+ key: key2,
4830
+ agent_type,
4831
+ is_enabled,
4832
+ is_configured
4833
+ } of agentKeys) {
4834
+ const configured = is_configured ?? Boolean(key2);
4835
+ if (!configured) continue;
3156
4836
  const agent = this.createAgent(agent_type, key2);
3157
4837
  if (!agent) continue;
3158
4838
  this.agents.set(agent_type, agent);
@@ -3176,8 +4856,15 @@ var OwneySDK = class {
3176
4856
  }
3177
4857
  createAgent(agentId, key2) {
3178
4858
  if (agentId === "zyfai") {
4859
+ if (!key2) return null;
3179
4860
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
3180
4861
  }
4862
+ if (agentId === "yieldseeker") {
4863
+ return new YieldseekerAgent(this.apiKey, {
4864
+ auth: { origin: this.yieldseekerSiweOrigin },
4865
+ baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
4866
+ });
4867
+ }
3181
4868
  return null;
3182
4869
  }
3183
4870
  /**
@@ -3222,7 +4909,7 @@ var OwneySDK = class {
3222
4909
  * If provided, ALL specified agents must support the chainId or the call
3223
4910
  * throws before activating any agent.
3224
4911
  */
3225
- async activateAgent(chainId, agentId) {
4912
+ async activateAgent(chainId, agentId, asset) {
3226
4913
  const state = this.requireState();
3227
4914
  await this.ensureAgentsInitialized();
3228
4915
  if (agentId !== void 0) {
@@ -3258,7 +4945,7 @@ var OwneySDK = class {
3258
4945
  this.activeAgents.add(id);
3259
4946
  }
3260
4947
  state.chainId = chainId;
3261
- await this.activateAgentsInTurn(agents, state, chainId);
4948
+ await this.activateAgentsInTurn(agents, state, chainId, asset);
3262
4949
  return;
3263
4950
  }
3264
4951
  const compatible = [...this.agents.values()].filter(
@@ -3279,7 +4966,7 @@ var OwneySDK = class {
3279
4966
  const enabledCompatible = compatible.filter(
3280
4967
  (agent) => !this.isAgentDisabled(agent.id)
3281
4968
  );
3282
- await this.activateAgentsInTurn(enabledCompatible, state, chainId);
4969
+ await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
3283
4970
  }
3284
4971
  /**
3285
4972
  * Activate agents ONE AT A TIME, each followed by its org policy.
@@ -3295,17 +4982,25 @@ var OwneySDK = class {
3295
4982
  * at a time anyway.
3296
4983
  *
3297
4984
  * Every agent is attempted even if an earlier one fails, so one declined
3298
- * signature can't deny the remaining agents their turn. The first failure is
3299
- * rethrown (matching the previous `Promise.all` rejection) once all agents
3300
- * have had a chance to activate.
4985
+ * signature can't deny the remaining agents their turn. Once all agents have
4986
+ * had a chance, a partial failure identifies the agents that still need a
4987
+ * retry; if none activated, the original provider error is preserved.
3301
4988
  */
3302
- async activateAgentsInTurn(agents, state, chainId) {
4989
+ async activateAgentsInTurn(agents, state, chainId, asset) {
3303
4990
  let firstError = null;
4991
+ const activatedAgentIds = [];
4992
+ const failedAgents = [];
3304
4993
  for (const agent of agents) {
3305
4994
  try {
3306
- await agent.activateAgent(state, chainId);
4995
+ await agent.activateAgent(state, chainId, asset);
3307
4996
  await this.applyOrgPolicyTo(agent, state, chainId);
4997
+ activatedAgentIds.push(agent.id);
3308
4998
  } catch (error) {
4999
+ failedAgents.push({
5000
+ agentId: agent.id,
5001
+ code: error instanceof OwneyError ? error.code : void 0,
5002
+ message: error instanceof Error ? error.message : String(error)
5003
+ });
3309
5004
  if (firstError === null) {
3310
5005
  firstError = error;
3311
5006
  } else {
@@ -3313,7 +5008,16 @@ var OwneySDK = class {
3313
5008
  }
3314
5009
  }
3315
5010
  }
3316
- if (firstError !== null) throw firstError;
5011
+ if (firstError === null) return;
5012
+ if (activatedAgentIds.length === 0) throw firstError;
5013
+ const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
5014
+ const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
5015
+ const remainingNames = failedAgentIds.map(this.formatAgentName).join(", ");
5016
+ throw new OwneyError(
5017
+ "AGENT_ACTIVATION_PARTIAL_FAILURE",
5018
+ `${activeNames} activated, but ${remainingNames} still needs activation. Try again and approve the remaining wallet request.`,
5019
+ { activatedAgentIds, failedAgentIds, failures: failedAgents }
5020
+ );
3317
5021
  }
3318
5022
  /**
3319
5023
  * Deposit into a specific agent, or distribute across all eligible agents.
@@ -3527,10 +5231,10 @@ var OwneySDK = class {
3527
5231
  agent,
3528
5232
  amount: i === agents.length - 1 ? perAgent + remainder : perAgent
3529
5233
  }));
3530
- const valid = splits.filter(
5234
+ const valid2 = splits.filter(
3531
5235
  (s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
3532
5236
  );
3533
- if (valid.length === agents.length) {
5237
+ if (valid2.length === agents.length) {
3534
5238
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
3535
5239
  }
3536
5240
  const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
@@ -3549,6 +5253,11 @@ var OwneySDK = class {
3549
5253
  )
3550
5254
  }));
3551
5255
  }
5256
+ formatAgentName(agentId) {
5257
+ if (agentId === "zyfai") return "Zyfai";
5258
+ if (agentId === "yieldseeker") return "Yieldseeker";
5259
+ return agentId;
5260
+ }
3552
5261
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
3553
5262
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
3554
5263
  const parsedAmount = BigInt(amount);
@@ -3580,12 +5289,12 @@ var OwneySDK = class {
3580
5289
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
3581
5290
  );
3582
5291
  const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
3583
- const position = (balance.positions ?? []).find((p) => {
5292
+ const position2 = (balance.positions ?? []).find((p) => {
3584
5293
  const positionChain = p.chain.trim().toUpperCase();
3585
5294
  const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
3586
5295
  return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
3587
5296
  });
3588
- return !!token && Number(token.amount) > 0 || !!position;
5297
+ return !!token && Number(token.amount) > 0 || !!position2;
3589
5298
  } catch (error) {
3590
5299
  if (requireReliableRead) {
3591
5300
  throw new OwneyError(
@@ -3700,6 +5409,10 @@ var OwneySDK = class {
3700
5409
  }
3701
5410
  const requested = BigInt(amount);
3702
5411
  const aggregated = await this.getBalances();
5412
+ const unavailableAgents = eligibleAgents.filter(
5413
+ (agent) => !(agent.id in aggregated.agentBalances)
5414
+ );
5415
+ const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
3703
5416
  const balances = projectAgentBalancesForAsset(
3704
5417
  eligibleAgents,
3705
5418
  aggregated.agentBalances,
@@ -3708,7 +5421,18 @@ var OwneySDK = class {
3708
5421
  assetInfo.decimals
3709
5422
  );
3710
5423
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
3711
- if (totalAvailable < requested) {
5424
+ if (totalAvailable === 0n && unavailableAgents.length > 0) {
5425
+ throw new OwneyError(
5426
+ "WITHDRAW_BALANCE_UNAVAILABLE",
5427
+ `Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
5428
+ {
5429
+ asset,
5430
+ unavailableAgents: unavailableAgentIds,
5431
+ agentErrors: aggregated.agentErrors
5432
+ }
5433
+ );
5434
+ }
5435
+ if (totalAvailable < requested && unavailableAgents.length === 0) {
3712
5436
  throw new OwneyError(
3713
5437
  "WITHDRAW_INSUFFICIENT_BALANCE",
3714
5438
  `Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
@@ -3719,6 +5443,7 @@ var OwneySDK = class {
3719
5443
  }
3720
5444
  );
3721
5445
  }
5446
+ const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
3722
5447
  const disabledBalances = balances.filter(
3723
5448
  (b) => this.isAgentDisabled(b.agent.id)
3724
5449
  );
@@ -3727,7 +5452,7 @@ var OwneySDK = class {
3727
5452
  );
3728
5453
  const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
3729
5454
  disabledBalances,
3730
- requested
5455
+ plannedTarget
3731
5456
  );
3732
5457
  const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
3733
5458
  const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
@@ -3737,7 +5462,9 @@ var OwneySDK = class {
3737
5462
  }));
3738
5463
  const plans = [...disabledPlans, ...enabledPlans];
3739
5464
  const results = {};
3740
- const agentErrors = {};
5465
+ const agentErrors = {
5466
+ ...aggregated.agentErrors ?? {}
5467
+ };
3741
5468
  for (let i = 0; i < plans.length; i++) {
3742
5469
  const p = plans[i];
3743
5470
  if (p.planned === 0n) continue;
@@ -3784,7 +5511,8 @@ var OwneySDK = class {
3784
5511
  requested: amount,
3785
5512
  withdrawn: withdrawn.toString(),
3786
5513
  partialResults: results,
3787
- agentErrors
5514
+ agentErrors,
5515
+ ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
3788
5516
  }
3789
5517
  );
3790
5518
  }
@@ -3802,7 +5530,10 @@ var OwneySDK = class {
3802
5530
  if (agentId) {
3803
5531
  const agent = this.getAgent(agentId);
3804
5532
  const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3805
- return result;
5533
+ return {
5534
+ ...result,
5535
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5536
+ };
3806
5537
  }
3807
5538
  let totalBalance = 0;
3808
5539
  const results = {};
@@ -3810,7 +5541,13 @@ var OwneySDK = class {
3810
5541
  const balanceResults = await Promise.allSettled(
3811
5542
  entries.map(async ([id, agent]) => {
3812
5543
  const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3813
- return [id, b];
5544
+ return [
5545
+ id,
5546
+ {
5547
+ ...b,
5548
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5549
+ }
5550
+ ];
3814
5551
  })
3815
5552
  );
3816
5553
  let successCount = 0;
@@ -3832,6 +5569,7 @@ var OwneySDK = class {
3832
5569
  const retryDelay = rateLimitDelay(reason);
3833
5570
  if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
3834
5571
  agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
5572
+ console.error(`[owney-sdk] Balance fetch failed for agent "${agentId2}":`, reason);
3835
5573
  }
3836
5574
  if (successCount === 0) {
3837
5575
  throw new OwneyError(
@@ -3944,7 +5682,10 @@ var OwneySDK = class {
3944
5682
  Promise.all(
3945
5683
  entries.map(async ([id, agent]) => {
3946
5684
  const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3947
- return [id, balanceForApyScope(b, chainId, tokenSymbol)];
5685
+ return [
5686
+ id,
5687
+ balanceForApyScope(b, chainId, tokenSymbol)
5688
+ ];
3948
5689
  })
3949
5690
  )
3950
5691
  ]);
@@ -4125,10 +5866,10 @@ var OwneySDK = class {
4125
5866
  );
4126
5867
  }
4127
5868
  const provider = this.requireConnectedProvider();
4128
- const wallet = (0, import_viem6.createWalletClient)({
5869
+ const wallet = (0, import_viem9.createWalletClient)({
4129
5870
  account: state.walletAddress,
4130
5871
  chain: VIEM_CHAIN2[chainId],
4131
- transport: (0, import_viem6.custom)(provider)
5872
+ transport: (0, import_viem9.custom)(provider)
4132
5873
  });
4133
5874
  const hash = await wallet.writeContract({
4134
5875
  address: token,
@@ -4138,9 +5879,9 @@ var OwneySDK = class {
4138
5879
  account: state.walletAddress,
4139
5880
  chain: VIEM_CHAIN2[chainId]
4140
5881
  });
4141
- const publicClient = (0, import_viem6.createPublicClient)({
5882
+ const publicClient = (0, import_viem9.createPublicClient)({
4142
5883
  chain: VIEM_CHAIN2[chainId],
4143
- transport: (0, import_viem6.custom)(provider)
5884
+ transport: (0, import_viem9.custom)(provider)
4144
5885
  });
4145
5886
  const receipt = await publicClient.waitForTransactionReceipt({
4146
5887
  hash,
@@ -4177,7 +5918,9 @@ var OwneySDK = class {
4177
5918
  return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
4178
5919
  }
4179
5920
  const results = {};
4180
- const agentEntries = [...this.agents.entries()];
5921
+ const agentEntries = [...this.agents.entries()].filter(
5922
+ ([id]) => !this.isAgentDisabled(id)
5923
+ );
4181
5924
  const apyResults = await Promise.all(
4182
5925
  agentEntries.map(async ([id, agent]) => {
4183
5926
  const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
@@ -4252,13 +5995,13 @@ var OwneySDK = class {
4252
5995
  };
4253
5996
 
4254
5997
  // src/agents/zyfai/zyfai.siwx.ts
4255
- var import_viem7 = require("viem");
4256
- var import_siwe = require("siwe");
5998
+ var import_viem10 = require("viem");
5999
+ var import_siwe2 = require("siwe");
4257
6000
  var import_sdk2 = require("@zyfai/sdk");
4258
6001
 
4259
6002
  // src/agents/zyfai/zyfai.siwx-cache.ts
4260
- var KEY_PREFIX2 = "owney.siwx.session";
4261
- var storage2 = () => {
6003
+ var KEY_PREFIX4 = "owney.siwx.session";
6004
+ var storage4 = () => {
4262
6005
  if (typeof window === "undefined") return null;
4263
6006
  try {
4264
6007
  return window.localStorage;
@@ -4266,8 +6009,8 @@ var storage2 = () => {
4266
6009
  return null;
4267
6010
  }
4268
6011
  };
4269
- var buildKey2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}`;
4270
- var legacyKeyPrefix2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}:`;
6012
+ var buildKey3 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
6013
+ var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
4271
6014
  var memorySiwxSessions = /* @__PURE__ */ new Map();
4272
6015
  var readLegacySiwxSession = (store, address) => {
4273
6016
  if (!store) return null;
@@ -4298,17 +6041,17 @@ var readLegacySiwxSession = (store, address) => {
4298
6041
  };
4299
6042
  var readSiwxSession = (address, chainId) => {
4300
6043
  if (typeof window === "undefined") return null;
4301
- const key2 = buildKey2(address);
4302
- const store = storage2();
4303
- let raw = null;
6044
+ const key2 = buildKey3(address);
6045
+ const store = storage4();
6046
+ let raw2 = null;
4304
6047
  try {
4305
- raw = store?.getItem(key2) ?? null;
6048
+ raw2 = store?.getItem(key2) ?? null;
4306
6049
  } catch {
4307
- raw = null;
6050
+ raw2 = null;
4308
6051
  }
4309
- if (raw) {
6052
+ if (raw2) {
4310
6053
  try {
4311
- return JSON.parse(raw);
6054
+ return JSON.parse(raw2);
4312
6055
  } catch {
4313
6056
  memorySiwxSessions.delete(key2);
4314
6057
  try {
@@ -4327,18 +6070,18 @@ var readSiwxSession = (address, chainId) => {
4327
6070
  };
4328
6071
  var writeSiwxSession = (address, _chainId, session) => {
4329
6072
  if (typeof window === "undefined") return;
4330
- const key2 = buildKey2(address);
6073
+ const key2 = buildKey3(address);
4331
6074
  memorySiwxSessions.set(key2, session);
4332
- const store = storage2();
6075
+ const store = storage4();
4333
6076
  try {
4334
6077
  store?.setItem(key2, JSON.stringify(session));
4335
6078
  } catch {
4336
6079
  }
4337
6080
  };
4338
6081
  var clearSiwxSession = (address, _chainId) => {
4339
- const key2 = buildKey2(address);
6082
+ const key2 = buildKey3(address);
4340
6083
  memorySiwxSessions.delete(key2);
4341
- const store = storage2();
6084
+ const store = storage4();
4342
6085
  try {
4343
6086
  store?.removeItem(key2);
4344
6087
  } catch {
@@ -4378,8 +6121,8 @@ function buildSIWXConfig(deps) {
4378
6121
  statement: STATEMENT,
4379
6122
  issuedAt,
4380
6123
  toString() {
4381
- return new import_siwe.SiweMessage({
4382
- address: (0, import_viem7.getAddress)(accountAddress),
6124
+ return new import_siwe2.SiweMessage({
6125
+ address: (0, import_viem10.getAddress)(accountAddress),
4383
6126
  chainId: numericChainId(chainId),
4384
6127
  domain,
4385
6128
  uri,
@@ -4421,7 +6164,7 @@ function buildSIWXConfig(deps) {
4421
6164
  const persistSession = async (session) => {
4422
6165
  const address = session.data.accountAddress;
4423
6166
  const id = numericChainId(session.data.chainId);
4424
- const message = new import_siwe.SiweMessage(session.message);
6167
+ const message = new import_siwe2.SiweMessage(session.message);
4425
6168
  const login = await post("/auth/login", {
4426
6169
  message,
4427
6170
  signature: session.signature,
@@ -4471,6 +6214,7 @@ function createOwneySIWX(config) {
4471
6214
  NotConnectedError,
4472
6215
  OwneyError,
4473
6216
  OwneySDK,
6217
+ YieldseekerAgent,
4474
6218
  createOwneySIWX,
4475
6219
  setOwneyDebug
4476
6220
  });