@owney/sdk 0.7.15 → 0.7.16-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.js CHANGED
@@ -306,9 +306,9 @@ function mapWeightedApyByChain(raw) {
306
306
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
307
307
  const perAsset = {};
308
308
  for (const [symbol, apy] of Object.entries(tokenApy)) {
309
- const token = symbol;
310
- if (!SUPPORTED_TOKENS.includes(token)) continue;
311
- perAsset[token] = apy;
309
+ const token2 = symbol;
310
+ if (!SUPPORTED_TOKENS.includes(token2)) continue;
311
+ perAsset[token2] = apy;
312
312
  }
313
313
  if (Object.keys(perAsset).length > 0) out[chainId] = perAsset;
314
314
  }
@@ -477,23 +477,23 @@ function computeAllocationApy(positions) {
477
477
  weightedSum += apy * value;
478
478
  totalValue += value;
479
479
  const chainId = resolveChainId(p.chain);
480
- const token = p.asset;
480
+ const token2 = p.asset;
481
481
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
482
- if (!SUPPORTED_TOKENS.includes(token)) continue;
482
+ if (!SUPPORTED_TOKENS.includes(token2)) continue;
483
483
  const perAsset = buckets[chainId] ?? {};
484
- const cell = perAsset[token] ?? { weightedSum: 0, value: 0 };
484
+ const cell = perAsset[token2] ?? { weightedSum: 0, value: 0 };
485
485
  cell.weightedSum += apy * value;
486
486
  cell.value += value;
487
- perAsset[token] = cell;
487
+ perAsset[token2] = cell;
488
488
  buckets[chainId] = perAsset;
489
489
  }
490
490
  const apyByChainAndAsset = {};
491
491
  for (const [chainKey, perAsset] of Object.entries(buckets)) {
492
492
  const chainId = Number(chainKey);
493
493
  const out = {};
494
- for (const [token, cell] of Object.entries(perAsset ?? {})) {
494
+ for (const [token2, cell] of Object.entries(perAsset ?? {})) {
495
495
  if (cell && cell.value > 0) {
496
- out[token] = cell.weightedSum / cell.value;
496
+ out[token2] = cell.weightedSum / cell.value;
497
497
  }
498
498
  }
499
499
  if (Object.keys(out).length > 0) apyByChainAndAsset[chainId] = out;
@@ -507,9 +507,9 @@ var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4d
507
507
  function topicToAddress(topic) {
508
508
  return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
509
509
  }
510
- function extractWithdrawnAmount(logs, recipient, token, decimals) {
510
+ function extractWithdrawnAmount(logs, recipient, token2, decimals) {
511
511
  const wantRecipient = recipient.toLowerCase();
512
- const wantToken = token.toLowerCase();
512
+ const wantToken = token2.toLowerCase();
513
513
  let total = 0n;
514
514
  let matched = false;
515
515
  for (const log of logs) {
@@ -535,11 +535,11 @@ var InvalidHistoryCursorError = class extends Error {
535
535
  function encodeHistoryCursor(payload) {
536
536
  return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
537
537
  }
538
- function decodeHistoryCursor(token) {
539
- if (!token) throw new InvalidHistoryCursorError("Cursor is empty.");
538
+ function decodeHistoryCursor(token2) {
539
+ if (!token2) throw new InvalidHistoryCursorError("Cursor is empty.");
540
540
  let parsed;
541
541
  try {
542
- const json = Buffer.from(token, "base64").toString("utf8");
542
+ const json = Buffer.from(token2, "base64").toString("utf8");
543
543
  parsed = JSON.parse(json);
544
544
  } catch {
545
545
  throw new InvalidHistoryCursorError("Cursor is not valid base64 JSON.");
@@ -562,8 +562,8 @@ var storage = () => {
562
562
  };
563
563
  var buildKey = (address) => `${KEY_PREFIX}:${address.toLowerCase()}`;
564
564
  var legacyKeyPrefix = (address) => `${KEY_PREFIX}:${address.toLowerCase()}:`;
565
- var isJwtExpired = (token) => {
566
- const parts = token.split(".");
565
+ var isJwtExpired = (token2) => {
566
+ const parts = token2.split(".");
567
567
  if (parts.length !== 3) return false;
568
568
  try {
569
569
  const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
@@ -1665,20 +1665,20 @@ var ZyfaiAgent = class _ZyfaiAgent {
1665
1665
  }
1666
1666
  }
1667
1667
  // --- IAgent: Fund operations ---
1668
- async withdraw(state, chainId, token, amount) {
1668
+ async withdraw(state, chainId, token2, amount) {
1669
1669
  const validChainId = isValidChainId(chainId);
1670
1670
  await this.ensureConnected(state, validChainId);
1671
1671
  const raw = await this.sdk.withdrawFunds(
1672
1672
  this.getAddress(),
1673
1673
  validChainId,
1674
1674
  amount,
1675
- token
1675
+ token2
1676
1676
  );
1677
1677
  if (!raw.success) {
1678
1678
  throw new OwneyError(
1679
1679
  "WITHDRAW_FAILED",
1680
1680
  raw.message || "Zyfai withdraw failed.",
1681
- { chainId: validChainId, token, amount, response: raw },
1681
+ { chainId: validChainId, token: token2, amount, response: raw },
1682
1682
  this.id
1683
1683
  );
1684
1684
  }
@@ -1829,227 +1829,52 @@ var ZyfaiAgent = class _ZyfaiAgent {
1829
1829
  }
1830
1830
  };
1831
1831
 
1832
- // src/lib/routing-api.ts
1833
- var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
1834
- async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL) {
1835
- const url = `${baseUrl}/api/v1/agent/org-config`;
1832
+ // src/agents/yieldseeker/yieldseeker.agent.ts
1833
+ import {
1834
+ createPublicClient as createPublicClient3,
1835
+ createWalletClient as createWalletClient2,
1836
+ custom as custom2,
1837
+ getAddress as getAddress2,
1838
+ isAddress,
1839
+ parseUnits
1840
+ } from "viem";
1841
+ import { base as base3 } from "viem/chains";
1842
+
1843
+ // src/lib/chain-guard.ts
1844
+ var CHAIN_NAMES = {
1845
+ 1: "Ethereum",
1846
+ 8453: "Base",
1847
+ 42161: "Arbitrum"
1848
+ };
1849
+ function chainName(chainId) {
1850
+ return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
1851
+ }
1852
+ async function ensureWalletOnChain(pub, wallet, expected) {
1853
+ const actual = await pub.getChainId();
1854
+ if (actual === expected) return;
1836
1855
  try {
1837
- const res = await fetch(url, {
1838
- method: "GET",
1839
- headers: {
1840
- "Content-Type": "application/json",
1841
- "x-owney-api-key": `${apiKey}`
1842
- }
1843
- });
1844
- if (!res.ok) {
1845
- if (res.status !== 404) {
1846
- console.warn(
1847
- `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
1848
- );
1849
- }
1850
- return null;
1851
- }
1852
- const json = await res.json();
1853
- const policy = json.success ? json.data ?? null : null;
1854
- debugLog(
1855
- "owney-sdk",
1856
- policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
1857
- policy ?? void 0
1858
- );
1859
- return policy;
1856
+ await wallet.switchChain({ id: expected });
1860
1857
  } catch (error) {
1861
- console.warn(
1862
- "[owney-sdk] Could not read org agent config (non-fatal):",
1863
- error instanceof Error ? error.message : String(error)
1864
- );
1865
- return null;
1866
- }
1867
- }
1868
- async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
1869
- const url = `${baseUrl}/api/v1/agent/keys`;
1870
- const res = await fetch(url, {
1871
- method: "GET",
1872
- headers: {
1873
- "Content-Type": "application/json",
1874
- "x-owney-api-key": `${apiKey}`
1875
- }
1876
- });
1877
- if (!res.ok) {
1878
- const text = await res.text().catch(() => "");
1879
1858
  throw new OwneyError(
1880
- "API_ROUTING_ERROR",
1881
- `Routing API error ${res.status}: ${text}`,
1882
- { statusCode: res.status, responseBody: text }
1859
+ "CHAIN_MISMATCH",
1860
+ `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
1861
+ {
1862
+ expectedChainId: expected,
1863
+ actualChainId: actual,
1864
+ cause: error instanceof Error ? error.message : String(error)
1865
+ }
1883
1866
  );
1884
1867
  }
1885
- const json = await res.json();
1886
- if (!json.success) {
1868
+ const after = await pub.getChainId();
1869
+ if (after !== expected) {
1887
1870
  throw new OwneyError(
1888
- "API_ROUTING_FAILED",
1889
- `Routing API request failed: ${json.message}`,
1890
- { message: json.message }
1891
- );
1892
- }
1893
- return json.data;
1894
- }
1895
-
1896
- // src/lib/health-report.ts
1897
- var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1898
- async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
1899
- try {
1900
- await fetch(`${baseUrl}/api/v1/agent/health-report`, {
1901
- method: "POST",
1902
- headers: {
1903
- "Content-Type": "application/json",
1904
- "x-owney-api-key": apiKey
1905
- },
1906
- body: JSON.stringify({
1907
- agent_type: agentType,
1908
- error_code: errorCode,
1909
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1910
- })
1911
- });
1912
- } catch (err) {
1913
- console.warn(
1914
- `[owney-sdk] health-report failed for agent "${agentType}":`,
1915
- err instanceof Error ? err.message : err
1916
- );
1917
- }
1918
- }
1919
- async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
1920
- try {
1921
- return await fn();
1922
- } catch (err) {
1923
- const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
1924
- void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
1925
- throw err;
1926
- }
1927
- }
1928
-
1929
- // src/lib/helpers/withdraw-helper.ts
1930
- import { parseUnits } from "viem";
1931
- function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
1932
- const target = asset.toUpperCase();
1933
- return agents.map((agent) => {
1934
- const agentBalance = aggregated[agent.id];
1935
- const tokenBalance = agentBalance?.tokens.find(
1936
- (t) => t.chainId === chainId && t.asset.toUpperCase() === target
1871
+ "CHAIN_MISMATCH",
1872
+ `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
1873
+ { expectedChainId: expected, actualChainId: after }
1937
1874
  );
1938
- if (!tokenBalance) return { agent, balance: 0n };
1939
- return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
1940
- });
1941
- }
1942
- function planProportionalShares(balances, requested, totalAvailable) {
1943
- const plans = balances.map(({ agent, balance }) => ({
1944
- agent,
1945
- balance,
1946
- planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
1947
- }));
1948
- const assigned = plans.reduce((s, p) => s + p.planned, 0n);
1949
- let remainder = requested - assigned;
1950
- const byHeadroom = [...plans].sort((a, b) => {
1951
- const diff = b.balance - b.planned - (a.balance - a.planned);
1952
- return diff > 0n ? 1 : diff < 0n ? -1 : 0;
1953
- });
1954
- for (const p of byHeadroom) {
1955
- if (remainder === 0n) break;
1956
- const headroom = p.balance - p.planned;
1957
- if (headroom <= 0n) continue;
1958
- const take = headroom < remainder ? headroom : remainder;
1959
- p.planned += take;
1960
- remainder -= take;
1961
- }
1962
- return plans;
1963
- }
1964
- function planDisabledDrain(disabled, requested) {
1965
- const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
1966
- const plans = [];
1967
- let remaining = requested;
1968
- for (const { agent, balance } of sorted) {
1969
- if (remaining === 0n) {
1970
- plans.push({ agent, balance, planned: 0n });
1971
- continue;
1972
- }
1973
- const take = balance < remaining ? balance : remaining;
1974
- plans.push({ agent, balance, planned: take });
1975
- remaining -= take;
1976
- }
1977
- return { plans, remaining };
1978
- }
1979
- function redistributeShare(plans, fromIndex, amount, candidatePool) {
1980
- const pool = candidatePool ?? plans.slice(fromIndex + 1);
1981
- const candidates = pool.filter((c) => c.balance - c.planned > 0n);
1982
- const totalHeadroom = candidates.reduce(
1983
- (s, c) => s + (c.balance - c.planned),
1984
- 0n
1985
- );
1986
- if (totalHeadroom === 0n) return;
1987
- let distributed = 0n;
1988
- for (const c of candidates) {
1989
- const headroom = c.balance - c.planned;
1990
- const proportional = headroom * amount / totalHeadroom;
1991
- const give = proportional > headroom ? headroom : proportional;
1992
- c.planned += give;
1993
- distributed += give;
1994
- }
1995
- let leftover = amount - distributed;
1996
- for (const c of candidates) {
1997
- if (leftover === 0n) break;
1998
- const headroom = c.balance - c.planned;
1999
- if (headroom <= 0n) continue;
2000
- const take = headroom < leftover ? headroom : leftover;
2001
- c.planned += take;
2002
- leftover -= take;
2003
- }
2004
- }
2005
- function sumWithdrawnAmount(results) {
2006
- return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
2007
- }
2008
-
2009
- // src/lib/helpers/account-apy-helper.ts
2010
- function aggregateApyByChainAndAsset(agentApys, agentBalances) {
2011
- const sums = {};
2012
- const weights = {};
2013
- for (const id of Object.keys(agentApys)) {
2014
- const cells = agentApys[id].apyByChainAndAsset;
2015
- const balance = agentBalances[id] ?? 0;
2016
- if (!cells || balance <= 0) continue;
2017
- for (const [chainKey, perAsset] of Object.entries(cells)) {
2018
- if (!perAsset) continue;
2019
- const chainId = Number(chainKey);
2020
- for (const [asset, apyValue] of Object.entries(perAsset)) {
2021
- const apy = Number(apyValue ?? 0);
2022
- if (apy === 0) continue;
2023
- sums[chainId] ??= {};
2024
- weights[chainId] ??= {};
2025
- sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
2026
- weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
2027
- }
2028
- }
2029
- }
2030
- const out = {};
2031
- for (const chainKey of Object.keys(sums)) {
2032
- const chainId = Number(chainKey);
2033
- const perAssetOut = {};
2034
- for (const asset of Object.keys(sums[chainId])) {
2035
- const w = weights[chainId][asset];
2036
- if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
2037
- }
2038
- if (Object.keys(perAssetOut).length > 0) {
2039
- out[chainId] = perAssetOut;
2040
- }
2041
1875
  }
2042
- return out;
2043
1876
  }
2044
1877
 
2045
- // src/client.ts
2046
- import {
2047
- createPublicClient as createPublicClient2,
2048
- createWalletClient,
2049
- custom
2050
- } from "viem";
2051
- import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
2052
-
2053
1878
  // src/lib/transfer-auth.ts
2054
1879
  import { bytesToHex } from "viem";
2055
1880
  var ERC20_META_ABI = [
@@ -2073,10 +1898,10 @@ function buildTransferWithAuthorizationTypedData(input) {
2073
1898
  message: input.message
2074
1899
  };
2075
1900
  }
2076
- async function readTokenMeta(publicClient, token) {
1901
+ async function readTokenMeta(publicClient, token2) {
2077
1902
  const [tokenName, tokenVersion] = await Promise.all([
2078
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
2079
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
1903
+ publicClient.readContract({ address: token2, abi: ERC20_META_ABI, functionName: "name" }),
1904
+ publicClient.readContract({ address: token2, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
2080
1905
  ]);
2081
1906
  return { tokenName, tokenVersion };
2082
1907
  }
@@ -2087,16 +1912,17 @@ function randomAuthNonce() {
2087
1912
  }
2088
1913
 
2089
1914
  // src/lib/sponsor-client.ts
2090
- var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1915
+ var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2091
1916
  async function postSponsorTransferAuth(input) {
2092
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1917
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2093
1918
  let res;
2094
1919
  try {
2095
- res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
1920
+ res = await fetch(`${base5}/api/v1/sponsor/erc20-transfer-auth`, {
2096
1921
  method: "POST",
2097
1922
  headers: {
2098
1923
  "content-type": "application/json",
2099
- "x-owney-api-key": input.apiKey
1924
+ "x-owney-api-key": input.apiKey,
1925
+ ...input.yieldseekerSignature ? { "x-signature": input.yieldseekerSignature } : {}
2100
1926
  },
2101
1927
  body: JSON.stringify(input.body)
2102
1928
  });
@@ -2129,14 +1955,15 @@ async function postSponsorTransferAuth(input) {
2129
1955
  return parsed.data;
2130
1956
  }
2131
1957
  async function postSponsorPermit2Transfer(input) {
2132
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1958
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2133
1959
  let res;
2134
1960
  try {
2135
- res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
1961
+ res = await fetch(`${base5}/api/v1/sponsor/permit2-transfer`, {
2136
1962
  method: "POST",
2137
1963
  headers: {
2138
1964
  "content-type": "application/json",
2139
- "x-owney-api-key": input.apiKey
1965
+ "x-owney-api-key": input.apiKey,
1966
+ ...input.yieldseekerSignature ? { "x-signature": input.yieldseekerSignature } : {}
2140
1967
  },
2141
1968
  body: JSON.stringify(input.body)
2142
1969
  });
@@ -2167,11 +1994,11 @@ async function postSponsorPermit2Transfer(input) {
2167
1994
  return parsed.data;
2168
1995
  }
2169
1996
  async function getSponsorRelayerAddress(input) {
2170
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1997
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2171
1998
  let res;
2172
1999
  try {
2173
2000
  res = await fetch(
2174
- `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2001
+ `${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2175
2002
  {
2176
2003
  headers: { "x-owney-api-key": input.apiKey }
2177
2004
  }
@@ -2264,217 +2091,1394 @@ function randomPermit2Nonce() {
2264
2091
  globalThis.crypto.getRandomValues(bytes);
2265
2092
  return BigInt(bytesToHex2(bytes));
2266
2093
  }
2267
- async function readPermit2Allowance(publicClient, token, owner) {
2094
+ async function readPermit2Allowance(publicClient, token2, owner) {
2268
2095
  return publicClient.readContract({
2269
- address: token,
2096
+ address: token2,
2270
2097
  abi: ERC20_ALLOWANCE_ABI,
2271
2098
  functionName: "allowance",
2272
2099
  args: [owner, PERMIT2_ADDRESS]
2273
2100
  });
2274
2101
  }
2275
- async function readErc20Balance(publicClient, token, owner) {
2276
- return publicClient.readContract({
2277
- address: token,
2278
- abi: ERC20_ALLOWANCE_ABI,
2279
- functionName: "balanceOf",
2280
- args: [owner]
2102
+ async function readErc20Balance(publicClient, token2, owner) {
2103
+ return publicClient.readContract({
2104
+ address: token2,
2105
+ abi: ERC20_ALLOWANCE_ABI,
2106
+ functionName: "balanceOf",
2107
+ args: [owner]
2108
+ });
2109
+ }
2110
+
2111
+ // src/lib/sponsored-deposit.ts
2112
+ var AUTH_WINDOW_SECONDS = 15 * 60;
2113
+ var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
2114
+ function provideDepositVerificationContext(callback, context) {
2115
+ callback[verificationSetter]?.(context);
2116
+ }
2117
+ function makeVerificationAwareDepositCallback(implementation) {
2118
+ let nextVerification;
2119
+ const callback = async (smartWallet, chainId, amount) => {
2120
+ const verification = nextVerification;
2121
+ nextVerification = void 0;
2122
+ return implementation(smartWallet, chainId, amount, verification);
2123
+ };
2124
+ Object.defineProperty(callback, verificationSetter, {
2125
+ value: (context) => {
2126
+ nextVerification = context;
2127
+ }
2128
+ });
2129
+ return callback;
2130
+ }
2131
+ function makeSponsoredDepositCallback(deps) {
2132
+ const post = deps.httpPost ?? postSponsorTransferAuth;
2133
+ return makeVerificationAwareDepositCallback(
2134
+ async (smartWallet, chainId, amount, verification) => {
2135
+ const cid = chainId;
2136
+ const token2 = deps.tokenAddressByChain[cid];
2137
+ if (!token2) {
2138
+ throw new OwneyError(
2139
+ "CHAIN_UNSUPPORTED",
2140
+ `No sponsored token configured for chain ${chainId}`
2141
+ );
2142
+ }
2143
+ const pub = deps.getPublicClient(cid);
2144
+ const wallet = deps.getWalletClient(cid);
2145
+ await ensureWalletOnChain(pub, wallet, cid);
2146
+ try {
2147
+ const balance = await readErc20Balance(pub, token2, deps.ownerAddress);
2148
+ if (balance < BigInt(amount)) {
2149
+ throw new OwneyError(
2150
+ "DEPOSIT_INSUFFICIENT_BALANCE",
2151
+ "Insufficient balance for this deposit.",
2152
+ { token: token2, chainId: cid, balance: balance.toString(), amount }
2153
+ );
2154
+ }
2155
+ } catch (err) {
2156
+ if (err instanceof OwneyError) throw err;
2157
+ console.warn(
2158
+ "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
2159
+ err instanceof Error ? err.message : String(err)
2160
+ );
2161
+ }
2162
+ const { tokenName, tokenVersion } = await readTokenMeta(pub, token2);
2163
+ const validAfter = 0n;
2164
+ const validBefore = BigInt(
2165
+ Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
2166
+ );
2167
+ const nonce = randomAuthNonce();
2168
+ const typedData = buildTransferWithAuthorizationTypedData({
2169
+ token: token2,
2170
+ chainId: cid,
2171
+ tokenName,
2172
+ tokenVersion,
2173
+ message: {
2174
+ from: deps.ownerAddress,
2175
+ to: smartWallet,
2176
+ value: BigInt(amount),
2177
+ validAfter,
2178
+ validBefore,
2179
+ nonce
2180
+ }
2181
+ });
2182
+ const authSignature = await wallet.signTypedData({
2183
+ account: deps.ownerAddress,
2184
+ ...typedData
2185
+ });
2186
+ deps.onApproved?.();
2187
+ const result = await post({
2188
+ baseUrl: deps.baseUrl,
2189
+ apiKey: deps.apiKey,
2190
+ ...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
2191
+ body: {
2192
+ chainId: cid,
2193
+ token: token2,
2194
+ from: deps.ownerAddress,
2195
+ to: smartWallet,
2196
+ value: amount,
2197
+ validAfter: validAfter.toString(),
2198
+ validBefore: validBefore.toString(),
2199
+ nonce,
2200
+ authSignature,
2201
+ tokenName,
2202
+ tokenVersion
2203
+ }
2204
+ });
2205
+ return result.txHash;
2206
+ }
2207
+ );
2208
+ }
2209
+
2210
+ // src/agents/yieldseeker/yieldseeker.auth.ts
2211
+ import { SiweMessage, generateNonce } from "siwe";
2212
+ import {
2213
+ createPublicClient as createPublicClient2,
2214
+ createWalletClient,
2215
+ custom,
2216
+ getAddress
2217
+ } from "viem";
2218
+ import { base as base2 } from "viem/chains";
2219
+
2220
+ // src/agents/yieldseeker/yieldseeker.auth-cache.ts
2221
+ var KEY_PREFIX2 = "owney.yieldseeker.session";
2222
+ var storage2 = () => {
2223
+ if (typeof window === "undefined") return null;
2224
+ try {
2225
+ return window.localStorage;
2226
+ } catch {
2227
+ return null;
2228
+ }
2229
+ };
2230
+ var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
2231
+ var memorySessions2 = /* @__PURE__ */ new Map();
2232
+ var isValidSession = (session) => {
2233
+ if (!session?.token) return false;
2234
+ try {
2235
+ const parsed = JSON.parse(atob(session.token));
2236
+ return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
2237
+ } catch {
2238
+ return false;
2239
+ }
2240
+ };
2241
+ var readYieldseekerSession = (address, chainId) => {
2242
+ if (typeof window === "undefined") return null;
2243
+ const key2 = buildKey2(address, chainId);
2244
+ const store = storage2();
2245
+ let raw = null;
2246
+ try {
2247
+ raw = store?.getItem(key2) ?? null;
2248
+ } catch {
2249
+ raw = null;
2250
+ }
2251
+ if (raw) {
2252
+ try {
2253
+ const parsed = JSON.parse(raw);
2254
+ if (isValidSession(parsed)) return parsed.token;
2255
+ } catch {
2256
+ }
2257
+ memorySessions2.delete(key2);
2258
+ try {
2259
+ store?.removeItem(key2);
2260
+ } catch {
2261
+ }
2262
+ return null;
2263
+ }
2264
+ const cached = memorySessions2.get(key2);
2265
+ if (isValidSession(cached)) return cached.token;
2266
+ if (cached) memorySessions2.delete(key2);
2267
+ return null;
2268
+ };
2269
+ var writeYieldseekerSession = (address, chainId, token2) => {
2270
+ if (typeof window === "undefined") return;
2271
+ const session = { token: token2 };
2272
+ if (!isValidSession(session)) return;
2273
+ const key2 = buildKey2(address, chainId);
2274
+ memorySessions2.set(key2, session);
2275
+ const store = storage2();
2276
+ try {
2277
+ store?.setItem(key2, JSON.stringify(session));
2278
+ } catch {
2279
+ }
2280
+ };
2281
+ var clearYieldseekerSession = (address, chainId) => {
2282
+ const key2 = buildKey2(address, chainId);
2283
+ memorySessions2.delete(key2);
2284
+ const store = storage2();
2285
+ try {
2286
+ store?.removeItem(key2);
2287
+ } catch {
2288
+ }
2289
+ };
2290
+
2291
+ // src/agents/yieldseeker/yieldseeker.auth.ts
2292
+ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
2293
+ return new SiweMessage({
2294
+ domain: "yieldseeker.xyz",
2295
+ address: getAddress(address),
2296
+ uri: "https://yieldseeker.xyz",
2297
+ version: "1",
2298
+ chainId,
2299
+ nonce: (dependencies.nonce ?? generateNonce)(),
2300
+ issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
2301
+ }).prepareMessage();
2302
+ }
2303
+ function encodeYieldseekerAuthToken(token2) {
2304
+ const bytes = new TextEncoder().encode(JSON.stringify(token2));
2305
+ let binary = "";
2306
+ for (const byte of bytes) binary += String.fromCharCode(byte);
2307
+ return btoa(binary);
2308
+ }
2309
+ var YieldseekerAuth = class {
2310
+ constructor(dependencies = {}) {
2311
+ this.dependencies = dependencies;
2312
+ }
2313
+ dependencies;
2314
+ tokens = /* @__PURE__ */ new Map();
2315
+ pending = /* @__PURE__ */ new Map();
2316
+ scopes = /* @__PURE__ */ new Map();
2317
+ key(state, chainId) {
2318
+ return `${state.walletAddress.toLowerCase()}:${chainId}`;
2319
+ }
2320
+ async getToken(state, chainId) {
2321
+ const key2 = this.key(state, chainId);
2322
+ const scope = { address: state.walletAddress, chainId };
2323
+ this.scopes.set(key2, scope);
2324
+ const cached = this.tokens.get(key2);
2325
+ if (cached) return cached;
2326
+ const persisted = readYieldseekerSession(scope.address, scope.chainId);
2327
+ if (persisted) {
2328
+ this.tokens.set(key2, persisted);
2329
+ return persisted;
2330
+ }
2331
+ const inFlight = this.pending.get(key2);
2332
+ if (inFlight) return inFlight;
2333
+ const request = this.sign(state, chainId).then((token2) => {
2334
+ this.tokens.set(key2, token2);
2335
+ writeYieldseekerSession(scope.address, scope.chainId, token2);
2336
+ return token2;
2337
+ });
2338
+ this.pending.set(key2, request);
2339
+ try {
2340
+ return await request;
2341
+ } finally {
2342
+ this.pending.delete(key2);
2343
+ }
2344
+ }
2345
+ clear(state, chainId) {
2346
+ if (!state || chainId === void 0) {
2347
+ for (const scope of this.scopes.values()) {
2348
+ clearYieldseekerSession(scope.address, scope.chainId);
2349
+ }
2350
+ this.tokens.clear();
2351
+ this.pending.clear();
2352
+ this.scopes.clear();
2353
+ return;
2354
+ }
2355
+ const key2 = this.key(state, chainId);
2356
+ this.tokens.delete(key2);
2357
+ this.pending.delete(key2);
2358
+ this.scopes.delete(key2);
2359
+ clearYieldseekerSession(state.walletAddress, chainId);
2360
+ }
2361
+ async sign(state, chainId) {
2362
+ const account = getAddress(state.walletAddress);
2363
+ const publicClient = createPublicClient2({
2364
+ chain: base2,
2365
+ transport: custom(state.provider)
2366
+ });
2367
+ const walletClient = createWalletClient({
2368
+ account,
2369
+ chain: base2,
2370
+ transport: custom(state.provider)
2371
+ });
2372
+ await ensureWalletOnChain(
2373
+ publicClient,
2374
+ walletClient,
2375
+ 8453
2376
+ );
2377
+ const message = createYieldseekerSiweMessage(
2378
+ account,
2379
+ chainId,
2380
+ this.dependencies
2381
+ );
2382
+ const signature = await walletClient.signMessage({ account, message });
2383
+ return encodeYieldseekerAuthToken({ message, signature });
2384
+ }
2385
+ };
2386
+
2387
+ // src/agents/yieldseeker/yieldseeker.client.ts
2388
+ var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2389
+ function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
2390
+ return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
2391
+ }
2392
+ var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
2393
+ var YieldseekerApiError = class extends Error {
2394
+ constructor(status, providerCode, responseFields) {
2395
+ super(`Yieldseeker request failed (${status}): ${providerCode}`);
2396
+ this.status = status;
2397
+ this.providerCode = providerCode;
2398
+ this.responseFields = responseFields;
2399
+ this.name = "YieldseekerApiError";
2400
+ }
2401
+ status;
2402
+ providerCode;
2403
+ responseFields;
2404
+ get isAuthenticationError() {
2405
+ return this.status === 401 || this.status === 403;
2406
+ }
2407
+ };
2408
+ function providerError(body, fallback) {
2409
+ if (!body || typeof body !== "object") return { code: fallback };
2410
+ const record = body;
2411
+ return {
2412
+ code: typeof record.message === "string" ? record.message : fallback,
2413
+ fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
2414
+ };
2415
+ }
2416
+ var YieldseekerApiClient = class {
2417
+ constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch) {
2418
+ this.owneyApiKey = owneyApiKey;
2419
+ this.baseUrl = baseUrl;
2420
+ this.fetchFn = fetchFn;
2421
+ }
2422
+ owneyApiKey;
2423
+ baseUrl;
2424
+ fetchFn;
2425
+ async request(path, options = {}) {
2426
+ const controller = new AbortController();
2427
+ const timer = setTimeout(
2428
+ () => controller.abort(),
2429
+ options.timeoutMs ?? 15e3
2430
+ );
2431
+ try {
2432
+ const response = await this.fetchFn(`${this.baseUrl}${path}`, {
2433
+ method: options.method ?? "GET",
2434
+ headers: {
2435
+ "Content-Type": "application/json",
2436
+ "x-owney-api-key": this.owneyApiKey,
2437
+ ...options.signature ? { "X-Signature": options.signature } : {}
2438
+ },
2439
+ body: options.body ? JSON.stringify(options.body) : void 0,
2440
+ signal: controller.signal
2441
+ });
2442
+ const payload = await response.json().catch(() => null);
2443
+ if (!response.ok) {
2444
+ const error = providerError(payload, `HTTP_${response.status}`);
2445
+ throw new YieldseekerApiError(
2446
+ response.status,
2447
+ error.code,
2448
+ error.fields
2449
+ );
2450
+ }
2451
+ if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
2452
+ return payload.data;
2453
+ }
2454
+ return payload;
2455
+ } catch (error) {
2456
+ if (error instanceof YieldseekerApiError) throw error;
2457
+ if (error instanceof DOMException && error.name === "AbortError") {
2458
+ throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
2459
+ }
2460
+ throw new YieldseekerApiError(0, "NETWORK_ERROR", {
2461
+ cause: error instanceof Error ? error.message : String(error)
2462
+ });
2463
+ } finally {
2464
+ clearTimeout(timer);
2465
+ }
2466
+ }
2467
+ };
2468
+
2469
+ // src/agents/yieldseeker/yieldseeker.mapper.ts
2470
+ import { formatUnits } from "viem";
2471
+ var REBALANCE_AMOUNT_DECIMALS = {
2472
+ USDC: 6,
2473
+ WETH: 18
2474
+ };
2475
+ function rebalanceAmount(value, tokenSymbol) {
2476
+ const decimals = REBALANCE_AMOUNT_DECIMALS[tokenSymbol.toUpperCase()];
2477
+ if (decimals === void 0) {
2478
+ return invalid(
2479
+ "history",
2480
+ `unsupported rebalance token ${tokenSymbol || "<empty>"}`
2481
+ );
2482
+ }
2483
+ try {
2484
+ return formatUnits(BigInt(value), decimals);
2485
+ } catch {
2486
+ return invalid("history", `invalid rebalance amount ${value}`);
2487
+ }
2488
+ }
2489
+ function invalid(endpoint, detail) {
2490
+ throw new OwneyError(
2491
+ "AGENT_INVALID_RESPONSE",
2492
+ `Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
2493
+ { endpoint, detail },
2494
+ "yieldseeker"
2495
+ );
2496
+ }
2497
+ function requireObject(value, endpoint) {
2498
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2499
+ return invalid(endpoint, "expected an object");
2500
+ }
2501
+ return value;
2502
+ }
2503
+ function requireEnvelope(value, key2, endpoint) {
2504
+ const root = requireObject(value, endpoint);
2505
+ return requireObject(root[key2], endpoint);
2506
+ }
2507
+ function token(value) {
2508
+ return {
2509
+ chain: value.chain,
2510
+ chainId: value.chainId,
2511
+ asset: value.asset,
2512
+ amount: String(value.amount)
2513
+ };
2514
+ }
2515
+ function position(value) {
2516
+ return {
2517
+ chain: String(value.chain),
2518
+ protocol: String(value.protocol),
2519
+ ...value.protocolId ? { protocolId: value.protocolId } : {},
2520
+ ...value.pool ? { pool: value.pool } : {},
2521
+ asset: String(value.asset),
2522
+ amount: String(value.amount),
2523
+ ...value.amountRaw !== void 0 ? { amountRaw: value.amountRaw } : {},
2524
+ ...typeof value.apy === "number" ? { apy: value.apy } : {},
2525
+ ...typeof value.tvl === "number" ? { tvl: value.tvl } : {},
2526
+ ...typeof value.liquidity === "number" ? { liquidity: value.liquidity } : {}
2527
+ };
2528
+ }
2529
+ function mapApyMatrix(matrix) {
2530
+ const mapped = {};
2531
+ for (const [chain, assets] of Object.entries(matrix ?? {})) {
2532
+ const chainId = Number(chain);
2533
+ if (chainId !== 8453 || !assets || typeof assets !== "object") continue;
2534
+ mapped[8453] = {};
2535
+ for (const [asset, apy] of Object.entries(assets)) {
2536
+ if ((asset === "USDC" || asset === "WETH") && typeof apy === "number") {
2537
+ mapped[8453][asset] = apy;
2538
+ }
2539
+ }
2540
+ }
2541
+ return mapped;
2542
+ }
2543
+ function mapYieldseekerProfile(value) {
2544
+ const profile = requireEnvelope(
2545
+ value,
2546
+ "profile",
2547
+ "profile"
2548
+ );
2549
+ if (typeof profile.address !== "string" || typeof profile.smartWallet !== "string" || !Array.isArray(profile.chains) || typeof profile.hasActiveSessionKey !== "boolean" || !Array.isArray(profile.protocols)) {
2550
+ return invalid("profile", "missing required fields");
2551
+ }
2552
+ return {
2553
+ address: profile.address,
2554
+ smartWallet: profile.smartWallet,
2555
+ chains: profile.chains.map(Number),
2556
+ hasActiveSessionKey: profile.hasActiveSessionKey,
2557
+ protocols: profile.protocols.map(String)
2558
+ };
2559
+ }
2560
+ function mapYieldseekerBalances(value) {
2561
+ const balances = requireEnvelope(
2562
+ value,
2563
+ "balances",
2564
+ "balances"
2565
+ );
2566
+ if (typeof balances.smartWallet !== "string" || typeof balances.totalBalance !== "string" || typeof balances.totalBalanceAsset !== "string" || !Array.isArray(balances.tokens) || !Array.isArray(balances.positions)) {
2567
+ return invalid("balances", "missing required fields");
2568
+ }
2569
+ return {
2570
+ smartWallet: balances.smartWallet,
2571
+ totalBalance: balances.totalBalance,
2572
+ totalBalanceAsset: balances.totalBalanceAsset.toLowerCase(),
2573
+ tokens: balances.tokens.map(token),
2574
+ positions: balances.positions.map(position)
2575
+ };
2576
+ }
2577
+ function mapYieldseekerEarnings(value) {
2578
+ const earnings = requireEnvelope(
2579
+ value,
2580
+ "earnings",
2581
+ "earnings"
2582
+ );
2583
+ if (typeof earnings.smartWallet !== "string" || typeof earnings.lifetimeEarnings !== "number" || !Array.isArray(earnings.tokens)) {
2584
+ return invalid("earnings", "missing required fields");
2585
+ }
2586
+ return {
2587
+ smartWallet: earnings.smartWallet,
2588
+ lifetimeEarnings: earnings.lifetimeEarnings,
2589
+ tokens: earnings.tokens.map(token)
2590
+ };
2591
+ }
2592
+ function mapYieldseekerApy(value) {
2593
+ const apy = requireEnvelope(
2594
+ value,
2595
+ "apy",
2596
+ "apy"
2597
+ );
2598
+ if (typeof apy.walletAddress !== "string" || typeof apy.weightedApyAfterFee !== "number" || !apy.apyByChainAndAsset || typeof apy.apyByChainAndAsset !== "object" || !Array.isArray(apy.history)) {
2599
+ return invalid("apy", "missing required fields");
2600
+ }
2601
+ return {
2602
+ walletAddress: apy.walletAddress,
2603
+ weightedApyAfterFee: apy.weightedApyAfterFee,
2604
+ apyByChainAndAsset: mapApyMatrix(apy.apyByChainAndAsset),
2605
+ history: apy.history.map((point) => ({
2606
+ date: String(point.date),
2607
+ apy: Number(point.apy)
2608
+ }))
2609
+ };
2610
+ }
2611
+ function historyAction(value) {
2612
+ const normalized = value.trim().toLowerCase();
2613
+ if (normalized === "rebalance") return "Rebalance";
2614
+ if (normalized === "deposit") return "Deposit";
2615
+ if (normalized === "top up" || normalized === "topup") return "Top up";
2616
+ if (normalized === "withdraw" || normalized === "withdrawal")
2617
+ return "Withdraw";
2618
+ if (normalized === "earned" || normalized === "earnings") return "Earned";
2619
+ return invalid("history", `unsupported action ${value}`);
2620
+ }
2621
+ function historyItem(value) {
2622
+ return {
2623
+ agent: "yieldseeker",
2624
+ action: historyAction(value.action),
2625
+ date: String(value.date),
2626
+ oldApy: value.oldApy === null ? null : String(value.oldApy),
2627
+ newApy: value.newApy === null ? null : String(value.newApy),
2628
+ transactions: (value.transactions ?? []).map((transaction) => ({
2629
+ txHashes: transaction.txHashes.map(String),
2630
+ ...typeof transaction.chainId === "number" ? { chainId: transaction.chainId } : {},
2631
+ ...typeof transaction.tokenSymbol === "string" ? { tokenSymbol: transaction.tokenSymbol } : {},
2632
+ ...typeof transaction.amount === "string" ? { amount: transaction.amount } : {}
2633
+ })),
2634
+ rebalanceLog: (value.rebalanceLog ?? []).map((entry) => ({
2635
+ fromProtocol: String(entry.fromProtocol),
2636
+ toProtocol: String(entry.toProtocol),
2637
+ ...entry.fromPool ? { fromPool: entry.fromPool } : {},
2638
+ ...entry.toPool ? { toPool: entry.toPool } : {},
2639
+ tokenSymbol: String(entry.tokenSymbol),
2640
+ // Unlike Yieldseeker's other read-response amounts, live history
2641
+ // rebalance amounts are returned in the token's smallest unit. Normalize
2642
+ // them at the adapter boundary so every Owney consumer receives the
2643
+ // shared human-readable decimal shape.
2644
+ amount: rebalanceAmount(String(entry.amount), String(entry.tokenSymbol)),
2645
+ status: entry.status
2646
+ }))
2647
+ };
2648
+ }
2649
+ function mapYieldseekerHistory(value) {
2650
+ const history = requireEnvelope(
2651
+ value,
2652
+ "history",
2653
+ "history"
2654
+ );
2655
+ if (!Array.isArray(history.data) || typeof history.hasMore !== "boolean") {
2656
+ return invalid("history", "missing required fields");
2657
+ }
2658
+ return {
2659
+ data: history.data.map(historyItem),
2660
+ hasMore: history.hasMore,
2661
+ ...history.hasMore && typeof history.nextCursor === "string" ? { nextCursor: history.nextCursor } : {}
2662
+ };
2663
+ }
2664
+ function mapYieldseekerAgentApy(value) {
2665
+ const agentApy = requireEnvelope(
2666
+ value,
2667
+ "agentApy",
2668
+ "agent APY"
2669
+ );
2670
+ if (typeof agentApy.averageApy !== "number") {
2671
+ return invalid("agent APY", "missing averageApy");
2672
+ }
2673
+ return {
2674
+ averageApy: agentApy.averageApy,
2675
+ ...agentApy.detailedApys ? {
2676
+ detailedApys: {
2677
+ apyPerAsset: mapApyMatrix(agentApy.detailedApys.apyPerAsset)
2678
+ }
2679
+ } : {}
2680
+ };
2681
+ }
2682
+
2683
+ // src/agents/yieldseeker/yieldseeker.agent.ts
2684
+ function query(params) {
2685
+ const search = new URLSearchParams();
2686
+ for (const [key2, value] of Object.entries(params)) {
2687
+ if (value !== void 0) search.set(key2, String(value));
2688
+ }
2689
+ const encoded = search.toString();
2690
+ return encoded ? `?${encoded}` : "";
2691
+ }
2692
+ var YieldseekerAgent = class {
2693
+ id = "yieldseeker";
2694
+ balanceComposition = "tokens-plus-positions";
2695
+ supportedChainIds = [8453];
2696
+ supportedAssets = [
2697
+ {
2698
+ chainId: 8453,
2699
+ chain: "BASE",
2700
+ assets: [
2701
+ { symbol: "USDC", minDepositAmount: "1" },
2702
+ { symbol: "WETH", minDepositAmount: "1" }
2703
+ ]
2704
+ }
2705
+ ];
2706
+ api;
2707
+ auth;
2708
+ transactionExecutor;
2709
+ unwindReceiptWaiter;
2710
+ activated = /* @__PURE__ */ new Set();
2711
+ constructor(owneyApiKey, options = {}) {
2712
+ this.api = new YieldseekerApiClient(
2713
+ owneyApiKey,
2714
+ options.baseUrl ?? getYieldseekerProxyBaseUrl(),
2715
+ options.fetchFn
2716
+ );
2717
+ this.auth = new YieldseekerAuth(options.auth);
2718
+ this.transactionExecutor = options.transactionExecutor;
2719
+ this.unwindReceiptWaiter = options.unwindReceiptWaiter;
2720
+ }
2721
+ async disconnect() {
2722
+ this.auth.clear();
2723
+ this.activated.clear();
2724
+ }
2725
+ async activateAgent(state, chainId, asset) {
2726
+ this.assertChain(chainId);
2727
+ await this.auth.getToken(state, chainId);
2728
+ if (asset === "USDC" || asset === "WETH") {
2729
+ await this.ensureActivated(state, chainId, asset);
2730
+ }
2731
+ }
2732
+ async deposit(state, chainId, amount, asset, depositCallback) {
2733
+ this.assertChain(chainId);
2734
+ this.assertAsset(asset);
2735
+ if (BigInt(amount) <= 0n) {
2736
+ throw new OwneyError(
2737
+ "DEPOSIT_AMOUNT_BELOW_MINIMUM",
2738
+ "Yieldseeker deposits must be greater than zero.",
2739
+ { amount, minDepositAmount: "1" },
2740
+ this.id
2741
+ );
2742
+ }
2743
+ await this.ensureActivated(state, chainId, asset);
2744
+ const response = await this.walletRequest(
2745
+ state,
2746
+ chainId,
2747
+ "/deposits",
2748
+ { method: "POST", body: { chainId, asset, amount } }
2749
+ );
2750
+ const deposit = response?.deposit;
2751
+ if (!deposit?.transaction || typeof deposit.amount !== "string" || deposit.amount !== amount || typeof deposit.smartWallet !== "string" || !isAddress(deposit.smartWallet)) {
2752
+ throw this.invalidResponse("deposit");
2753
+ }
2754
+ if (depositCallback) {
2755
+ provideDepositVerificationContext(depositCallback, {
2756
+ agentId: "yieldseeker",
2757
+ // Re-read after /deposits so an authentication retry cannot forward
2758
+ // the stale token that Yieldseeker just rejected.
2759
+ signature: await this.auth.getToken(state, chainId)
2760
+ });
2761
+ }
2762
+ const txHash = depositCallback ? await depositCallback(deposit.smartWallet, chainId, amount) : await this.submitTransaction(state, chainId, deposit.transaction);
2763
+ const confirmation = await this.walletRequest(
2764
+ state,
2765
+ chainId,
2766
+ "/deposits/confirm",
2767
+ { method: "POST", body: { chainId, asset } }
2768
+ );
2769
+ if (typeof confirmation?.confirmation?.autoseekQueued !== "boolean") {
2770
+ throw this.invalidResponse("deposit confirmation", {
2771
+ transactionHash: txHash,
2772
+ transactionConfirmed: true
2773
+ });
2774
+ }
2775
+ return {
2776
+ txHash,
2777
+ smartWallet: deposit.smartWallet,
2778
+ amount: deposit.amount
2779
+ };
2780
+ }
2781
+ async withdraw(state, chainId, asset, amount) {
2782
+ this.assertChain(chainId);
2783
+ this.assertAsset(asset);
2784
+ if (amount !== void 0 && BigInt(amount) <= 0n) {
2785
+ throw new OwneyError(
2786
+ "WITHDRAW_FAILED",
2787
+ "Yieldseeker withdrawals must be greater than zero.",
2788
+ { amount },
2789
+ this.id
2790
+ );
2791
+ }
2792
+ const balances = await this.getBalances(state, chainId);
2793
+ const decimals = asset === "USDC" ? 6 : 18;
2794
+ const targetAsset = asset.toUpperCase();
2795
+ const idle = balances.tokens.filter(
2796
+ (token2) => token2.chainId === chainId && token2.asset.toUpperCase() === targetAsset
2797
+ ).reduce((total, token2) => total + parseUnits(token2.amount, decimals), 0n);
2798
+ const positions = (balances.positions ?? []).filter(
2799
+ (position2) => this.positionMatchesChain(position2.chain, chainId) && position2.asset.toUpperCase() === targetAsset
2800
+ ).map((position2) => ({
2801
+ position: position2,
2802
+ amount: position2.amountRaw !== void 0 ? BigInt(position2.amountRaw) : parseUnits(position2.amount, decimals)
2803
+ }));
2804
+ const deployed = positions.reduce(
2805
+ (total, position2) => total + position2.amount,
2806
+ 0n
2807
+ );
2808
+ const totalAvailable = idle + deployed;
2809
+ const requested = amount === void 0 ? totalAvailable : BigInt(amount);
2810
+ if (requested > totalAvailable) {
2811
+ throw new OwneyError(
2812
+ "WITHDRAW_INSUFFICIENT_BALANCE",
2813
+ `Requested withdrawal "${requested.toString()}" exceeds available Yieldseeker balance "${totalAvailable.toString()}" for asset "${asset}".`,
2814
+ {
2815
+ asset,
2816
+ requested: requested.toString(),
2817
+ available: totalAvailable.toString()
2818
+ },
2819
+ this.id
2820
+ );
2821
+ }
2822
+ const requiredFromPositions = requested > idle ? requested - idle : 0n;
2823
+ const unwindPlans = [];
2824
+ let remainingToUnwind = requiredFromPositions;
2825
+ for (const { position: position2, amount: positionAmount } of positions) {
2826
+ if (remainingToUnwind === 0n) break;
2827
+ if (positionAmount <= 0n) continue;
2828
+ if (!position2.protocolId || !isAddress(position2.protocolId)) {
2829
+ throw this.invalidResponse("position", {
2830
+ reason: "A deployed position is missing its vault address.",
2831
+ protocol: position2.protocol,
2832
+ pool: position2.pool
2833
+ });
2834
+ }
2835
+ const unwindAmount = positionAmount < remainingToUnwind ? positionAmount : remainingToUnwind;
2836
+ unwindPlans.push({
2837
+ vaultAddress: getAddress2(position2.protocolId),
2838
+ amount: unwindAmount
2839
+ });
2840
+ remainingToUnwind -= unwindAmount;
2841
+ }
2842
+ if (remainingToUnwind > 0n) {
2843
+ throw this.invalidResponse("balances", {
2844
+ reason: "Deployed position balances could not cover the unwind.",
2845
+ required: requiredFromPositions.toString(),
2846
+ planned: (requiredFromPositions - remainingToUnwind).toString()
2847
+ });
2848
+ }
2849
+ for (const plan of unwindPlans) {
2850
+ const response2 = await this.walletRequest(
2851
+ state,
2852
+ chainId,
2853
+ "/positions/unwind",
2854
+ {
2855
+ method: "POST",
2856
+ body: {
2857
+ chainId,
2858
+ asset,
2859
+ vaultAddress: plan.vaultAddress,
2860
+ amount: plan.amount.toString()
2861
+ }
2862
+ }
2863
+ );
2864
+ const transactionHash = response2?.unwind?.transactionHash;
2865
+ if (!this.isTransactionHash(transactionHash)) {
2866
+ throw this.invalidResponse("position unwind");
2867
+ }
2868
+ await this.waitForUnwindReceipt(state, chainId, transactionHash);
2869
+ }
2870
+ const response = await this.walletRequest(
2871
+ state,
2872
+ chainId,
2873
+ "/withdrawals",
2874
+ {
2875
+ method: "POST",
2876
+ body: { chainId, asset, ...amount !== void 0 ? { amount } : {} }
2877
+ }
2878
+ );
2879
+ const withdrawal = response?.withdrawal;
2880
+ if (!withdrawal?.transaction || typeof withdrawal.amount !== "string") {
2881
+ throw this.invalidResponse("withdrawal");
2882
+ }
2883
+ const txHash = await this.submitTransaction(
2884
+ state,
2885
+ chainId,
2886
+ withdrawal.transaction
2887
+ );
2888
+ return {
2889
+ txHash,
2890
+ type: amount === void 0 ? "full" : "partial",
2891
+ amount: withdrawal.amount
2892
+ };
2893
+ }
2894
+ async getBalances(state, chainId) {
2895
+ return mapYieldseekerBalances(
2896
+ await this.walletRequest(
2897
+ state,
2898
+ chainId,
2899
+ `/balances${query({ chainId })}`
2900
+ )
2901
+ );
2902
+ }
2903
+ async getEarnings(state, chainId) {
2904
+ return mapYieldseekerEarnings(
2905
+ await this.walletRequest(
2906
+ state,
2907
+ chainId,
2908
+ `/earnings${query({ chainId })}`
2909
+ )
2910
+ );
2911
+ }
2912
+ async getAccountApy(state, chainId, days, tokenSymbol) {
2913
+ return mapYieldseekerApy(
2914
+ await this.walletRequest(
2915
+ state,
2916
+ chainId,
2917
+ `/apy${query({ chainId, days, tokenSymbol })}`
2918
+ )
2919
+ );
2920
+ }
2921
+ async getHistory(state, chainId, options) {
2922
+ return mapYieldseekerHistory(
2923
+ await this.walletRequest(
2924
+ state,
2925
+ chainId,
2926
+ `/history${query({
2927
+ chainId,
2928
+ limit: options?.limit ?? 10,
2929
+ cursor: options?.cursor,
2930
+ fromDate: options?.fromDate,
2931
+ toDate: options?.toDate
2932
+ })}`
2933
+ )
2934
+ );
2935
+ }
2936
+ async getUserProfile(state, chainId) {
2937
+ return mapYieldseekerProfile(
2938
+ await this.walletRequest(state, chainId, `/profile${query({ chainId })}`)
2939
+ );
2940
+ }
2941
+ async getAgentApy(days, options) {
2942
+ this.assertOptionalChain(options?.chainId);
2943
+ return mapYieldseekerAgentApy(
2944
+ await this.api.request(
2945
+ `/agent/apy${query({
2946
+ days,
2947
+ tokenSymbol: options?.tokenSymbol,
2948
+ chainId: options?.chainId
2949
+ })}`
2950
+ )
2951
+ );
2952
+ }
2953
+ async ensureActivated(state, chainId, asset) {
2954
+ const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${asset}`;
2955
+ if (this.activated.has(key2)) return;
2956
+ const response = await this.walletRequest(
2957
+ state,
2958
+ chainId,
2959
+ "/activation",
2960
+ { method: "POST", body: { chainId, asset } }
2961
+ );
2962
+ if (typeof response?.activation?.smartWallet !== "string" || typeof response.activation.deployed !== "boolean" || typeof response.activation.hasActiveSessionKey !== "boolean") {
2963
+ throw this.invalidResponse("activation");
2964
+ }
2965
+ this.activated.add(key2);
2966
+ }
2967
+ async walletRequest(state, chainId, path, options = {}) {
2968
+ this.assertChain(chainId);
2969
+ let signature = await this.auth.getToken(state, chainId);
2970
+ try {
2971
+ return await this.api.request(path, { ...options, signature });
2972
+ } catch (error) {
2973
+ if (!(error instanceof YieldseekerApiError)) throw error;
2974
+ if (error.isAuthenticationError) {
2975
+ this.auth.clear(state, chainId);
2976
+ signature = await this.auth.getToken(state, chainId);
2977
+ try {
2978
+ return await this.api.request(path, { ...options, signature });
2979
+ } catch (retryError) {
2980
+ throw this.mapApiError(retryError);
2981
+ }
2982
+ }
2983
+ throw this.mapApiError(error);
2984
+ }
2985
+ }
2986
+ mapApiError(error) {
2987
+ if (!(error instanceof YieldseekerApiError)) {
2988
+ return new OwneyError(
2989
+ "AGENT_API_ERROR",
2990
+ "Yieldseeker request failed.",
2991
+ { cause: error instanceof Error ? error.message : String(error) },
2992
+ this.id
2993
+ );
2994
+ }
2995
+ const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
2996
+ return new OwneyError(
2997
+ code,
2998
+ `Yieldseeker request failed: ${error.providerCode}.`,
2999
+ {
3000
+ statusCode: error.status,
3001
+ providerCode: error.providerCode,
3002
+ ...error.responseFields ? { fields: error.responseFields } : {}
3003
+ },
3004
+ this.id
3005
+ );
3006
+ }
3007
+ async submitTransaction(state, chainId, transaction) {
3008
+ if (this.transactionExecutor) {
3009
+ return this.transactionExecutor(state, chainId, transaction);
3010
+ }
3011
+ if (typeof transaction.from !== "string" || typeof transaction.to !== "string" || typeof transaction.data !== "string" || typeof transaction.value !== "string" || transaction.chainId !== chainId) {
3012
+ throw this.invalidResponse("transaction");
3013
+ }
3014
+ const account = getAddress2(state.walletAddress);
3015
+ if (getAddress2(transaction.from) !== account) {
3016
+ throw this.invalidResponse("transaction", {
3017
+ reason: "transaction.from does not match the connected wallet"
3018
+ });
3019
+ }
3020
+ const walletClient = createWalletClient2({
3021
+ account,
3022
+ chain: base3,
3023
+ transport: custom2(state.provider)
3024
+ });
3025
+ const publicClient = createPublicClient3({
3026
+ chain: base3,
3027
+ transport: custom2(state.provider)
3028
+ });
3029
+ await ensureWalletOnChain(
3030
+ publicClient,
3031
+ walletClient,
3032
+ 8453
3033
+ );
3034
+ const hash = await walletClient.sendTransaction({
3035
+ account,
3036
+ chain: base3,
3037
+ to: getAddress2(transaction.to),
3038
+ data: transaction.data,
3039
+ value: BigInt(transaction.value)
3040
+ });
3041
+ const receipt = await publicClient.waitForTransactionReceipt({
3042
+ hash,
3043
+ confirmations: 1
3044
+ });
3045
+ if (receipt.status !== "success") {
3046
+ throw new OwneyError(
3047
+ "AGENT_TRANSACTION_REVERTED",
3048
+ `Yieldseeker transaction reverted (${hash}).`,
3049
+ { transactionHash: hash },
3050
+ this.id
3051
+ );
3052
+ }
3053
+ return hash;
3054
+ }
3055
+ async waitForUnwindReceipt(state, chainId, transactionHash) {
3056
+ if (this.unwindReceiptWaiter) {
3057
+ await this.unwindReceiptWaiter(state, chainId, transactionHash);
3058
+ return;
3059
+ }
3060
+ const publicClient = createPublicClient3({
3061
+ chain: base3,
3062
+ transport: custom2(state.provider)
3063
+ });
3064
+ const receipt = await publicClient.waitForTransactionReceipt({
3065
+ hash: transactionHash,
3066
+ confirmations: 1
3067
+ });
3068
+ if (receipt.status !== "success") {
3069
+ throw new OwneyError(
3070
+ "AGENT_TRANSACTION_REVERTED",
3071
+ `Yieldseeker position unwind reverted (${transactionHash}).`,
3072
+ { transactionHash },
3073
+ this.id
3074
+ );
3075
+ }
3076
+ }
3077
+ positionMatchesChain(chain, chainId) {
3078
+ const normalized = chain.trim().toUpperCase();
3079
+ return normalized === String(chainId) || normalized === "BASE";
3080
+ }
3081
+ isTransactionHash(value) {
3082
+ return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
3083
+ }
3084
+ assertChain(chainId) {
3085
+ if (chainId !== 8453) {
3086
+ throw new OwneyError(
3087
+ "CHAIN_UNSUPPORTED",
3088
+ `Yieldseeker does not support chain ${chainId}.`,
3089
+ { chainId, supportedChainIds: [8453] },
3090
+ this.id
3091
+ );
3092
+ }
3093
+ }
3094
+ assertOptionalChain(chainId) {
3095
+ if (chainId !== void 0) this.assertChain(chainId);
3096
+ }
3097
+ assertAsset(asset) {
3098
+ if (asset !== "USDC" && asset !== "WETH") {
3099
+ throw new OwneyError(
3100
+ "ASSET_UNSUPPORTED",
3101
+ `Yieldseeker does not support asset ${asset}.`,
3102
+ { asset, supportedAssets: ["USDC", "WETH"] },
3103
+ this.id
3104
+ );
3105
+ }
3106
+ }
3107
+ invalidResponse(operation, details = {}) {
3108
+ return new OwneyError(
3109
+ "AGENT_INVALID_RESPONSE",
3110
+ `Yieldseeker returned an invalid ${operation} response.`,
3111
+ details,
3112
+ this.id
3113
+ );
3114
+ }
3115
+ };
3116
+
3117
+ // src/lib/routing-api.ts
3118
+ var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3119
+ async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
3120
+ const url = `${baseUrl}/api/v1/agent/org-config`;
3121
+ try {
3122
+ const res = await fetch(url, {
3123
+ method: "GET",
3124
+ headers: {
3125
+ "Content-Type": "application/json",
3126
+ "x-owney-api-key": `${apiKey}`
3127
+ }
3128
+ });
3129
+ if (!res.ok) {
3130
+ if (res.status !== 404) {
3131
+ console.warn(
3132
+ `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
3133
+ );
3134
+ }
3135
+ return null;
3136
+ }
3137
+ const json = await res.json();
3138
+ const policy = json.success ? json.data ?? null : null;
3139
+ debugLog(
3140
+ "owney-sdk",
3141
+ policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
3142
+ policy ?? void 0
3143
+ );
3144
+ return policy;
3145
+ } catch (error) {
3146
+ console.warn(
3147
+ "[owney-sdk] Could not read org agent config (non-fatal):",
3148
+ error instanceof Error ? error.message : String(error)
3149
+ );
3150
+ return null;
3151
+ }
3152
+ }
3153
+ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
3154
+ const url = `${baseUrl}/api/v1/agent/keys`;
3155
+ const res = await fetch(url, {
3156
+ method: "GET",
3157
+ headers: {
3158
+ "Content-Type": "application/json",
3159
+ "x-owney-api-key": `${apiKey}`
3160
+ }
3161
+ });
3162
+ if (!res.ok) {
3163
+ const text = await res.text().catch(() => "");
3164
+ throw new OwneyError(
3165
+ "API_ROUTING_ERROR",
3166
+ `Routing API error ${res.status}: ${text}`,
3167
+ { statusCode: res.status, responseBody: text }
3168
+ );
3169
+ }
3170
+ const json = await res.json();
3171
+ if (!json.success) {
3172
+ throw new OwneyError(
3173
+ "API_ROUTING_FAILED",
3174
+ `Routing API request failed: ${json.message}`,
3175
+ { message: json.message }
3176
+ );
3177
+ }
3178
+ return json.data;
3179
+ }
3180
+
3181
+ // src/lib/health-report.ts
3182
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3183
+ async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
3184
+ try {
3185
+ await fetch(`${baseUrl}/api/v1/agent/health-report`, {
3186
+ method: "POST",
3187
+ headers: {
3188
+ "Content-Type": "application/json",
3189
+ "x-owney-api-key": apiKey
3190
+ },
3191
+ body: JSON.stringify({
3192
+ agent_type: agentType,
3193
+ error_code: errorCode,
3194
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3195
+ })
3196
+ });
3197
+ } catch (err) {
3198
+ console.warn(
3199
+ `[owney-sdk] health-report failed for agent "${agentType}":`,
3200
+ err instanceof Error ? err.message : err
3201
+ );
3202
+ }
3203
+ }
3204
+ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
3205
+ try {
3206
+ return await fn();
3207
+ } catch (err) {
3208
+ const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
3209
+ void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
3210
+ throw err;
3211
+ }
3212
+ }
3213
+
3214
+ // src/lib/helpers/withdraw-helper.ts
3215
+ import { parseUnits as parseUnits2 } from "viem";
3216
+ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
3217
+ const target = asset.toUpperCase();
3218
+ return agents.map((agent) => {
3219
+ const agentBalance = aggregated[agent.id];
3220
+ const tokenBalance = agentBalance?.tokens.find(
3221
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
3222
+ );
3223
+ let balance = tokenBalance ? parseUnits2(tokenBalance.amount, decimals) : 0n;
3224
+ if (agent.balanceComposition === "tokens-plus-positions") {
3225
+ const chainNameById = {
3226
+ 1: "ETHEREUM",
3227
+ 8453: "BASE",
3228
+ 42161: "ARBITRUM"
3229
+ };
3230
+ const targetChain = chainNameById[chainId];
3231
+ for (const position2 of agentBalance?.positions ?? []) {
3232
+ const positionChain = position2.chain.trim().toUpperCase();
3233
+ const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
3234
+ if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
3235
+ if (position2.amountRaw !== void 0) {
3236
+ try {
3237
+ balance += BigInt(position2.amountRaw);
3238
+ continue;
3239
+ } catch {
3240
+ }
3241
+ }
3242
+ balance += parseUnits2(position2.amount, decimals);
3243
+ }
3244
+ }
3245
+ return { agent, balance };
3246
+ });
3247
+ }
3248
+ function planProportionalShares(balances, requested, totalAvailable) {
3249
+ const plans = balances.map(({ agent, balance }) => ({
3250
+ agent,
3251
+ balance,
3252
+ planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
3253
+ }));
3254
+ const assigned = plans.reduce((s, p) => s + p.planned, 0n);
3255
+ let remainder = requested - assigned;
3256
+ const byHeadroom = [...plans].sort((a, b) => {
3257
+ const diff = b.balance - b.planned - (a.balance - a.planned);
3258
+ return diff > 0n ? 1 : diff < 0n ? -1 : 0;
2281
3259
  });
3260
+ for (const p of byHeadroom) {
3261
+ if (remainder === 0n) break;
3262
+ const headroom = p.balance - p.planned;
3263
+ if (headroom <= 0n) continue;
3264
+ const take = headroom < remainder ? headroom : remainder;
3265
+ p.planned += take;
3266
+ remainder -= take;
3267
+ }
3268
+ return plans;
2282
3269
  }
2283
-
2284
- // src/lib/chain-guard.ts
2285
- var CHAIN_NAMES = {
2286
- 1: "Ethereum",
2287
- 8453: "Base",
2288
- 42161: "Arbitrum"
2289
- };
2290
- function chainName(chainId) {
2291
- return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
3270
+ function planDisabledDrain(disabled, requested) {
3271
+ const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
3272
+ (a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
3273
+ );
3274
+ const plans = [];
3275
+ let remaining = requested;
3276
+ for (const { agent, balance } of sorted) {
3277
+ if (remaining === 0n) {
3278
+ plans.push({ agent, balance, planned: 0n });
3279
+ continue;
3280
+ }
3281
+ const take = balance < remaining ? balance : remaining;
3282
+ plans.push({ agent, balance, planned: take });
3283
+ remaining -= take;
3284
+ }
3285
+ return { plans, remaining };
2292
3286
  }
2293
- async function ensureWalletOnChain(pub, wallet, expected) {
2294
- const actual = await pub.getChainId();
2295
- if (actual === expected) return;
2296
- try {
2297
- await wallet.switchChain({ id: expected });
2298
- } catch (error) {
2299
- throw new OwneyError(
2300
- "CHAIN_MISMATCH",
2301
- `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
2302
- {
2303
- expectedChainId: expected,
2304
- actualChainId: actual,
2305
- cause: error instanceof Error ? error.message : String(error)
2306
- }
2307
- );
3287
+ function redistributeShare(plans, fromIndex, amount, candidatePool) {
3288
+ const pool = candidatePool ?? plans.slice(fromIndex + 1);
3289
+ const candidates = pool.filter((c) => c.balance - c.planned > 0n);
3290
+ const totalHeadroom = candidates.reduce(
3291
+ (s, c) => s + (c.balance - c.planned),
3292
+ 0n
3293
+ );
3294
+ if (totalHeadroom === 0n) return;
3295
+ let distributed = 0n;
3296
+ for (const c of candidates) {
3297
+ const headroom = c.balance - c.planned;
3298
+ const proportional = headroom * amount / totalHeadroom;
3299
+ const give = proportional > headroom ? headroom : proportional;
3300
+ c.planned += give;
3301
+ distributed += give;
2308
3302
  }
2309
- const after = await pub.getChainId();
2310
- if (after !== expected) {
2311
- throw new OwneyError(
2312
- "CHAIN_MISMATCH",
2313
- `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
2314
- { expectedChainId: expected, actualChainId: after }
2315
- );
3303
+ let leftover = amount - distributed;
3304
+ for (const c of candidates) {
3305
+ if (leftover === 0n) break;
3306
+ const headroom = c.balance - c.planned;
3307
+ if (headroom <= 0n) continue;
3308
+ const take = headroom < leftover ? headroom : leftover;
3309
+ c.planned += take;
3310
+ leftover -= take;
2316
3311
  }
2317
3312
  }
3313
+ function sumWithdrawnAmount(results) {
3314
+ return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
3315
+ }
2318
3316
 
2319
- // src/lib/sponsored-deposit.ts
2320
- var AUTH_WINDOW_SECONDS = 15 * 60;
2321
- function makeSponsoredDepositCallback(deps) {
2322
- const post = deps.httpPost ?? postSponsorTransferAuth;
2323
- return async (smartWallet, chainId, amount) => {
2324
- const cid = chainId;
2325
- const token = deps.tokenAddressByChain[cid];
2326
- if (!token) {
2327
- throw new OwneyError(
2328
- "CHAIN_UNSUPPORTED",
2329
- `No sponsored token configured for chain ${chainId}`
2330
- );
3317
+ // src/lib/helpers/account-apy-helper.ts
3318
+ function balanceForApyScope(balance, chainId, tokenSymbol) {
3319
+ if (!tokenSymbol) {
3320
+ const total = Number(balance.totalBalance);
3321
+ return Number.isFinite(total) && total > 0 ? total : 0;
3322
+ }
3323
+ const normalizedToken = tokenSymbol.toUpperCase();
3324
+ return balance.tokens.reduce((total, token2) => {
3325
+ if (Number(token2.chainId) !== chainId || String(token2.asset).toUpperCase() !== normalizedToken) {
3326
+ return total;
2331
3327
  }
2332
- const pub = deps.getPublicClient(cid);
2333
- const wallet = deps.getWalletClient(cid);
2334
- await ensureWalletOnChain(pub, wallet, cid);
2335
- try {
2336
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2337
- if (balance < BigInt(amount)) {
2338
- throw new OwneyError(
2339
- "DEPOSIT_INSUFFICIENT_BALANCE",
2340
- "Insufficient balance for this deposit.",
2341
- { token, chainId: cid, balance: balance.toString(), amount }
2342
- );
2343
- }
2344
- } catch (err) {
2345
- if (err instanceof OwneyError) throw err;
2346
- console.warn(
2347
- "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
2348
- err instanceof Error ? err.message : String(err)
2349
- );
2350
- }
2351
- const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
2352
- const validAfter = 0n;
2353
- const validBefore = BigInt(
2354
- Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
2355
- );
2356
- const nonce = randomAuthNonce();
2357
- const typedData = buildTransferWithAuthorizationTypedData({
2358
- token,
2359
- chainId: cid,
2360
- tokenName,
2361
- tokenVersion,
2362
- message: {
2363
- from: deps.ownerAddress,
2364
- to: smartWallet,
2365
- value: BigInt(amount),
2366
- validAfter,
2367
- validBefore,
2368
- nonce
2369
- }
2370
- });
2371
- const authSignature = await wallet.signTypedData({
2372
- account: deps.ownerAddress,
2373
- ...typedData
2374
- });
2375
- deps.onApproved?.();
2376
- const result = await post({
2377
- baseUrl: deps.baseUrl,
2378
- apiKey: deps.apiKey,
2379
- body: {
2380
- chainId: cid,
2381
- token,
2382
- from: deps.ownerAddress,
2383
- to: smartWallet,
2384
- value: amount,
2385
- validAfter: validAfter.toString(),
2386
- validBefore: validBefore.toString(),
2387
- nonce,
2388
- authSignature,
2389
- tokenName,
2390
- tokenVersion
3328
+ const amount = Number(token2.amount);
3329
+ return Number.isFinite(amount) && amount > 0 ? total + amount : total;
3330
+ }, 0);
3331
+ }
3332
+ function aggregateApyHistory(agentApys, agentBalances) {
3333
+ const byDate = /* @__PURE__ */ new Map();
3334
+ for (const [id, accountApy] of Object.entries(agentApys)) {
3335
+ const balance = agentBalances[id] ?? 0;
3336
+ if (!Number.isFinite(balance) || balance <= 0) continue;
3337
+ for (const point of accountApy.history ?? []) {
3338
+ const apy = Number(point.apy);
3339
+ if (!point.date || !Number.isFinite(apy)) continue;
3340
+ const current = byDate.get(point.date) ?? { weightedSum: 0, weight: 0 };
3341
+ current.weightedSum += apy * balance;
3342
+ current.weight += balance;
3343
+ byDate.set(point.date, current);
3344
+ }
3345
+ }
3346
+ return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
3347
+ date,
3348
+ apy: value.weightedSum / value.weight
3349
+ }));
3350
+ }
3351
+ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
3352
+ const sums = {};
3353
+ const weights = {};
3354
+ for (const id of Object.keys(agentApys)) {
3355
+ const cells = agentApys[id].apyByChainAndAsset;
3356
+ const balance = agentBalances[id] ?? 0;
3357
+ if (!cells || balance <= 0) continue;
3358
+ for (const [chainKey, perAsset] of Object.entries(cells)) {
3359
+ if (!perAsset) continue;
3360
+ const chainId = Number(chainKey);
3361
+ for (const [asset, apyValue] of Object.entries(perAsset)) {
3362
+ const apy = Number(apyValue ?? 0);
3363
+ if (apy === 0) continue;
3364
+ sums[chainId] ??= {};
3365
+ weights[chainId] ??= {};
3366
+ sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
3367
+ weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
2391
3368
  }
2392
- });
2393
- return result.txHash;
2394
- };
3369
+ }
3370
+ }
3371
+ const out = {};
3372
+ for (const chainKey of Object.keys(sums)) {
3373
+ const chainId = Number(chainKey);
3374
+ const perAssetOut = {};
3375
+ for (const asset of Object.keys(sums[chainId])) {
3376
+ const w = weights[chainId][asset];
3377
+ if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
3378
+ }
3379
+ if (Object.keys(perAssetOut).length > 0) {
3380
+ out[chainId] = perAssetOut;
3381
+ }
3382
+ }
3383
+ return out;
2395
3384
  }
2396
3385
 
3386
+ // src/client.ts
3387
+ import {
3388
+ createPublicClient as createPublicClient4,
3389
+ createWalletClient as createWalletClient3,
3390
+ custom as custom3
3391
+ } from "viem";
3392
+ import { base as base4, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
3393
+
2397
3394
  // src/lib/sponsored-weth-deposit.ts
2398
3395
  var PERMIT_WINDOW_SECONDS = 15 * 60;
2399
3396
  function makeSponsoredWethCallback(deps) {
2400
3397
  const get = deps.httpGet ?? getSponsorRelayerAddress;
2401
3398
  const post = deps.httpPost ?? postSponsorPermit2Transfer;
2402
- return async (smartWallet, chainId, amount) => {
2403
- const cid = chainId;
2404
- const token = deps.tokenAddressByChain[cid];
2405
- if (!token) {
2406
- throw new OwneyError(
2407
- "CHAIN_UNSUPPORTED",
2408
- `No sponsored WETH configured for chain ${chainId}`
2409
- );
2410
- }
2411
- const amountWei = BigInt(amount);
2412
- const pub = deps.getPublicClient(cid);
2413
- const wallet = deps.getWalletClient(cid);
2414
- await ensureWalletOnChain(pub, wallet, cid);
2415
- try {
2416
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2417
- if (balance < amountWei) {
3399
+ return makeVerificationAwareDepositCallback(
3400
+ async (smartWallet, chainId, amount, verification) => {
3401
+ const cid = chainId;
3402
+ const token2 = deps.tokenAddressByChain[cid];
3403
+ if (!token2) {
2418
3404
  throw new OwneyError(
2419
- "DEPOSIT_INSUFFICIENT_BALANCE",
2420
- "Insufficient WETH balance for this deposit.",
2421
- { token, chainId: cid, balance: balance.toString(), amount }
3405
+ "CHAIN_UNSUPPORTED",
3406
+ `No sponsored WETH configured for chain ${chainId}`
2422
3407
  );
2423
3408
  }
2424
- } catch (err) {
2425
- if (err instanceof OwneyError) throw err;
2426
- console.warn(
2427
- "[owney-sdk] WETH balance pre-check failed (non-fatal):",
2428
- err instanceof Error ? err.message : String(err)
2429
- );
2430
- }
2431
- const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
2432
- if (allowance < amountWei) {
2433
- throw new OwneyError(
2434
- "PERMIT2_APPROVAL_REQUIRED",
2435
- "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
2436
- { token, chainId: cid, allowance: allowance.toString(), amount }
3409
+ const amountWei = BigInt(amount);
3410
+ const pub = deps.getPublicClient(cid);
3411
+ const wallet = deps.getWalletClient(cid);
3412
+ await ensureWalletOnChain(pub, wallet, cid);
3413
+ try {
3414
+ const balance = await readErc20Balance(pub, token2, deps.ownerAddress);
3415
+ if (balance < amountWei) {
3416
+ throw new OwneyError(
3417
+ "DEPOSIT_INSUFFICIENT_BALANCE",
3418
+ "Insufficient WETH balance for this deposit.",
3419
+ { token: token2, chainId: cid, balance: balance.toString(), amount }
3420
+ );
3421
+ }
3422
+ } catch (err) {
3423
+ if (err instanceof OwneyError) throw err;
3424
+ console.warn(
3425
+ "[owney-sdk] WETH balance pre-check failed (non-fatal):",
3426
+ err instanceof Error ? err.message : String(err)
3427
+ );
3428
+ }
3429
+ const allowance = await readPermit2Allowance(
3430
+ pub,
3431
+ token2,
3432
+ deps.ownerAddress
2437
3433
  );
2438
- }
2439
- const relayer = await get({
2440
- baseUrl: deps.baseUrl,
2441
- apiKey: deps.apiKey,
2442
- chainId: cid
2443
- });
2444
- const nonce = randomPermit2Nonce();
2445
- const deadline = BigInt(
2446
- Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
2447
- );
2448
- const typedData = buildPermitTransferFromTypedData({
2449
- chainId: cid,
2450
- message: {
2451
- permitted: { token, amount: amountWei },
2452
- spender: relayer,
2453
- nonce,
2454
- deadline
3434
+ if (allowance < amountWei) {
3435
+ throw new OwneyError(
3436
+ "PERMIT2_APPROVAL_REQUIRED",
3437
+ "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
3438
+ { token: token2, chainId: cid, allowance: allowance.toString(), amount }
3439
+ );
2455
3440
  }
2456
- });
2457
- const signature = await wallet.signTypedData({
2458
- account: deps.ownerAddress,
2459
- ...typedData
2460
- });
2461
- deps.onApproved?.();
2462
- const result = await post({
2463
- baseUrl: deps.baseUrl,
2464
- apiKey: deps.apiKey,
2465
- body: {
3441
+ const relayer = await get({
3442
+ baseUrl: deps.baseUrl,
3443
+ apiKey: deps.apiKey,
3444
+ chainId: cid
3445
+ });
3446
+ const nonce = randomPermit2Nonce();
3447
+ const deadline = BigInt(
3448
+ Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
3449
+ );
3450
+ const typedData = buildPermitTransferFromTypedData({
2466
3451
  chainId: cid,
2467
- token,
2468
- from: deps.ownerAddress,
2469
- to: smartWallet,
2470
- amount,
2471
- nonce: nonce.toString(),
2472
- deadline: deadline.toString(),
2473
- signature
2474
- }
2475
- });
2476
- return result.txHash;
2477
- };
3452
+ message: {
3453
+ permitted: { token: token2, amount: amountWei },
3454
+ spender: relayer,
3455
+ nonce,
3456
+ deadline
3457
+ }
3458
+ });
3459
+ const signature = await wallet.signTypedData({
3460
+ account: deps.ownerAddress,
3461
+ ...typedData
3462
+ });
3463
+ deps.onApproved?.();
3464
+ const result = await post({
3465
+ baseUrl: deps.baseUrl,
3466
+ apiKey: deps.apiKey,
3467
+ ...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
3468
+ body: {
3469
+ chainId: cid,
3470
+ token: token2,
3471
+ from: deps.ownerAddress,
3472
+ to: smartWallet,
3473
+ amount,
3474
+ nonce: nonce.toString(),
3475
+ deadline: deadline.toString(),
3476
+ signature
3477
+ }
3478
+ });
3479
+ return result.txHash;
3480
+ }
3481
+ );
2478
3482
  }
2479
3483
 
2480
3484
  // src/lib/sponsored-calls-deposit.ts
@@ -2506,8 +3510,8 @@ function makeSponsoredCallsCallback(deps) {
2506
3510
  };
2507
3511
  return async (smartWallet, chainId, amount) => {
2508
3512
  const cid = chainId;
2509
- const token = deps.tokenAddressByChain[cid];
2510
- if (!token) {
3513
+ const token2 = deps.tokenAddressByChain[cid];
3514
+ if (!token2) {
2511
3515
  throw new OwneyError(
2512
3516
  "CHAIN_UNSUPPORTED",
2513
3517
  `No sponsored token configured for chain ${chainId}`
@@ -2533,7 +3537,7 @@ function makeSponsoredCallsCallback(deps) {
2533
3537
  from: deps.ownerAddress,
2534
3538
  chainId: toHex(chainId),
2535
3539
  atomicRequired: false,
2536
- calls: [{ to: token, value: "0x0", data }],
3540
+ calls: [{ to: token2, value: "0x0", data }],
2537
3541
  capabilities: {
2538
3542
  paymasterService: { url: absolutePaymasterUrl() }
2539
3543
  }
@@ -2572,10 +3576,10 @@ function makeSponsoredCallsCallback(deps) {
2572
3576
  function encodeMultiAgentCursor(map) {
2573
3577
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
2574
3578
  }
2575
- function decodeMultiAgentCursor(token) {
3579
+ function decodeMultiAgentCursor(token2) {
2576
3580
  let parsed;
2577
3581
  try {
2578
- const json = Buffer.from(token, "base64").toString("utf8");
3582
+ const json = Buffer.from(token2, "base64").toString("utf8");
2579
3583
  parsed = JSON.parse(json);
2580
3584
  } catch {
2581
3585
  throw new InvalidHistoryCursorError(
@@ -2595,7 +3599,7 @@ var SPONSORED_USDC_BY_CHAIN = {
2595
3599
  1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
2596
3600
  };
2597
3601
  var VIEM_CHAIN2 = {
2598
- 8453: base2,
3602
+ 8453: base4,
2599
3603
  42161: arbitrum2,
2600
3604
  1: mainnet2
2601
3605
  };
@@ -2625,6 +3629,7 @@ var OwneySDK = class {
2625
3629
  orgAgentConfig;
2626
3630
  orgAgentConfigPromise = null;
2627
3631
  zyfaiRpcUrls;
3632
+ yieldseekerApiBaseUrl;
2628
3633
  routingApiBaseUrl;
2629
3634
  referralSource;
2630
3635
  cachedSponsoredCallback = null;
@@ -2636,6 +3641,7 @@ var OwneySDK = class {
2636
3641
  this.apiKey = config.apiKey;
2637
3642
  if (config.debug) setOwneyDebug(true);
2638
3643
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
3644
+ this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
2639
3645
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2640
3646
  this.paymasterServiceUrl = config.paymasterServiceUrl;
2641
3647
  this.referralSource = config.referralSource;
@@ -2738,14 +3744,14 @@ var OwneySDK = class {
2738
3744
  // Casts work around viem's chain-narrowed Client vs the generic
2739
3745
  // PublicClient/WalletClient param types — structurally identical at
2740
3746
  // runtime, but the two share a name TS treats as unrelated.
2741
- getPublicClient: (cid) => createPublicClient2({
3747
+ getPublicClient: (cid) => createPublicClient4({
2742
3748
  chain: VIEM_CHAIN2[cid],
2743
- transport: custom(provider)
3749
+ transport: custom3(provider)
2744
3750
  }),
2745
- getWalletClient: (cid) => createWalletClient({
3751
+ getWalletClient: (cid) => createWalletClient3({
2746
3752
  account: owner,
2747
3753
  chain: VIEM_CHAIN2[cid],
2748
- transport: custom(provider)
3754
+ transport: custom3(provider)
2749
3755
  })
2750
3756
  });
2751
3757
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -2791,14 +3797,14 @@ var OwneySDK = class {
2791
3797
  // Casts work around viem's chain-narrowed Client vs the generic
2792
3798
  // PublicClient/WalletClient param types — structurally identical at
2793
3799
  // runtime, but the two share a name TS treats as unrelated.
2794
- getPublicClient: (cid) => createPublicClient2({
3800
+ getPublicClient: (cid) => createPublicClient4({
2795
3801
  chain: VIEM_CHAIN2[cid],
2796
- transport: custom(provider)
3802
+ transport: custom3(provider)
2797
3803
  }),
2798
- getWalletClient: (cid) => createWalletClient({
3804
+ getWalletClient: (cid) => createWalletClient3({
2799
3805
  account: owner,
2800
3806
  chain: VIEM_CHAIN2[cid],
2801
- transport: custom(provider)
3807
+ transport: custom3(provider)
2802
3808
  })
2803
3809
  });
2804
3810
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -2829,12 +3835,10 @@ var OwneySDK = class {
2829
3835
  this.orgAgentConfigPromise = fetchOrgAgentConfig(
2830
3836
  this.apiKey,
2831
3837
  this.routingApiBaseUrl
2832
- ).then(
2833
- (config) => {
2834
- this.orgAgentConfig = config;
2835
- return config;
2836
- }
2837
- );
3838
+ ).then((config) => {
3839
+ this.orgAgentConfig = config;
3840
+ return config;
3841
+ });
2838
3842
  }
2839
3843
  return this.orgAgentConfigPromise;
2840
3844
  }
@@ -2874,7 +3878,14 @@ var OwneySDK = class {
2874
3878
  this.routingApiBaseUrl
2875
3879
  );
2876
3880
  this.disabledAgents.clear();
2877
- for (const { key: key2, agent_type, is_enabled } of agentKeys) {
3881
+ for (const {
3882
+ key: key2,
3883
+ agent_type,
3884
+ is_enabled,
3885
+ is_configured
3886
+ } of agentKeys) {
3887
+ const configured = is_configured ?? Boolean(key2);
3888
+ if (!configured) continue;
2878
3889
  const agent = this.createAgent(agent_type, key2);
2879
3890
  if (!agent) continue;
2880
3891
  this.agents.set(agent_type, agent);
@@ -2898,10 +3909,47 @@ var OwneySDK = class {
2898
3909
  }
2899
3910
  createAgent(agentId, key2) {
2900
3911
  if (agentId === "zyfai") {
3912
+ if (!key2) return null;
2901
3913
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
2902
3914
  }
3915
+ if (agentId === "yieldseeker") {
3916
+ return new YieldseekerAgent(this.apiKey, {
3917
+ baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
3918
+ });
3919
+ }
2903
3920
  return null;
2904
3921
  }
3922
+ /**
3923
+ * Discover the agents actually returned by routing for this organization.
3924
+ * Consumers should use this instead of hardcoding a global agent roster.
3925
+ */
3926
+ async getAvailableAgents(options = {}) {
3927
+ await this.ensureAgentsInitialized();
3928
+ const { chainId, asset, includeDisabled = false } = options;
3929
+ const available = [];
3930
+ for (const [id, agent] of this.agents) {
3931
+ const isEnabled = !this.isAgentDisabled(id);
3932
+ if (!includeDisabled && !isEnabled) continue;
3933
+ if (chainId !== void 0 && !agent.supportedChainIds.includes(chainId)) {
3934
+ continue;
3935
+ }
3936
+ if (asset !== void 0) {
3937
+ const supportsAsset = agent.supportedAssets.some(
3938
+ (entry) => (chainId === void 0 || entry.chainId === chainId) && entry.assets.some((candidate) => candidate.symbol === asset)
3939
+ );
3940
+ if (!supportsAsset) {
3941
+ continue;
3942
+ }
3943
+ }
3944
+ available.push({
3945
+ id,
3946
+ isEnabled,
3947
+ supportedChainIds: agent.supportedChainIds,
3948
+ supportedAssets: agent.supportedAssets
3949
+ });
3950
+ }
3951
+ return available;
3952
+ }
2905
3953
  // --- Account lifecycle ---
2906
3954
  /**
2907
3955
  * Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
@@ -2913,7 +3961,7 @@ var OwneySDK = class {
2913
3961
  * If provided, ALL specified agents must support the chainId or the call
2914
3962
  * throws before activating any agent.
2915
3963
  */
2916
- async activateAgent(chainId, agentId) {
3964
+ async activateAgent(chainId, agentId, asset) {
2917
3965
  const state = this.requireState();
2918
3966
  await this.ensureAgentsInitialized();
2919
3967
  if (agentId !== void 0) {
@@ -2949,9 +3997,7 @@ var OwneySDK = class {
2949
3997
  this.activeAgents.add(id);
2950
3998
  }
2951
3999
  state.chainId = chainId;
2952
- this.activateAgentsInTurn(agents, state, chainId).catch((error) => {
2953
- console.error("activateAgent background init failed:", error);
2954
- });
4000
+ await this.activateAgentsInTurn(agents, state, chainId, asset);
2955
4001
  return;
2956
4002
  }
2957
4003
  const compatible = [...this.agents.values()].filter(
@@ -2972,11 +4018,7 @@ var OwneySDK = class {
2972
4018
  const enabledCompatible = compatible.filter(
2973
4019
  (agent) => !this.isAgentDisabled(agent.id)
2974
4020
  );
2975
- this.activateAgentsInTurn(enabledCompatible, state, chainId).catch(
2976
- (error) => {
2977
- console.error("activateAgent background init failed:", error);
2978
- }
2979
- );
4021
+ await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
2980
4022
  }
2981
4023
  /**
2982
4024
  * Activate agents ONE AT A TIME, each followed by its org policy.
@@ -2996,11 +4038,11 @@ var OwneySDK = class {
2996
4038
  * rethrown (matching the previous `Promise.all` rejection) once all agents
2997
4039
  * have had a chance to activate.
2998
4040
  */
2999
- async activateAgentsInTurn(agents, state, chainId) {
4041
+ async activateAgentsInTurn(agents, state, chainId, asset) {
3000
4042
  let firstError = null;
3001
4043
  for (const agent of agents) {
3002
4044
  try {
3003
- await agent.activateAgent(state, chainId);
4045
+ await agent.activateAgent(state, chainId, asset);
3004
4046
  await this.applyOrgPolicyTo(agent, state, chainId);
3005
4047
  } catch (error) {
3006
4048
  if (firstError === null) {
@@ -3209,10 +4251,10 @@ var OwneySDK = class {
3209
4251
  try {
3210
4252
  const balance = await agent.getBalances(state, chainId);
3211
4253
  const target = asset.toLowerCase();
3212
- const token = balance.tokens.find(
4254
+ const token2 = balance.tokens.find(
3213
4255
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
3214
4256
  );
3215
- return !!token && Number(token.amount) > 0;
4257
+ return !!token2 && Number(token2.amount) > 0;
3216
4258
  } catch {
3217
4259
  return false;
3218
4260
  }
@@ -3263,9 +4305,9 @@ var OwneySDK = class {
3263
4305
  const { asset, amount, agentId } = options;
3264
4306
  const state = this.requireState();
3265
4307
  const chainId = this.requireChainId();
3266
- const token = asset;
4308
+ const token2 = asset;
3267
4309
  const assetInfo = SupportedAssets.find(
3268
- (a) => a.chainId === chainId && a.symbol === token
4310
+ (a) => a.chainId === chainId && a.symbol === token2
3269
4311
  );
3270
4312
  if (!assetInfo) {
3271
4313
  throw new OwneyError(
@@ -3279,7 +4321,7 @@ var OwneySDK = class {
3279
4321
  return withFailureReporting(
3280
4322
  this.apiKey,
3281
4323
  agent.id,
3282
- () => agent.withdraw(state, chainId, token, amount),
4324
+ () => agent.withdraw(state, chainId, token2, amount),
3283
4325
  this.routingApiBaseUrl
3284
4326
  );
3285
4327
  }
@@ -3289,7 +4331,7 @@ var OwneySDK = class {
3289
4331
  const agentErrors2 = {};
3290
4332
  for (const agent of eligibleAgents) {
3291
4333
  try {
3292
- results2[agent.id] = await agent.withdraw(state, chainId, token);
4334
+ results2[agent.id] = await agent.withdraw(state, chainId, token2);
3293
4335
  } catch (err) {
3294
4336
  console.error(`withdraw failed for agent "${agent.id}":`, err);
3295
4337
  agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
@@ -3316,6 +4358,10 @@ var OwneySDK = class {
3316
4358
  }
3317
4359
  const requested = BigInt(amount);
3318
4360
  const aggregated = await this.getBalances();
4361
+ const unavailableAgents = eligibleAgents.filter(
4362
+ (agent) => !(agent.id in aggregated.agentBalances)
4363
+ );
4364
+ const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
3319
4365
  const balances = projectAgentBalancesForAsset(
3320
4366
  eligibleAgents,
3321
4367
  aggregated.agentBalances,
@@ -3324,7 +4370,18 @@ var OwneySDK = class {
3324
4370
  assetInfo.decimals
3325
4371
  );
3326
4372
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
3327
- if (totalAvailable < requested) {
4373
+ if (totalAvailable === 0n && unavailableAgents.length > 0) {
4374
+ throw new OwneyError(
4375
+ "WITHDRAW_BALANCE_UNAVAILABLE",
4376
+ `Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
4377
+ {
4378
+ asset,
4379
+ unavailableAgents: unavailableAgentIds,
4380
+ agentErrors: aggregated.agentErrors
4381
+ }
4382
+ );
4383
+ }
4384
+ if (totalAvailable < requested && unavailableAgents.length === 0) {
3328
4385
  throw new OwneyError(
3329
4386
  "WITHDRAW_INSUFFICIENT_BALANCE",
3330
4387
  `Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
@@ -3335,6 +4392,7 @@ var OwneySDK = class {
3335
4392
  }
3336
4393
  );
3337
4394
  }
4395
+ const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
3338
4396
  const disabledBalances = balances.filter(
3339
4397
  (b) => this.isAgentDisabled(b.agent.id)
3340
4398
  );
@@ -3343,7 +4401,7 @@ var OwneySDK = class {
3343
4401
  );
3344
4402
  const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
3345
4403
  disabledBalances,
3346
- requested
4404
+ plannedTarget
3347
4405
  );
3348
4406
  const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
3349
4407
  const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
@@ -3353,7 +4411,9 @@ var OwneySDK = class {
3353
4411
  }));
3354
4412
  const plans = [...disabledPlans, ...enabledPlans];
3355
4413
  const results = {};
3356
- const agentErrors = {};
4414
+ const agentErrors = {
4415
+ ...aggregated.agentErrors ?? {}
4416
+ };
3357
4417
  for (let i = 0; i < plans.length; i++) {
3358
4418
  const p = plans[i];
3359
4419
  if (p.planned === 0n) continue;
@@ -3361,7 +4421,7 @@ var OwneySDK = class {
3361
4421
  results[p.agent.id] = await p.agent.withdraw(
3362
4422
  state,
3363
4423
  chainId,
3364
- token,
4424
+ token2,
3365
4425
  p.planned.toString()
3366
4426
  );
3367
4427
  } catch (err) {
@@ -3400,7 +4460,8 @@ var OwneySDK = class {
3400
4460
  requested: amount,
3401
4461
  withdrawn: withdrawn.toString(),
3402
4462
  partialResults: results,
3403
- agentErrors
4463
+ agentErrors,
4464
+ ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
3404
4465
  }
3405
4466
  );
3406
4467
  }
@@ -3441,7 +4502,12 @@ var OwneySDK = class {
3441
4502
  continue;
3442
4503
  }
3443
4504
  const reason = settledResult.reason;
3444
- agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
4505
+ const message = reason instanceof Error ? reason.message : String(reason);
4506
+ agentErrors[agentId2] = message;
4507
+ console.error(
4508
+ `[owney-sdk] Balance fetch failed for agent "${agentId2}":`,
4509
+ reason
4510
+ );
3445
4511
  }
3446
4512
  if (successCount === 0) {
3447
4513
  throw new OwneyError(
@@ -3453,7 +4519,8 @@ var OwneySDK = class {
3453
4519
  return {
3454
4520
  totalBalance: String(totalBalance),
3455
4521
  totalBalanceAsset: "usdc",
3456
- agentBalances: results
4522
+ agentBalances: results,
4523
+ ...Object.keys(agentErrors).length > 0 ? { agentErrors } : {}
3457
4524
  };
3458
4525
  }
3459
4526
  /**
@@ -3547,7 +4614,10 @@ var OwneySDK = class {
3547
4614
  Promise.all(
3548
4615
  entries.map(async ([id, agent]) => {
3549
4616
  const b = await agent.getBalances(state, chainId);
3550
- return [id, Number(b.totalBalance)];
4617
+ return [
4618
+ id,
4619
+ balanceForApyScope(b, chainId, tokenSymbol)
4620
+ ];
3551
4621
  })
3552
4622
  )
3553
4623
  ]);
@@ -3575,10 +4645,12 @@ var OwneySDK = class {
3575
4645
  }
3576
4646
  }
3577
4647
  const apyByChainAndAsset = aggregateApyByChainAndAsset(results, balances);
4648
+ const history = aggregateApyHistory(results, balances);
3578
4649
  return {
3579
4650
  totalApy: String(totalApy),
3580
4651
  agentApy: results,
3581
- apyByChainAndAsset
4652
+ apyByChainAndAsset,
4653
+ history
3582
4654
  };
3583
4655
  }
3584
4656
  /**
@@ -3700,30 +4772,30 @@ var OwneySDK = class {
3700
4772
  const state = this.requireState();
3701
4773
  const chainId = this.requireChainId();
3702
4774
  this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
3703
- const token = SPONSORED_WETH_BY_CHAIN[chainId];
3704
- if (!token) {
4775
+ const token2 = SPONSORED_WETH_BY_CHAIN[chainId];
4776
+ if (!token2) {
3705
4777
  throw new OwneyError(
3706
4778
  "CHAIN_UNSUPPORTED",
3707
4779
  `No sponsored WETH on chain ${chainId}`
3708
4780
  );
3709
4781
  }
3710
4782
  const provider = this.requireConnectedProvider();
3711
- const wallet = createWalletClient({
4783
+ const wallet = createWalletClient3({
3712
4784
  account: state.walletAddress,
3713
4785
  chain: VIEM_CHAIN2[chainId],
3714
- transport: custom(provider)
4786
+ transport: custom3(provider)
3715
4787
  });
3716
4788
  const hash = await wallet.writeContract({
3717
- address: token,
4789
+ address: token2,
3718
4790
  abi: ERC20_ALLOWANCE_ABI,
3719
4791
  functionName: "approve",
3720
4792
  args: [PERMIT2_ADDRESS, MAX_UINT256],
3721
4793
  account: state.walletAddress,
3722
4794
  chain: VIEM_CHAIN2[chainId]
3723
4795
  });
3724
- const publicClient = createPublicClient2({
4796
+ const publicClient = createPublicClient4({
3725
4797
  chain: VIEM_CHAIN2[chainId],
3726
- transport: custom(provider)
4798
+ transport: custom3(provider)
3727
4799
  });
3728
4800
  const receipt = await publicClient.waitForTransactionReceipt({
3729
4801
  hash,
@@ -3759,7 +4831,9 @@ var OwneySDK = class {
3759
4831
  return this.getAgent(agentId).getAgentApy(days, agentOptions);
3760
4832
  }
3761
4833
  const results = {};
3762
- const agentEntries = [...this.agents.entries()];
4834
+ const agentEntries = [...this.agents.entries()].filter(
4835
+ ([id]) => !this.isAgentDisabled(id)
4836
+ );
3763
4837
  const apyResults = await Promise.all(
3764
4838
  agentEntries.map(async ([id, agent]) => {
3765
4839
  const apy = await agent.getAgentApy(days, agentOptions);
@@ -3832,13 +4906,13 @@ var OwneySDK = class {
3832
4906
  };
3833
4907
 
3834
4908
  // src/agents/zyfai/zyfai.siwx.ts
3835
- import { getAddress } from "viem";
3836
- import { SiweMessage } from "siwe";
4909
+ import { getAddress as getAddress3 } from "viem";
4910
+ import { SiweMessage as SiweMessage2 } from "siwe";
3837
4911
  import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
3838
4912
 
3839
4913
  // src/agents/zyfai/zyfai.siwx-cache.ts
3840
- var KEY_PREFIX2 = "owney.siwx.session";
3841
- var storage2 = () => {
4914
+ var KEY_PREFIX3 = "owney.siwx.session";
4915
+ var storage3 = () => {
3842
4916
  if (typeof window === "undefined") return null;
3843
4917
  try {
3844
4918
  return window.localStorage;
@@ -3846,8 +4920,8 @@ var storage2 = () => {
3846
4920
  return null;
3847
4921
  }
3848
4922
  };
3849
- var buildKey2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}`;
3850
- var legacyKeyPrefix2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}:`;
4923
+ var buildKey3 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}`;
4924
+ var legacyKeyPrefix2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}:`;
3851
4925
  var memorySiwxSessions = /* @__PURE__ */ new Map();
3852
4926
  var readLegacySiwxSession = (store, address) => {
3853
4927
  if (!store) return null;
@@ -3878,8 +4952,8 @@ var readLegacySiwxSession = (store, address) => {
3878
4952
  };
3879
4953
  var readSiwxSession = (address, chainId) => {
3880
4954
  if (typeof window === "undefined") return null;
3881
- const key2 = buildKey2(address);
3882
- const store = storage2();
4955
+ const key2 = buildKey3(address);
4956
+ const store = storage3();
3883
4957
  let raw = null;
3884
4958
  try {
3885
4959
  raw = store?.getItem(key2) ?? null;
@@ -3907,18 +4981,18 @@ var readSiwxSession = (address, chainId) => {
3907
4981
  };
3908
4982
  var writeSiwxSession = (address, _chainId, session) => {
3909
4983
  if (typeof window === "undefined") return;
3910
- const key2 = buildKey2(address);
4984
+ const key2 = buildKey3(address);
3911
4985
  memorySiwxSessions.set(key2, session);
3912
- const store = storage2();
4986
+ const store = storage3();
3913
4987
  try {
3914
4988
  store?.setItem(key2, JSON.stringify(session));
3915
4989
  } catch {
3916
4990
  }
3917
4991
  };
3918
4992
  var clearSiwxSession = (address, _chainId) => {
3919
- const key2 = buildKey2(address);
4993
+ const key2 = buildKey3(address);
3920
4994
  memorySiwxSessions.delete(key2);
3921
- const store = storage2();
4995
+ const store = storage3();
3922
4996
  try {
3923
4997
  store?.removeItem(key2);
3924
4998
  } catch {
@@ -3958,8 +5032,8 @@ function buildSIWXConfig(deps) {
3958
5032
  statement: STATEMENT,
3959
5033
  issuedAt,
3960
5034
  toString() {
3961
- return new SiweMessage({
3962
- address: getAddress(accountAddress),
5035
+ return new SiweMessage2({
5036
+ address: getAddress3(accountAddress),
3963
5037
  chainId: numericChainId(chainId),
3964
5038
  domain,
3965
5039
  uri,
@@ -4001,7 +5075,7 @@ function buildSIWXConfig(deps) {
4001
5075
  const persistSession = async (session) => {
4002
5076
  const address = session.data.accountAddress;
4003
5077
  const id = numericChainId(session.data.chainId);
4004
- const message = new SiweMessage(session.message);
5078
+ const message = new SiweMessage2(session.message);
4005
5079
  const login = await post("/auth/login", {
4006
5080
  message,
4007
5081
  signature: session.signature,
@@ -4049,6 +5123,7 @@ export {
4049
5123
  NotConnectedError,
4050
5124
  OwneyError,
4051
5125
  OwneySDK,
5126
+ YieldseekerAgent,
4052
5127
  createOwneySIWX,
4053
5128
  setOwneyDebug
4054
5129
  };