@owney/sdk 0.7.24 → 0.7.25-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
@@ -1,3 +1,23 @@
1
+ // src/lib/deposit-batch-callback.ts
2
+ var batchCallbacks = /* @__PURE__ */ new WeakMap();
3
+ var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
4
+ function toBatchTransfer(to, amount, verification) {
5
+ return {
6
+ to,
7
+ amount,
8
+ ...verification ? {
9
+ yieldseeker: {
10
+ signature: verification.signature,
11
+ userId: verification.userId,
12
+ agentId: verification.yieldseekerAgentId
13
+ }
14
+ } : {}
15
+ };
16
+ }
17
+ function registerDepositBatch(callback, transfer) {
18
+ batchCallbacks.set(callback, transfer);
19
+ }
20
+
1
21
  // src/errors.ts
2
22
  var OwneyError = class extends Error {
3
23
  code;
@@ -277,18 +297,18 @@ function tokenDecimals(symbol, explicit) {
277
297
  return explicit;
278
298
  return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
279
299
  }
280
- function mapDeposit(raw) {
300
+ function mapDeposit(raw2) {
281
301
  return {
282
- txHash: raw.txHash,
283
- smartWallet: raw.smartWallet,
284
- amount: raw.amount
302
+ txHash: raw2.txHash,
303
+ smartWallet: raw2.smartWallet,
304
+ amount: raw2.amount
285
305
  };
286
306
  }
287
- function mapWithdraw(raw) {
307
+ function mapWithdraw(raw2) {
288
308
  return {
289
- txHash: raw.txHash,
290
- type: raw.type,
291
- amount: raw.amount
309
+ txHash: raw2.txHash,
310
+ type: raw2.type,
311
+ amount: raw2.amount
292
312
  };
293
313
  }
294
314
  var CHAIN_ID_TO_NAME = {
@@ -307,10 +327,10 @@ function resolveChainId(chain) {
307
327
  if (Number.isFinite(asNum) && asNum > 0) return asNum;
308
328
  return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
309
329
  }
310
- function mapPendingAllocations(raw) {
311
- if (!Array.isArray(raw)) return void 0;
330
+ function mapPendingAllocations(raw2) {
331
+ if (!Array.isArray(raw2)) return void 0;
312
332
  const pending = [];
313
- for (const entry of raw) {
333
+ for (const entry of raw2) {
314
334
  if (typeof entry !== "object" || entry === null) continue;
315
335
  const e = entry;
316
336
  if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
@@ -333,8 +353,8 @@ function mapPendingAllocations(raw) {
333
353
  }
334
354
  return pending.length > 0 ? pending : void 0;
335
355
  }
336
- function mapBalances(raw, _chainId, smartWallet) {
337
- const portfolio = raw.portfolio;
356
+ function mapBalances(raw2, _chainId, smartWallet) {
357
+ const portfolio = raw2.portfolio;
338
358
  const portfolioByChain = portfolio.portfolioByChain ?? {};
339
359
  let totalBalance = 0;
340
360
  const tokens = [];
@@ -403,8 +423,8 @@ function sumTokenValues(tokens) {
403
423
  function sumTokenEarnings(tokens) {
404
424
  return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
405
425
  }
406
- function mapEarnings(raw, smartWallet) {
407
- const totalEarningsByChain = raw.data.totalEarningsByChainWithFee ?? raw.data.totalEarningsByChain ?? {};
426
+ function mapEarnings(raw2, smartWallet) {
427
+ const totalEarningsByChain = raw2.data.totalEarningsByChainWithFee ?? raw2.data.totalEarningsByChain ?? {};
408
428
  const tokens = [];
409
429
  for (const [chainIdKey, tokensBySymbol] of Object.entries(
410
430
  totalEarningsByChain
@@ -423,15 +443,15 @@ function mapEarnings(raw, smartWallet) {
423
443
  return {
424
444
  smartWallet,
425
445
  lifetimeEarnings: sumTokenEarnings(
426
- raw.data.totalEarningsByTokenWithFee ?? raw.data.totalEarningsByToken
446
+ raw2.data.totalEarningsByTokenWithFee ?? raw2.data.totalEarningsByToken
427
447
  ),
428
448
  tokens
429
449
  };
430
450
  }
431
- function mapWeightedApyByChain(raw) {
432
- if (!raw) return void 0;
451
+ function mapWeightedApyByChain(raw2) {
452
+ if (!raw2) return void 0;
433
453
  const out = {};
434
- for (const [chainKey, tokenApy] of Object.entries(raw)) {
454
+ for (const [chainKey, tokenApy] of Object.entries(raw2)) {
435
455
  const chainId = Number(chainKey);
436
456
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
437
457
  const perAsset = {};
@@ -471,8 +491,8 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
471
491
  }
472
492
  return totalBalance > 0 ? weightedSum / totalBalance : null;
473
493
  }
474
- function mapApyHistory(raw, chainId, tokenSymbol) {
475
- const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
494
+ function mapApyHistory(raw2, chainId, tokenSymbol) {
495
+ const history = Object.entries(raw2.history ?? {}).map(([date, entry]) => ({
476
496
  date,
477
497
  apy: rawPoolApyForChain(entry, chainId, tokenSymbol),
478
498
  // Provider position balances are treated as decimal amounts of the
@@ -488,9 +508,9 @@ function mapApyHistory(raw, chainId, tokenSymbol) {
488
508
  } : {}
489
509
  })).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
490
510
  return {
491
- walletAddress: raw.walletAddress,
492
- weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
493
- apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain),
511
+ walletAddress: raw2.walletAddress,
512
+ weightedApyAfterFee: raw2.weightedApyAfterFee ? sumTokenValues(raw2.weightedApyAfterFee) : void 0,
513
+ apyByChainAndAsset: mapWeightedApyByChain(raw2.weightedApyAfterFeeByChain),
494
514
  history
495
515
  };
496
516
  }
@@ -586,23 +606,23 @@ function mapEntries(rawEntries, chainId) {
586
606
  };
587
607
  });
588
608
  }
589
- function mapUserProfile(raw, userAddress) {
609
+ function mapUserProfile(raw2, userAddress) {
590
610
  return {
591
611
  address: userAddress,
592
- smartWallet: raw.smartWallet || "",
593
- chains: raw.chains || [],
594
- strategy: raw.strategy,
595
- hasActiveSessionKey: raw.hasActiveSessionKey || false,
596
- protocols: raw.protocols || [],
597
- splitting: raw.splitting,
598
- minSplits: raw.minSplits
612
+ smartWallet: raw2.smartWallet || "",
613
+ chains: raw2.chains || [],
614
+ strategy: raw2.strategy,
615
+ hasActiveSessionKey: raw2.hasActiveSessionKey || false,
616
+ protocols: raw2.protocols || [],
617
+ splitting: raw2.splitting,
618
+ minSplits: raw2.minSplits
599
619
  };
600
620
  }
601
- function mapApyByStrategy(raw) {
621
+ function mapApyByStrategy(raw2) {
602
622
  const apyPerAsset = {};
603
623
  let apySum = 0;
604
624
  let apyCount = 0;
605
- for (const entry of raw.data) {
625
+ for (const entry of raw2.data) {
606
626
  const supported = SupportedAssets.find(
607
627
  (asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
608
628
  );
@@ -714,9 +734,9 @@ function netDeltaForSnapshot(entry, chainId, asset) {
714
734
  debugLog("zyfai:earnings", "gross fallback for daily earnings", { gross });
715
735
  return gross;
716
736
  }
717
- function mapDailyEarnings(raw, chainId, tokenSymbol) {
737
+ function mapDailyEarnings(raw2, chainId, tokenSymbol) {
718
738
  const wanted = tokenSymbol?.toUpperCase();
719
- const snapshots = [...raw.data ?? []].sort(
739
+ const snapshots = [...raw2.data ?? []].sort(
720
740
  (a, b) => a.snapshot_date.localeCompare(b.snapshot_date)
721
741
  );
722
742
  const byAsset = /* @__PURE__ */ new Map();
@@ -731,7 +751,7 @@ function mapDailyEarnings(raw, chainId, tokenSymbol) {
731
751
  }
732
752
  }
733
753
  const assets = [...byAsset.entries()].map(([asset, series]) => ({ asset, points: series.points })).sort((a, b) => a.asset.localeCompare(b.asset));
734
- return { walletAddress: raw.walletAddress, chainId, assets };
754
+ return { walletAddress: raw2.walletAddress, chainId, assets };
735
755
  }
736
756
 
737
757
  // src/agents/zyfai/zyfai.withdraw-amount.ts
@@ -842,15 +862,15 @@ var readSession = (address, _chainId) => {
842
862
  if (typeof window === "undefined") return null;
843
863
  const key2 = buildKey(address);
844
864
  const store = storage();
845
- let raw = null;
865
+ let raw2 = null;
846
866
  try {
847
- raw = store?.getItem(key2) ?? null;
867
+ raw2 = store?.getItem(key2) ?? null;
848
868
  } catch {
849
- raw = null;
869
+ raw2 = null;
850
870
  }
851
- if (raw) {
871
+ if (raw2) {
852
872
  try {
853
- const parsed = JSON.parse(raw);
873
+ const parsed = JSON.parse(raw2);
854
874
  if (isFreshSession(parsed)) return parsed;
855
875
  } catch {
856
876
  }
@@ -1018,8 +1038,8 @@ function buildChainsByProtocol(protocols, dashboardChainIds) {
1018
1038
  }
1019
1039
  return result;
1020
1040
  }
1021
- function flattenAvailablePools(raw) {
1022
- const byChain = raw ?? {};
1041
+ function flattenAvailablePools(raw2) {
1042
+ const byChain = raw2 ?? {};
1023
1043
  const names = [];
1024
1044
  for (const byToken of Object.values(byChain ?? {})) {
1025
1045
  for (const entry of Object.values(byToken ?? {})) {
@@ -1522,8 +1542,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
1522
1542
  const poolResults = await Promise.all(
1523
1543
  universe.map(async (protocol) => {
1524
1544
  try {
1525
- const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
1526
- return [protocol.id, flattenAvailablePools(raw)];
1545
+ const raw2 = await this.sdk.getAvailablePools(protocol.id, strategy);
1546
+ return [protocol.id, flattenAvailablePools(raw2)];
1527
1547
  } catch (error) {
1528
1548
  console.warn(
1529
1549
  `[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
@@ -1589,14 +1609,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1589
1609
  async readWalletState(ownerAddress) {
1590
1610
  try {
1591
1611
  const { portfolio } = await this.sdk.getPositions(ownerAddress);
1592
- const raw = portfolio;
1612
+ const raw2 = portfolio;
1593
1613
  debugLog("zyfai:onboard", "wallet state from getPositions", {
1594
- predeployed: raw?.predeployed,
1595
- hasActiveSessionKey: raw?.hasActiveSessionKey
1614
+ predeployed: raw2?.predeployed,
1615
+ hasActiveSessionKey: raw2?.hasActiveSessionKey
1596
1616
  });
1597
1617
  return {
1598
- predeployed: raw?.predeployed,
1599
- hasActiveSessionKey: raw?.hasActiveSessionKey
1618
+ predeployed: raw2?.predeployed,
1619
+ hasActiveSessionKey: raw2?.hasActiveSessionKey
1600
1620
  };
1601
1621
  } catch (error) {
1602
1622
  console.warn(
@@ -1882,14 +1902,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1882
1902
  return { txHash, smartWallet, amount };
1883
1903
  }
1884
1904
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
1885
- const raw = await this.sdk.depositFunds(
1905
+ const raw2 = await this.sdk.depositFunds(
1886
1906
  this.getAddress(),
1887
1907
  validChainId,
1888
1908
  amount,
1889
1909
  asset,
1890
1910
  "aggressive"
1891
1911
  );
1892
- return mapDeposit(raw);
1912
+ return mapDeposit(raw2);
1893
1913
  } catch (error) {
1894
1914
  throw error;
1895
1915
  }
@@ -1898,27 +1918,27 @@ var ZyfaiAgent = class _ZyfaiAgent {
1898
1918
  async withdraw(state, chainId, token, amount) {
1899
1919
  const validChainId = isValidChainId(chainId);
1900
1920
  await this.ensureConnected(state, validChainId);
1901
- const raw = await this.sdk.withdrawFunds(
1921
+ const raw2 = await this.sdk.withdrawFunds(
1902
1922
  this.getAddress(),
1903
1923
  validChainId,
1904
1924
  amount,
1905
1925
  token
1906
1926
  );
1907
- if (!raw.success) {
1927
+ if (!raw2.success) {
1908
1928
  throw new OwneyError(
1909
1929
  "WITHDRAW_FAILED",
1910
- raw.message || "Zyfai withdraw failed.",
1911
- { chainId: validChainId, token, amount, response: raw },
1930
+ raw2.message || "Zyfai withdraw failed.",
1931
+ { chainId: validChainId, token, amount, response: raw2 },
1912
1932
  this.id
1913
1933
  );
1914
1934
  }
1915
- return mapWithdraw(raw);
1935
+ return mapWithdraw(raw2);
1916
1936
  }
1917
1937
  // --- IAgent: Portfolio reads ---
1918
1938
  async getBalances(state, chainId) {
1919
1939
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
1920
- const raw = await this.sdk.getPortfolio(this.getAddress());
1921
- return mapBalances(raw, validChainId, smartWallet);
1940
+ const raw2 = await this.sdk.getPortfolio(this.getAddress());
1941
+ return mapBalances(raw2, validChainId, smartWallet);
1922
1942
  }
1923
1943
  earningsKey(state, chainId, smartWallet) {
1924
1944
  return JSON.stringify([
@@ -1931,11 +1951,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
1931
1951
  const existing = this.earningsReads.get(key2);
1932
1952
  if (existing) return existing;
1933
1953
  const generation = this.earningsGeneration;
1934
- const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw) => {
1954
+ const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw2) => {
1935
1955
  if (generation === this.earningsGeneration) {
1936
- this.earningsSnapshot = { key: key2, raw, at: Date.now() };
1956
+ this.earningsSnapshot = { key: key2, raw: raw2, at: Date.now() };
1937
1957
  }
1938
- return raw;
1958
+ return raw2;
1939
1959
  }).finally(() => {
1940
1960
  if (this.earningsReads.get(key2) === pending)
1941
1961
  this.earningsReads.delete(key2);
@@ -1945,11 +1965,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
1945
1965
  }
1946
1966
  async getEarnings(state, chainId) {
1947
1967
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1948
- const raw = await this.readEarnings(
1968
+ const raw2 = await this.readEarnings(
1949
1969
  this.earningsKey(state, chainId, smartWallet),
1950
1970
  smartWallet
1951
1971
  );
1952
- return mapEarnings(raw, smartWallet);
1972
+ return mapEarnings(raw2, smartWallet);
1953
1973
  }
1954
1974
  async refreshEarnings(state, chainId) {
1955
1975
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
@@ -1976,17 +1996,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
1976
1996
  }
1977
1997
  async getAccountApy(state, chainId, days, tokenSymbol) {
1978
1998
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1979
- const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
1980
- return mapApyHistory(raw, chainId, tokenSymbol);
1999
+ const raw2 = await this.sdk.getDailyApyHistory(smartWallet, days);
2000
+ return mapApyHistory(raw2, chainId, tokenSymbol);
1981
2001
  }
1982
2002
  async getDailyEarnings(state, chainId, days, tokenSymbol) {
1983
2003
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1984
2004
  const start = new Date(Date.now() - (DayFilterMapping[days] + 1) * 864e5);
1985
- const raw = await this.sdk.getDailyEarnings(
2005
+ const raw2 = await this.sdk.getDailyEarnings(
1986
2006
  smartWallet,
1987
2007
  start.toISOString().slice(0, 10)
1988
2008
  );
1989
- return mapDailyEarnings(raw, chainId, tokenSymbol);
2009
+ return mapDailyEarnings(raw2, chainId, tokenSymbol);
1990
2010
  }
1991
2011
  /**
1992
2012
  * Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
@@ -2021,7 +2041,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2021
2041
  const matched = [];
2022
2042
  let backendExhausted = false;
2023
2043
  for (let i = 0; i < MAX_BACKEND_CALLS && matched.length < limit; i++) {
2024
- const raw = await this.sdk.getHistory(smartWallet, validChainId, {
2044
+ const raw2 = await this.sdk.getHistory(smartWallet, validChainId, {
2025
2045
  limit: backendPageSize,
2026
2046
  offset,
2027
2047
  fromDate: options?.fromDate,
@@ -2032,13 +2052,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
2032
2052
  // asset's rows and handing back a page that filters to nothing.
2033
2053
  assetType
2034
2054
  });
2035
- raw.data.forEach((entry, idx) => {
2055
+ raw2.data.forEach((entry, idx) => {
2036
2056
  if (entry.chainId === validChainId) {
2037
2057
  matched.push({ entry, rawIdx: offset + idx });
2038
2058
  }
2039
2059
  });
2040
- offset += raw.data.length;
2041
- if (raw.data.length < backendPageSize) {
2060
+ offset += raw2.data.length;
2061
+ if (raw2.data.length < backendPageSize) {
2042
2062
  backendExhausted = true;
2043
2063
  break;
2044
2064
  }
@@ -2060,18 +2080,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
2060
2080
  }
2061
2081
  async getUserProfile(state, chainId) {
2062
2082
  await this.connectAuth(state, chainId);
2063
- const raw = await this.sdk.getUserDetails();
2083
+ const raw2 = await this.sdk.getUserDetails();
2064
2084
  debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
2065
2085
  asset: "USDC (default \u2014 no asset passed)",
2066
- splitting: raw.splitting,
2067
- minSplits: raw.minSplits,
2068
- strategy: raw.strategy,
2069
- chains: raw.chains,
2070
- protocolCount: raw.protocols?.length,
2071
- hasActiveSessionKey: raw.hasActiveSessionKey,
2072
- smartWallet: raw.smartWallet
2086
+ splitting: raw2.splitting,
2087
+ minSplits: raw2.minSplits,
2088
+ strategy: raw2.strategy,
2089
+ chains: raw2.chains,
2090
+ protocolCount: raw2.protocols?.length,
2091
+ hasActiveSessionKey: raw2.hasActiveSessionKey,
2092
+ smartWallet: raw2.smartWallet
2073
2093
  });
2074
- return mapUserProfile(raw, this.connectedAddress);
2094
+ return mapUserProfile(raw2, this.connectedAddress);
2075
2095
  }
2076
2096
  async ensureAutoSelectProtocols(state, chainId, asset) {
2077
2097
  await this.connectAuth(state, chainId);
@@ -2090,201 +2110,2076 @@ var ZyfaiAgent = class _ZyfaiAgent {
2090
2110
  }
2091
2111
  // --- IAgent: Discovery (no wallet required) ---
2092
2112
  async getAgentApy(days, options) {
2093
- const raw = await this.sdk.getAPYPerStrategy(
2113
+ const raw2 = await this.sdk.getAPYPerStrategy(
2094
2114
  false,
2095
2115
  DayFilterMapping[days],
2096
2116
  "aggressive",
2097
2117
  options?.chainId,
2098
2118
  options?.tokenSymbol
2099
2119
  );
2100
- return mapApyByStrategy(raw);
2120
+ return mapApyByStrategy(raw2);
2101
2121
  }
2102
2122
  };
2103
2123
 
2104
- // src/lib/routing-api.ts
2105
- var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2106
- async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2107
- const url = `${baseUrl}/api/v1/agent/org-config`;
2124
+ // src/agents/yieldseeker/yieldseeker.agent.ts
2125
+ import {
2126
+ createPublicClient as createPublicClient3,
2127
+ createWalletClient as createWalletClient2,
2128
+ custom as custom2,
2129
+ encodeFunctionData,
2130
+ erc20Abi,
2131
+ getAddress as getAddress2,
2132
+ isAddress as isAddress2
2133
+ } from "viem";
2134
+ import { base as base3 } from "viem/chains";
2135
+
2136
+ // src/lib/chain-guard.ts
2137
+ var CHAIN_NAMES = {
2138
+ 1: "Ethereum",
2139
+ 8453: "Base",
2140
+ 42161: "Arbitrum"
2141
+ };
2142
+ function chainName(chainId) {
2143
+ return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
2144
+ }
2145
+ async function ensureWalletOnChain(pub, wallet, expected) {
2146
+ const actual = await pub.getChainId();
2147
+ if (actual === expected) return;
2108
2148
  try {
2109
- const res = await fetch(url, {
2110
- method: "GET",
2111
- headers: {
2112
- "Content-Type": "application/json",
2113
- "x-owney-api-key": `${apiKey}`
2114
- }
2115
- });
2116
- if (!res.ok) {
2117
- if (res.status !== 404) {
2118
- console.warn(
2119
- `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
2120
- );
2121
- }
2122
- return null;
2123
- }
2124
- const json = await res.json();
2125
- const policy = json.success ? json.data ?? null : null;
2126
- debugLog(
2127
- "owney-sdk",
2128
- policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
2129
- policy ?? void 0
2130
- );
2131
- return policy;
2149
+ await wallet.switchChain({ id: expected });
2132
2150
  } catch (error) {
2133
- console.warn(
2134
- "[owney-sdk] Could not read org agent config (non-fatal):",
2135
- error instanceof Error ? error.message : String(error)
2136
- );
2137
- return null;
2138
- }
2139
- }
2140
- async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2141
- const url = `${baseUrl}/api/v1/agent/keys`;
2142
- const res = await fetch(url, {
2143
- method: "GET",
2144
- headers: {
2145
- "Content-Type": "application/json",
2146
- "x-owney-api-key": `${apiKey}`
2147
- }
2148
- });
2149
- if (!res.ok) {
2150
- const text = await res.text().catch(() => "");
2151
2151
  throw new OwneyError(
2152
- "API_ROUTING_ERROR",
2153
- `Routing API error ${res.status}: ${text}`,
2154
- { statusCode: res.status, responseBody: text }
2152
+ "CHAIN_MISMATCH",
2153
+ `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
2154
+ {
2155
+ expectedChainId: expected,
2156
+ actualChainId: actual,
2157
+ cause: error instanceof Error ? error.message : String(error)
2158
+ }
2155
2159
  );
2156
2160
  }
2157
- const json = await res.json();
2158
- if (!json.success) {
2161
+ const after = await pub.getChainId();
2162
+ if (after !== expected) {
2159
2163
  throw new OwneyError(
2160
- "API_ROUTING_FAILED",
2161
- `Routing API request failed: ${json.message}`,
2162
- { message: json.message }
2164
+ "CHAIN_MISMATCH",
2165
+ `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
2166
+ { expectedChainId: expected, actualChainId: after }
2163
2167
  );
2164
2168
  }
2165
- return json.data;
2166
2169
  }
2167
2170
 
2168
- // src/lib/health-report.ts
2169
- var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2170
- async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
2171
+ // src/lib/transfer-auth.ts
2172
+ import { bytesToHex } from "viem";
2173
+
2174
+ // src/lib/sponsor-client.ts
2175
+ var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2176
+ async function postPaymasterIntent(input) {
2177
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2178
+ let res;
2171
2179
  try {
2172
- await fetch(`${baseUrl}/api/v1/agent/health-report`, {
2180
+ res = await fetch(`${base5}/api/v1/sponsor/paymaster-intent`, {
2173
2181
  method: "POST",
2174
2182
  headers: {
2175
- "Content-Type": "application/json",
2176
- "x-owney-api-key": apiKey
2183
+ "content-type": "application/json",
2184
+ "x-owney-api-key": input.apiKey,
2185
+ Authorization: `Signature ${input.yieldseekerSignature}`
2177
2186
  },
2178
- body: JSON.stringify({
2179
- agent_type: agentType,
2180
- error_code: errorCode,
2181
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2182
- })
2187
+ body: JSON.stringify(input.body)
2183
2188
  });
2184
- } catch (err) {
2185
- console.warn(
2186
- `[owney-sdk] health-report failed for agent "${agentType}":`,
2187
- err instanceof Error ? err.message : err
2189
+ } catch (networkError) {
2190
+ throw new OwneyError(
2191
+ "SPONSOR_REQUEST_FAILED",
2192
+ `Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2193
+ { cause: String(networkError), safeToFallback: true }
2188
2194
  );
2189
2195
  }
2190
- }
2191
- async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
2196
+ const text = await res.text();
2197
+ let parsed = null;
2192
2198
  try {
2193
- return await fn();
2194
- } catch (err) {
2195
- const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
2196
- void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
2197
- throw err;
2199
+ parsed = JSON.parse(text);
2200
+ } catch {
2198
2201
  }
2199
- }
2200
-
2201
- // src/lib/helpers/withdraw-helper.ts
2202
- import { parseUnits } from "viem";
2203
- function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
2204
- const target = asset.toUpperCase();
2205
- return agents.map((agent) => {
2206
- const agentBalance = aggregated[agent.id];
2207
- const tokenBalance = agentBalance?.tokens.find(
2208
- (t) => t.chainId === chainId && t.asset.toUpperCase() === target
2202
+ if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
2203
+ throw new OwneyError(
2204
+ "SPONSOR_REQUEST_FAILED",
2205
+ `Paymaster intent API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2206
+ {
2207
+ statusCode: res.status,
2208
+ responseBody: text.slice(0, 500),
2209
+ safeToFallback: true
2210
+ }
2209
2211
  );
2210
- if (!tokenBalance) return { agent, balance: 0n };
2211
- return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
2212
- });
2213
- }
2214
- function planProportionalShares(balances, requested, totalAvailable) {
2215
- const plans = balances.map(({ agent, balance }) => ({
2216
- agent,
2217
- balance,
2218
- planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
2219
- }));
2220
- const assigned = plans.reduce((s, p) => s + p.planned, 0n);
2221
- let remainder = requested - assigned;
2222
- const byHeadroom = [...plans].sort((a, b) => {
2223
- const diff = b.balance - b.planned - (a.balance - a.planned);
2224
- return diff > 0n ? 1 : diff < 0n ? -1 : 0;
2225
- });
2226
- for (const p of byHeadroom) {
2227
- if (remainder === 0n) break;
2228
- const headroom = p.balance - p.planned;
2229
- if (headroom <= 0n) continue;
2230
- const take = headroom < remainder ? headroom : remainder;
2231
- p.planned += take;
2232
- remainder -= take;
2233
2212
  }
2234
- return plans;
2213
+ return parsed.data;
2235
2214
  }
2236
- function planDisabledDrain(disabled, requested) {
2237
- const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
2238
- const plans = [];
2239
- let remaining = requested;
2240
- for (const { agent, balance } of sorted) {
2241
- if (remaining === 0n) {
2242
- plans.push({ agent, balance, planned: 0n });
2243
- continue;
2244
- }
2245
- const take = balance < remaining ? balance : remaining;
2246
- plans.push({ agent, balance, planned: take });
2247
- remaining -= take;
2215
+ async function getSponsorRelayerAddress(input) {
2216
+ const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2217
+ let res;
2218
+ try {
2219
+ res = await fetch(
2220
+ `${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2221
+ {
2222
+ headers: { "x-owney-api-key": input.apiKey }
2223
+ }
2224
+ );
2225
+ } catch (networkError) {
2226
+ throw new OwneyError(
2227
+ "SPONSOR_REQUEST_FAILED",
2228
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2229
+ { cause: String(networkError), safeToFallback: true }
2230
+ );
2248
2231
  }
2249
- return { plans, remaining };
2250
- }
2251
- function redistributeShare(plans, fromIndex, amount, candidatePool) {
2252
- const pool = candidatePool ?? plans.slice(fromIndex + 1);
2253
- const candidates = pool.filter((c) => c.balance - c.planned > 0n);
2254
- const totalHeadroom = candidates.reduce(
2255
- (s, c) => s + (c.balance - c.planned),
2256
- 0n
2257
- );
2258
- if (totalHeadroom === 0n) return;
2259
- let distributed = 0n;
2260
- for (const c of candidates) {
2261
- const headroom = c.balance - c.planned;
2262
- const proportional = headroom * amount / totalHeadroom;
2263
- const give = proportional > headroom ? headroom : proportional;
2264
- c.planned += give;
2265
- distributed += give;
2232
+ const text = await res.text();
2233
+ let parsed = null;
2234
+ try {
2235
+ parsed = JSON.parse(text);
2236
+ } catch {
2266
2237
  }
2267
- let leftover = amount - distributed;
2268
- for (const c of candidates) {
2269
- if (leftover === 0n) break;
2270
- const headroom = c.balance - c.planned;
2271
- if (headroom <= 0n) continue;
2272
- const take = headroom < leftover ? headroom : leftover;
2273
- c.planned += take;
2274
- leftover -= take;
2238
+ if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
2239
+ throw new OwneyError(
2240
+ "SPONSOR_REQUEST_FAILED",
2241
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2242
+ {
2243
+ statusCode: res.status,
2244
+ responseBody: text.slice(0, 500),
2245
+ safeToFallback: true
2246
+ }
2247
+ );
2275
2248
  }
2249
+ return parsed.data.relayer;
2276
2250
  }
2277
- function sumWithdrawnAmount(results) {
2278
- return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
2251
+ async function postSponsorBatchTransfer(input) {
2252
+ let res;
2253
+ try {
2254
+ res = await fetch(
2255
+ `${input.baseUrl ?? ROUTING_API_BASE_URL}/api/v1/sponsor/permit2-batch`,
2256
+ {
2257
+ method: "POST",
2258
+ headers: {
2259
+ "content-type": "application/json",
2260
+ "x-owney-api-key": input.apiKey
2261
+ },
2262
+ body: JSON.stringify(input.body)
2263
+ }
2264
+ );
2265
+ } catch {
2266
+ throw new OwneyError(
2267
+ "SPONSOR_REQUEST_FAILED",
2268
+ "Deposit status is unknown. Retry the same amount to check it.",
2269
+ { safeToFallback: false }
2270
+ );
2271
+ }
2272
+ const parsed = await res.json().catch(() => null);
2273
+ if (!res.ok || !parsed?.success || !/^0x[0-9a-fA-F]{64}$/.test(parsed.data?.txHash ?? "")) {
2274
+ throw new OwneyError(
2275
+ "SPONSOR_REQUEST_FAILED",
2276
+ "Deposit could not be confirmed. Retry the same amount to check its status.",
2277
+ {
2278
+ statusCode: res.status,
2279
+ safeToFallback: false,
2280
+ notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
2281
+ }
2282
+ );
2283
+ }
2284
+ return parsed.data;
2279
2285
  }
2280
2286
 
2281
- // src/lib/helpers/account-apy-helper.ts
2282
- function balanceForApyScope(balance, chainId, tokenSymbol) {
2283
- if (!tokenSymbol) {
2284
- const total = Number(balance.totalBalance);
2287
+ // src/lib/permit2.ts
2288
+ import { bytesToHex as bytesToHex2 } from "viem";
2289
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2290
+ function permit2ApprovalAmount(requiredAmount) {
2291
+ if (requiredAmount <= 0n) {
2292
+ throw new Error("Permit2 approval requires a positive deposit amount");
2293
+ }
2294
+ return requiredAmount;
2295
+ }
2296
+ var ERC20_ALLOWANCE_ABI = [
2297
+ {
2298
+ type: "function",
2299
+ name: "allowance",
2300
+ stateMutability: "view",
2301
+ inputs: [
2302
+ { name: "owner", type: "address" },
2303
+ { name: "spender", type: "address" }
2304
+ ],
2305
+ outputs: [{ name: "", type: "uint256" }]
2306
+ },
2307
+ {
2308
+ type: "function",
2309
+ name: "approve",
2310
+ stateMutability: "nonpayable",
2311
+ inputs: [
2312
+ { name: "spender", type: "address" },
2313
+ { name: "amount", type: "uint256" }
2314
+ ],
2315
+ outputs: [{ name: "", type: "bool" }]
2316
+ },
2317
+ {
2318
+ type: "function",
2319
+ name: "balanceOf",
2320
+ stateMutability: "view",
2321
+ inputs: [{ name: "account", type: "address" }],
2322
+ outputs: [{ name: "", type: "uint256" }]
2323
+ }
2324
+ ];
2325
+ function randomPermit2Nonce() {
2326
+ const bytes = new Uint8Array(32);
2327
+ globalThis.crypto.getRandomValues(bytes);
2328
+ return BigInt(bytesToHex2(bytes));
2329
+ }
2330
+ async function readPermit2Allowance(publicClient, token, owner) {
2331
+ return publicClient.readContract({
2332
+ address: token,
2333
+ abi: ERC20_ALLOWANCE_ABI,
2334
+ functionName: "allowance",
2335
+ args: [owner, PERMIT2_ADDRESS]
2336
+ });
2337
+ }
2338
+ async function readErc20Balance(publicClient, token, owner) {
2339
+ return publicClient.readContract({
2340
+ address: token,
2341
+ abi: ERC20_ALLOWANCE_ABI,
2342
+ functionName: "balanceOf",
2343
+ args: [owner]
2344
+ });
2345
+ }
2346
+
2347
+ // src/lib/sponsored-deposit.ts
2348
+ var AUTH_WINDOW_SECONDS = 15 * 60;
2349
+ var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
2350
+ function provideDepositVerificationContext(callback, context) {
2351
+ callback[verificationSetter]?.(context);
2352
+ }
2353
+ function makeVerificationAwareDepositCallback(implementation) {
2354
+ let nextVerification;
2355
+ const callback = async (smartWallet, chainId, amount) => {
2356
+ const verification = nextVerification;
2357
+ nextVerification = void 0;
2358
+ return implementation(smartWallet, chainId, amount, verification);
2359
+ };
2360
+ Object.defineProperty(callback, verificationSetter, {
2361
+ value: (context) => {
2362
+ nextVerification = context;
2363
+ }
2364
+ });
2365
+ return callback;
2366
+ }
2367
+
2368
+ // src/agents/yieldseeker/yieldseeker.auth.ts
2369
+ import { SiweMessage, generateNonce } from "siwe";
2370
+ import {
2371
+ createPublicClient as createPublicClient2,
2372
+ createWalletClient,
2373
+ custom,
2374
+ getAddress
2375
+ } from "viem";
2376
+ import { base as base2 } from "viem/chains";
2377
+
2378
+ // src/agents/yieldseeker/yieldseeker.auth-cache.ts
2379
+ var KEY_PREFIX2 = "owney.yieldseeker.session.v5";
2380
+ var INVALIDATED_KEY_PREFIXES = [
2381
+ "owney.yieldseeker.session",
2382
+ "owney.yieldseeker.session.v3",
2383
+ "owney.yieldseeker.session.v4"
2384
+ ];
2385
+ var storage2 = () => {
2386
+ if (typeof window === "undefined") return null;
2387
+ try {
2388
+ return window.localStorage;
2389
+ } catch {
2390
+ return null;
2391
+ }
2392
+ };
2393
+ var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
2394
+ var invalidatedKeys = (address, chainId) => INVALIDATED_KEY_PREFIXES.map(
2395
+ (prefix) => `${prefix}:${address.toLowerCase()}:${chainId}`
2396
+ );
2397
+ var clearInvalidatedSessions = (store, address, chainId) => {
2398
+ for (const key2 of invalidatedKeys(address, chainId)) {
2399
+ memorySessions2.delete(key2);
2400
+ try {
2401
+ store?.removeItem(key2);
2402
+ } catch {
2403
+ }
2404
+ }
2405
+ };
2406
+ var memorySessions2 = /* @__PURE__ */ new Map();
2407
+ var isValidSession = (session) => {
2408
+ if (!session?.token) return false;
2409
+ try {
2410
+ const parsed = JSON.parse(atob(session.token));
2411
+ return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
2412
+ } catch {
2413
+ return false;
2414
+ }
2415
+ };
2416
+ var readYieldseekerSession = (address, chainId) => {
2417
+ if (typeof window === "undefined") return null;
2418
+ const key2 = buildKey2(address, chainId);
2419
+ const store = storage2();
2420
+ clearInvalidatedSessions(store, address, chainId);
2421
+ let raw2 = null;
2422
+ try {
2423
+ raw2 = store?.getItem(key2) ?? null;
2424
+ } catch {
2425
+ raw2 = null;
2426
+ }
2427
+ if (raw2) {
2428
+ try {
2429
+ const parsed = JSON.parse(raw2);
2430
+ if (isValidSession(parsed)) return parsed.token;
2431
+ } catch {
2432
+ }
2433
+ memorySessions2.delete(key2);
2434
+ try {
2435
+ store?.removeItem(key2);
2436
+ } catch {
2437
+ }
2438
+ return null;
2439
+ }
2440
+ const cached = memorySessions2.get(key2);
2441
+ if (isValidSession(cached)) return cached.token;
2442
+ if (cached) memorySessions2.delete(key2);
2443
+ return null;
2444
+ };
2445
+ var writeYieldseekerSession = (address, chainId, token) => {
2446
+ if (typeof window === "undefined") return;
2447
+ const session = { token };
2448
+ if (!isValidSession(session)) return;
2449
+ const key2 = buildKey2(address, chainId);
2450
+ memorySessions2.set(key2, session);
2451
+ const store = storage2();
2452
+ try {
2453
+ store?.setItem(key2, JSON.stringify(session));
2454
+ } catch {
2455
+ }
2456
+ };
2457
+ var clearYieldseekerSession = (address, chainId) => {
2458
+ const key2 = buildKey2(address, chainId);
2459
+ memorySessions2.delete(key2);
2460
+ const store = storage2();
2461
+ clearInvalidatedSessions(store, address, chainId);
2462
+ try {
2463
+ store?.removeItem(key2);
2464
+ } catch {
2465
+ }
2466
+ };
2467
+
2468
+ // src/agents/yieldseeker/yieldseeker.auth.ts
2469
+ function resolveSiweOrigin(override) {
2470
+ const origin = override?.trim() || (typeof window !== "undefined" ? window.location?.origin : void 0);
2471
+ if (!origin || origin === "null") {
2472
+ throw new Error("Yieldseeker sign-in requires the requesting app's browser origin.");
2473
+ }
2474
+ const url = new URL(origin);
2475
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
2476
+ throw new Error("Yieldseeker sign-in requires an HTTP or HTTPS app origin.");
2477
+ }
2478
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
2479
+ throw new Error("Yieldseeker SIWE override must be an origin without credentials, path, query, or fragment.");
2480
+ }
2481
+ return url;
2482
+ }
2483
+ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
2484
+ const url = resolveSiweOrigin(dependencies.origin);
2485
+ return new SiweMessage({
2486
+ scheme: url.protocol.slice(0, -1),
2487
+ domain: url.host,
2488
+ address: getAddress(address),
2489
+ uri: url.origin,
2490
+ version: "1",
2491
+ chainId,
2492
+ nonce: (dependencies.nonce ?? generateNonce)(),
2493
+ issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
2494
+ }).prepareMessage();
2495
+ }
2496
+ function encodeYieldseekerAuthToken(token) {
2497
+ const bytes = new TextEncoder().encode(JSON.stringify(token));
2498
+ let binary = "";
2499
+ for (const byte of bytes) binary += String.fromCharCode(byte);
2500
+ return btoa(binary);
2501
+ }
2502
+ var YieldseekerAuth = class {
2503
+ constructor(dependencies = {}) {
2504
+ this.dependencies = dependencies;
2505
+ }
2506
+ dependencies;
2507
+ tokens = /* @__PURE__ */ new Map();
2508
+ pending = /* @__PURE__ */ new Map();
2509
+ scopes = /* @__PURE__ */ new Map();
2510
+ key(state, chainId) {
2511
+ return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
2512
+ }
2513
+ async getToken(state, chainId) {
2514
+ const key2 = this.key(state, chainId);
2515
+ const scope = { address: state.walletAddress, chainId };
2516
+ this.scopes.set(key2, scope);
2517
+ const cached = this.tokens.get(key2);
2518
+ if (cached) return cached;
2519
+ const persisted = readYieldseekerSession(scope.address, scope.chainId);
2520
+ if (persisted && this.matchesOrigin(persisted)) {
2521
+ this.tokens.set(key2, persisted);
2522
+ return persisted;
2523
+ }
2524
+ if (persisted) clearYieldseekerSession(scope.address, scope.chainId);
2525
+ const inFlight = this.pending.get(key2);
2526
+ if (inFlight) return inFlight;
2527
+ const request = this.sign(state, chainId).then((token) => {
2528
+ if (this.pending.get(key2) !== request) throw new Error("Wallet sign-in session changed. Please try again.");
2529
+ this.tokens.set(key2, token);
2530
+ writeYieldseekerSession(scope.address, scope.chainId, token);
2531
+ return token;
2532
+ });
2533
+ this.pending.set(key2, request);
2534
+ try {
2535
+ return await request;
2536
+ } finally {
2537
+ if (this.pending.get(key2) === request) this.pending.delete(key2);
2538
+ }
2539
+ }
2540
+ async refreshToken(state, chainId, rejectedToken) {
2541
+ const key2 = this.key(state, chainId);
2542
+ if (this.tokens.get(key2) === rejectedToken) {
2543
+ this.tokens.delete(key2);
2544
+ clearYieldseekerSession(state.walletAddress, chainId);
2545
+ }
2546
+ return this.getToken(state, chainId);
2547
+ }
2548
+ matchesOrigin(token) {
2549
+ try {
2550
+ const message = new SiweMessage(JSON.parse(atob(token)).message);
2551
+ const url = resolveSiweOrigin(this.dependencies.origin);
2552
+ return message.domain === url.host && message.uri === url.origin && message.scheme === url.protocol.slice(0, -1);
2553
+ } catch {
2554
+ return false;
2555
+ }
2556
+ }
2557
+ clear(state, chainId) {
2558
+ if (!state || chainId === void 0) {
2559
+ for (const scope of this.scopes.values()) {
2560
+ clearYieldseekerSession(scope.address, scope.chainId);
2561
+ }
2562
+ this.tokens.clear();
2563
+ this.pending.clear();
2564
+ this.scopes.clear();
2565
+ return;
2566
+ }
2567
+ const key2 = this.key(state, chainId);
2568
+ this.tokens.delete(key2);
2569
+ this.pending.delete(key2);
2570
+ this.scopes.delete(key2);
2571
+ clearYieldseekerSession(state.walletAddress, chainId);
2572
+ }
2573
+ async sign(state, chainId) {
2574
+ const account = getAddress(state.walletAddress);
2575
+ const publicClient = createPublicClient2({
2576
+ chain: base2,
2577
+ transport: custom(state.provider)
2578
+ });
2579
+ const walletClient = createWalletClient({
2580
+ account,
2581
+ chain: base2,
2582
+ transport: custom(state.provider)
2583
+ });
2584
+ await ensureWalletOnChain(
2585
+ publicClient,
2586
+ walletClient,
2587
+ 8453
2588
+ );
2589
+ const message = createYieldseekerSiweMessage(
2590
+ account,
2591
+ chainId,
2592
+ this.dependencies
2593
+ );
2594
+ const signature = await walletClient.signMessage({ account, message });
2595
+ return encodeYieldseekerAuthToken({ message, signature });
2596
+ }
2597
+ };
2598
+
2599
+ // src/agents/yieldseeker/yieldseeker.identity-cache.ts
2600
+ var KEY_PREFIX3 = "owney.yieldseeker.identity.v1";
2601
+ var YIELDSEEKER_IDENTITY_TTL_MS = 24 * 60 * 60 * 1e3;
2602
+ var memoryIdentities = /* @__PURE__ */ new Map();
2603
+ var storage3 = () => {
2604
+ if (typeof window === "undefined") return null;
2605
+ try {
2606
+ return window.localStorage;
2607
+ } catch {
2608
+ return null;
2609
+ }
2610
+ };
2611
+ var keyFor = (walletAddress, chainId) => `${KEY_PREFIX3}:${walletAddress.toLowerCase()}:${chainId}`;
2612
+ function valid(value, walletAddress, chainId, now) {
2613
+ return Boolean(
2614
+ value && typeof value.userId === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value.userId) && value.walletAddress.toLowerCase() === walletAddress.toLowerCase() && value.chainId === chainId && Number.isSafeInteger(value.expiresAt) && value.expiresAt > now
2615
+ );
2616
+ }
2617
+ function readYieldseekerIdentity(walletAddress, chainId, now = Date.now()) {
2618
+ if (typeof window === "undefined") return null;
2619
+ const key2 = keyFor(walletAddress, chainId);
2620
+ const store = storage3();
2621
+ let parsed = null;
2622
+ try {
2623
+ const raw2 = store?.getItem(key2);
2624
+ parsed = raw2 ? JSON.parse(raw2) : null;
2625
+ } catch {
2626
+ parsed = null;
2627
+ }
2628
+ const candidate = parsed ?? memoryIdentities.get(key2);
2629
+ if (valid(candidate, walletAddress, chainId, now)) {
2630
+ memoryIdentities.set(key2, candidate);
2631
+ return { userId: candidate.userId };
2632
+ }
2633
+ memoryIdentities.delete(key2);
2634
+ try {
2635
+ store?.removeItem(key2);
2636
+ } catch {
2637
+ }
2638
+ return null;
2639
+ }
2640
+ function writeYieldseekerIdentity(walletAddress, chainId, userId, now = Date.now()) {
2641
+ if (typeof window === "undefined") return;
2642
+ const identity = {
2643
+ userId,
2644
+ walletAddress,
2645
+ chainId,
2646
+ expiresAt: now + YIELDSEEKER_IDENTITY_TTL_MS
2647
+ };
2648
+ if (!valid(identity, walletAddress, chainId, now)) return;
2649
+ const key2 = keyFor(walletAddress, chainId);
2650
+ memoryIdentities.set(key2, identity);
2651
+ try {
2652
+ storage3()?.setItem(key2, JSON.stringify(identity));
2653
+ } catch {
2654
+ }
2655
+ }
2656
+ function clearYieldseekerIdentity(walletAddress, chainId) {
2657
+ const key2 = keyFor(walletAddress, chainId);
2658
+ memoryIdentities.delete(key2);
2659
+ try {
2660
+ storage3()?.removeItem(key2);
2661
+ } catch {
2662
+ }
2663
+ }
2664
+
2665
+ // src/agents/yieldseeker/yieldseeker.client.ts
2666
+ var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2667
+ function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
2668
+ return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
2669
+ }
2670
+ var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
2671
+ var YieldseekerApiError = class extends Error {
2672
+ constructor(status, providerCode, responseFields) {
2673
+ super(`Yieldseeker request failed (${status}): ${providerCode}`);
2674
+ this.status = status;
2675
+ this.providerCode = providerCode;
2676
+ this.responseFields = responseFields;
2677
+ this.name = "YieldseekerApiError";
2678
+ }
2679
+ status;
2680
+ providerCode;
2681
+ responseFields;
2682
+ get isAuthenticationError() {
2683
+ return this.status === 401 || this.status === 403;
2684
+ }
2685
+ };
2686
+ function providerError(body, fallback) {
2687
+ if (!body || typeof body !== "object") return { code: fallback };
2688
+ const record = body;
2689
+ return {
2690
+ code: typeof record.message === "string" ? record.message : fallback,
2691
+ fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
2692
+ };
2693
+ }
2694
+ var YieldseekerApiClient = class {
2695
+ constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch.bind(globalThis)) {
2696
+ this.owneyApiKey = owneyApiKey;
2697
+ this.baseUrl = baseUrl;
2698
+ this.fetchFn = fetchFn;
2699
+ }
2700
+ owneyApiKey;
2701
+ baseUrl;
2702
+ fetchFn;
2703
+ async request(path, options = {}) {
2704
+ const controller = new AbortController();
2705
+ const timer = setTimeout(
2706
+ () => controller.abort(),
2707
+ options.timeoutMs ?? 15e3
2708
+ );
2709
+ try {
2710
+ const response = await this.fetchFn(`${this.baseUrl}${path}`, {
2711
+ method: options.method ?? "GET",
2712
+ headers: {
2713
+ "Content-Type": "application/json",
2714
+ "x-owney-api-key": this.owneyApiKey,
2715
+ ...options.signature ? { Authorization: `Signature ${options.signature}` } : {}
2716
+ },
2717
+ body: options.body ? JSON.stringify(options.body) : void 0,
2718
+ signal: controller.signal
2719
+ });
2720
+ const payload = await response.json().catch(() => null);
2721
+ if (!response.ok) {
2722
+ const error = providerError(payload, `HTTP_${response.status}`);
2723
+ throw new YieldseekerApiError(
2724
+ response.status,
2725
+ error.code,
2726
+ error.fields
2727
+ );
2728
+ }
2729
+ if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
2730
+ return payload.data;
2731
+ }
2732
+ return payload;
2733
+ } catch (error) {
2734
+ if (error instanceof YieldseekerApiError) throw error;
2735
+ if (error instanceof DOMException && error.name === "AbortError") {
2736
+ throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
2737
+ }
2738
+ throw new YieldseekerApiError(0, "NETWORK_ERROR", {
2739
+ cause: error instanceof Error ? error.message : String(error)
2740
+ });
2741
+ } finally {
2742
+ clearTimeout(timer);
2743
+ }
2744
+ }
2745
+ };
2746
+
2747
+ // src/agents/yieldseeker/yieldseeker.mapper.ts
2748
+ import { formatUnits, isAddress } from "viem";
2749
+
2750
+ // src/lib/helpers/snapshot-apy.ts
2751
+ var DAY_MS = 864e5;
2752
+ function snapshotTime(date) {
2753
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
2754
+ const time = Date.parse(date);
2755
+ return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === date ? time : void 0;
2756
+ }
2757
+ function returnFactor(value) {
2758
+ if (typeof value !== "number" && typeof value !== "string") return void 0;
2759
+ if (typeof value === "string" && value.trim() === "") return void 0;
2760
+ const factor = Number(value);
2761
+ return Number.isFinite(factor) && factor >= 0 ? factor : void 0;
2762
+ }
2763
+ function calculateSnapshotApy(snapshots, lookbackDays, now = Date.now()) {
2764
+ if (!Number.isFinite(lookbackDays) || lookbackDays <= 0 || !Number.isFinite(now)) {
2765
+ return void 0;
2766
+ }
2767
+ const points = snapshots.flatMap((snapshot) => {
2768
+ const time = snapshotTime(snapshot.date);
2769
+ return time !== void 0 && time <= now ? [{ snapshot, time }] : [];
2770
+ }).sort((a, b) => a.time - b.time);
2771
+ const end = points.at(-1);
2772
+ if (!end) return void 0;
2773
+ const cutoff = end.time - lookbackDays * DAY_MS;
2774
+ const start = points.find((point) => point.time >= cutoff);
2775
+ const actualDays = (end.time - start.time) / DAY_MS;
2776
+ if (actualDays <= 0) return void 0;
2777
+ const startFactor = returnFactor(start.snapshot.cumulativeReturnFactor);
2778
+ const endFactor = returnFactor(end.snapshot.cumulativeReturnFactor);
2779
+ if (startFactor === void 0 || startFactor === 0 || endFactor === void 0) {
2780
+ return void 0;
2781
+ }
2782
+ const periodReturn = endFactor / startFactor - 1;
2783
+ const apy = Math.pow(1 + periodReturn, 365 / actualDays) - 1;
2784
+ return Number.isFinite(apy) ? apy : void 0;
2785
+ }
2786
+
2787
+ // src/agents/yieldseeker/yieldseeker.types.ts
2788
+ var YIELDSEEKER_ASSET_METADATA = {
2789
+ USDC: {
2790
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2791
+ decimals: 6
2792
+ },
2793
+ WETH: {
2794
+ address: "0x4200000000000000000000000000000000000006",
2795
+ decimals: 18
2796
+ }
2797
+ };
2798
+
2799
+ // src/agents/yieldseeker/yieldseeker.mapper.ts
2800
+ function invalid(endpoint, detail) {
2801
+ throw new OwneyError(
2802
+ "AGENT_INVALID_RESPONSE",
2803
+ `Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
2804
+ { endpoint, detail },
2805
+ "yieldseeker"
2806
+ );
2807
+ }
2808
+ function raw(value, endpoint) {
2809
+ if (typeof value !== "string" || !/^-?[0-9]+$/.test(value)) {
2810
+ return invalid(endpoint, "expected a base-10 integer string");
2811
+ }
2812
+ return BigInt(value);
2813
+ }
2814
+ function decimal(value, decimals, endpoint) {
2815
+ return formatUnits(raw(value, endpoint), decimals);
2816
+ }
2817
+ function usd(rawAmount, decimals, price) {
2818
+ return Number(formatUnits(rawAmount, decimals)) * price;
2819
+ }
2820
+ function percent(value) {
2821
+ const result = Number(value);
2822
+ return Number.isFinite(result) ? result * 100 : 0;
2823
+ }
2824
+ var YIELDSEEKER_POSITIVE_YIELD_FEE_RATE = 0.1;
2825
+ function publicApyAfterYieldseekerFee(value) {
2826
+ const grossPercent = percent(value);
2827
+ if (grossPercent <= 0) return grossPercent;
2828
+ const netPercent = grossPercent * (1 - YIELDSEEKER_POSITIVE_YIELD_FEE_RATE);
2829
+ return Math.round(netPercent * 1e12) / 1e12;
2830
+ }
2831
+ function riskAdjustedApyForDays(option, days) {
2832
+ if (days === "7D") return option.riskAdjustedApy7dAverage;
2833
+ if (days === "30D") return option.riskAdjustedApy30dAverage;
2834
+ return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
2835
+ }
2836
+ function assetAddressValue(record, address) {
2837
+ const entry = Object.entries(record).find(
2838
+ ([key2]) => key2.toLowerCase() === address.toLowerCase()
2839
+ );
2840
+ return entry?.[1] ?? "0";
2841
+ }
2842
+ function position(value, asset, baseAssetDecimals) {
2843
+ const option = value?.yieldOption;
2844
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress(option.address)) {
2845
+ return invalid("yield positions", "missing vault metadata");
2846
+ }
2847
+ return {
2848
+ chain: "BASE",
2849
+ protocol: option.provider,
2850
+ protocolId: option.address,
2851
+ pool: option.name,
2852
+ asset,
2853
+ // `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
2854
+ // differ from the underlying asset. Yieldseeker already converts it to
2855
+ // underlying base-asset units in `assetsBase`; pair that value with the
2856
+ // snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
2857
+ // share quantity separately because withdraw-from-position expects it.
2858
+ amount: decimal(
2859
+ value.assetsBase,
2860
+ baseAssetDecimals,
2861
+ "yield positions"
2862
+ ),
2863
+ amountRaw: String(value.assetsRaw),
2864
+ apy: percent(option.riskAdjustedApy),
2865
+ tvl: Number(option.totalDepositsUsd),
2866
+ liquidity: Number(option.withdrawableDepositsUsd)
2867
+ };
2868
+ }
2869
+ function mapYieldseekerBalances(contexts) {
2870
+ const tokens = [];
2871
+ const assetBalances = [];
2872
+ const positions = [];
2873
+ let totalUsd = 0;
2874
+ for (const context of contexts) {
2875
+ const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
2876
+ assetBalances.push({
2877
+ chain: "BASE",
2878
+ chainId: 8453,
2879
+ asset: context.asset,
2880
+ amount: decimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
2881
+ });
2882
+ const idle = assetAddressValue(
2883
+ context.snapshot.tokenBalances,
2884
+ metadata.address
2885
+ );
2886
+ tokens.push({
2887
+ chain: "BASE",
2888
+ chainId: 8453,
2889
+ asset: context.asset,
2890
+ amount: decimal(idle, metadata.decimals, "snapshot")
2891
+ });
2892
+ positions.push(
2893
+ ...context.positions.map(
2894
+ (entry) => position(
2895
+ entry,
2896
+ context.asset,
2897
+ context.snapshot.baseAssetDecimals
2898
+ )
2899
+ )
2900
+ );
2901
+ totalUsd += usd(
2902
+ raw(context.snapshot.totalValueBase, "snapshot"),
2903
+ context.snapshot.baseAssetDecimals,
2904
+ context.snapshot.baseAssetPriceUsd
2905
+ );
2906
+ }
2907
+ return {
2908
+ ...contexts.length === 1 ? { smartWallet: contexts[0].wallet.walletAddress } : {},
2909
+ totalBalance: String(totalUsd),
2910
+ totalBalanceAsset: "usdc",
2911
+ assetBalances,
2912
+ tokens,
2913
+ positions
2914
+ };
2915
+ }
2916
+ function mapYieldseekerEarnings(contexts) {
2917
+ const tokens = [];
2918
+ let lifetimeEarnings = 0;
2919
+ for (const context of contexts) {
2920
+ const amount = raw(context.snapshot.cumulativeYieldBase, "snapshot");
2921
+ tokens.push({
2922
+ chain: "BASE",
2923
+ chainId: 8453,
2924
+ asset: context.asset,
2925
+ amount: formatUnits(amount, context.snapshot.baseAssetDecimals)
2926
+ });
2927
+ lifetimeEarnings += usd(
2928
+ amount,
2929
+ context.snapshot.baseAssetDecimals,
2930
+ context.snapshot.baseAssetPriceUsd
2931
+ );
2932
+ }
2933
+ return {
2934
+ smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
2935
+ lifetimeEarnings,
2936
+ tokens
2937
+ };
2938
+ }
2939
+ function apyForDays(context, days, now) {
2940
+ if (days === "7D") return percent(context.snapshot.apy7d);
2941
+ if (days === "30D") return percent(context.snapshot.apy30d);
2942
+ const apy = calculateSnapshotApy(context.historic?.dailyYieldSnapshots ?? [], 14, now);
2943
+ const apyPercent = apy === void 0 ? void 0 : apy * 100;
2944
+ return apyPercent !== void 0 && Number.isFinite(apyPercent) ? apyPercent : void 0;
2945
+ }
2946
+ function dailyApy(point) {
2947
+ const total = raw(point.totalValueBase, "historic position");
2948
+ const earned = raw(point.dailyYieldBase, "historic position");
2949
+ const principal = total - earned;
2950
+ if (principal <= 0n || earned === 0n) return 0;
2951
+ return Number(earned) / Number(principal) * 365 * 100;
2952
+ }
2953
+ function aggregateHistory(contexts, dayCount, now) {
2954
+ const today = new Date(now).toISOString().slice(0, 10);
2955
+ const cutoff = new Date(Date.parse(today) - (dayCount - 1) * 864e5).toISOString().slice(0, 10);
2956
+ const assets = new Set(contexts.map((context) => context.asset.toUpperCase()));
2957
+ const unit = assets.size === 1 ? [...assets][0] : "USD";
2958
+ const byDate = /* @__PURE__ */ new Map();
2959
+ for (const context of contexts) {
2960
+ const points = context.historic?.dailyYieldSnapshots ?? [];
2961
+ for (const point of points) {
2962
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(point.date) || !Number.isFinite(Date.parse(point.date)) || point.date < cutoff || point.date > today) continue;
2963
+ const tokens = Number(decimal(point.totalValueBase, point.baseAssetDecimals, "historic position"));
2964
+ const amount = assets.size === 1 ? tokens : tokens * point.baseAssetPriceUsd;
2965
+ if (!Number.isFinite(amount) || amount < 0) {
2966
+ invalid("historic position", "expected a finite non-negative balance");
2967
+ }
2968
+ const current = byDate.get(point.date) ?? { weighted: 0, amount: 0 };
2969
+ current.weighted += dailyApy(point) * amount;
2970
+ current.amount += amount;
2971
+ byDate.set(point.date, current);
2972
+ }
2973
+ }
2974
+ return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
2975
+ date,
2976
+ apy: value.amount > 0 ? value.weighted / value.amount : 0,
2977
+ historicalBalance: { amount: value.amount, unit }
2978
+ }));
2979
+ }
2980
+ function mapYieldseekerApy(walletAddress, contexts, days, now = Date.now()) {
2981
+ let weighted = 0;
2982
+ let totalUsd = 0;
2983
+ const byAsset = {};
2984
+ for (const context of contexts) {
2985
+ const valueUsd = usd(
2986
+ raw(context.snapshot.totalValueBase, "snapshot"),
2987
+ context.snapshot.baseAssetDecimals,
2988
+ context.snapshot.baseAssetPriceUsd
2989
+ );
2990
+ const apy = apyForDays(context, days, now);
2991
+ if (apy === void 0) continue;
2992
+ weighted += apy * valueUsd;
2993
+ totalUsd += valueUsd;
2994
+ byAsset[context.asset] = apy;
2995
+ }
2996
+ const dayCount = Number(days.slice(0, -1));
2997
+ return {
2998
+ walletAddress,
2999
+ ...days !== "14D" || Object.keys(byAsset).length > 0 ? { weightedApyAfterFee: totalUsd > 0 ? weighted / totalUsd : 0 } : {},
3000
+ apyByChainAndAsset: { 8453: byAsset },
3001
+ history: aggregateHistory(contexts, dayCount, now)
3002
+ };
3003
+ }
3004
+ function actionType(value) {
3005
+ const normalized = value.toLowerCase();
3006
+ if (normalized.includes("deposit")) return "Deposit";
3007
+ if (normalized.includes("withdraw")) return "Withdraw";
3008
+ if (normalized.includes("yield") || normalized.includes("earn"))
3009
+ return "Earned";
3010
+ return "Rebalance";
3011
+ }
3012
+ function transactionHashes(details) {
3013
+ if (!details) return [];
3014
+ const values = [
3015
+ details.transactionHash,
3016
+ details.txHash,
3017
+ ...Array.isArray(details.transactionHashes) ? details.transactionHashes : [],
3018
+ ...Array.isArray(details.txHashes) ? details.txHashes : []
3019
+ ];
3020
+ return values.filter(
3021
+ (value) => typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value)
3022
+ ).filter((value, index, all) => all.indexOf(value) === index);
3023
+ }
3024
+ function actionEntry(action) {
3025
+ if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
3026
+ return {
3027
+ agent: "yieldseeker",
3028
+ action: actionType(action.actionType),
3029
+ date: action.createdDate,
3030
+ oldApy: null,
3031
+ newApy: null,
3032
+ transactions: [
3033
+ {
3034
+ txHashes: transactionHashes(action.details),
3035
+ chainId: 8453
3036
+ }
3037
+ ],
3038
+ rebalanceLog: []
3039
+ };
3040
+ }
3041
+ function depositDestination(context, movement) {
3042
+ const to = movement.toAddress.toLowerCase();
3043
+ const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
3044
+ if (option) return { protocol: option.provider || "Vault", pool: option.name, tokenSymbol: context.asset };
3045
+ const receipt = context.historic?.assets.find((asset) => asset.chainId === movement.chainId && !asset.isSpam && asset.address.toLowerCase() === to && asset.address.toLowerCase() !== context.agent.assetAddress.toLowerCase());
3046
+ const minted = receipt && context.historic?.movements.some((candidate) => candidate.chainId === movement.chainId && candidate.transactionHash.toLowerCase() === movement.transactionHash.toLowerCase() && candidate.assetAddress.toLowerCase() === receipt.address.toLowerCase() && candidate.fromAddress.toLowerCase() === "0x0000000000000000000000000000000000000000" && candidate.toAddress.toLowerCase() === context.wallet.walletAddress.toLowerCase() && raw(candidate.assetAmount, "historic position") > 0n);
3047
+ if (receipt && minted) return { protocol: "Vault", pool: receipt.name || receipt.symbol, tokenSymbol: context.asset };
3048
+ return void 0;
3049
+ }
3050
+ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses, destination) {
3051
+ const from = movement.fromAddress.toLowerCase();
3052
+ const to = movement.toAddress.toLowerCase();
3053
+ const owner = ownerAddress.toLowerCase();
3054
+ const agentWallet = wallet.walletAddress.toLowerCase();
3055
+ const baseAsset = agent.assetAddress.toLowerCase();
3056
+ if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
3057
+ return void 0;
3058
+ }
3059
+ let action;
3060
+ if (to === agentWallet && !vaultAddresses.has(from)) {
3061
+ action = "Top up";
3062
+ } else if (from === agentWallet && to === owner) {
3063
+ action = "Withdraw";
3064
+ } else if (from === agentWallet && destination) {
3065
+ action = "Deposit";
3066
+ }
3067
+ if (!action) return void 0;
3068
+ return {
3069
+ agent: "yieldseeker",
3070
+ action,
3071
+ ...action === "Deposit" && destination ? { positions: [{
3072
+ ...destination,
3073
+ amount: decimal(movement.assetAmount, YIELDSEEKER_ASSET_METADATA[asset].decimals, "historic position")
3074
+ }] } : {},
3075
+ date: movement.blockDate,
3076
+ oldApy: null,
3077
+ newApy: null,
3078
+ transactions: [
3079
+ {
3080
+ txHashes: [movement.transactionHash],
3081
+ chainId: agent.chainId,
3082
+ tokenSymbol: asset,
3083
+ amount: decimal(
3084
+ movement.assetAmount,
3085
+ YIELDSEEKER_ASSET_METADATA[asset].decimals,
3086
+ "historic position"
3087
+ )
3088
+ }
3089
+ ],
3090
+ rebalanceLog: []
3091
+ };
3092
+ }
3093
+ function mapYieldseekerHistory(contexts, options) {
3094
+ const entries = contexts.flatMap((context) => {
3095
+ const seenMovements = /* @__PURE__ */ new Set();
3096
+ const movements = (context.historic?.movements ?? []).filter((movement) => {
3097
+ const key2 = `${movement.chainId}:${movement.transactionHash.toLowerCase()}:${movement.logIndex}`;
3098
+ if (seenMovements.has(key2)) return false;
3099
+ seenMovements.add(key2);
3100
+ return true;
3101
+ });
3102
+ return [
3103
+ ...movements.map(
3104
+ (movement) => movementEntry(
3105
+ movement,
3106
+ context.wallet,
3107
+ context.agent,
3108
+ context.asset,
3109
+ options.ownerAddress,
3110
+ options.vaultAddresses,
3111
+ depositDestination(context, movement)
3112
+ )
3113
+ ),
3114
+ ...(context.actions ?? []).map(actionEntry)
3115
+ ].filter((entry) => entry !== void 0);
3116
+ });
3117
+ const grouped = /* @__PURE__ */ new Map();
3118
+ const ungrouped = [];
3119
+ for (const entry of entries) {
3120
+ const tx = entry.transactions[0];
3121
+ const hash = tx?.txHashes[0];
3122
+ if (!hash) {
3123
+ ungrouped.push(entry);
3124
+ continue;
3125
+ }
3126
+ const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}`;
3127
+ const previous = grouped.get(key2);
3128
+ if (!previous) {
3129
+ grouped.set(key2, entry);
3130
+ continue;
3131
+ }
3132
+ if (entry.action === "Deposit" && entry.positions?.length) {
3133
+ if (!previous.positions?.length) {
3134
+ grouped.set(key2, entry);
3135
+ continue;
3136
+ }
3137
+ previous.positions.push(...entry.positions);
3138
+ previous.transactions.push(...entry.transactions);
3139
+ }
3140
+ }
3141
+ const filtered = [...grouped.values(), ...ungrouped].filter((entry) => (!options.fromDate || entry.date >= options.fromDate) && (!options.toDate || entry.date <= options.toDate)).sort((left, right) => right.date.localeCompare(left.date));
3142
+ return {
3143
+ data: filtered.slice(0, options.limit),
3144
+ // v1 returns the whole action/movement collection and defines no cursor.
3145
+ // Report a terminal page so callers never loop over the same prefix.
3146
+ hasMore: false
3147
+ };
3148
+ }
3149
+ function mapYieldseekerProfile(address, contexts) {
3150
+ const protocols = /* @__PURE__ */ new Set();
3151
+ for (const context of contexts) {
3152
+ for (const current of context.positions) {
3153
+ if (current.yieldOption?.provider) {
3154
+ protocols.add(String(current.yieldOption.provider));
3155
+ }
3156
+ }
3157
+ }
3158
+ return {
3159
+ address,
3160
+ smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
3161
+ chains: contexts.length > 0 ? [8453] : [],
3162
+ hasActiveSessionKey: contexts.some(
3163
+ (context) => context.wallet.initializedDate != null
3164
+ ),
3165
+ protocols: [...protocols]
3166
+ };
3167
+ }
3168
+ function mapYieldseekerAgentApy(options, days) {
3169
+ const perAsset = {};
3170
+ const all = [];
3171
+ for (const entry of options) {
3172
+ const apys = entry.yieldOptions.map(
3173
+ (option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
3174
+ ).filter(Number.isFinite);
3175
+ if (apys.length === 0) continue;
3176
+ const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
3177
+ perAsset[entry.asset] = average;
3178
+ all.push(average);
3179
+ }
3180
+ return {
3181
+ averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
3182
+ detailedApys: { apyPerAsset: { 8453: perAsset } }
3183
+ };
3184
+ }
3185
+
3186
+ // src/agents/yieldseeker/yieldseeker.agent.ts
3187
+ var OWNEY_AGENT_NAME = "owney";
3188
+ var YIELDSEEKER_USERNAME_PREFIX = "owney_";
3189
+ var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
3190
+ var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
3191
+ var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
3192
+ function generateYieldseekerUsername() {
3193
+ const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
3194
+ return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
3195
+ }
3196
+ function isUsernameConflict(error) {
3197
+ if (!(error instanceof YieldseekerApiError)) return false;
3198
+ const code = error.providerCode.toUpperCase();
3199
+ return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
3200
+ }
3201
+ var YIELDSEEKER_AGENT_WALLET_ABI = [
3202
+ {
3203
+ type: "function",
3204
+ name: "withdrawAssetToUser",
3205
+ stateMutability: "nonpayable",
3206
+ inputs: [
3207
+ { name: "recipient", type: "address" },
3208
+ { name: "asset", type: "address" },
3209
+ { name: "amount", type: "uint256" }
3210
+ ],
3211
+ outputs: []
3212
+ },
3213
+ {
3214
+ type: "function",
3215
+ name: "withdrawAllAssetToUser",
3216
+ stateMutability: "nonpayable",
3217
+ inputs: [
3218
+ { name: "recipient", type: "address" },
3219
+ { name: "asset", type: "address" }
3220
+ ],
3221
+ outputs: []
3222
+ }
3223
+ ];
3224
+ function query(params) {
3225
+ const search = new URLSearchParams();
3226
+ for (const [key2, value] of Object.entries(params)) {
3227
+ if (value !== void 0) search.set(key2, String(value));
3228
+ }
3229
+ const encoded = search.toString();
3230
+ return encoded ? `?${encoded}` : "";
3231
+ }
3232
+ var YieldseekerAgent = class {
3233
+ id = "yieldseeker";
3234
+ balanceComposition = "tokens-plus-positions";
3235
+ supportedChainIds = [8453];
3236
+ supportedAssets = [
3237
+ {
3238
+ chainId: 8453,
3239
+ chain: "BASE",
3240
+ assets: [
3241
+ { symbol: "USDC", minDepositAmount: "10000000" },
3242
+ { symbol: "WETH", minDepositAmount: "1" }
3243
+ ]
3244
+ }
3245
+ ];
3246
+ api;
3247
+ auth;
3248
+ transactionExecutor;
3249
+ unwindReceiptWaiter;
3250
+ agentContexts = /* @__PURE__ */ new Map();
3251
+ users = /* @__PURE__ */ new Map();
3252
+ pendingAgents = /* @__PURE__ */ new Map();
3253
+ yieldOptions = /* @__PURE__ */ new Map();
3254
+ pendingYieldOptions = /* @__PURE__ */ new Map();
3255
+ constructor(owneyApiKey, options = {}) {
3256
+ this.api = new YieldseekerApiClient(
3257
+ owneyApiKey,
3258
+ options.baseUrl ?? getYieldseekerProxyBaseUrl(),
3259
+ options.fetchFn
3260
+ );
3261
+ this.auth = new YieldseekerAuth(options.auth);
3262
+ this.transactionExecutor = options.transactionExecutor;
3263
+ this.unwindReceiptWaiter = options.unwindReceiptWaiter;
3264
+ }
3265
+ async disconnect() {
3266
+ this.auth.clear();
3267
+ for (const key2 of this.users.keys()) {
3268
+ const [walletAddress, chainId] = key2.split(":");
3269
+ clearYieldseekerIdentity(walletAddress, Number(chainId));
3270
+ }
3271
+ this.users.clear();
3272
+ this.agentContexts.clear();
3273
+ this.pendingAgents.clear();
3274
+ }
3275
+ async activateAgent(state, chainId, asset) {
3276
+ this.assertChain(chainId);
3277
+ const targetAsset = asset ?? "USDC";
3278
+ this.assertAsset(targetAsset);
3279
+ await this.ensureAgent(state, chainId, targetAsset);
3280
+ }
3281
+ async deposit(state, chainId, amount, asset, depositCallback) {
3282
+ this.assertChain(chainId);
3283
+ this.assertAsset(asset);
3284
+ if (BigInt(amount) <= 0n) {
3285
+ throw new OwneyError(
3286
+ "DEPOSIT_AMOUNT_BELOW_MINIMUM",
3287
+ "Yieldseeker deposits must be greater than zero.",
3288
+ { amount, minDepositAmount: "1" },
3289
+ this.id
3290
+ );
3291
+ }
3292
+ const context = await this.ensureAgent(state, chainId, asset);
3293
+ let txHash;
3294
+ try {
3295
+ if (depositCallback) {
3296
+ provideDepositVerificationContext(depositCallback, {
3297
+ agentId: "yieldseeker",
3298
+ signature: await this.auth.getToken(state, chainId),
3299
+ userId: context.user.userId,
3300
+ yieldseekerAgentId: context.agent.agentId
3301
+ });
3302
+ txHash = await depositCallback(
3303
+ context.wallet.walletAddress,
3304
+ chainId,
3305
+ amount
3306
+ );
3307
+ await this.waitForReceipt(state, chainId, txHash);
3308
+ } else {
3309
+ txHash = await this.submitTransaction(state, chainId, {
3310
+ from: getAddress2(state.walletAddress),
3311
+ to: YIELDSEEKER_ASSET_METADATA[asset].address,
3312
+ data: encodeFunctionData({
3313
+ abi: erc20Abi,
3314
+ functionName: "transfer",
3315
+ args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
3316
+ }),
3317
+ value: "0",
3318
+ chainId
3319
+ });
3320
+ }
3321
+ } finally {
3322
+ await this.refreshSnapshotAfterMovement(
3323
+ state,
3324
+ chainId,
3325
+ context,
3326
+ "deposit"
3327
+ );
3328
+ }
3329
+ return {
3330
+ txHash,
3331
+ smartWallet: context.wallet.walletAddress,
3332
+ amount
3333
+ };
3334
+ }
3335
+ async withdraw(state, chainId, asset, amount) {
3336
+ this.assertChain(chainId);
3337
+ this.assertAsset(asset);
3338
+ if (amount !== void 0 && BigInt(amount) <= 0n) {
3339
+ throw new OwneyError(
3340
+ "WITHDRAW_FAILED",
3341
+ "Yieldseeker withdrawals must be greater than zero.",
3342
+ { amount },
3343
+ this.id
3344
+ );
3345
+ }
3346
+ const context = await this.findAgent(state, chainId, asset);
3347
+ if (!context) {
3348
+ throw new OwneyError(
3349
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3350
+ `No Yieldseeker ${asset} agent exists for this wallet.`,
3351
+ { asset, available: "0" },
3352
+ this.id
3353
+ );
3354
+ }
3355
+ try {
3356
+ const portfolio = await this.loadPortfolioContext(
3357
+ state,
3358
+ chainId,
3359
+ context
3360
+ );
3361
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3362
+ const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
3363
+ ([address]) => address.toLowerCase() === metadata.address.toLowerCase()
3364
+ );
3365
+ const idle = BigInt(idleEntry?.[1] ?? "0");
3366
+ const deployed = portfolio.positions.reduce(
3367
+ (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
3368
+ 0n
3369
+ );
3370
+ const totalAvailable = idle + deployed;
3371
+ const requested = amount === void 0 ? totalAvailable : BigInt(amount);
3372
+ if (requested > totalAvailable) {
3373
+ throw new OwneyError(
3374
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3375
+ `Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
3376
+ {
3377
+ asset,
3378
+ requested: requested.toString(),
3379
+ available: totalAvailable.toString()
3380
+ },
3381
+ this.id
3382
+ );
3383
+ }
3384
+ let remaining = requested > idle ? requested - idle : 0n;
3385
+ for (const position2 of portfolio.positions) {
3386
+ if (remaining === 0n) break;
3387
+ const available = BigInt(position2.withdrawableAssetsRaw);
3388
+ if (available <= 0n) continue;
3389
+ const assetsRaw = available < remaining ? available : remaining;
3390
+ const response = await this.walletRequest(
3391
+ state,
3392
+ chainId,
3393
+ this.agentPath(context, "withdraw-from-position"),
3394
+ {
3395
+ method: "POST",
3396
+ body: {
3397
+ chainId,
3398
+ vaultAddress: position2.yieldOption.address,
3399
+ assetsRaw: assetsRaw.toString()
3400
+ }
3401
+ }
3402
+ );
3403
+ if (!this.isTransactionHash(response?.transactionHash)) {
3404
+ throw this.invalidResponse("position withdrawal");
3405
+ }
3406
+ await this.waitForReceipt(state, chainId, response.transactionHash);
3407
+ remaining -= assetsRaw;
3408
+ }
3409
+ if (remaining > 0n) {
3410
+ throw this.invalidResponse("yield positions", {
3411
+ reason: "Withdrawable positions could not cover the request.",
3412
+ remaining: remaining.toString()
3413
+ });
3414
+ }
3415
+ const account = getAddress2(state.walletAddress);
3416
+ const txHash = await this.submitTransaction(state, chainId, {
3417
+ from: account,
3418
+ to: getAddress2(context.wallet.walletAddress),
3419
+ data: amount === void 0 ? encodeFunctionData({
3420
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
3421
+ functionName: "withdrawAllAssetToUser",
3422
+ args: [account, metadata.address]
3423
+ }) : encodeFunctionData({
3424
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
3425
+ functionName: "withdrawAssetToUser",
3426
+ args: [account, metadata.address, requested]
3427
+ }),
3428
+ value: "0",
3429
+ chainId
3430
+ });
3431
+ return {
3432
+ txHash,
3433
+ type: amount === void 0 ? "full" : "partial",
3434
+ amount: requested.toString()
3435
+ };
3436
+ } finally {
3437
+ await this.refreshSnapshotAfterMovement(
3438
+ state,
3439
+ chainId,
3440
+ context,
3441
+ "withdrawal"
3442
+ );
3443
+ }
3444
+ }
3445
+ async getBalances(state, chainId) {
3446
+ this.assertChain(chainId);
3447
+ return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
3448
+ }
3449
+ async getEarnings(state, chainId) {
3450
+ this.assertChain(chainId);
3451
+ return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
3452
+ }
3453
+ async getAccountApy(state, chainId, days, tokenSymbol) {
3454
+ this.assertChain(chainId);
3455
+ const asset = tokenSymbol?.toUpperCase();
3456
+ if (asset !== void 0) this.assertAsset(asset);
3457
+ const contexts = await this.loadPortfolio(state, chainId, {
3458
+ ...asset ? { asset } : {},
3459
+ historic: true
3460
+ });
3461
+ return mapYieldseekerApy(state.walletAddress, contexts, days);
3462
+ }
3463
+ async getHistory(state, chainId, options) {
3464
+ this.assertChain(chainId);
3465
+ const asset = options?.tokenSymbol?.toUpperCase();
3466
+ if (asset !== void 0) this.assertAsset(asset);
3467
+ const contexts = await this.loadPortfolio(state, chainId, {
3468
+ ...asset ? { asset } : {},
3469
+ historic: true,
3470
+ actions: true
3471
+ });
3472
+ const catalog = await Promise.all(
3473
+ [...new Set(contexts.map((context) => context.asset))].map(
3474
+ (contextAsset) => this.loadYieldOptions(contextAsset)
3475
+ )
3476
+ );
3477
+ const vaultAddresses = new Set(
3478
+ catalog.flat().filter(
3479
+ (yieldOption) => yieldOption.chainId === chainId && isAddress2(yieldOption.address)
3480
+ ).map((yieldOption) => yieldOption.address.toLowerCase())
3481
+ );
3482
+ return mapYieldseekerHistory(contexts, {
3483
+ limit: options?.limit ?? 10,
3484
+ ownerAddress: state.walletAddress,
3485
+ vaultAddresses,
3486
+ ...options?.fromDate ? { fromDate: options.fromDate } : {},
3487
+ ...options?.toDate ? { toDate: options.toDate } : {}
3488
+ });
3489
+ }
3490
+ async getUserProfile(state, chainId) {
3491
+ this.assertChain(chainId);
3492
+ return mapYieldseekerProfile(
3493
+ state.walletAddress,
3494
+ await this.loadPortfolio(state, chainId, {})
3495
+ );
3496
+ }
3497
+ async getAgentApy(days, options) {
3498
+ this.assertOptionalChain(options?.chainId);
3499
+ const requested = options?.tokenSymbol?.toUpperCase();
3500
+ if (requested !== void 0) this.assertAsset(requested);
3501
+ const assets = requested ? [requested] : ["USDC", "WETH"];
3502
+ const values = await Promise.all(
3503
+ assets.map(async (asset) => {
3504
+ return { asset, yieldOptions: await this.loadYieldOptions(asset) };
3505
+ })
3506
+ );
3507
+ return mapYieldseekerAgentApy(values, days);
3508
+ }
3509
+ async loadYieldOptions(asset) {
3510
+ const cached = this.yieldOptions.get(asset);
3511
+ if (cached && cached.expiresAt > Date.now()) return cached.value;
3512
+ const pending = this.pendingYieldOptions.get(asset);
3513
+ if (pending) return pending;
3514
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3515
+ const request = this.api.request(
3516
+ `/chains/8453/assets/${metadata.address}/yield-options`
3517
+ ).then((response) => {
3518
+ if (!Array.isArray(response?.yieldOptions)) {
3519
+ throw this.invalidResponse("yield options");
3520
+ }
3521
+ this.yieldOptions.set(asset, {
3522
+ expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
3523
+ value: response.yieldOptions
3524
+ });
3525
+ return response.yieldOptions;
3526
+ }).finally(() => this.pendingYieldOptions.delete(asset));
3527
+ this.pendingYieldOptions.set(asset, request);
3528
+ return request;
3529
+ }
3530
+ userKey(state, chainId) {
3531
+ return `${state.walletAddress.toLowerCase()}:${chainId}`;
3532
+ }
3533
+ contextKey(state, chainId, asset) {
3534
+ return `${this.userKey(state, chainId)}:${asset}`;
3535
+ }
3536
+ async resolveUser(state, chainId) {
3537
+ const key2 = this.userKey(state, chainId);
3538
+ const inMemory = this.users.get(key2);
3539
+ if (inMemory) return inMemory;
3540
+ const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
3541
+ if (persisted) {
3542
+ this.users.set(key2, persisted);
3543
+ return persisted;
3544
+ }
3545
+ const walletAddress = getAddress2(state.walletAddress);
3546
+ let user = null;
3547
+ try {
3548
+ const login = await this.providerRequest(
3549
+ state,
3550
+ chainId,
3551
+ "/users/login-with-wallet",
3552
+ { method: "POST", body: { walletAddress } }
3553
+ );
3554
+ user = login?.user ?? null;
3555
+ if (!user) {
3556
+ throw this.invalidResponse("wallet login", {
3557
+ reason: "A successful login returned no user."
3558
+ });
3559
+ }
3560
+ } catch (error) {
3561
+ if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
3562
+ if (error instanceof OwneyError) throw error;
3563
+ throw this.mapApiError(error);
3564
+ }
3565
+ let created;
3566
+ for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
3567
+ try {
3568
+ created = await this.providerRequest(
3569
+ state,
3570
+ chainId,
3571
+ "/users",
3572
+ {
3573
+ method: "POST",
3574
+ body: {
3575
+ walletAddress,
3576
+ username: generateYieldseekerUsername()
3577
+ }
3578
+ }
3579
+ );
3580
+ break;
3581
+ } catch (createError) {
3582
+ const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
3583
+ if (canRetry) continue;
3584
+ throw this.mapApiError(createError);
3585
+ }
3586
+ }
3587
+ user = created?.user ?? null;
3588
+ }
3589
+ if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
3590
+ throw this.invalidResponse("wallet identity");
3591
+ }
3592
+ const resolved = { userId: user.userId };
3593
+ this.users.set(key2, resolved);
3594
+ writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
3595
+ return resolved;
3596
+ }
3597
+ forgetUser(state, chainId) {
3598
+ this.users.delete(this.userKey(state, chainId));
3599
+ clearYieldseekerIdentity(state.walletAddress, chainId);
3600
+ }
3601
+ async ensureAgent(state, chainId, asset) {
3602
+ const key2 = this.contextKey(state, chainId, asset);
3603
+ const cached = this.agentContexts.get(key2);
3604
+ if (cached) return cached;
3605
+ const pending = this.pendingAgents.get(key2);
3606
+ if (pending) return pending;
3607
+ const request = this.resolveAgent(state, chainId, asset, true).then(
3608
+ async (context) => {
3609
+ if (!context) throw this.invalidResponse("agent creation");
3610
+ await this.deployAgent(state, chainId, context);
3611
+ this.agentContexts.set(key2, context);
3612
+ return context;
3613
+ }
3614
+ );
3615
+ this.pendingAgents.set(key2, request);
3616
+ try {
3617
+ return await request;
3618
+ } finally {
3619
+ this.pendingAgents.delete(key2);
3620
+ }
3621
+ }
3622
+ async findAgent(state, chainId, asset) {
3623
+ const key2 = this.contextKey(state, chainId, asset);
3624
+ const cached = this.agentContexts.get(key2);
3625
+ if (cached) return cached;
3626
+ const context = await this.resolveAgent(state, chainId, asset, false);
3627
+ if (context) this.agentContexts.set(key2, context);
3628
+ return context;
3629
+ }
3630
+ async resolveAgent(state, chainId, asset, createIfMissing) {
3631
+ const user = await this.resolveUser(state, chainId);
3632
+ const response = await this.walletRequest(
3633
+ state,
3634
+ chainId,
3635
+ `/users/${user.userId}/agents`
3636
+ );
3637
+ if (!Array.isArray(response?.agents)) {
3638
+ throw this.invalidResponse("agent list");
3639
+ }
3640
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3641
+ let agent = response.agents.find(
3642
+ (candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
3643
+ );
3644
+ if (!agent && createIfMissing) {
3645
+ const created = await this.walletRequest(
3646
+ state,
3647
+ chainId,
3648
+ `/users/${user.userId}/agents`,
3649
+ {
3650
+ method: "POST",
3651
+ body: {
3652
+ name: OWNEY_AGENT_NAME,
3653
+ emoji: "\u{1F989}",
3654
+ chainId,
3655
+ assetAddress: metadata.address,
3656
+ type: "vault",
3657
+ rulePreset: null
3658
+ }
3659
+ }
3660
+ );
3661
+ agent = created?.agent;
3662
+ }
3663
+ if (!agent) return null;
3664
+ this.assertAgent(agent);
3665
+ const walletResponse = await this.walletRequest(
3666
+ state,
3667
+ chainId,
3668
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3669
+ );
3670
+ if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
3671
+ throw this.invalidResponse("agent wallet");
3672
+ }
3673
+ return { user, agent, wallet: walletResponse.agentWallet, asset };
3674
+ }
3675
+ async loadPortfolio(state, chainId, options) {
3676
+ const user = await this.resolveUser(state, chainId);
3677
+ const response = await this.walletRequest(
3678
+ state,
3679
+ chainId,
3680
+ `/users/${user.userId}/agents`
3681
+ );
3682
+ if (!Array.isArray(response?.agents)) {
3683
+ throw this.invalidResponse("agent list");
3684
+ }
3685
+ const contexts = [];
3686
+ for (const agent of response.agents) {
3687
+ const asset = this.assetForAgent(agent);
3688
+ if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
3689
+ continue;
3690
+ }
3691
+ this.assertAgent(agent);
3692
+ const walletResponse = await this.walletRequest(
3693
+ state,
3694
+ chainId,
3695
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3696
+ );
3697
+ if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
3698
+ throw this.invalidResponse("agent wallet");
3699
+ }
3700
+ const context = {
3701
+ user,
3702
+ agent,
3703
+ wallet: walletResponse.agentWallet,
3704
+ asset
3705
+ };
3706
+ this.agentContexts.set(this.contextKey(state, chainId, asset), context);
3707
+ contexts.push(context);
3708
+ }
3709
+ return Promise.all(
3710
+ contexts.map(
3711
+ (context) => this.loadPortfolioContext(state, chainId, context, options)
3712
+ )
3713
+ );
3714
+ }
3715
+ async loadPortfolioContext(state, chainId, context, options = {}) {
3716
+ const [snapshot, positions, historic, actions] = await Promise.all([
3717
+ this.walletRequest(
3718
+ state,
3719
+ chainId,
3720
+ `${this.agentPath(context, "snapshot")}${query({
3721
+ shouldOnlyUseRecentValue: true,
3722
+ shouldAllowStaleOnError: true
3723
+ })}`
3724
+ ),
3725
+ this.walletRequest(
3726
+ state,
3727
+ chainId,
3728
+ this.agentPath(context, "yield-positions")
3729
+ ),
3730
+ options.historic ? this.walletRequest(
3731
+ state,
3732
+ chainId,
3733
+ this.agentPath(context, "wallet/historic-position")
3734
+ ) : Promise.resolve(void 0),
3735
+ options.actions ? this.walletRequest(
3736
+ state,
3737
+ chainId,
3738
+ this.agentPath(context, "actions")
3739
+ ) : Promise.resolve(void 0)
3740
+ ]);
3741
+ if (!snapshot?.agentSnapshot) {
3742
+ throw this.invalidResponse("agent snapshot");
3743
+ }
3744
+ if (!Array.isArray(positions?.yieldPositions)) {
3745
+ throw this.invalidResponse("yield positions");
3746
+ }
3747
+ return {
3748
+ ...context,
3749
+ snapshot: snapshot.agentSnapshot,
3750
+ positions: positions.yieldPositions,
3751
+ ...historic?.position ? { historic: historic.position } : {},
3752
+ ...actions?.actions ? { actions: actions.actions } : {}
3753
+ };
3754
+ }
3755
+ async deployAgent(state, chainId, context) {
3756
+ if (context.wallet.initializedDate != null) return;
3757
+ const walletAddress = context.wallet.walletAddress.toLowerCase();
3758
+ const deployed = await this.walletRequest(
3759
+ state,
3760
+ chainId,
3761
+ this.agentPath(context, "deploy"),
3762
+ { method: "POST", body: {} }
3763
+ );
3764
+ if (!deployed?.agentWallet || !isAddress2(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
3765
+ throw this.invalidResponse("agent deployment", {
3766
+ reason: "Deploy did not return the expected Agent Wallet."
3767
+ });
3768
+ }
3769
+ context.wallet = deployed.agentWallet;
3770
+ }
3771
+ async refreshSnapshotAfterMovement(state, chainId, context, movement) {
3772
+ try {
3773
+ const response = await this.walletRequest(
3774
+ state,
3775
+ chainId,
3776
+ `${this.agentPath(context, "snapshot")}${query({
3777
+ shouldForceRefresh: true
3778
+ })}`
3779
+ );
3780
+ if (!response?.agentSnapshot) {
3781
+ throw this.invalidResponse("agent snapshot refresh");
3782
+ }
3783
+ } catch (error) {
3784
+ console.warn(
3785
+ `[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
3786
+ error
3787
+ );
3788
+ }
3789
+ }
3790
+ agentPath(context, suffix) {
3791
+ return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
3792
+ }
3793
+ async walletRequest(state, chainId, path, options = {}) {
3794
+ try {
3795
+ return await this.providerRequest(state, chainId, path, options);
3796
+ } catch (error) {
3797
+ throw this.mapApiError(error);
3798
+ }
3799
+ }
3800
+ async providerRequest(state, chainId, path, options = {}) {
3801
+ this.assertChain(chainId);
3802
+ const request = (signature2) => this.api.request(path, {
3803
+ ...options,
3804
+ signature: signature2
3805
+ });
3806
+ let signature = await this.auth.getToken(state, chainId);
3807
+ try {
3808
+ return await request(signature);
3809
+ } catch (error) {
3810
+ if (!(error instanceof YieldseekerApiError)) throw error;
3811
+ if (error.providerCode === "NO_USER") throw error;
3812
+ if (!error.isAuthenticationError) throw error;
3813
+ signature = await this.auth.refreshToken(state, chainId, signature);
3814
+ try {
3815
+ return await request(signature);
3816
+ } catch (retryError) {
3817
+ if (retryError instanceof YieldseekerApiError && retryError.isAuthenticationError) {
3818
+ this.forgetUser(state, chainId);
3819
+ }
3820
+ throw retryError;
3821
+ }
3822
+ }
3823
+ }
3824
+ mapApiError(error) {
3825
+ if (!(error instanceof YieldseekerApiError)) {
3826
+ return new OwneyError(
3827
+ "AGENT_API_ERROR",
3828
+ "Yieldseeker request failed.",
3829
+ { cause: error instanceof Error ? error.message : String(error) },
3830
+ this.id
3831
+ );
3832
+ }
3833
+ const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
3834
+ return new OwneyError(
3835
+ code,
3836
+ `Yieldseeker request failed: ${error.providerCode}.`,
3837
+ {
3838
+ statusCode: error.status,
3839
+ providerCode: error.providerCode,
3840
+ ...error.responseFields ? { fields: error.responseFields } : {}
3841
+ },
3842
+ this.id
3843
+ );
3844
+ }
3845
+ async submitTransaction(state, chainId, transaction) {
3846
+ if (this.transactionExecutor) {
3847
+ return this.transactionExecutor(state, chainId, transaction);
3848
+ }
3849
+ this.assertTransaction(transaction, state, chainId);
3850
+ const account = getAddress2(state.walletAddress);
3851
+ const walletClient = createWalletClient2({
3852
+ account,
3853
+ chain: base3,
3854
+ transport: custom2(state.provider)
3855
+ });
3856
+ const publicClient = createPublicClient3({
3857
+ chain: base3,
3858
+ transport: custom2(state.provider)
3859
+ });
3860
+ await ensureWalletOnChain(
3861
+ publicClient,
3862
+ walletClient,
3863
+ 8453
3864
+ );
3865
+ const hash = await walletClient.sendTransaction({
3866
+ account,
3867
+ chain: base3,
3868
+ to: getAddress2(transaction.to),
3869
+ data: transaction.data,
3870
+ value: BigInt(transaction.value)
3871
+ });
3872
+ const receipt = await publicClient.waitForTransactionReceipt({
3873
+ hash,
3874
+ confirmations: 1
3875
+ });
3876
+ if (receipt.status !== "success") {
3877
+ throw new OwneyError(
3878
+ "AGENT_TRANSACTION_REVERTED",
3879
+ `Yieldseeker transaction reverted (${hash}).`,
3880
+ { transactionHash: hash },
3881
+ this.id
3882
+ );
3883
+ }
3884
+ return hash;
3885
+ }
3886
+ async waitForReceipt(state, chainId, transactionHash) {
3887
+ if (this.unwindReceiptWaiter) {
3888
+ await this.unwindReceiptWaiter(state, chainId, transactionHash);
3889
+ return;
3890
+ }
3891
+ const publicClient = createPublicClient3({
3892
+ chain: base3,
3893
+ transport: custom2(state.provider)
3894
+ });
3895
+ const receipt = await publicClient.waitForTransactionReceipt({
3896
+ hash: transactionHash,
3897
+ confirmations: 1
3898
+ });
3899
+ if (receipt.status !== "success") {
3900
+ throw new OwneyError(
3901
+ "AGENT_TRANSACTION_REVERTED",
3902
+ `Yieldseeker transaction reverted (${transactionHash}).`,
3903
+ { transactionHash },
3904
+ this.id
3905
+ );
3906
+ }
3907
+ }
3908
+ assertTransaction(transaction, state, chainId) {
3909
+ if (!transaction || typeof transaction.from !== "string" || !isAddress2(transaction.from) || typeof transaction.to !== "string" || !isAddress2(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || getAddress2(transaction.from) !== getAddress2(state.walletAddress)) {
3910
+ throw this.invalidResponse("transaction");
3911
+ }
3912
+ }
3913
+ assertAgent(agent) {
3914
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress2(agent.assetAddress) || agent.chainId !== 8453) {
3915
+ throw this.invalidResponse("agent");
3916
+ }
3917
+ }
3918
+ isOwneyAgent(agent) {
3919
+ return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
3920
+ }
3921
+ assetForAgent(agent) {
3922
+ for (const asset of ["USDC", "WETH"]) {
3923
+ if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
3924
+ return asset;
3925
+ }
3926
+ }
3927
+ return null;
3928
+ }
3929
+ isTransactionHash(value) {
3930
+ return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
3931
+ }
3932
+ assertChain(chainId) {
3933
+ if (chainId !== 8453) {
3934
+ throw new OwneyError(
3935
+ "CHAIN_UNSUPPORTED",
3936
+ `Yieldseeker does not support chain ${chainId}.`,
3937
+ { chainId, supportedChainIds: [8453] },
3938
+ this.id
3939
+ );
3940
+ }
3941
+ }
3942
+ assertOptionalChain(chainId) {
3943
+ if (chainId !== void 0) this.assertChain(chainId);
3944
+ }
3945
+ assertAsset(asset) {
3946
+ if (asset !== "USDC" && asset !== "WETH") {
3947
+ throw new OwneyError(
3948
+ "ASSET_UNSUPPORTED",
3949
+ `Yieldseeker does not support asset ${asset} in the Owney rollout.`,
3950
+ {
3951
+ asset,
3952
+ supportedAssets: ["USDC", "WETH"],
3953
+ providerAlsoAdvertises: ["cbBTC"]
3954
+ },
3955
+ this.id
3956
+ );
3957
+ }
3958
+ }
3959
+ invalidResponse(operation, details = {}) {
3960
+ return new OwneyError(
3961
+ "AGENT_INVALID_RESPONSE",
3962
+ `Yieldseeker returned an invalid ${operation} response.`,
3963
+ details,
3964
+ this.id
3965
+ );
3966
+ }
3967
+ };
3968
+
3969
+ // src/lib/routing-api.ts
3970
+ var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3971
+ async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
3972
+ const url = `${baseUrl}/api/v1/agent/org-config`;
3973
+ try {
3974
+ const res = await fetch(url, {
3975
+ method: "GET",
3976
+ headers: {
3977
+ "Content-Type": "application/json",
3978
+ "x-owney-api-key": `${apiKey}`
3979
+ }
3980
+ });
3981
+ if (!res.ok) {
3982
+ if (res.status !== 404) {
3983
+ console.warn(
3984
+ `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
3985
+ );
3986
+ }
3987
+ return null;
3988
+ }
3989
+ const json = await res.json();
3990
+ const policy = json.success ? json.data ?? null : null;
3991
+ debugLog(
3992
+ "owney-sdk",
3993
+ policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
3994
+ policy ?? void 0
3995
+ );
3996
+ return policy;
3997
+ } catch (error) {
3998
+ console.warn(
3999
+ "[owney-sdk] Could not read org agent config (non-fatal):",
4000
+ error instanceof Error ? error.message : String(error)
4001
+ );
4002
+ return null;
4003
+ }
4004
+ }
4005
+ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
4006
+ const url = `${baseUrl}/api/v1/agent/keys`;
4007
+ const res = await fetch(url, {
4008
+ method: "GET",
4009
+ headers: {
4010
+ "Content-Type": "application/json",
4011
+ "x-owney-api-key": `${apiKey}`
4012
+ }
4013
+ });
4014
+ if (!res.ok) {
4015
+ const text = await res.text().catch(() => "");
4016
+ throw new OwneyError(
4017
+ "API_ROUTING_ERROR",
4018
+ `Routing API error ${res.status}: ${text}`,
4019
+ { statusCode: res.status, responseBody: text }
4020
+ );
4021
+ }
4022
+ const json = await res.json();
4023
+ if (!json.success) {
4024
+ throw new OwneyError(
4025
+ "API_ROUTING_FAILED",
4026
+ `Routing API request failed: ${json.message}`,
4027
+ { message: json.message }
4028
+ );
4029
+ }
4030
+ return json.data;
4031
+ }
4032
+
4033
+ // src/lib/health-report.ts
4034
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
4035
+ async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
4036
+ try {
4037
+ await fetch(`${baseUrl}/api/v1/agent/health-report`, {
4038
+ method: "POST",
4039
+ headers: {
4040
+ "Content-Type": "application/json",
4041
+ "x-owney-api-key": apiKey
4042
+ },
4043
+ body: JSON.stringify({
4044
+ agent_type: agentType,
4045
+ error_code: errorCode,
4046
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4047
+ })
4048
+ });
4049
+ } catch (err) {
4050
+ console.warn(
4051
+ `[owney-sdk] health-report failed for agent "${agentType}":`,
4052
+ err instanceof Error ? err.message : err
4053
+ );
4054
+ }
4055
+ }
4056
+ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
4057
+ try {
4058
+ return await fn();
4059
+ } catch (err) {
4060
+ const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
4061
+ void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
4062
+ throw err;
4063
+ }
4064
+ }
4065
+
4066
+ // src/lib/helpers/withdraw-helper.ts
4067
+ import { parseUnits } from "viem";
4068
+ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
4069
+ const target = asset.toUpperCase();
4070
+ return agents.map((agent) => {
4071
+ const agentBalance = aggregated[agent.id];
4072
+ const tokenBalance = agentBalance?.tokens.find(
4073
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
4074
+ );
4075
+ let balance = tokenBalance ? parseUnits(tokenBalance.amount, decimals) : 0n;
4076
+ if (agent.balanceComposition === "tokens-plus-positions") {
4077
+ const chainNameById = {
4078
+ 1: "ETHEREUM",
4079
+ 8453: "BASE",
4080
+ 42161: "ARBITRUM"
4081
+ };
4082
+ const targetChain = chainNameById[chainId];
4083
+ for (const position2 of agentBalance?.positions ?? []) {
4084
+ const positionChain = position2.chain.trim().toUpperCase();
4085
+ const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
4086
+ if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
4087
+ if (position2.amountRaw !== void 0) {
4088
+ try {
4089
+ balance += BigInt(position2.amountRaw);
4090
+ continue;
4091
+ } catch {
4092
+ }
4093
+ }
4094
+ balance += parseUnits(position2.amount, decimals);
4095
+ }
4096
+ }
4097
+ return { agent, balance };
4098
+ });
4099
+ }
4100
+ function planProportionalShares(balances, requested, totalAvailable) {
4101
+ const plans = balances.map(({ agent, balance }) => ({
4102
+ agent,
4103
+ balance,
4104
+ planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
4105
+ }));
4106
+ const assigned = plans.reduce((s, p) => s + p.planned, 0n);
4107
+ let remainder = requested - assigned;
4108
+ const byHeadroom = [...plans].sort((a, b) => {
4109
+ const diff = b.balance - b.planned - (a.balance - a.planned);
4110
+ return diff > 0n ? 1 : diff < 0n ? -1 : 0;
4111
+ });
4112
+ for (const p of byHeadroom) {
4113
+ if (remainder === 0n) break;
4114
+ const headroom = p.balance - p.planned;
4115
+ if (headroom <= 0n) continue;
4116
+ const take = headroom < remainder ? headroom : remainder;
4117
+ p.planned += take;
4118
+ remainder -= take;
4119
+ }
4120
+ return plans;
4121
+ }
4122
+ function planDisabledDrain(disabled, requested) {
4123
+ const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
4124
+ (a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
4125
+ );
4126
+ const plans = [];
4127
+ let remaining = requested;
4128
+ for (const { agent, balance } of sorted) {
4129
+ if (remaining === 0n) {
4130
+ plans.push({ agent, balance, planned: 0n });
4131
+ continue;
4132
+ }
4133
+ const take = balance < remaining ? balance : remaining;
4134
+ plans.push({ agent, balance, planned: take });
4135
+ remaining -= take;
4136
+ }
4137
+ return { plans, remaining };
4138
+ }
4139
+ function redistributeShare(plans, fromIndex, amount, candidatePool) {
4140
+ const pool = candidatePool ?? plans.slice(fromIndex + 1);
4141
+ const candidates = pool.filter((c) => c.balance - c.planned > 0n);
4142
+ const totalHeadroom = candidates.reduce(
4143
+ (s, c) => s + (c.balance - c.planned),
4144
+ 0n
4145
+ );
4146
+ if (totalHeadroom === 0n) return;
4147
+ let distributed = 0n;
4148
+ for (const c of candidates) {
4149
+ const headroom = c.balance - c.planned;
4150
+ const proportional = headroom * amount / totalHeadroom;
4151
+ const give = proportional > headroom ? headroom : proportional;
4152
+ c.planned += give;
4153
+ distributed += give;
4154
+ }
4155
+ let leftover = amount - distributed;
4156
+ for (const c of candidates) {
4157
+ if (leftover === 0n) break;
4158
+ const headroom = c.balance - c.planned;
4159
+ if (headroom <= 0n) continue;
4160
+ const take = headroom < leftover ? headroom : leftover;
4161
+ c.planned += take;
4162
+ leftover -= take;
4163
+ }
4164
+ }
4165
+ function sumWithdrawnAmount(results) {
4166
+ return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
4167
+ }
4168
+
4169
+ // src/lib/helpers/account-apy-helper.ts
4170
+ function balanceForApyScope(balance, chainId, tokenSymbol) {
4171
+ if (!tokenSymbol) {
4172
+ const total = Number(balance.totalBalance);
2285
4173
  return Number.isFinite(total) && total > 0 ? total : 0;
2286
4174
  }
2287
4175
  const normalizedToken = tokenSymbol.toUpperCase();
4176
+ const snapshots = balance.assetBalances?.filter(
4177
+ (token) => Number(token.chainId) === chainId && String(token.asset).toUpperCase() === normalizedToken
4178
+ );
4179
+ if (snapshots?.length) {
4180
+ const amount = snapshots.reduce((sum, token) => sum + Number(token.amount), 0);
4181
+ if (Number.isFinite(amount)) return Math.max(0, amount);
4182
+ }
2288
4183
  return balance.tokens.reduce((total, token) => {
2289
4184
  if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
2290
4185
  return total;
@@ -2326,471 +4221,342 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
2326
4221
  const cells = agentApys[id].apyByChainAndAsset;
2327
4222
  const balance = agentBalances[id] ?? 0;
2328
4223
  if (!cells || balance <= 0) continue;
2329
- for (const [chainKey, perAsset] of Object.entries(cells)) {
2330
- if (!perAsset) continue;
2331
- const chainId = Number(chainKey);
2332
- for (const [asset, apyValue] of Object.entries(perAsset)) {
2333
- const apy = Number(apyValue ?? 0);
2334
- if (apy === 0) continue;
2335
- sums[chainId] ??= {};
2336
- weights[chainId] ??= {};
2337
- sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
2338
- weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
2339
- }
2340
- }
2341
- }
2342
- const out = {};
2343
- for (const chainKey of Object.keys(sums)) {
2344
- const chainId = Number(chainKey);
2345
- const perAssetOut = {};
2346
- for (const asset of Object.keys(sums[chainId])) {
2347
- const w = weights[chainId][asset];
2348
- if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
2349
- }
2350
- if (Object.keys(perAssetOut).length > 0) {
2351
- out[chainId] = perAssetOut;
2352
- }
2353
- }
2354
- return out;
2355
- }
2356
-
2357
- // src/client.ts
2358
- import {
2359
- createPublicClient as createPublicClient2,
2360
- createWalletClient,
2361
- custom
2362
- } from "viem";
2363
- import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
2364
-
2365
- // src/lib/transfer-auth.ts
2366
- import { bytesToHex } from "viem";
2367
- var ERC20_META_ABI = [
2368
- { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
2369
- { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
2370
- ];
2371
- function buildTransferWithAuthorizationTypedData(input) {
2372
- return {
2373
- domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
2374
- types: {
2375
- TransferWithAuthorization: [
2376
- { name: "from", type: "address" },
2377
- { name: "to", type: "address" },
2378
- { name: "value", type: "uint256" },
2379
- { name: "validAfter", type: "uint256" },
2380
- { name: "validBefore", type: "uint256" },
2381
- { name: "nonce", type: "bytes32" }
2382
- ]
2383
- },
2384
- primaryType: "TransferWithAuthorization",
2385
- message: input.message
2386
- };
2387
- }
2388
- async function readTokenMeta(publicClient, token) {
2389
- const [tokenName, tokenVersion] = await Promise.all([
2390
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
2391
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
2392
- ]);
2393
- return { tokenName, tokenVersion };
2394
- }
2395
- function randomAuthNonce() {
2396
- const bytes = new Uint8Array(32);
2397
- globalThis.crypto.getRandomValues(bytes);
2398
- return bytesToHex(bytes);
2399
- }
2400
-
2401
- // src/lib/sponsor-client.ts
2402
- var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2403
- async function postSponsorTransferAuth(input) {
2404
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2405
- let res;
2406
- try {
2407
- res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
2408
- method: "POST",
2409
- headers: {
2410
- "content-type": "application/json",
2411
- "x-owney-api-key": input.apiKey
2412
- },
2413
- body: JSON.stringify(input.body)
2414
- });
2415
- } catch (networkError) {
2416
- throw new OwneyError(
2417
- "SPONSOR_REQUEST_FAILED",
2418
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2419
- { cause: String(networkError) }
2420
- );
2421
- }
2422
- const text = await res.text();
2423
- let parsed = null;
2424
- try {
2425
- parsed = JSON.parse(text);
2426
- } catch {
2427
- }
2428
- if (!res.ok || !parsed?.success || !parsed.data) {
2429
- throw new OwneyError(
2430
- "SPONSOR_REQUEST_FAILED",
2431
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2432
- {
2433
- statusCode: res.status,
2434
- responseBody: text.slice(0, 500),
2435
- // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
2436
- // before broadcast, so it is safe to fall back to a user-paid deposit.
2437
- safeToFallback: res.status === 503
2438
- }
2439
- );
2440
- }
2441
- return parsed.data;
2442
- }
2443
- async function postSponsorPermit2Transfer(input) {
2444
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2445
- let res;
2446
- try {
2447
- res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
2448
- method: "POST",
2449
- headers: {
2450
- "content-type": "application/json",
2451
- "x-owney-api-key": input.apiKey
2452
- },
2453
- body: JSON.stringify(input.body)
2454
- });
2455
- } catch (networkError) {
2456
- throw new OwneyError(
2457
- "SPONSOR_REQUEST_FAILED",
2458
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2459
- { cause: String(networkError), safeToFallback: false }
2460
- );
2461
- }
2462
- const text = await res.text();
2463
- let parsed = null;
2464
- try {
2465
- parsed = JSON.parse(text);
2466
- } catch {
2467
- }
2468
- if (!res.ok || !parsed?.success || !parsed.data) {
2469
- throw new OwneyError(
2470
- "SPONSOR_REQUEST_FAILED",
2471
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2472
- {
2473
- statusCode: res.status,
2474
- responseBody: text.slice(0, 500),
2475
- safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
2476
- }
2477
- );
2478
- }
2479
- return parsed.data;
2480
- }
2481
- async function getSponsorRelayerAddress(input) {
2482
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2483
- let res;
2484
- try {
2485
- res = await fetch(
2486
- `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2487
- {
2488
- headers: { "x-owney-api-key": input.apiKey }
2489
- }
2490
- );
2491
- } catch (networkError) {
2492
- throw new OwneyError(
2493
- "SPONSOR_REQUEST_FAILED",
2494
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2495
- { cause: String(networkError), safeToFallback: true }
2496
- );
2497
- }
2498
- const text = await res.text();
2499
- let parsed = null;
2500
- try {
2501
- parsed = JSON.parse(text);
2502
- } catch {
2503
- }
2504
- if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
2505
- throw new OwneyError(
2506
- "SPONSOR_REQUEST_FAILED",
2507
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2508
- {
2509
- statusCode: res.status,
2510
- responseBody: text.slice(0, 500),
2511
- safeToFallback: true
2512
- }
2513
- );
2514
- }
2515
- return parsed.data.relayer;
2516
- }
2517
-
2518
- // src/lib/permit2.ts
2519
- import { bytesToHex as bytesToHex2 } from "viem";
2520
- var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2521
- var MAX_UINT256 = 2n ** 256n - 1n;
2522
- var ERC20_ALLOWANCE_ABI = [
2523
- {
2524
- type: "function",
2525
- name: "allowance",
2526
- stateMutability: "view",
2527
- inputs: [
2528
- { name: "owner", type: "address" },
2529
- { name: "spender", type: "address" }
2530
- ],
2531
- outputs: [{ name: "", type: "uint256" }]
2532
- },
2533
- {
2534
- type: "function",
2535
- name: "approve",
2536
- stateMutability: "nonpayable",
2537
- inputs: [
2538
- { name: "spender", type: "address" },
2539
- { name: "amount", type: "uint256" }
2540
- ],
2541
- outputs: [{ name: "", type: "bool" }]
2542
- },
2543
- {
2544
- type: "function",
2545
- name: "balanceOf",
2546
- stateMutability: "view",
2547
- inputs: [{ name: "account", type: "address" }],
2548
- outputs: [{ name: "", type: "uint256" }]
4224
+ for (const [chainKey, perAsset] of Object.entries(cells)) {
4225
+ if (!perAsset) continue;
4226
+ const chainId = Number(chainKey);
4227
+ for (const [asset, apyValue] of Object.entries(perAsset)) {
4228
+ const apy = Number(apyValue ?? 0);
4229
+ if (apy === 0) continue;
4230
+ sums[chainId] ??= {};
4231
+ weights[chainId] ??= {};
4232
+ sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
4233
+ weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
4234
+ }
4235
+ }
2549
4236
  }
2550
- ];
2551
- function buildPermitTransferFromTypedData(input) {
4237
+ const out = {};
4238
+ for (const chainKey of Object.keys(sums)) {
4239
+ const chainId = Number(chainKey);
4240
+ const perAssetOut = {};
4241
+ for (const asset of Object.keys(sums[chainId])) {
4242
+ const w = weights[chainId][asset];
4243
+ if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
4244
+ }
4245
+ if (Object.keys(perAssetOut).length > 0) {
4246
+ out[chainId] = perAssetOut;
4247
+ }
4248
+ }
4249
+ return out;
4250
+ }
4251
+
4252
+ // src/client.ts
4253
+ import {
4254
+ createPublicClient as createPublicClient4,
4255
+ createWalletClient as createWalletClient3,
4256
+ custom as custom3
4257
+ } from "viem";
4258
+ import { base as base4, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
4259
+
4260
+ // src/lib/sponsored-token-batch.ts
4261
+ import {
4262
+ isAddressEqual,
4263
+ keccak256,
4264
+ toBytes
4265
+ } from "viem";
4266
+
4267
+ // src/lib/permit2-batch.ts
4268
+ import { parseAbi as parseAbi2, hashStruct } from "viem";
4269
+ var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
4270
+ var PERMIT_BATCH_TYPES = {
4271
+ PermitBatchWitnessTransferFrom: [
4272
+ { name: "permitted", type: "TokenPermissions[]" },
4273
+ { name: "spender", type: "address" },
4274
+ { name: "nonce", type: "uint256" },
4275
+ { name: "deadline", type: "uint256" },
4276
+ { name: "witness", type: "Deposit" }
4277
+ ],
4278
+ Deposit: [{ name: "recipients", type: "address[]" }],
4279
+ TokenPermissions: [
4280
+ { name: "token", type: "address" },
4281
+ { name: "amount", type: "uint256" }
4282
+ ]
4283
+ };
4284
+ var PERMIT2_BATCH_ABI = parseAbi2([
4285
+ "struct TokenPermissions { address token; uint256 amount; }",
4286
+ "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
4287
+ "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
4288
+ "function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
4289
+ "function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
4290
+ ]);
4291
+ function batchPermit(b) {
4292
+ return {
4293
+ permitted: b.transfers.map((t) => ({
4294
+ token: b.token,
4295
+ amount: BigInt(t.amount)
4296
+ })),
4297
+ nonce: BigInt(b.nonce),
4298
+ deadline: BigInt(b.deadline)
4299
+ };
4300
+ }
4301
+ function batchTypedData(b, spender) {
2552
4302
  return {
2553
4303
  domain: {
2554
4304
  name: "Permit2",
2555
- chainId: input.chainId,
2556
- verifyingContract: PERMIT2_ADDRESS
4305
+ chainId: b.chainId,
4306
+ verifyingContract: BATCH_PERMIT2_ADDRESS
2557
4307
  },
2558
- types: {
2559
- PermitTransferFrom: [
2560
- { name: "permitted", type: "TokenPermissions" },
2561
- { name: "spender", type: "address" },
2562
- { name: "nonce", type: "uint256" },
2563
- { name: "deadline", type: "uint256" }
2564
- ],
2565
- TokenPermissions: [
2566
- { name: "token", type: "address" },
2567
- { name: "amount", type: "uint256" }
2568
- ]
2569
- },
2570
- primaryType: "PermitTransferFrom",
2571
- message: input.message
4308
+ types: PERMIT_BATCH_TYPES,
4309
+ primaryType: "PermitBatchWitnessTransferFrom",
4310
+ message: {
4311
+ ...batchPermit(b),
4312
+ spender,
4313
+ witness: { recipients: b.transfers.map((t) => t.to) }
4314
+ }
2572
4315
  };
2573
4316
  }
2574
- function randomPermit2Nonce() {
2575
- const bytes = new Uint8Array(32);
2576
- globalThis.crypto.getRandomValues(bytes);
2577
- return BigInt(bytesToHex2(bytes));
4317
+
4318
+ // src/lib/sponsored-token-batch.ts
4319
+ var memory = /* @__PURE__ */ new Map();
4320
+ var inflight = /* @__PURE__ */ new Map();
4321
+ var planOf = (transfers) => JSON.stringify(
4322
+ transfers.map((t) => ({
4323
+ to: t.to.toLowerCase(),
4324
+ amount: BigInt(t.amount).toString()
4325
+ }))
4326
+ );
4327
+ function read(key2) {
4328
+ return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
2578
4329
  }
2579
- async function readPermit2Allowance(publicClient, token, owner) {
2580
- return publicClient.readContract({
2581
- address: token,
2582
- abi: ERC20_ALLOWANCE_ABI,
2583
- functionName: "allowance",
2584
- args: [owner, PERMIT2_ADDRESS]
4330
+ function save(key2, body) {
4331
+ const value = JSON.stringify({
4332
+ ...body,
4333
+ transfers: body.transfers.map(({ to, amount }) => ({ to, amount }))
2585
4334
  });
4335
+ if (typeof window === "undefined") memory.set(key2, value);
4336
+ else window.localStorage.setItem(key2, value);
2586
4337
  }
2587
- async function readErc20Balance(publicClient, token, owner) {
2588
- return publicClient.readContract({
2589
- address: token,
2590
- abi: ERC20_ALLOWANCE_ABI,
2591
- functionName: "balanceOf",
2592
- args: [owner]
2593
- });
4338
+ function clear(key2) {
4339
+ if (typeof window === "undefined") memory.delete(key2);
4340
+ else window.localStorage.removeItem(key2);
2594
4341
  }
2595
-
2596
- // src/lib/chain-guard.ts
2597
- var CHAIN_NAMES = {
2598
- 1: "Ethereum",
2599
- 8453: "Base",
2600
- 42161: "Arbitrum"
2601
- };
2602
- function chainName(chainId) {
2603
- return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
4342
+ function sponsorTokenBatch(i) {
4343
+ const key2 = `owney.token-batch.v1:${keccak256(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
4344
+ const plan = planOf(i.transfers);
4345
+ const active = inflight.get(key2);
4346
+ if (active) {
4347
+ if (active.plan !== plan)
4348
+ return Promise.reject(
4349
+ new Error(
4350
+ "A token deposit is already in progress. Wait for its result before depositing again."
4351
+ )
4352
+ );
4353
+ return active.promise;
4354
+ }
4355
+ const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
4356
+ inflight.set(key2, { plan, promise });
4357
+ return promise;
2604
4358
  }
2605
- async function ensureWalletOnChain(pub, wallet, expected) {
2606
- const actual = await pub.getChainId();
2607
- if (actual === expected) return;
2608
- try {
2609
- await wallet.switchChain({ id: expected });
2610
- } catch (error) {
2611
- throw new OwneyError(
2612
- "CHAIN_MISMATCH",
2613
- `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
2614
- {
2615
- expectedChainId: expected,
2616
- actualChainId: actual,
2617
- cause: error instanceof Error ? error.message : String(error)
4359
+ async function execute(i, key2, plan) {
4360
+ if (!i.transfers.length || i.transfers.length > 16 || i.transfers.some(
4361
+ (t) => BigInt(t.amount) <= 0n || BigInt(t.amount) >= 1n << 256n
4362
+ ) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
4363
+ throw new Error("Invalid token deposit shares.");
4364
+ const send = async (initial) => {
4365
+ let body = initial;
4366
+ save(key2, body);
4367
+ try {
4368
+ if (!body.serializedTransaction) {
4369
+ const prepared = await postSponsorBatchTransfer({
4370
+ apiKey: i.apiKey,
4371
+ baseUrl: i.baseUrl,
4372
+ body
4373
+ });
4374
+ if (!prepared.serializedTransaction || keccak256(prepared.serializedTransaction) !== prepared.txHash)
4375
+ throw new Error(
4376
+ "Sponsorship API did not return a valid prepared transaction."
4377
+ );
4378
+ body = {
4379
+ ...body,
4380
+ serializedTransaction: prepared.serializedTransaction
4381
+ };
4382
+ save(key2, body);
2618
4383
  }
2619
- );
4384
+ const result = await postSponsorBatchTransfer({
4385
+ apiKey: i.apiKey,
4386
+ baseUrl: i.baseUrl,
4387
+ body
4388
+ });
4389
+ if (result.txHash !== keccak256(body.serializedTransaction))
4390
+ throw new Error(
4391
+ "Sponsorship receipt does not match the pending transaction."
4392
+ );
4393
+ clear(key2);
4394
+ return result.txHash;
4395
+ } catch (error) {
4396
+ if (!body.serializedTransaction || error instanceof OwneyError && error.details?.notSubmitted === true)
4397
+ clear(key2);
4398
+ throw error;
4399
+ }
4400
+ };
4401
+ const saved = read(key2);
4402
+ if (saved) {
4403
+ const previous = JSON.parse(saved);
4404
+ if (previous.chainId !== i.chainId || !isAddressEqual(previous.from, i.owner) || !isAddressEqual(previous.token, i.token) || planOf(previous.transfers) !== plan)
4405
+ throw new Error(
4406
+ "Retry the previous token deposit and agent split first to reconcile its status."
4407
+ );
4408
+ i.onApproved?.();
4409
+ return send({ ...previous, transfers: i.transfers });
2620
4410
  }
2621
- const after = await pub.getChainId();
2622
- if (after !== expected) {
4411
+ const total = i.transfers.reduce((sum, t) => sum + BigInt(t.amount), 0n);
4412
+ const [balance, allowance] = await Promise.all([
4413
+ readErc20Balance(i.pub, i.token, i.owner),
4414
+ readPermit2Allowance(i.pub, i.token, i.owner)
4415
+ ]);
4416
+ if (balance < total)
2623
4417
  throw new OwneyError(
2624
- "CHAIN_MISMATCH",
2625
- `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
2626
- { expectedChainId: expected, actualChainId: after }
4418
+ "DEPOSIT_INSUFFICIENT_BALANCE",
4419
+ "Insufficient token balance for this deposit."
2627
4420
  );
2628
- }
4421
+ if (allowance < total)
4422
+ throw new OwneyError(
4423
+ "PERMIT2_APPROVAL_REQUIRED",
4424
+ "token deposits need a one-time Permit2 approval."
4425
+ );
4426
+ const relayer = await getSponsorRelayerAddress({
4427
+ apiKey: i.apiKey,
4428
+ baseUrl: i.baseUrl,
4429
+ chainId: i.chainId
4430
+ });
4431
+ const now = (await i.pub.getBlock()).timestamp;
4432
+ const unsigned = {
4433
+ chainId: i.chainId,
4434
+ token: i.token,
4435
+ from: i.owner,
4436
+ transfers: i.transfers,
4437
+ nonce: randomPermit2Nonce().toString(),
4438
+ deadline: (now + 900n).toString()
4439
+ };
4440
+ const signature = await i.wallet.signTypedData({
4441
+ account: i.owner,
4442
+ ...batchTypedData(unsigned, relayer)
4443
+ });
4444
+ i.onApproved?.();
4445
+ return send({ ...unsigned, signature });
2629
4446
  }
2630
4447
 
2631
- // src/lib/sponsored-deposit.ts
2632
- var AUTH_WINDOW_SECONDS = 15 * 60;
2633
- function makeSponsoredDepositCallback(deps) {
2634
- const post = deps.httpPost ?? postSponsorTransferAuth;
2635
- return async (smartWallet, chainId, amount) => {
2636
- const cid = chainId;
2637
- const token = deps.tokenAddressByChain[cid];
2638
- if (!token) {
4448
+ // src/lib/sponsored-token-deposit.ts
4449
+ function makeSponsoredTokenCallback(deps) {
4450
+ const batch = async (chainId, transfers) => {
4451
+ if (chainId !== 8453 && chainId !== 42161 && chainId !== 1)
2639
4452
  throw new OwneyError(
2640
4453
  "CHAIN_UNSUPPORTED",
2641
4454
  `No sponsored token configured for chain ${chainId}`
2642
4455
  );
2643
- }
2644
- const pub = deps.getPublicClient(cid);
2645
- const wallet = deps.getWalletClient(cid);
2646
- await ensureWalletOnChain(pub, wallet, cid);
2647
- try {
2648
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2649
- if (balance < BigInt(amount)) {
2650
- throw new OwneyError(
2651
- "DEPOSIT_INSUFFICIENT_BALANCE",
2652
- "Insufficient balance for this deposit.",
2653
- { token, chainId: cid, balance: balance.toString(), amount }
2654
- );
2655
- }
2656
- } catch (err) {
2657
- if (err instanceof OwneyError) throw err;
2658
- console.warn(
2659
- "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
2660
- err instanceof Error ? err.message : String(err)
4456
+ const token = deps.tokenAddressByChain[chainId];
4457
+ if (!token)
4458
+ throw new OwneyError(
4459
+ "CHAIN_UNSUPPORTED",
4460
+ `No sponsored token configured for chain ${chainId}`
2661
4461
  );
2662
- }
2663
- const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
2664
- const validAfter = 0n;
2665
- const validBefore = BigInt(
2666
- Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
2667
- );
2668
- const nonce = randomAuthNonce();
2669
- const typedData = buildTransferWithAuthorizationTypedData({
2670
- token,
2671
- chainId: cid,
2672
- tokenName,
2673
- tokenVersion,
2674
- message: {
2675
- from: deps.ownerAddress,
2676
- to: smartWallet,
2677
- value: BigInt(amount),
2678
- validAfter,
2679
- validBefore,
2680
- nonce
2681
- }
2682
- });
2683
- const authSignature = await wallet.signTypedData({
2684
- account: deps.ownerAddress,
2685
- ...typedData
2686
- });
2687
- deps.onApproved?.();
2688
- const result = await post({
2689
- baseUrl: deps.baseUrl,
4462
+ const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
4463
+ await ensureWalletOnChain(pub, wallet, chainId);
4464
+ return sponsorTokenBatch({
2690
4465
  apiKey: deps.apiKey,
2691
- body: {
2692
- chainId: cid,
2693
- token,
2694
- from: deps.ownerAddress,
2695
- to: smartWallet,
2696
- value: amount,
2697
- validAfter: validAfter.toString(),
2698
- validBefore: validBefore.toString(),
2699
- nonce,
2700
- authSignature,
2701
- tokenName,
2702
- tokenVersion
2703
- }
4466
+ baseUrl: deps.baseUrl,
4467
+ owner: deps.ownerAddress,
4468
+ token,
4469
+ chainId,
4470
+ transfers,
4471
+ pub,
4472
+ wallet,
4473
+ onApproved: deps.onApproved
2704
4474
  });
2705
- return result.txHash;
2706
4475
  };
4476
+ const callback = makeVerificationAwareDepositCallback(
4477
+ (to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
4478
+ );
4479
+ registerDepositBatch(callback, batch);
4480
+ return callback;
2707
4481
  }
2708
4482
 
2709
- // src/lib/sponsored-weth-deposit.ts
2710
- var PERMIT_WINDOW_SECONDS = 15 * 60;
2711
- function makeSponsoredWethCallback(deps) {
2712
- const get = deps.httpGet ?? getSponsorRelayerAddress;
2713
- const post = deps.httpPost ?? postSponsorPermit2Transfer;
2714
- return async (smartWallet, chainId, amount) => {
2715
- const cid = chainId;
2716
- const token = deps.tokenAddressByChain[cid];
2717
- if (!token) {
2718
- throw new OwneyError(
2719
- "CHAIN_UNSUPPORTED",
2720
- `No sponsored WETH configured for chain ${chainId}`
4483
+ // src/lib/agent-deposit-batch.ts
4484
+ function deferred() {
4485
+ let resolve, reject;
4486
+ const promise = new Promise((yes, no) => {
4487
+ resolve = yes;
4488
+ reject = no;
4489
+ });
4490
+ void promise.catch(() => {
4491
+ });
4492
+ return { promise, resolve, reject };
4493
+ }
4494
+ async function runAgentDepositBatch(chainId, legs, transfer) {
4495
+ const funding = deferred();
4496
+ const tasks = [];
4497
+ const transfers = [];
4498
+ try {
4499
+ for (const leg of legs) {
4500
+ const ready = deferred();
4501
+ let entered = false;
4502
+ const callback = makeVerificationAwareDepositCallback(
4503
+ (to, cid, amount, verification) => {
4504
+ if (entered || cid !== chainId || BigInt(amount) !== BigInt(leg.amount)) {
4505
+ const error = new Error(
4506
+ "Agent changed its prepared deposit share."
4507
+ );
4508
+ ready.reject(error);
4509
+ throw error;
4510
+ }
4511
+ entered = true;
4512
+ ready.resolve(toBatchTransfer(to, amount, verification));
4513
+ return funding.promise;
4514
+ }
2721
4515
  );
2722
- }
2723
- const amountWei = BigInt(amount);
2724
- const pub = deps.getPublicClient(cid);
2725
- const wallet = deps.getWalletClient(cid);
2726
- await ensureWalletOnChain(pub, wallet, cid);
2727
- try {
2728
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2729
- if (balance < amountWei) {
2730
- throw new OwneyError(
2731
- "DEPOSIT_INSUFFICIENT_BALANCE",
2732
- "Insufficient WETH balance for this deposit.",
2733
- { token, chainId: cid, balance: balance.toString(), amount }
2734
- );
2735
- }
2736
- } catch (err) {
2737
- if (err instanceof OwneyError) throw err;
2738
- console.warn(
2739
- "[owney-sdk] WETH balance pre-check failed (non-fatal):",
2740
- err instanceof Error ? err.message : String(err)
4516
+ const task = Promise.resolve().then(() => leg.run(callback));
4517
+ tasks.push(task);
4518
+ void task.then(
4519
+ () => {
4520
+ if (!entered)
4521
+ ready.reject(
4522
+ new Error("Agent did not prepare a deposit transfer.")
4523
+ );
4524
+ },
4525
+ (error) => ready.reject(error)
2741
4526
  );
4527
+ transfers.push(await ready.promise);
4528
+ }
4529
+ const txHash = await transfer(chainId, transfers);
4530
+ funding.resolve(txHash);
4531
+ const settled = await Promise.allSettled(tasks);
4532
+ const agentResults = {};
4533
+ const failures = [];
4534
+ for (const [index, result] of settled.entries()) {
4535
+ if (result.status === "fulfilled")
4536
+ agentResults[legs[index].id] = result.value;
4537
+ else failures.push(legs[index].id);
2742
4538
  }
2743
- const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
2744
- if (allowance < amountWei) {
4539
+ if (failures.length)
2745
4540
  throw new OwneyError(
2746
- "PERMIT2_APPROVAL_REQUIRED",
2747
- "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
2748
- { token, chainId: cid, allowance: allowance.toString(), amount }
4541
+ "DEPOSIT_PARTIAL_FAILURE",
4542
+ "The deposit was sent to all agents, but some agent updates could not be confirmed. Check activity before depositing again.",
4543
+ {
4544
+ txHash,
4545
+ fundsSubmitted: true,
4546
+ agentResults,
4547
+ failedAgentIds: failures
4548
+ }
2749
4549
  );
2750
- }
2751
- const relayer = await get({
2752
- baseUrl: deps.baseUrl,
2753
- apiKey: deps.apiKey,
2754
- chainId: cid
2755
- });
2756
- const nonce = randomPermit2Nonce();
2757
- const deadline = BigInt(
2758
- Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
2759
- );
2760
- const typedData = buildPermitTransferFromTypedData({
2761
- chainId: cid,
2762
- message: {
2763
- permitted: { token, amount: amountWei },
2764
- spender: relayer,
2765
- nonce,
2766
- deadline
2767
- }
2768
- });
2769
- const signature = await wallet.signTypedData({
2770
- account: deps.ownerAddress,
2771
- ...typedData
2772
- });
2773
- deps.onApproved?.();
2774
- const result = await post({
2775
- baseUrl: deps.baseUrl,
2776
- apiKey: deps.apiKey,
2777
- body: {
2778
- chainId: cid,
2779
- token,
2780
- from: deps.ownerAddress,
2781
- to: smartWallet,
2782
- amount,
2783
- nonce: nonce.toString(),
2784
- deadline: deadline.toString(),
2785
- signature
2786
- }
2787
- });
2788
- return result.txHash;
2789
- };
4550
+ return { agentResults };
4551
+ } catch (error) {
4552
+ funding.reject(error);
4553
+ await Promise.allSettled(tasks);
4554
+ throw error;
4555
+ }
2790
4556
  }
2791
4557
 
2792
4558
  // src/lib/sponsored-calls-deposit.ts
2793
- import { encodeFunctionData, erc20Abi, toHex } from "viem";
4559
+ import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi2, toHex } from "viem";
2794
4560
  var DEFAULT_POLL_INTERVAL_MS = 1500;
2795
4561
  var DEFAULT_MAX_POLLS = 30;
2796
4562
  async function paymasterSupported(provider, owner, chainId) {
@@ -2816,7 +4582,7 @@ function makeSponsoredCallsCallback(deps) {
2816
4582
  }
2817
4583
  return new URL(configured, origin).toString();
2818
4584
  };
2819
- return async (smartWallet, chainId, amount) => {
4585
+ const batch = async (chainId, transfers) => {
2820
4586
  const cid = chainId;
2821
4587
  const token = deps.tokenAddressByChain[cid];
2822
4588
  if (!token) {
@@ -2832,11 +4598,42 @@ function makeSponsoredCallsCallback(deps) {
2832
4598
  { chainId }
2833
4599
  );
2834
4600
  }
2835
- const data = encodeFunctionData({
2836
- abi: erc20Abi,
2837
- functionName: "transfer",
2838
- args: [smartWallet, BigInt(amount)]
2839
- });
4601
+ const calls = transfers.map((transfer) => ({
4602
+ to: token,
4603
+ value: "0x0",
4604
+ data: encodeFunctionData2({
4605
+ abi: erc20Abi2,
4606
+ functionName: "transfer",
4607
+ args: [transfer.to, BigInt(transfer.amount)]
4608
+ })
4609
+ }));
4610
+ let paymasterUrl = absolutePaymasterUrl();
4611
+ for (const transfer of transfers) {
4612
+ const verification = transfer.yieldseeker;
4613
+ if (!verification) continue;
4614
+ if (chainId !== 8453)
4615
+ throw new OwneyError(
4616
+ "CHAIN_UNSUPPORTED",
4617
+ `Yieldseeker sponsorship is not available on chain ${chainId}.`
4618
+ );
4619
+ const { intent } = await postPaymasterIntent({
4620
+ baseUrl: deps.routingApiBaseUrl,
4621
+ apiKey: deps.apiKey,
4622
+ yieldseekerSignature: verification.signature,
4623
+ body: {
4624
+ chainId,
4625
+ token,
4626
+ from: deps.ownerAddress,
4627
+ to: transfer.to,
4628
+ amount: transfer.amount,
4629
+ yieldseekerUserId: verification.userId,
4630
+ yieldseekerAgentId: verification.agentId
4631
+ }
4632
+ });
4633
+ const url = new URL(paymasterUrl);
4634
+ url.searchParams.append("owneyIntent", intent);
4635
+ paymasterUrl = url.toString();
4636
+ }
2840
4637
  const sendResult = await deps.provider.request({
2841
4638
  method: "wallet_sendCalls",
2842
4639
  params: [
@@ -2844,10 +4641,10 @@ function makeSponsoredCallsCallback(deps) {
2844
4641
  version: "2.0.0",
2845
4642
  from: deps.ownerAddress,
2846
4643
  chainId: toHex(chainId),
2847
- atomicRequired: false,
2848
- calls: [{ to: token, value: "0x0", data }],
4644
+ atomicRequired: transfers.length > 1,
4645
+ calls,
2849
4646
  capabilities: {
2850
- paymasterService: { url: absolutePaymasterUrl() }
4647
+ paymasterService: { url: paymasterUrl }
2851
4648
  }
2852
4649
  }
2853
4650
  ]
@@ -2867,7 +4664,24 @@ function makeSponsoredCallsCallback(deps) {
2867
4664
  params: [callsId]
2868
4665
  });
2869
4666
  const txHash = status?.receipts?.[0]?.transactionHash;
2870
- if (txHash) return txHash;
4667
+ if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
4668
+ throw new OwneyError(
4669
+ "SPONSOR_REQUEST_FAILED",
4670
+ "The sponsored deposit did not complete successfully.",
4671
+ { chainId, callsId, safeToFallback: false }
4672
+ );
4673
+ }
4674
+ if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
4675
+ if (status?.receipts?.some(
4676
+ (receipt) => receipt.transactionHash !== txHash
4677
+ ))
4678
+ throw new OwneyError(
4679
+ "SPONSORED_CALLS_NO_RECEIPT",
4680
+ "The wallet returned multiple transactions for an atomic deposit. Check activity before trying again.",
4681
+ { chainId, callsId }
4682
+ );
4683
+ return txHash;
4684
+ }
2871
4685
  if (pollIntervalMs > 0) {
2872
4686
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2873
4687
  }
@@ -2878,6 +4692,11 @@ function makeSponsoredCallsCallback(deps) {
2878
4692
  { chainId, callsId }
2879
4693
  );
2880
4694
  };
4695
+ const callback = makeVerificationAwareDepositCallback(
4696
+ (to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
4697
+ );
4698
+ registerDepositBatch(callback, batch);
4699
+ return callback;
2881
4700
  }
2882
4701
 
2883
4702
  // src/client.ts
@@ -2907,7 +4726,7 @@ var SPONSORED_USDC_BY_CHAIN = {
2907
4726
  1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
2908
4727
  };
2909
4728
  var VIEM_CHAIN2 = {
2910
- 8453: base2,
4729
+ 8453: base4,
2911
4730
  42161: arbitrum2,
2912
4731
  1: mainnet2
2913
4732
  };
@@ -2916,6 +4735,11 @@ var SPONSORED_WETH_BY_CHAIN = {
2916
4735
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
2917
4736
  1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
2918
4737
  };
4738
+ var SPONSORED_TOKENS_BY_ASSET = { USDC: SPONSORED_USDC_BY_CHAIN, WETH: SPONSORED_WETH_BY_CHAIN };
4739
+ function sponsoredTokensFor(asset) {
4740
+ if (!Object.prototype.hasOwnProperty.call(SPONSORED_TOKENS_BY_ASSET, asset)) throw new OwneyError("ASSET_UNSUPPORTED", `No sponsored token configured for ${asset}`);
4741
+ return SPONSORED_TOKENS_BY_ASSET[asset];
4742
+ }
2919
4743
  function shouldFallbackToUserPaid(error, asset, appCallback) {
2920
4744
  return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
2921
4745
  }
@@ -2937,6 +4761,8 @@ var OwneySDK = class {
2937
4761
  orgAgentConfig;
2938
4762
  orgAgentConfigPromise = null;
2939
4763
  zyfaiRpcUrls;
4764
+ yieldseekerApiBaseUrl;
4765
+ yieldseekerSiweOrigin;
2940
4766
  routingApiBaseUrl;
2941
4767
  referralSource;
2942
4768
  cachedSponsoredCallback = null;
@@ -2959,6 +4785,8 @@ var OwneySDK = class {
2959
4785
  this.apiKey = config.apiKey;
2960
4786
  if (config.debug) setOwneyDebug(true);
2961
4787
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
4788
+ this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
4789
+ this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
2962
4790
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2963
4791
  this.paymasterServiceUrl = config.paymasterServiceUrl;
2964
4792
  this.referralSource = config.referralSource;
@@ -2992,6 +4820,7 @@ var OwneySDK = class {
2992
4820
  * After calling this, `connect()` must be called again before using agent methods.
2993
4821
  */
2994
4822
  async disconnect() {
4823
+ this.state = null;
2995
4824
  for (const agent of this.agents.values()) {
2996
4825
  await agent.disconnect();
2997
4826
  }
@@ -3045,18 +4874,13 @@ var OwneySDK = class {
3045
4874
  }
3046
4875
  return this.state.provider;
3047
4876
  }
3048
- /**
3049
- * Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
3050
- * used when the caller omits `depositCallback`. Wraps the connected EIP-1193
3051
- * provider with viem `custom(provider)` to read token meta and sign the
3052
- * `TransferWithAuthorization`, then POSTs to the sponsor API.
3053
- */
4877
+ /** Builds the default USDC batch callback for the connected wallet. */
3054
4878
  getDefaultSponsoredCallback(onApproved) {
3055
4879
  if (!onApproved && this.cachedSponsoredCallback)
3056
4880
  return this.cachedSponsoredCallback;
3057
4881
  const provider = this.requireConnectedProvider();
3058
4882
  const owner = this.state.walletAddress;
3059
- const callback = makeSponsoredDepositCallback({
4883
+ const callback = makeSponsoredTokenCallback({
3060
4884
  apiKey: this.apiKey,
3061
4885
  baseUrl: this.routingApiBaseUrl,
3062
4886
  ownerAddress: owner,
@@ -3065,35 +4889,32 @@ var OwneySDK = class {
3065
4889
  // Casts work around viem's chain-narrowed Client vs the generic
3066
4890
  // PublicClient/WalletClient param types — structurally identical at
3067
4891
  // runtime, but the two share a name TS treats as unrelated.
3068
- getPublicClient: (cid) => createPublicClient2({
4892
+ getPublicClient: (cid) => createPublicClient4({
3069
4893
  chain: VIEM_CHAIN2[cid],
3070
- transport: custom(provider)
4894
+ transport: custom3(provider)
3071
4895
  }),
3072
- getWalletClient: (cid) => createWalletClient({
4896
+ getWalletClient: (cid) => createWalletClient3({
3073
4897
  account: owner,
3074
4898
  chain: VIEM_CHAIN2[cid],
3075
- transport: custom(provider)
4899
+ transport: custom3(provider)
3076
4900
  })
3077
4901
  });
3078
4902
  if (!onApproved) this.cachedSponsoredCallback = callback;
3079
4903
  return callback;
3080
4904
  }
3081
- /**
3082
- * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
3083
- * callback used when the caller omits `depositCallback` for a WETH
3084
- * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
3085
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
3086
- */
4905
+ /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
3087
4906
  getDefaultSponsoredCallsCallback(asset, onApproved) {
3088
4907
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
3089
4908
  if (!onApproved && cached) return cached;
3090
4909
  const provider = this.requireConnectedProvider();
3091
4910
  const callback = makeSponsoredCallsCallback({
4911
+ apiKey: this.apiKey,
4912
+ routingApiBaseUrl: this.routingApiBaseUrl,
3092
4913
  provider,
3093
4914
  ownerAddress: this.state.walletAddress,
3094
4915
  paymasterServiceUrl: this.paymasterServiceUrl,
3095
4916
  onApproved,
3096
- tokenAddressByChain: asset === "WETH" ? SPONSORED_WETH_BY_CHAIN : SPONSORED_USDC_BY_CHAIN
4917
+ tokenAddressByChain: sponsoredTokensFor(asset)
3097
4918
  });
3098
4919
  if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
3099
4920
  return callback;
@@ -3102,14 +4923,14 @@ var OwneySDK = class {
3102
4923
  * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
3103
4924
  * callback used when the caller omits `depositCallback` for a WETH deposit.
3104
4925
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
3105
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
4926
+ * single-use batch authorization instead of an EIP-3009 authorization.
3106
4927
  */
3107
4928
  getDefaultWethSponsoredCallback(onApproved) {
3108
4929
  if (!onApproved && this.cachedWethSponsoredCallback)
3109
4930
  return this.cachedWethSponsoredCallback;
3110
4931
  const provider = this.requireConnectedProvider();
3111
4932
  const owner = this.state.walletAddress;
3112
- const callback = makeSponsoredWethCallback({
4933
+ const callback = makeSponsoredTokenCallback({
3113
4934
  apiKey: this.apiKey,
3114
4935
  baseUrl: this.routingApiBaseUrl,
3115
4936
  ownerAddress: owner,
@@ -3118,14 +4939,14 @@ var OwneySDK = class {
3118
4939
  // Casts work around viem's chain-narrowed Client vs the generic
3119
4940
  // PublicClient/WalletClient param types — structurally identical at
3120
4941
  // runtime, but the two share a name TS treats as unrelated.
3121
- getPublicClient: (cid) => createPublicClient2({
4942
+ getPublicClient: (cid) => createPublicClient4({
3122
4943
  chain: VIEM_CHAIN2[cid],
3123
- transport: custom(provider)
4944
+ transport: custom3(provider)
3124
4945
  }),
3125
- getWalletClient: (cid) => createWalletClient({
4946
+ getWalletClient: (cid) => createWalletClient3({
3126
4947
  account: owner,
3127
4948
  chain: VIEM_CHAIN2[cid],
3128
- transport: custom(provider)
4949
+ transport: custom3(provider)
3129
4950
  })
3130
4951
  });
3131
4952
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -3156,12 +4977,10 @@ var OwneySDK = class {
3156
4977
  this.orgAgentConfigPromise = fetchOrgAgentConfig(
3157
4978
  this.apiKey,
3158
4979
  this.routingApiBaseUrl
3159
- ).then(
3160
- (config) => {
3161
- this.orgAgentConfig = config;
3162
- return config;
3163
- }
3164
- );
4980
+ ).then((config) => {
4981
+ this.orgAgentConfig = config;
4982
+ return config;
4983
+ });
3165
4984
  }
3166
4985
  return this.orgAgentConfigPromise;
3167
4986
  }
@@ -3201,7 +5020,14 @@ var OwneySDK = class {
3201
5020
  this.routingApiBaseUrl
3202
5021
  );
3203
5022
  this.disabledAgents.clear();
3204
- for (const { key: key2, agent_type, is_enabled } of agentKeys) {
5023
+ for (const {
5024
+ key: key2,
5025
+ agent_type,
5026
+ is_enabled,
5027
+ is_configured
5028
+ } of agentKeys) {
5029
+ const configured = is_configured ?? Boolean(key2);
5030
+ if (!configured) continue;
3205
5031
  const agent = this.createAgent(agent_type, key2);
3206
5032
  if (!agent) continue;
3207
5033
  this.agents.set(agent_type, agent);
@@ -3225,8 +5051,15 @@ var OwneySDK = class {
3225
5051
  }
3226
5052
  createAgent(agentId, key2) {
3227
5053
  if (agentId === "zyfai") {
5054
+ if (!key2) return null;
3228
5055
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
3229
5056
  }
5057
+ if (agentId === "yieldseeker") {
5058
+ return new YieldseekerAgent(this.apiKey, {
5059
+ auth: { origin: this.yieldseekerSiweOrigin },
5060
+ baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
5061
+ });
5062
+ }
3230
5063
  return null;
3231
5064
  }
3232
5065
  /**
@@ -3271,9 +5104,10 @@ var OwneySDK = class {
3271
5104
  * If provided, ALL specified agents must support the chainId or the call
3272
5105
  * throws before activating any agent.
3273
5106
  */
3274
- async activateAgent(chainId, agentId) {
5107
+ async activateAgent(chainId, agentId, asset) {
3275
5108
  const state = this.requireState();
3276
5109
  await this.ensureAgentsInitialized();
5110
+ this.assertActivationSession(state);
3277
5111
  if (agentId !== void 0) {
3278
5112
  if (agentId.length === 0) {
3279
5113
  throw new OwneyError(
@@ -3307,7 +5141,7 @@ var OwneySDK = class {
3307
5141
  this.activeAgents.add(id);
3308
5142
  }
3309
5143
  state.chainId = chainId;
3310
- await this.activateAgentsInTurn(agents, state, chainId);
5144
+ await this.activateAgentsInTurn(agents, state, chainId, asset);
3311
5145
  return;
3312
5146
  }
3313
5147
  const compatible = [...this.agents.values()].filter(
@@ -3328,7 +5162,12 @@ var OwneySDK = class {
3328
5162
  const enabledCompatible = compatible.filter(
3329
5163
  (agent) => !this.isAgentDisabled(agent.id)
3330
5164
  );
3331
- await this.activateAgentsInTurn(enabledCompatible, state, chainId);
5165
+ await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
5166
+ }
5167
+ assertActivationSession(state) {
5168
+ if (this.state !== state) {
5169
+ throw new OwneyError("NOT_CONNECTED", "Wallet connection changed. Please try again.");
5170
+ }
3332
5171
  }
3333
5172
  /**
3334
5173
  * Activate agents ONE AT A TIME, each followed by its org policy.
@@ -3343,26 +5182,51 @@ var OwneySDK = class {
3343
5182
  * Serializing costs no real wall-clock: the user can only approve one prompt
3344
5183
  * at a time anyway.
3345
5184
  *
3346
- * Every agent is attempted even if an earlier one fails, so one declined
3347
- * signature can't deny the remaining agents their turn. The first failure is
3348
- * rethrown (matching the previous `Promise.all` rejection) once all agents
3349
- * have had a chance to activate.
5185
+ * Stop at the first failure so a canceled sign-in does not open another
5186
+ * agent's wallet prompt. Report any earlier successes for diagnostics; the
5187
+ * app discards the session when the complete sign-in does not succeed.
3350
5188
  */
3351
- async activateAgentsInTurn(agents, state, chainId) {
5189
+ async activateAgentsInTurn(agents, state, chainId, asset) {
3352
5190
  let firstError = null;
5191
+ const activatedAgentIds = [];
5192
+ const failedAgents = [];
3353
5193
  for (const agent of agents) {
5194
+ this.assertActivationSession(state);
3354
5195
  try {
3355
- await agent.activateAgent(state, chainId);
5196
+ await agent.activateAgent(state, chainId, asset);
5197
+ this.assertActivationSession(state);
3356
5198
  await this.applyOrgPolicyTo(agent, state, chainId);
5199
+ this.assertActivationSession(state);
5200
+ activatedAgentIds.push(agent.id);
3357
5201
  } catch (error) {
5202
+ this.assertActivationSession(state);
5203
+ const message = error !== null && typeof error === "object" && "message" in error && typeof error.message === "string" ? error.message : typeof error === "string" ? error : "No activation error message was returned.";
5204
+ failedAgents.push({
5205
+ agentId: agent.id,
5206
+ code: error instanceof OwneyError ? error.code : void 0,
5207
+ message,
5208
+ ...error instanceof OwneyError && error.details ? { details: error.details } : {}
5209
+ });
3358
5210
  if (firstError === null) {
3359
5211
  firstError = error;
3360
5212
  } else {
3361
5213
  console.error(`activateAgent(${agent.id}) failed:`, error);
3362
5214
  }
5215
+ break;
3363
5216
  }
3364
5217
  }
3365
- if (firstError !== null) throw firstError;
5218
+ if (firstError === null) return;
5219
+ if (activatedAgentIds.length === 0) throw firstError;
5220
+ const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
5221
+ const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
5222
+ const failureMessages = failedAgents.map(
5223
+ ({ agentId, message }) => `${this.formatAgentName(agentId)} activation failed: ${message}`
5224
+ ).join(" ");
5225
+ throw new OwneyError(
5226
+ "AGENT_ACTIVATION_PARTIAL_FAILURE",
5227
+ `${activeNames} activated. ${failureMessages}`,
5228
+ { activatedAgentIds, failedAgentIds, failures: failedAgents }
5229
+ );
3366
5230
  }
3367
5231
  /**
3368
5232
  * Deposit into a specific agent, or distribute across all eligible agents.
@@ -3372,7 +5236,8 @@ var OwneySDK = class {
3372
5236
  * @param options.asset - Asset symbol to deposit (e.g. "USDC")
3373
5237
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
3374
5238
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
3375
- * split amount and smart wallet address — expect multiple wallet prompts.
5239
+ * split amount and smart wallet address. Default sponsored deposits batch
5240
+ * all shares into one signature; custom callbacks still run once per agent.
3376
5241
  * @param options.agentId - Optional explicit target. Otherwise split equally,
3377
5242
  * or fund remaining agents when a recovery deposit cannot meet every minimum.
3378
5243
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
@@ -3457,6 +5322,39 @@ var OwneySDK = class {
3457
5322
  }
3458
5323
  );
3459
5324
  }
5325
+ const batchTransfer = getDepositBatchTransfer(effectiveCallback);
5326
+ if (!depositCallback && batchTransfer) {
5327
+ return runAgentDepositBatch(
5328
+ chainId,
5329
+ agentAmounts.map(({ agent, amount: amount2 }) => ({
5330
+ id: agent.id,
5331
+ amount: amount2,
5332
+ run: (callback) => withFailureReporting(
5333
+ this.apiKey,
5334
+ agent.id,
5335
+ () => agent.deposit(state, chainId, amount2, asset, callback),
5336
+ this.routingApiBaseUrl
5337
+ )
5338
+ })),
5339
+ async (cid, transfers) => {
5340
+ try {
5341
+ return await batchTransfer(cid, transfers);
5342
+ } catch (error) {
5343
+ if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
5344
+ throw error;
5345
+ const requiredAmount = transfers.reduce(
5346
+ (sum, transfer) => sum + BigInt(transfer.amount),
5347
+ 0n
5348
+ );
5349
+ await this.approvePermit2(
5350
+ asset,
5351
+ requiredAmount
5352
+ );
5353
+ return batchTransfer(cid, transfers);
5354
+ }
5355
+ }
5356
+ );
5357
+ }
3460
5358
  const agentResults = {};
3461
5359
  for (const [
3462
5360
  index,
@@ -3496,7 +5394,7 @@ var OwneySDK = class {
3496
5394
  *
3497
5395
  * 1. Missing Permit2 allowance: when the app did not supply its own
3498
5396
  * callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
3499
- * WETH deposit, this is the wallet's first gasless WETH deposit. We send
5397
+ * token deposit, this is the wallet's first Permit2 deposit for that token. We send
3500
5398
  * the one-time (user-paid) Permit2 approval via `approvePermit2()` and
3501
5399
  * retry the SAME sponsored attempt once. Bounded to one approval attempt
3502
5400
  * per call so a wallet/agent that keeps reporting the allowance as
@@ -3533,12 +5431,15 @@ var OwneySDK = class {
3533
5431
  try {
3534
5432
  return await attempt(effectiveCallback);
3535
5433
  } catch (error) {
3536
- if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
5434
+ if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
3537
5435
  approvalAttempted = true;
3538
5436
  console.warn(
3539
- "[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
5437
+ "[owney-sdk] First token deposit: sending one-time Permit2 approval..."
5438
+ );
5439
+ await this.approvePermit2(
5440
+ asset,
5441
+ BigInt(amount)
3540
5442
  );
3541
- await this.approvePermit2();
3542
5443
  continue;
3543
5444
  }
3544
5445
  if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
@@ -3576,10 +5477,10 @@ var OwneySDK = class {
3576
5477
  agent,
3577
5478
  amount: i === agents.length - 1 ? perAgent + remainder : perAgent
3578
5479
  }));
3579
- const valid = splits.filter(
5480
+ const valid2 = splits.filter(
3580
5481
  (s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
3581
5482
  );
3582
- if (valid.length === agents.length) {
5483
+ if (valid2.length === agents.length) {
3583
5484
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
3584
5485
  }
3585
5486
  const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
@@ -3598,6 +5499,11 @@ var OwneySDK = class {
3598
5499
  )
3599
5500
  }));
3600
5501
  }
5502
+ formatAgentName(agentId) {
5503
+ if (agentId === "zyfai") return "Zyfai";
5504
+ if (agentId === "yieldseeker") return "Yieldseeker";
5505
+ return agentId;
5506
+ }
3601
5507
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
3602
5508
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
3603
5509
  const parsedAmount = BigInt(amount);
@@ -3629,12 +5535,12 @@ var OwneySDK = class {
3629
5535
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
3630
5536
  );
3631
5537
  const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
3632
- const position = (balance.positions ?? []).find((p) => {
5538
+ const position2 = (balance.positions ?? []).find((p) => {
3633
5539
  const positionChain = p.chain.trim().toUpperCase();
3634
5540
  const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
3635
5541
  return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
3636
5542
  });
3637
- return !!token && Number(token.amount) > 0 || !!position;
5543
+ return !!token && Number(token.amount) > 0 || !!position2;
3638
5544
  } catch (error) {
3639
5545
  if (requireReliableRead) {
3640
5546
  throw new OwneyError(
@@ -3749,6 +5655,10 @@ var OwneySDK = class {
3749
5655
  }
3750
5656
  const requested = BigInt(amount);
3751
5657
  const aggregated = await this.getBalances();
5658
+ const unavailableAgents = eligibleAgents.filter(
5659
+ (agent) => !(agent.id in aggregated.agentBalances)
5660
+ );
5661
+ const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
3752
5662
  const balances = projectAgentBalancesForAsset(
3753
5663
  eligibleAgents,
3754
5664
  aggregated.agentBalances,
@@ -3757,7 +5667,18 @@ var OwneySDK = class {
3757
5667
  assetInfo.decimals
3758
5668
  );
3759
5669
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
3760
- if (totalAvailable < requested) {
5670
+ if (totalAvailable === 0n && unavailableAgents.length > 0) {
5671
+ throw new OwneyError(
5672
+ "WITHDRAW_BALANCE_UNAVAILABLE",
5673
+ `Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
5674
+ {
5675
+ asset,
5676
+ unavailableAgents: unavailableAgentIds,
5677
+ agentErrors: aggregated.agentErrors
5678
+ }
5679
+ );
5680
+ }
5681
+ if (totalAvailable < requested && unavailableAgents.length === 0) {
3761
5682
  throw new OwneyError(
3762
5683
  "WITHDRAW_INSUFFICIENT_BALANCE",
3763
5684
  `Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
@@ -3768,6 +5689,7 @@ var OwneySDK = class {
3768
5689
  }
3769
5690
  );
3770
5691
  }
5692
+ const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
3771
5693
  const disabledBalances = balances.filter(
3772
5694
  (b) => this.isAgentDisabled(b.agent.id)
3773
5695
  );
@@ -3776,7 +5698,7 @@ var OwneySDK = class {
3776
5698
  );
3777
5699
  const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
3778
5700
  disabledBalances,
3779
- requested
5701
+ plannedTarget
3780
5702
  );
3781
5703
  const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
3782
5704
  const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
@@ -3786,7 +5708,9 @@ var OwneySDK = class {
3786
5708
  }));
3787
5709
  const plans = [...disabledPlans, ...enabledPlans];
3788
5710
  const results = {};
3789
- const agentErrors = {};
5711
+ const agentErrors = {
5712
+ ...aggregated.agentErrors ?? {}
5713
+ };
3790
5714
  for (let i = 0; i < plans.length; i++) {
3791
5715
  const p = plans[i];
3792
5716
  if (p.planned === 0n) continue;
@@ -3833,7 +5757,8 @@ var OwneySDK = class {
3833
5757
  requested: amount,
3834
5758
  withdrawn: withdrawn.toString(),
3835
5759
  partialResults: results,
3836
- agentErrors
5760
+ agentErrors,
5761
+ ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
3837
5762
  }
3838
5763
  );
3839
5764
  }
@@ -3851,7 +5776,10 @@ var OwneySDK = class {
3851
5776
  if (agentId) {
3852
5777
  const agent = this.getAgent(agentId);
3853
5778
  const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3854
- return result;
5779
+ return {
5780
+ ...result,
5781
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5782
+ };
3855
5783
  }
3856
5784
  let totalBalance = 0;
3857
5785
  const results = {};
@@ -3859,7 +5787,13 @@ var OwneySDK = class {
3859
5787
  const balanceResults = await Promise.allSettled(
3860
5788
  entries.map(async ([id, agent]) => {
3861
5789
  const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
3862
- return [id, b];
5790
+ return [
5791
+ id,
5792
+ {
5793
+ ...b,
5794
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5795
+ }
5796
+ ];
3863
5797
  })
3864
5798
  );
3865
5799
  let successCount = 0;
@@ -3881,6 +5815,7 @@ var OwneySDK = class {
3881
5815
  const retryDelay = rateLimitDelay(reason);
3882
5816
  if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
3883
5817
  agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
5818
+ console.error(`[owney-sdk] Balance fetch failed for agent "${agentId2}":`, reason);
3884
5819
  }
3885
5820
  if (successCount === 0) {
3886
5821
  throw new OwneyError(
@@ -4059,7 +5994,10 @@ var OwneySDK = class {
4059
5994
  Promise.all(
4060
5995
  entries.map(async ([id, agent]) => {
4061
5996
  const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
4062
- return [id, balanceForApyScope(b, chainId, tokenSymbol)];
5997
+ return [
5998
+ id,
5999
+ balanceForApyScope(b, chainId, tokenSymbol)
6000
+ ];
4063
6001
  })
4064
6002
  )
4065
6003
  ]);
@@ -4220,43 +6158,44 @@ var OwneySDK = class {
4220
6158
  return pending;
4221
6159
  }
4222
6160
  /**
4223
- * One-time, user-paid approval of Permit2 on the sponsored WETH token for
4224
- * the active chain. Required once per wallet per chain before gasless WETH
4225
- * deposits; afterwards deposit() is signature-only. Resolves only after the
4226
- * approval transaction is mined (1 confirmation), so a subsequent deposit()
4227
- * will see the new allowance; throws if the transaction reverted.
6161
+ * User-paid approval of Permit2 on the selected token for the active chain.
6162
+ * Approves exactly the pending deposit amount. Another approval is required
6163
+ * for a later deposit once this allowance has been consumed. Resolves after
6164
+ * one confirmation so the subsequent deposit attempt sees the new allowance.
6165
+ *
6166
+ * @param requiredAmount Raw base-unit amount the pending deposit must cover.
4228
6167
  * @returns the approval transaction hash.
4229
6168
  */
4230
- async approvePermit2(asset = "WETH") {
4231
- void asset;
6169
+ async approvePermit2(asset = "WETH", requiredAmount = 0n) {
4232
6170
  const state = this.requireState();
4233
6171
  const chainId = this.requireChainId();
4234
- this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
4235
- const token = SPONSORED_WETH_BY_CHAIN[chainId];
6172
+ this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
6173
+ const token = sponsoredTokensFor(asset)[chainId];
4236
6174
  if (!token) {
4237
6175
  throw new OwneyError(
4238
6176
  "CHAIN_UNSUPPORTED",
4239
- `No sponsored WETH on chain ${chainId}`
6177
+ `No sponsored token on chain ${chainId}`
4240
6178
  );
4241
6179
  }
4242
6180
  const provider = this.requireConnectedProvider();
4243
- const wallet = createWalletClient({
6181
+ const publicClient = createPublicClient4({
6182
+ chain: VIEM_CHAIN2[chainId],
6183
+ transport: custom3(provider)
6184
+ });
6185
+ const approvalAmount = permit2ApprovalAmount(requiredAmount);
6186
+ const wallet = createWalletClient3({
4244
6187
  account: state.walletAddress,
4245
6188
  chain: VIEM_CHAIN2[chainId],
4246
- transport: custom(provider)
6189
+ transport: custom3(provider)
4247
6190
  });
4248
6191
  const hash = await wallet.writeContract({
4249
6192
  address: token,
4250
6193
  abi: ERC20_ALLOWANCE_ABI,
4251
6194
  functionName: "approve",
4252
- args: [PERMIT2_ADDRESS, MAX_UINT256],
6195
+ args: [PERMIT2_ADDRESS, approvalAmount],
4253
6196
  account: state.walletAddress,
4254
6197
  chain: VIEM_CHAIN2[chainId]
4255
6198
  });
4256
- const publicClient = createPublicClient2({
4257
- chain: VIEM_CHAIN2[chainId],
4258
- transport: custom(provider)
4259
- });
4260
6199
  const receipt = await publicClient.waitForTransactionReceipt({
4261
6200
  hash,
4262
6201
  confirmations: 1
@@ -4292,7 +6231,9 @@ var OwneySDK = class {
4292
6231
  return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
4293
6232
  }
4294
6233
  const results = {};
4295
- const agentEntries = [...this.agents.entries()];
6234
+ const agentEntries = [...this.agents.entries()].filter(
6235
+ ([id]) => !this.isAgentDisabled(id)
6236
+ );
4296
6237
  const apyResults = await Promise.all(
4297
6238
  agentEntries.map(async ([id, agent]) => {
4298
6239
  const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
@@ -4367,13 +6308,13 @@ var OwneySDK = class {
4367
6308
  };
4368
6309
 
4369
6310
  // src/agents/zyfai/zyfai.siwx.ts
4370
- import { getAddress } from "viem";
4371
- import { SiweMessage } from "siwe";
6311
+ import { getAddress as getAddress3 } from "viem";
6312
+ import { SiweMessage as SiweMessage2 } from "siwe";
4372
6313
  import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
4373
6314
 
4374
6315
  // src/agents/zyfai/zyfai.siwx-cache.ts
4375
- var KEY_PREFIX2 = "owney.siwx.session";
4376
- var storage2 = () => {
6316
+ var KEY_PREFIX4 = "owney.siwx.session";
6317
+ var storage4 = () => {
4377
6318
  if (typeof window === "undefined") return null;
4378
6319
  try {
4379
6320
  return window.localStorage;
@@ -4381,8 +6322,8 @@ var storage2 = () => {
4381
6322
  return null;
4382
6323
  }
4383
6324
  };
4384
- var buildKey2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}`;
4385
- var legacyKeyPrefix2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}:`;
6325
+ var buildKey3 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
6326
+ var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
4386
6327
  var memorySiwxSessions = /* @__PURE__ */ new Map();
4387
6328
  var readLegacySiwxSession = (store, address) => {
4388
6329
  if (!store) return null;
@@ -4413,17 +6354,17 @@ var readLegacySiwxSession = (store, address) => {
4413
6354
  };
4414
6355
  var readSiwxSession = (address, chainId) => {
4415
6356
  if (typeof window === "undefined") return null;
4416
- const key2 = buildKey2(address);
4417
- const store = storage2();
4418
- let raw = null;
6357
+ const key2 = buildKey3(address);
6358
+ const store = storage4();
6359
+ let raw2 = null;
4419
6360
  try {
4420
- raw = store?.getItem(key2) ?? null;
6361
+ raw2 = store?.getItem(key2) ?? null;
4421
6362
  } catch {
4422
- raw = null;
6363
+ raw2 = null;
4423
6364
  }
4424
- if (raw) {
6365
+ if (raw2) {
4425
6366
  try {
4426
- return JSON.parse(raw);
6367
+ return JSON.parse(raw2);
4427
6368
  } catch {
4428
6369
  memorySiwxSessions.delete(key2);
4429
6370
  try {
@@ -4442,18 +6383,18 @@ var readSiwxSession = (address, chainId) => {
4442
6383
  };
4443
6384
  var writeSiwxSession = (address, _chainId, session) => {
4444
6385
  if (typeof window === "undefined") return;
4445
- const key2 = buildKey2(address);
6386
+ const key2 = buildKey3(address);
4446
6387
  memorySiwxSessions.set(key2, session);
4447
- const store = storage2();
6388
+ const store = storage4();
4448
6389
  try {
4449
6390
  store?.setItem(key2, JSON.stringify(session));
4450
6391
  } catch {
4451
6392
  }
4452
6393
  };
4453
6394
  var clearSiwxSession = (address, _chainId) => {
4454
- const key2 = buildKey2(address);
6395
+ const key2 = buildKey3(address);
4455
6396
  memorySiwxSessions.delete(key2);
4456
- const store = storage2();
6397
+ const store = storage4();
4457
6398
  try {
4458
6399
  store?.removeItem(key2);
4459
6400
  } catch {
@@ -4493,8 +6434,8 @@ function buildSIWXConfig(deps) {
4493
6434
  statement: STATEMENT,
4494
6435
  issuedAt,
4495
6436
  toString() {
4496
- return new SiweMessage({
4497
- address: getAddress(accountAddress),
6437
+ return new SiweMessage2({
6438
+ address: getAddress3(accountAddress),
4498
6439
  chainId: numericChainId(chainId),
4499
6440
  domain,
4500
6441
  uri,
@@ -4536,7 +6477,7 @@ function buildSIWXConfig(deps) {
4536
6477
  const persistSession = async (session) => {
4537
6478
  const address = session.data.accountAddress;
4538
6479
  const id = numericChainId(session.data.chainId);
4539
- const message = new SiweMessage(session.message);
6480
+ const message = new SiweMessage2(session.message);
4540
6481
  const login = await post("/auth/login", {
4541
6482
  message,
4542
6483
  signature: session.signature,
@@ -4585,6 +6526,7 @@ export {
4585
6526
  NotConnectedError,
4586
6527
  OwneyError,
4587
6528
  OwneySDK,
6529
+ YieldseekerAgent,
4588
6530
  createOwneySIWX,
4589
6531
  setOwneyDebug
4590
6532
  };