@owney/sdk 0.7.25-beta.0 → 0.7.25-beta.3

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,80 +2110,28 @@ 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`;
2108
- 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;
2132
- } 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
- throw new OwneyError(
2152
- "API_ROUTING_ERROR",
2153
- `Routing API error ${res.status}: ${text}`,
2154
- { statusCode: res.status, responseBody: text }
2155
- );
2156
- }
2157
- const json = await res.json();
2158
- if (!json.success) {
2159
- throw new OwneyError(
2160
- "API_ROUTING_FAILED",
2161
- `Routing API request failed: ${json.message}`,
2162
- { message: json.message }
2163
- );
2164
- }
2165
- return json.data;
2166
- }
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";
2167
2135
 
2168
2136
  // src/lib/chain-guard.ts
2169
2137
  var CHAIN_NAMES = {
@@ -2200,148 +2168,132 @@ async function ensureWalletOnChain(pub, wallet, expected) {
2200
2168
  }
2201
2169
  }
2202
2170
 
2203
- // src/lib/swap/swap-api.ts
2204
- async function request(baseUrl, apiKey, path, init) {
2205
- const url = `${baseUrl}/api/v1/swap${path}`;
2206
- const res = await fetch(url, {
2207
- method: init?.method ?? "GET",
2208
- headers: {
2209
- "Content-Type": "application/json",
2210
- "x-owney-api-key": apiKey
2211
- },
2212
- ...init ? { body: JSON.stringify(init.body) } : {}
2213
- });
2214
- if (!res.ok) {
2215
- const text = await res.text().catch(() => "");
2216
- if (res.status === 429) {
2217
- throw new OwneyError(
2218
- "SWAP_RATE_LIMITED",
2219
- "Swap provider is rate limiting, retry shortly",
2220
- { statusCode: res.status }
2221
- );
2222
- }
2223
- if (res.status === 403) {
2224
- throw new OwneyError(
2225
- "SWAP_DISABLED",
2226
- "Swap is not enabled for this organization",
2227
- { statusCode: res.status }
2228
- );
2229
- }
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;
2179
+ try {
2180
+ res = await fetch(`${base5}/api/v1/sponsor/paymaster-intent`, {
2181
+ method: "POST",
2182
+ headers: {
2183
+ "content-type": "application/json",
2184
+ "x-owney-api-key": input.apiKey,
2185
+ Authorization: `Signature ${input.yieldseekerSignature}`
2186
+ },
2187
+ body: JSON.stringify(input.body)
2188
+ });
2189
+ } catch (networkError) {
2230
2190
  throw new OwneyError(
2231
- "SWAP_REQUEST_FAILED",
2232
- `Swap API error ${res.status}: ${text}`,
2233
- { statusCode: res.status, responseBody: text }
2191
+ "SPONSOR_REQUEST_FAILED",
2192
+ `Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2193
+ { cause: String(networkError), safeToFallback: true }
2234
2194
  );
2235
2195
  }
2236
- const json = await res.json();
2237
- if (!json.success) {
2196
+ const text = await res.text();
2197
+ let parsed = null;
2198
+ try {
2199
+ parsed = JSON.parse(text);
2200
+ } catch {
2201
+ }
2202
+ if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
2238
2203
  throw new OwneyError(
2239
- "SWAP_REQUEST_FAILED",
2240
- `Swap API request failed: ${json.message ?? "unknown error"}`,
2241
- { message: json.message }
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
+ }
2242
2211
  );
2243
2212
  }
2244
- return json.data;
2213
+ return parsed.data;
2245
2214
  }
2246
- function createSwapApi(baseUrl, apiKey) {
2247
- return {
2248
- /** Source assets the user may pay with, and each chain's deposit targets. */
2249
- listTokens: () => request(baseUrl, apiKey, "/tokens"),
2250
- /**
2251
- * `walletAddress` is required even though the routing API could not infer
2252
- * it: the Fusion+ quoter binds a quote to whoever will sign the order and
2253
- * rejects the request without it.
2254
- */
2255
- quote: (params) => request(baseUrl, apiKey, "/quote", {
2256
- method: "POST",
2257
- body: {
2258
- srcChainId: params.from.chainId,
2259
- srcSymbol: params.from.symbol,
2260
- dstChainId: params.to.chainId,
2261
- dstSymbol: params.to.symbol,
2262
- amount: params.from.amount,
2263
- walletAddress: params.walletAddress,
2264
- ...params.direction ? { direction: params.direction } : {}
2265
- }
2266
- }),
2267
- /** Ready-to-send calldata for a same-chain swap. */
2268
- swapTx: (params) => request(baseUrl, apiKey, "/tx", {
2269
- method: "POST",
2270
- body: {
2271
- srcChainId: params.from.chainId,
2272
- srcSymbol: params.from.symbol,
2273
- dstChainId: params.to.chainId,
2274
- dstSymbol: params.to.symbol,
2275
- amount: params.from.amount,
2276
- walletAddress: params.walletAddress,
2277
- slippage: params.slippage,
2278
- ...params.direction ? { direction: params.direction } : {}
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 }
2279
2223
  }
2280
- }),
2281
- /**
2282
- * Builds a Fusion+ order server-side and returns EIP-712 typed data.
2283
- *
2284
- * Only HASHES go over the wire. The preimages never leave the browser —
2285
- * see swap.secrets.
2286
- */
2287
- buildOrder: (params) => request(baseUrl, apiKey, "/order/build", {
2288
- method: "POST",
2289
- body: {
2290
- srcChainId: params.from.chainId,
2291
- srcSymbol: params.from.symbol,
2292
- dstChainId: params.to.chainId,
2293
- dstSymbol: params.to.symbol,
2294
- amount: params.from.amount,
2295
- walletAddress: params.walletAddress,
2296
- secretHashes: params.secretHashes,
2297
- ...params.direction ? { direction: params.direction } : {},
2298
- ...params.receiver ? { receiver: params.receiver } : {}
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
+ );
2231
+ }
2232
+ const text = await res.text();
2233
+ let parsed = null;
2234
+ try {
2235
+ parsed = JSON.parse(text);
2236
+ } catch {
2237
+ }
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
2299
2246
  }
2300
- }),
2301
- submitOrder: (body) => request(baseUrl, apiKey, "/order", { method: "POST", body }),
2302
- /**
2303
- * Only call once `readyForSecrets` reports the escrow deployed. Publishing
2304
- * earlier hands a resolver the preimage while the user's funds are locked
2305
- * and nothing has been posted on the destination chain.
2306
- */
2307
- submitSecret: (orderHash, secret) => request(baseUrl, apiKey, "/order/secret", {
2308
- method: "POST",
2309
- body: { orderHash, secret }
2310
- }),
2311
- orderStatus: (orderHash) => request(baseUrl, apiKey, `/order/${orderHash}`),
2312
- readyForSecrets: (orderHash) => request(
2313
- baseUrl,
2314
- apiKey,
2315
- `/order/${orderHash}/ready-for-secrets`
2316
- )
2317
- };
2318
- }
2319
-
2320
- // src/lib/swap/swap.rpc.ts
2321
- import { fallback, http as http2 } from "viem";
2322
- var DEFAULT_RPC_URLS = {
2323
- 1: ["https://cloudflare-eth.com", "https://ethereum-rpc.publicnode.com"],
2324
- 8453: ["https://mainnet.base.org", "https://base-rpc.publicnode.com"],
2325
- 42161: [
2326
- "https://arb1.arbitrum.io/rpc",
2327
- "https://arbitrum-one-rpc.publicnode.com"
2328
- ]
2329
- };
2330
- function swapReadTransport(chainId, overrides) {
2331
- const override = overrides?.[chainId];
2332
- if (override) return http2(override);
2333
- const urls = DEFAULT_RPC_URLS[chainId];
2334
- if (!urls || urls.length === 0) return http2();
2335
- return fallback(urls.map((url) => http2(url)));
2247
+ );
2248
+ }
2249
+ return parsed.data.relayer;
2336
2250
  }
2337
- function receiptTimeoutMs(chainId) {
2338
- return chainId === 1 ? 6e5 : 18e4;
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;
2339
2285
  }
2340
2286
 
2341
2287
  // src/lib/permit2.ts
2342
- import { bytesToHex } from "viem";
2288
+ import { bytesToHex as bytesToHex2 } from "viem";
2343
2289
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2344
2290
  var MAX_UINT256 = 2n ** 256n - 1n;
2291
+ function permit2ApprovalAmount(requiredAmount) {
2292
+ if (requiredAmount <= 0n) {
2293
+ throw new Error("Permit2 approval requires a positive deposit amount");
2294
+ }
2295
+ return MAX_UINT256;
2296
+ }
2345
2297
  var ERC20_ALLOWANCE_ABI = [
2346
2298
  {
2347
2299
  type: "function",
@@ -2371,33 +2323,10 @@ var ERC20_ALLOWANCE_ABI = [
2371
2323
  outputs: [{ name: "", type: "uint256" }]
2372
2324
  }
2373
2325
  ];
2374
- function buildPermitTransferFromTypedData(input) {
2375
- return {
2376
- domain: {
2377
- name: "Permit2",
2378
- chainId: input.chainId,
2379
- verifyingContract: PERMIT2_ADDRESS
2380
- },
2381
- types: {
2382
- PermitTransferFrom: [
2383
- { name: "permitted", type: "TokenPermissions" },
2384
- { name: "spender", type: "address" },
2385
- { name: "nonce", type: "uint256" },
2386
- { name: "deadline", type: "uint256" }
2387
- ],
2388
- TokenPermissions: [
2389
- { name: "token", type: "address" },
2390
- { name: "amount", type: "uint256" }
2391
- ]
2392
- },
2393
- primaryType: "PermitTransferFrom",
2394
- message: input.message
2395
- };
2396
- }
2397
2326
  function randomPermit2Nonce() {
2398
2327
  const bytes = new Uint8Array(32);
2399
2328
  globalThis.crypto.getRandomValues(bytes);
2400
- return BigInt(bytesToHex(bytes));
2329
+ return BigInt(bytesToHex2(bytes));
2401
2330
  }
2402
2331
  async function readPermit2Allowance(publicClient, token, owner) {
2403
2332
  return publicClient.readContract({
@@ -2416,122 +2345,44 @@ async function readErc20Balance(publicClient, token, owner) {
2416
2345
  });
2417
2346
  }
2418
2347
 
2419
- // src/lib/swap/swap.secrets.ts
2420
- import { keccak256, toHex } from "viem";
2421
- var SECRET_BYTES = 32;
2422
- function randomBytes(length) {
2423
- const bytes = new Uint8Array(length);
2424
- const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
2425
- if (!cryptoObj?.getRandomValues) {
2426
- throw new Error(
2427
- "[owney-sdk] Secure randomness is unavailable, so a swap secret cannot be generated safely."
2428
- );
2429
- }
2430
- cryptoObj.getRandomValues(bytes);
2431
- return bytes;
2432
- }
2433
- function mintSecrets(count) {
2434
- if (!Number.isInteger(count) || count < 1) {
2435
- throw new Error(
2436
- `[owney-sdk] A swap needs at least one secret, got ${String(count)}.`
2437
- );
2438
- }
2439
- const secrets = [];
2440
- const secretHashes = [];
2441
- for (let i = 0; i < count; i++) {
2442
- const secret = toHex(randomBytes(SECRET_BYTES));
2443
- secrets.push(secret);
2444
- secretHashes.push(keccak256(secret));
2445
- }
2446
- return { secrets, secretHashes };
2348
+ // src/lib/sponsored-deposit.ts
2349
+ var AUTH_WINDOW_SECONDS = 15 * 60;
2350
+ var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
2351
+ function provideDepositVerificationContext(callback, context) {
2352
+ callback[verificationSetter]?.(context);
2447
2353
  }
2448
-
2449
- // src/lib/swap/swap.types.ts
2450
- var SWAP_TERMINAL_STATUSES = [
2451
- "executed",
2452
- "expired",
2453
- "cancelled",
2454
- "refunded"
2455
- ];
2456
- var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
2457
-
2458
- // src/lib/swap/swap.order-runner.ts
2459
- var DEFAULT_POLL_MS = 5e3;
2460
- var MAX_BACKOFF_MS = 3e4;
2461
- var backoffFor = (failures, base3) => Math.min(base3 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
2462
- var DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
2463
- async function runFusionOrder(deps, options) {
2464
- const {
2465
- orderHash,
2466
- secrets,
2467
- onStage,
2468
- pollIntervalMs = DEFAULT_POLL_MS,
2469
- timeoutMs = DEFAULT_TIMEOUT_MS
2470
- } = options;
2471
- const deadline = deps.now() + timeoutMs;
2472
- let failures = 0;
2473
- const published = /* @__PURE__ */ new Set();
2474
- onStage?.("swapping");
2475
- for (; ; ) {
2476
- if (deps.now() >= deadline) {
2477
- throw new OwneyError(
2478
- "SWAP_REQUEST_FAILED",
2479
- "Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
2480
- { orderHash }
2481
- );
2482
- }
2483
- let ready;
2484
- try {
2485
- ready = await deps.readyForSecrets(orderHash);
2486
- } catch {
2487
- ready = {};
2488
- }
2489
- for (const fill of ready.fills ?? []) {
2490
- if (published.has(fill.idx)) continue;
2491
- const secret = secrets[fill.idx];
2492
- if (secret === void 0) {
2493
- throw new OwneyError(
2494
- "SWAP_REQUEST_FAILED",
2495
- `Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
2496
- { orderHash, fillIndex: fill.idx }
2497
- );
2498
- }
2499
- try {
2500
- await deps.submitSecret(orderHash, secret);
2501
- published.add(fill.idx);
2502
- } catch {
2503
- failures += 1;
2504
- }
2505
- }
2506
- let status;
2507
- try {
2508
- ({ status } = await deps.orderStatus(orderHash));
2509
- failures = 0;
2510
- } catch {
2511
- failures += 1;
2512
- await deps.sleep(backoffFor(failures, pollIntervalMs));
2513
- continue;
2514
- }
2515
- if (status === "refunding") onStage?.("refunding");
2516
- if (isSwapTerminal(status)) {
2517
- if (status === "executed") {
2518
- onStage?.("swapped");
2519
- return { status, filled: true };
2520
- }
2521
- if (status === "refunded") onStage?.("refunded");
2522
- throw new OwneyError(
2523
- status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
2524
- status === "refunded" ? "The swap did not complete and your funds have been returned." : "The swap did not complete in time. Your funds will be returned once the timelock expires.",
2525
- { orderHash, status }
2526
- );
2354
+ function makeVerificationAwareDepositCallback(implementation) {
2355
+ let nextVerification;
2356
+ const callback = async (smartWallet, chainId, amount) => {
2357
+ const verification = nextVerification;
2358
+ nextVerification = void 0;
2359
+ return implementation(smartWallet, chainId, amount, verification);
2360
+ };
2361
+ Object.defineProperty(callback, verificationSetter, {
2362
+ value: (context) => {
2363
+ nextVerification = context;
2527
2364
  }
2528
- await deps.sleep(pollIntervalMs);
2529
- }
2365
+ });
2366
+ return callback;
2530
2367
  }
2531
2368
 
2532
- // src/lib/swap/swap.secret-store.ts
2533
- var KEY_PREFIX2 = "owney.swap.order";
2534
- var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
2369
+ // src/agents/yieldseeker/yieldseeker.auth.ts
2370
+ import { SiweMessage, generateNonce } from "siwe";
2371
+ import {
2372
+ createPublicClient as createPublicClient2,
2373
+ createWalletClient,
2374
+ custom,
2375
+ getAddress
2376
+ } from "viem";
2377
+ import { base as base2 } from "viem/chains";
2378
+
2379
+ // src/agents/yieldseeker/yieldseeker.auth-cache.ts
2380
+ var KEY_PREFIX2 = "owney.yieldseeker.session.v5";
2381
+ var INVALIDATED_KEY_PREFIXES = [
2382
+ "owney.yieldseeker.session",
2383
+ "owney.yieldseeker.session.v3",
2384
+ "owney.yieldseeker.session.v4"
2385
+ ];
2535
2386
  var storage2 = () => {
2536
2387
  if (typeof window === "undefined") return null;
2537
2388
  try {
@@ -2540,281 +2391,1649 @@ var storage2 = () => {
2540
2391
  return null;
2541
2392
  }
2542
2393
  };
2543
- var keyFor = (orderHash) => `${KEY_PREFIX2}.${orderHash}`;
2544
- function saveOrder(order) {
2545
- const store = storage2();
2546
- if (!store) return;
2394
+ var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
2395
+ var invalidatedKeys = (address, chainId) => INVALIDATED_KEY_PREFIXES.map(
2396
+ (prefix) => `${prefix}:${address.toLowerCase()}:${chainId}`
2397
+ );
2398
+ var clearInvalidatedSessions = (store, address, chainId) => {
2399
+ for (const key2 of invalidatedKeys(address, chainId)) {
2400
+ memorySessions2.delete(key2);
2401
+ try {
2402
+ store?.removeItem(key2);
2403
+ } catch {
2404
+ }
2405
+ }
2406
+ };
2407
+ var memorySessions2 = /* @__PURE__ */ new Map();
2408
+ var isValidSession = (session) => {
2409
+ if (!session?.token) return false;
2547
2410
  try {
2548
- store.setItem(keyFor(order.orderHash), JSON.stringify(order));
2411
+ const parsed = JSON.parse(atob(session.token));
2412
+ return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
2549
2413
  } catch {
2414
+ return false;
2550
2415
  }
2551
- }
2552
- function clearOrder(orderHash) {
2416
+ };
2417
+ var readYieldseekerSession = (address, chainId) => {
2418
+ if (typeof window === "undefined") return null;
2419
+ const key2 = buildKey2(address, chainId);
2553
2420
  const store = storage2();
2554
- if (!store) return;
2421
+ clearInvalidatedSessions(store, address, chainId);
2422
+ let raw2 = null;
2555
2423
  try {
2556
- store.removeItem(keyFor(orderHash));
2424
+ raw2 = store?.getItem(key2) ?? null;
2557
2425
  } catch {
2426
+ raw2 = null;
2558
2427
  }
2559
- }
2560
- function listOrders(now = Date.now()) {
2561
- const store = storage2();
2562
- if (!store) return [];
2563
- const out = [];
2564
- try {
2565
- const keys = [];
2566
- for (let i = 0; i < store.length; i++) {
2567
- const key2 = store.key(i);
2568
- if (key2?.startsWith(`${KEY_PREFIX2}.`)) keys.push(key2);
2428
+ if (raw2) {
2429
+ try {
2430
+ const parsed = JSON.parse(raw2);
2431
+ if (isValidSession(parsed)) return parsed.token;
2432
+ } catch {
2569
2433
  }
2570
- for (const key2 of keys) {
2571
- const raw = store.getItem(key2);
2572
- if (!raw) continue;
2573
- try {
2574
- const parsed = JSON.parse(raw);
2575
- if (now - parsed.createdAt > MAX_AGE_MS) {
2576
- store.removeItem(key2);
2577
- continue;
2578
- }
2579
- if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
2580
- out.push(parsed);
2581
- }
2582
- } catch {
2583
- store.removeItem(key2);
2434
+ memorySessions2.delete(key2);
2435
+ try {
2436
+ store?.removeItem(key2);
2437
+ } catch {
2438
+ }
2439
+ return null;
2440
+ }
2441
+ const cached = memorySessions2.get(key2);
2442
+ if (isValidSession(cached)) return cached.token;
2443
+ if (cached) memorySessions2.delete(key2);
2444
+ return null;
2445
+ };
2446
+ var writeYieldseekerSession = (address, chainId, token) => {
2447
+ if (typeof window === "undefined") return;
2448
+ const session = { token };
2449
+ if (!isValidSession(session)) return;
2450
+ const key2 = buildKey2(address, chainId);
2451
+ memorySessions2.set(key2, session);
2452
+ const store = storage2();
2453
+ try {
2454
+ store?.setItem(key2, JSON.stringify(session));
2455
+ } catch {
2456
+ }
2457
+ };
2458
+ var clearYieldseekerSession = (address, chainId) => {
2459
+ const key2 = buildKey2(address, chainId);
2460
+ memorySessions2.delete(key2);
2461
+ const store = storage2();
2462
+ clearInvalidatedSessions(store, address, chainId);
2463
+ try {
2464
+ store?.removeItem(key2);
2465
+ } catch {
2466
+ }
2467
+ };
2468
+
2469
+ // src/agents/yieldseeker/yieldseeker.auth.ts
2470
+ function resolveSiweOrigin(override) {
2471
+ const origin = override?.trim() || (typeof window !== "undefined" ? window.location?.origin : void 0);
2472
+ if (!origin || origin === "null") {
2473
+ throw new Error("Yieldseeker sign-in requires the requesting app's browser origin.");
2474
+ }
2475
+ const url = new URL(origin);
2476
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
2477
+ throw new Error("Yieldseeker sign-in requires an HTTP or HTTPS app origin.");
2478
+ }
2479
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
2480
+ throw new Error("Yieldseeker SIWE override must be an origin without credentials, path, query, or fragment.");
2481
+ }
2482
+ return url;
2483
+ }
2484
+ function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
2485
+ const url = resolveSiweOrigin(dependencies.origin);
2486
+ return new SiweMessage({
2487
+ scheme: url.protocol.slice(0, -1),
2488
+ domain: url.host,
2489
+ address: getAddress(address),
2490
+ uri: url.origin,
2491
+ version: "1",
2492
+ chainId,
2493
+ nonce: (dependencies.nonce ?? generateNonce)(),
2494
+ issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
2495
+ }).prepareMessage();
2496
+ }
2497
+ function encodeYieldseekerAuthToken(token) {
2498
+ const bytes = new TextEncoder().encode(JSON.stringify(token));
2499
+ let binary = "";
2500
+ for (const byte of bytes) binary += String.fromCharCode(byte);
2501
+ return btoa(binary);
2502
+ }
2503
+ var YieldseekerAuth = class {
2504
+ constructor(dependencies = {}) {
2505
+ this.dependencies = dependencies;
2506
+ }
2507
+ dependencies;
2508
+ tokens = /* @__PURE__ */ new Map();
2509
+ pending = /* @__PURE__ */ new Map();
2510
+ scopes = /* @__PURE__ */ new Map();
2511
+ key(state, chainId) {
2512
+ return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
2513
+ }
2514
+ async getToken(state, chainId) {
2515
+ const key2 = this.key(state, chainId);
2516
+ const scope = { address: state.walletAddress, chainId };
2517
+ this.scopes.set(key2, scope);
2518
+ const cached = this.tokens.get(key2);
2519
+ if (cached) return cached;
2520
+ const persisted = readYieldseekerSession(scope.address, scope.chainId);
2521
+ if (persisted && this.matchesOrigin(persisted)) {
2522
+ this.tokens.set(key2, persisted);
2523
+ return persisted;
2524
+ }
2525
+ if (persisted) clearYieldseekerSession(scope.address, scope.chainId);
2526
+ const inFlight = this.pending.get(key2);
2527
+ if (inFlight) return inFlight;
2528
+ const request = this.sign(state, chainId).then((token) => {
2529
+ if (this.pending.get(key2) !== request) throw new Error("Wallet sign-in session changed. Please try again.");
2530
+ this.tokens.set(key2, token);
2531
+ writeYieldseekerSession(scope.address, scope.chainId, token);
2532
+ return token;
2533
+ });
2534
+ this.pending.set(key2, request);
2535
+ try {
2536
+ return await request;
2537
+ } finally {
2538
+ if (this.pending.get(key2) === request) this.pending.delete(key2);
2539
+ }
2540
+ }
2541
+ async refreshToken(state, chainId, rejectedToken) {
2542
+ const key2 = this.key(state, chainId);
2543
+ if (this.tokens.get(key2) === rejectedToken) {
2544
+ this.tokens.delete(key2);
2545
+ clearYieldseekerSession(state.walletAddress, chainId);
2546
+ }
2547
+ return this.getToken(state, chainId);
2548
+ }
2549
+ matchesOrigin(token) {
2550
+ try {
2551
+ const message = new SiweMessage(JSON.parse(atob(token)).message);
2552
+ const url = resolveSiweOrigin(this.dependencies.origin);
2553
+ return message.domain === url.host && message.uri === url.origin && message.scheme === url.protocol.slice(0, -1);
2554
+ } catch {
2555
+ return false;
2556
+ }
2557
+ }
2558
+ clear(state, chainId) {
2559
+ if (!state || chainId === void 0) {
2560
+ for (const scope of this.scopes.values()) {
2561
+ clearYieldseekerSession(scope.address, scope.chainId);
2584
2562
  }
2563
+ this.tokens.clear();
2564
+ this.pending.clear();
2565
+ this.scopes.clear();
2566
+ return;
2585
2567
  }
2568
+ const key2 = this.key(state, chainId);
2569
+ this.tokens.delete(key2);
2570
+ this.pending.delete(key2);
2571
+ this.scopes.delete(key2);
2572
+ clearYieldseekerSession(state.walletAddress, chainId);
2573
+ }
2574
+ async sign(state, chainId) {
2575
+ const account = getAddress(state.walletAddress);
2576
+ const publicClient = createPublicClient2({
2577
+ chain: base2,
2578
+ transport: custom(state.provider)
2579
+ });
2580
+ const walletClient = createWalletClient({
2581
+ account,
2582
+ chain: base2,
2583
+ transport: custom(state.provider)
2584
+ });
2585
+ await ensureWalletOnChain(
2586
+ publicClient,
2587
+ walletClient,
2588
+ 8453
2589
+ );
2590
+ const message = createYieldseekerSiweMessage(
2591
+ account,
2592
+ chainId,
2593
+ this.dependencies
2594
+ );
2595
+ const signature = await walletClient.signMessage({ account, message });
2596
+ return encodeYieldseekerAuthToken({ message, signature });
2597
+ }
2598
+ };
2599
+
2600
+ // src/agents/yieldseeker/yieldseeker.identity-cache.ts
2601
+ var KEY_PREFIX3 = "owney.yieldseeker.identity.v1";
2602
+ var YIELDSEEKER_IDENTITY_TTL_MS = 24 * 60 * 60 * 1e3;
2603
+ var memoryIdentities = /* @__PURE__ */ new Map();
2604
+ var storage3 = () => {
2605
+ if (typeof window === "undefined") return null;
2606
+ try {
2607
+ return window.localStorage;
2608
+ } catch {
2609
+ return null;
2610
+ }
2611
+ };
2612
+ var keyFor = (walletAddress, chainId) => `${KEY_PREFIX3}:${walletAddress.toLowerCase()}:${chainId}`;
2613
+ function valid(value, walletAddress, chainId, now) {
2614
+ return Boolean(
2615
+ 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
2616
+ );
2617
+ }
2618
+ function readYieldseekerIdentity(walletAddress, chainId, now = Date.now()) {
2619
+ if (typeof window === "undefined") return null;
2620
+ const key2 = keyFor(walletAddress, chainId);
2621
+ const store = storage3();
2622
+ let parsed = null;
2623
+ try {
2624
+ const raw2 = store?.getItem(key2);
2625
+ parsed = raw2 ? JSON.parse(raw2) : null;
2626
+ } catch {
2627
+ parsed = null;
2628
+ }
2629
+ const candidate = parsed ?? memoryIdentities.get(key2);
2630
+ if (valid(candidate, walletAddress, chainId, now)) {
2631
+ memoryIdentities.set(key2, candidate);
2632
+ return { userId: candidate.userId };
2633
+ }
2634
+ memoryIdentities.delete(key2);
2635
+ try {
2636
+ store?.removeItem(key2);
2637
+ } catch {
2638
+ }
2639
+ return null;
2640
+ }
2641
+ function writeYieldseekerIdentity(walletAddress, chainId, userId, now = Date.now()) {
2642
+ if (typeof window === "undefined") return;
2643
+ const identity = {
2644
+ userId,
2645
+ walletAddress,
2646
+ chainId,
2647
+ expiresAt: now + YIELDSEEKER_IDENTITY_TTL_MS
2648
+ };
2649
+ if (!valid(identity, walletAddress, chainId, now)) return;
2650
+ const key2 = keyFor(walletAddress, chainId);
2651
+ memoryIdentities.set(key2, identity);
2652
+ try {
2653
+ storage3()?.setItem(key2, JSON.stringify(identity));
2654
+ } catch {
2655
+ }
2656
+ }
2657
+ function clearYieldseekerIdentity(walletAddress, chainId) {
2658
+ const key2 = keyFor(walletAddress, chainId);
2659
+ memoryIdentities.delete(key2);
2660
+ try {
2661
+ storage3()?.removeItem(key2);
2586
2662
  } catch {
2587
- return out;
2588
2663
  }
2589
- return out.sort((a, b) => b.createdAt - a.createdAt);
2590
2664
  }
2591
2665
 
2592
- // src/lib/swap/swap.executor.ts
2593
- var DEFAULT_SLIPPAGE = 1;
2594
- async function affordableAmount(deps, quoted) {
2595
- const balance = await deps.readSourceBalance();
2596
- if (balance >= quoted) return quoted;
2597
- debugLog("owney-sdk", "swap: trimming to the current source balance", {
2598
- quoted: quoted.toString(),
2599
- balance: balance.toString(),
2600
- short: (quoted - balance).toString()
2601
- });
2602
- return balance;
2666
+ // src/agents/yieldseeker/yieldseeker.client.ts
2667
+ var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2668
+ function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
2669
+ return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
2603
2670
  }
2604
- async function executeSwap(deps, options) {
2605
- const { quote, walletAddress, onStage } = options;
2606
- debugLog("owney-sdk", "swap: start", {
2607
- rail: quote.rail,
2608
- from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
2609
- to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
2610
- expected: quote.dst.amount,
2611
- floor: quote.dstAmountMin
2612
- });
2613
- const before = await deps.readTargetBalance();
2614
- debugLog("owney-sdk", "swap: target balance before", before.toString());
2615
- const result = quote.rail === "classic" ? await runClassic(deps, options) : await runFusion(deps, options, walletAddress);
2616
- const after = await deps.readTargetBalance();
2617
- const received = after - before;
2618
- debugLog("owney-sdk", "swap: target balance after", {
2619
- after: after.toString(),
2620
- received: received.toString()
2621
- });
2622
- if (received <= 0n) {
2623
- throw new OwneyError(
2624
- "SWAP_REQUEST_FAILED",
2625
- "The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
2626
- { rail: quote.rail, ...result }
2671
+ var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
2672
+ var YieldseekerApiError = class extends Error {
2673
+ constructor(status, providerCode, responseFields) {
2674
+ super(`Yieldseeker request failed (${status}): ${providerCode}`);
2675
+ this.status = status;
2676
+ this.providerCode = providerCode;
2677
+ this.responseFields = responseFields;
2678
+ this.name = "YieldseekerApiError";
2679
+ }
2680
+ status;
2681
+ providerCode;
2682
+ responseFields;
2683
+ get isAuthenticationError() {
2684
+ return this.status === 401 || this.status === 403;
2685
+ }
2686
+ };
2687
+ function providerError(body, fallback) {
2688
+ if (!body || typeof body !== "object") return { code: fallback };
2689
+ const record = body;
2690
+ return {
2691
+ code: typeof record.message === "string" ? record.message : fallback,
2692
+ fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
2693
+ };
2694
+ }
2695
+ var YieldseekerApiClient = class {
2696
+ constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch.bind(globalThis)) {
2697
+ this.owneyApiKey = owneyApiKey;
2698
+ this.baseUrl = baseUrl;
2699
+ this.fetchFn = fetchFn;
2700
+ }
2701
+ owneyApiKey;
2702
+ baseUrl;
2703
+ fetchFn;
2704
+ async request(path, options = {}) {
2705
+ const controller = new AbortController();
2706
+ const timer = setTimeout(
2707
+ () => controller.abort(),
2708
+ options.timeoutMs ?? 15e3
2627
2709
  );
2710
+ try {
2711
+ const response = await this.fetchFn(`${this.baseUrl}${path}`, {
2712
+ method: options.method ?? "GET",
2713
+ headers: {
2714
+ "Content-Type": "application/json",
2715
+ "x-owney-api-key": this.owneyApiKey,
2716
+ ...options.signature ? { Authorization: `Signature ${options.signature}` } : {}
2717
+ },
2718
+ body: options.body ? JSON.stringify(options.body) : void 0,
2719
+ signal: controller.signal
2720
+ });
2721
+ const payload = await response.json().catch(() => null);
2722
+ if (!response.ok) {
2723
+ const error = providerError(payload, `HTTP_${response.status}`);
2724
+ throw new YieldseekerApiError(
2725
+ response.status,
2726
+ error.code,
2727
+ error.fields
2728
+ );
2729
+ }
2730
+ if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
2731
+ return payload.data;
2732
+ }
2733
+ return payload;
2734
+ } catch (error) {
2735
+ if (error instanceof YieldseekerApiError) throw error;
2736
+ if (error instanceof DOMException && error.name === "AbortError") {
2737
+ throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
2738
+ }
2739
+ throw new YieldseekerApiError(0, "NETWORK_ERROR", {
2740
+ cause: error instanceof Error ? error.message : String(error)
2741
+ });
2742
+ } finally {
2743
+ clearTimeout(timer);
2744
+ }
2628
2745
  }
2629
- return { received: received.toString(), ...result };
2746
+ };
2747
+
2748
+ // src/agents/yieldseeker/yieldseeker.mapper.ts
2749
+ import { formatUnits, isAddress } from "viem";
2750
+
2751
+ // src/lib/helpers/snapshot-apy.ts
2752
+ var DAY_MS = 864e5;
2753
+ function snapshotTime(date) {
2754
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
2755
+ const time = Date.parse(date);
2756
+ return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === date ? time : void 0;
2630
2757
  }
2631
- async function runClassic(deps, options) {
2632
- const {
2633
- quote,
2634
- walletAddress,
2635
- slippage = DEFAULT_SLIPPAGE,
2636
- direction,
2637
- onStage
2638
- } = options;
2639
- const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
2640
- const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
2641
- onStage?.("quoting");
2642
- debugLog("owney-sdk", "swap: fetching classic calldata");
2643
- const { tx } = await deps.api.swapTx({
2644
- from: {
2645
- chainId: quote.src.chainId,
2646
- symbol: quote.src.symbol,
2647
- amount: amount.toString()
2648
- },
2649
- to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
2650
- walletAddress,
2651
- slippage,
2652
- ...direction ? { direction } : {}
2653
- });
2654
- const isNative = BigInt(tx.value ?? "0") > 0n;
2655
- if (!isNative) {
2656
- const needed = amount;
2657
- const current = await deps.readAllowance(tx.to);
2658
- debugLog("owney-sdk", "swap: allowance", {
2659
- spender: tx.to,
2660
- current: current.toString(),
2661
- needed: needed.toString()
2758
+ function returnFactor(value) {
2759
+ if (typeof value !== "number" && typeof value !== "string") return void 0;
2760
+ if (typeof value === "string" && value.trim() === "") return void 0;
2761
+ const factor = Number(value);
2762
+ return Number.isFinite(factor) && factor >= 0 ? factor : void 0;
2763
+ }
2764
+ function calculateSnapshotApy(snapshots, lookbackDays, now = Date.now()) {
2765
+ if (!Number.isFinite(lookbackDays) || lookbackDays <= 0 || !Number.isFinite(now)) {
2766
+ return void 0;
2767
+ }
2768
+ const points = snapshots.flatMap((snapshot) => {
2769
+ const time = snapshotTime(snapshot.date);
2770
+ return time !== void 0 && time <= now ? [{ snapshot, time }] : [];
2771
+ }).sort((a, b) => a.time - b.time);
2772
+ const end = points.at(-1);
2773
+ if (!end) return void 0;
2774
+ const cutoff = end.time - lookbackDays * DAY_MS;
2775
+ const start = points.find((point) => point.time >= cutoff);
2776
+ const actualDays = (end.time - start.time) / DAY_MS;
2777
+ if (actualDays <= 0) return void 0;
2778
+ const startFactor = returnFactor(start.snapshot.cumulativeReturnFactor);
2779
+ const endFactor = returnFactor(end.snapshot.cumulativeReturnFactor);
2780
+ if (startFactor === void 0 || startFactor === 0 || endFactor === void 0) {
2781
+ return void 0;
2782
+ }
2783
+ const periodReturn = endFactor / startFactor - 1;
2784
+ const apy = Math.pow(1 + periodReturn, 365 / actualDays) - 1;
2785
+ return Number.isFinite(apy) ? apy : void 0;
2786
+ }
2787
+
2788
+ // src/agents/yieldseeker/yieldseeker.types.ts
2789
+ var YIELDSEEKER_ASSET_METADATA = {
2790
+ USDC: {
2791
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2792
+ decimals: 6
2793
+ },
2794
+ WETH: {
2795
+ address: "0x4200000000000000000000000000000000000006",
2796
+ decimals: 18
2797
+ }
2798
+ };
2799
+
2800
+ // src/agents/yieldseeker/yieldseeker.mapper.ts
2801
+ function invalid(endpoint, detail) {
2802
+ throw new OwneyError(
2803
+ "AGENT_INVALID_RESPONSE",
2804
+ `Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
2805
+ { endpoint, detail },
2806
+ "yieldseeker"
2807
+ );
2808
+ }
2809
+ function raw(value, endpoint) {
2810
+ if (typeof value !== "string" || !/^-?[0-9]+$/.test(value)) {
2811
+ return invalid(endpoint, "expected a base-10 integer string");
2812
+ }
2813
+ return BigInt(value);
2814
+ }
2815
+ function decimal(value, decimals, endpoint) {
2816
+ return formatUnits(raw(value, endpoint), decimals);
2817
+ }
2818
+ function usd(rawAmount, decimals, price) {
2819
+ return Number(formatUnits(rawAmount, decimals)) * price;
2820
+ }
2821
+ function percent(value) {
2822
+ const result = Number(value);
2823
+ return Number.isFinite(result) ? result * 100 : 0;
2824
+ }
2825
+ var YIELDSEEKER_POSITIVE_YIELD_FEE_RATE = 0.1;
2826
+ function publicApyAfterYieldseekerFee(value) {
2827
+ const grossPercent = percent(value);
2828
+ if (grossPercent <= 0) return grossPercent;
2829
+ const netPercent = grossPercent * (1 - YIELDSEEKER_POSITIVE_YIELD_FEE_RATE);
2830
+ return Math.round(netPercent * 1e12) / 1e12;
2831
+ }
2832
+ function riskAdjustedApyForDays(option, days) {
2833
+ if (days === "7D") return option.riskAdjustedApy7dAverage;
2834
+ if (days === "30D") return option.riskAdjustedApy30dAverage;
2835
+ return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
2836
+ }
2837
+ function assetAddressValue(record, address) {
2838
+ const entry = Object.entries(record).find(
2839
+ ([key2]) => key2.toLowerCase() === address.toLowerCase()
2840
+ );
2841
+ return entry?.[1] ?? "0";
2842
+ }
2843
+ function position(value, asset, baseAssetDecimals) {
2844
+ const option = value?.yieldOption;
2845
+ if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress(option.address)) {
2846
+ return invalid("yield positions", "missing vault metadata");
2847
+ }
2848
+ return {
2849
+ chain: "BASE",
2850
+ protocol: option.provider,
2851
+ protocolId: option.address,
2852
+ pool: option.name,
2853
+ asset,
2854
+ // `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
2855
+ // differ from the underlying asset. Yieldseeker already converts it to
2856
+ // underlying base-asset units in `assetsBase`; pair that value with the
2857
+ // snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
2858
+ // share quantity separately because withdraw-from-position expects it.
2859
+ amount: decimal(
2860
+ value.assetsBase,
2861
+ baseAssetDecimals,
2862
+ "yield positions"
2863
+ ),
2864
+ amountRaw: String(value.assetsRaw),
2865
+ apy: percent(option.riskAdjustedApy),
2866
+ tvl: Number(option.totalDepositsUsd),
2867
+ liquidity: Number(option.withdrawableDepositsUsd)
2868
+ };
2869
+ }
2870
+ function mapYieldseekerBalances(contexts) {
2871
+ const tokens = [];
2872
+ const assetBalances = [];
2873
+ const positions = [];
2874
+ let totalUsd = 0;
2875
+ for (const context of contexts) {
2876
+ const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
2877
+ assetBalances.push({
2878
+ chain: "BASE",
2879
+ chainId: 8453,
2880
+ asset: context.asset,
2881
+ amount: decimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
2662
2882
  });
2663
- if (current < needed) {
2664
- onStage?.("approving");
2665
- await deps.ensureChain(quote.src.chainId);
2666
- await deps.approve(tx.to, MAX_UINT256);
2667
- }
2668
- }
2669
- onStage?.("signing");
2670
- await deps.ensureChain(quote.src.chainId);
2671
- debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
2672
- const txHash = await deps.sendTransaction({
2673
- to: tx.to,
2674
- data: tx.data,
2675
- value: tx.value ?? "0"
2676
- });
2677
- onStage?.("swapped");
2678
- return { txHash };
2883
+ const idle = assetAddressValue(
2884
+ context.snapshot.tokenBalances,
2885
+ metadata.address
2886
+ );
2887
+ tokens.push({
2888
+ chain: "BASE",
2889
+ chainId: 8453,
2890
+ asset: context.asset,
2891
+ amount: decimal(idle, metadata.decimals, "snapshot")
2892
+ });
2893
+ positions.push(
2894
+ ...context.positions.map(
2895
+ (entry) => position(
2896
+ entry,
2897
+ context.asset,
2898
+ context.snapshot.baseAssetDecimals
2899
+ )
2900
+ )
2901
+ );
2902
+ totalUsd += usd(
2903
+ raw(context.snapshot.totalValueBase, "snapshot"),
2904
+ context.snapshot.baseAssetDecimals,
2905
+ context.snapshot.baseAssetPriceUsd
2906
+ );
2907
+ }
2908
+ return {
2909
+ ...contexts.length === 1 ? { smartWallet: contexts[0].wallet.walletAddress } : {},
2910
+ totalBalance: String(totalUsd),
2911
+ totalBalanceAsset: "usdc",
2912
+ assetBalances,
2913
+ tokens,
2914
+ positions
2915
+ };
2679
2916
  }
2680
- async function runFusion(deps, options, walletAddress) {
2681
- const { quote, direction, onStage } = options;
2682
- const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
2683
- const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
2684
- if (quote.spender && !isNativeSource) {
2685
- const needed = amount;
2686
- const current = await deps.readAllowance(quote.spender);
2687
- debugLog("owney-sdk", "swap: fusion allowance", {
2688
- spender: quote.spender,
2689
- current: current.toString(),
2690
- needed: needed.toString()
2917
+ function mapYieldseekerEarnings(contexts) {
2918
+ const tokens = [];
2919
+ let lifetimeEarnings = 0;
2920
+ for (const context of contexts) {
2921
+ const amount = raw(context.snapshot.cumulativeYieldBase, "snapshot");
2922
+ tokens.push({
2923
+ chain: "BASE",
2924
+ chainId: 8453,
2925
+ asset: context.asset,
2926
+ amount: formatUnits(amount, context.snapshot.baseAssetDecimals)
2691
2927
  });
2692
- if (current < needed) {
2693
- onStage?.("approving");
2694
- await deps.ensureChain(quote.src.chainId);
2695
- await deps.approve(quote.spender, MAX_UINT256);
2696
- debugLog("owney-sdk", "swap: approved limit order protocol");
2928
+ lifetimeEarnings += usd(
2929
+ amount,
2930
+ context.snapshot.baseAssetDecimals,
2931
+ context.snapshot.baseAssetPriceUsd
2932
+ );
2933
+ }
2934
+ return {
2935
+ smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
2936
+ lifetimeEarnings,
2937
+ tokens
2938
+ };
2939
+ }
2940
+ function apyForDays(context, days, now) {
2941
+ if (days === "7D") return percent(context.snapshot.apy7d);
2942
+ if (days === "30D") return percent(context.snapshot.apy30d);
2943
+ const apy = calculateSnapshotApy(context.historic?.dailyYieldSnapshots ?? [], 14, now);
2944
+ const apyPercent = apy === void 0 ? void 0 : apy * 100;
2945
+ return apyPercent !== void 0 && Number.isFinite(apyPercent) ? apyPercent : void 0;
2946
+ }
2947
+ function dailyApy(point) {
2948
+ const total = raw(point.totalValueBase, "historic position");
2949
+ const earned = raw(point.dailyYieldBase, "historic position");
2950
+ const principal = total - earned;
2951
+ if (principal <= 0n || earned === 0n) return 0;
2952
+ return Number(earned) / Number(principal) * 365 * 100;
2953
+ }
2954
+ function aggregateHistory(contexts, dayCount, now) {
2955
+ const today = new Date(now).toISOString().slice(0, 10);
2956
+ const cutoff = new Date(Date.parse(today) - (dayCount - 1) * 864e5).toISOString().slice(0, 10);
2957
+ const assets = new Set(contexts.map((context) => context.asset.toUpperCase()));
2958
+ const unit = assets.size === 1 ? [...assets][0] : "USD";
2959
+ const byDate = /* @__PURE__ */ new Map();
2960
+ for (const context of contexts) {
2961
+ const points = context.historic?.dailyYieldSnapshots ?? [];
2962
+ for (const point of points) {
2963
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(point.date) || !Number.isFinite(Date.parse(point.date)) || point.date < cutoff || point.date > today) continue;
2964
+ const tokens = Number(decimal(point.totalValueBase, point.baseAssetDecimals, "historic position"));
2965
+ const amount = assets.size === 1 ? tokens : tokens * point.baseAssetPriceUsd;
2966
+ if (!Number.isFinite(amount) || amount < 0) {
2967
+ invalid("historic position", "expected a finite non-negative balance");
2968
+ }
2969
+ const current = byDate.get(point.date) ?? { weighted: 0, amount: 0 };
2970
+ current.weighted += dailyApy(point) * amount;
2971
+ current.amount += amount;
2972
+ byDate.set(point.date, current);
2697
2973
  }
2698
2974
  }
2699
- const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
2700
- onStage?.("quoting");
2701
- debugLog("owney-sdk", "swap: building fusion order", {
2702
- secrets: secretHashes.length
2703
- });
2704
- const built = await deps.api.buildOrder({
2705
- from: {
2706
- chainId: quote.src.chainId,
2707
- symbol: quote.src.symbol,
2708
- // The trimmed amount — the order is re-quoted at this size server-side.
2709
- amount: amount.toString()
2710
- },
2711
- to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
2975
+ return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
2976
+ date,
2977
+ apy: value.amount > 0 ? value.weighted / value.amount : 0,
2978
+ historicalBalance: { amount: value.amount, unit }
2979
+ }));
2980
+ }
2981
+ function mapYieldseekerApy(walletAddress, contexts, days, now = Date.now()) {
2982
+ let weighted = 0;
2983
+ let totalUsd = 0;
2984
+ const byAsset = {};
2985
+ for (const context of contexts) {
2986
+ const valueUsd = usd(
2987
+ raw(context.snapshot.totalValueBase, "snapshot"),
2988
+ context.snapshot.baseAssetDecimals,
2989
+ context.snapshot.baseAssetPriceUsd
2990
+ );
2991
+ const apy = apyForDays(context, days, now);
2992
+ if (apy === void 0) continue;
2993
+ weighted += apy * valueUsd;
2994
+ totalUsd += valueUsd;
2995
+ byAsset[context.asset] = apy;
2996
+ }
2997
+ const dayCount = Number(days.slice(0, -1));
2998
+ return {
2712
2999
  walletAddress,
2713
- secretHashes,
2714
- ...direction ? { direction } : {}
2715
- });
2716
- saveOrder({
2717
- orderHash: built.orderHash,
2718
- secrets,
2719
- srcChainId: quote.src.chainId,
2720
- srcSymbol: quote.src.symbol,
2721
- dstChainId: quote.dst.chainId,
2722
- dstSymbol: quote.dst.symbol,
2723
- amount: amount.toString(),
2724
- createdAt: Date.now()
2725
- });
2726
- debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
2727
- onStage?.("signing");
2728
- await deps.ensureChain(quote.src.chainId);
2729
- debugLog("owney-sdk", "swap: awaiting signature in wallet", {
2730
- signingOnChain: quote.src.chainId
2731
- });
2732
- const signature = await deps.signTypedData(built.typedData);
2733
- debugLog("owney-sdk", "swap: signed, submitting to relayer");
2734
- await deps.api.submitOrder({
2735
- srcChainId: quote.src.chainId,
2736
- // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
2737
- // the envelope here gets a bare 500 from the relayer.
2738
- order: built.order,
2739
- signature,
2740
- quoteId: built.quoteId,
2741
- // Single-fill orders must NOT carry secretHashes — the relayer rejects
2742
- // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
2743
- // order's hashlock, so repeating it here is redundant, and only a
2744
- // multi-fill order (a Merkle tree of hashes) needs them listed.
2745
- ...secretHashes.length > 1 ? { secretHashes } : {},
2746
- ...built.extension ? { extension: built.extension } : {}
3000
+ ...days !== "14D" || Object.keys(byAsset).length > 0 ? { weightedApyAfterFee: totalUsd > 0 ? weighted / totalUsd : 0 } : {},
3001
+ apyByChainAndAsset: { 8453: byAsset },
3002
+ history: aggregateHistory(contexts, dayCount, now)
3003
+ };
3004
+ }
3005
+ function actionType(value) {
3006
+ const normalized = value.toLowerCase();
3007
+ if (normalized.includes("deposit")) return "Deposit";
3008
+ if (normalized.includes("withdraw")) return "Withdraw";
3009
+ if (normalized.includes("yield") || normalized.includes("earn"))
3010
+ return "Earned";
3011
+ return "Rebalance";
3012
+ }
3013
+ function transactionHashes(details) {
3014
+ if (!details) return [];
3015
+ const values = [
3016
+ details.transactionHash,
3017
+ details.txHash,
3018
+ ...Array.isArray(details.transactionHashes) ? details.transactionHashes : [],
3019
+ ...Array.isArray(details.txHashes) ? details.txHashes : []
3020
+ ];
3021
+ return values.filter(
3022
+ (value) => typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value)
3023
+ ).filter((value, index, all) => all.indexOf(value) === index);
3024
+ }
3025
+ function actionEntry(action) {
3026
+ if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
3027
+ return {
3028
+ agent: "yieldseeker",
3029
+ action: actionType(action.actionType),
3030
+ date: action.createdDate,
3031
+ oldApy: null,
3032
+ newApy: null,
3033
+ transactions: [
3034
+ {
3035
+ txHashes: transactionHashes(action.details),
3036
+ chainId: 8453
3037
+ }
3038
+ ],
3039
+ rebalanceLog: []
3040
+ };
3041
+ }
3042
+ function depositDestination(context, movement) {
3043
+ const to = movement.toAddress.toLowerCase();
3044
+ const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
3045
+ if (option) return { protocol: option.provider || "Vault", pool: option.name, tokenSymbol: context.asset };
3046
+ const receipt = context.historic?.assets.find((asset) => asset.chainId === movement.chainId && !asset.isSpam && asset.address.toLowerCase() === to && asset.address.toLowerCase() !== context.agent.assetAddress.toLowerCase());
3047
+ 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);
3048
+ if (receipt && minted) return { protocol: "Vault", pool: receipt.name || receipt.symbol, tokenSymbol: context.asset };
3049
+ return void 0;
3050
+ }
3051
+ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses, destination) {
3052
+ const from = movement.fromAddress.toLowerCase();
3053
+ const to = movement.toAddress.toLowerCase();
3054
+ const owner = ownerAddress.toLowerCase();
3055
+ const agentWallet = wallet.walletAddress.toLowerCase();
3056
+ const baseAsset = agent.assetAddress.toLowerCase();
3057
+ if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
3058
+ return void 0;
3059
+ }
3060
+ let action;
3061
+ if (to === agentWallet && !vaultAddresses.has(from)) {
3062
+ action = "Top up";
3063
+ } else if (from === agentWallet && to === owner) {
3064
+ action = "Withdraw";
3065
+ } else if (from === agentWallet && destination) {
3066
+ action = "Deposit";
3067
+ }
3068
+ if (!action) return void 0;
3069
+ return {
3070
+ agent: "yieldseeker",
3071
+ action,
3072
+ ...action === "Deposit" && destination ? { positions: [{
3073
+ ...destination,
3074
+ amount: decimal(movement.assetAmount, YIELDSEEKER_ASSET_METADATA[asset].decimals, "historic position")
3075
+ }] } : {},
3076
+ date: movement.blockDate,
3077
+ oldApy: null,
3078
+ newApy: null,
3079
+ transactions: [
3080
+ {
3081
+ txHashes: [movement.transactionHash],
3082
+ chainId: agent.chainId,
3083
+ tokenSymbol: asset,
3084
+ amount: decimal(
3085
+ movement.assetAmount,
3086
+ YIELDSEEKER_ASSET_METADATA[asset].decimals,
3087
+ "historic position"
3088
+ )
3089
+ }
3090
+ ],
3091
+ rebalanceLog: []
3092
+ };
3093
+ }
3094
+ function mapYieldseekerHistory(contexts, options) {
3095
+ const entries = contexts.flatMap((context) => {
3096
+ const seenMovements = /* @__PURE__ */ new Set();
3097
+ const movements = (context.historic?.movements ?? []).filter((movement) => {
3098
+ const key2 = `${movement.chainId}:${movement.transactionHash.toLowerCase()}:${movement.logIndex}`;
3099
+ if (seenMovements.has(key2)) return false;
3100
+ seenMovements.add(key2);
3101
+ return true;
3102
+ });
3103
+ return [
3104
+ ...movements.map(
3105
+ (movement) => movementEntry(
3106
+ movement,
3107
+ context.wallet,
3108
+ context.agent,
3109
+ context.asset,
3110
+ options.ownerAddress,
3111
+ options.vaultAddresses,
3112
+ depositDestination(context, movement)
3113
+ )
3114
+ ),
3115
+ ...(context.actions ?? []).map(actionEntry)
3116
+ ].filter((entry) => entry !== void 0);
2747
3117
  });
2748
- debugLog("owney-sdk", "swap: order submitted, polling escrows");
2749
- try {
2750
- await runFusionOrder(deps.runner, {
2751
- orderHash: built.orderHash,
2752
- secrets,
2753
- ...onStage ? { onStage } : {}
3118
+ const grouped = /* @__PURE__ */ new Map();
3119
+ const ungrouped = [];
3120
+ for (const entry of entries) {
3121
+ const tx = entry.transactions[0];
3122
+ const hash = tx?.txHashes[0];
3123
+ if (!hash) {
3124
+ ungrouped.push(entry);
3125
+ continue;
3126
+ }
3127
+ const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}`;
3128
+ const previous = grouped.get(key2);
3129
+ if (!previous) {
3130
+ grouped.set(key2, entry);
3131
+ continue;
3132
+ }
3133
+ if (entry.action === "Deposit" && entry.positions?.length) {
3134
+ if (!previous.positions?.length) {
3135
+ grouped.set(key2, entry);
3136
+ continue;
3137
+ }
3138
+ previous.positions.push(...entry.positions);
3139
+ previous.transactions.push(...entry.transactions);
3140
+ }
3141
+ }
3142
+ 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));
3143
+ return {
3144
+ data: filtered.slice(0, options.limit),
3145
+ // v1 returns the whole action/movement collection and defines no cursor.
3146
+ // Report a terminal page so callers never loop over the same prefix.
3147
+ hasMore: false
3148
+ };
3149
+ }
3150
+ function mapYieldseekerProfile(address, contexts) {
3151
+ const protocols = /* @__PURE__ */ new Set();
3152
+ for (const context of contexts) {
3153
+ for (const current of context.positions) {
3154
+ if (current.yieldOption?.provider) {
3155
+ protocols.add(String(current.yieldOption.provider));
3156
+ }
3157
+ }
3158
+ }
3159
+ return {
3160
+ address,
3161
+ smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
3162
+ chains: contexts.length > 0 ? [8453] : [],
3163
+ hasActiveSessionKey: contexts.some(
3164
+ (context) => context.wallet.initializedDate != null
3165
+ ),
3166
+ protocols: [...protocols]
3167
+ };
3168
+ }
3169
+ function mapYieldseekerAgentApy(options, days) {
3170
+ const perAsset = {};
3171
+ const all = [];
3172
+ for (const entry of options) {
3173
+ const apys = entry.yieldOptions.map(
3174
+ (option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
3175
+ ).filter(Number.isFinite);
3176
+ if (apys.length === 0) continue;
3177
+ const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
3178
+ perAsset[entry.asset] = average;
3179
+ all.push(average);
3180
+ }
3181
+ return {
3182
+ averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
3183
+ detailedApys: { apyPerAsset: { 8453: perAsset } }
3184
+ };
3185
+ }
3186
+
3187
+ // src/agents/yieldseeker/yieldseeker.agent.ts
3188
+ var OWNEY_AGENT_NAME = "owney";
3189
+ var YIELDSEEKER_USERNAME_PREFIX = "owney_";
3190
+ var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
3191
+ var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
3192
+ var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
3193
+ function generateYieldseekerUsername() {
3194
+ const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
3195
+ return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
3196
+ }
3197
+ function isUsernameConflict(error) {
3198
+ if (!(error instanceof YieldseekerApiError)) return false;
3199
+ const code = error.providerCode.toUpperCase();
3200
+ return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
3201
+ }
3202
+ var YIELDSEEKER_AGENT_WALLET_ABI = [
3203
+ {
3204
+ type: "function",
3205
+ name: "withdrawAssetToUser",
3206
+ stateMutability: "nonpayable",
3207
+ inputs: [
3208
+ { name: "recipient", type: "address" },
3209
+ { name: "asset", type: "address" },
3210
+ { name: "amount", type: "uint256" }
3211
+ ],
3212
+ outputs: []
3213
+ },
3214
+ {
3215
+ type: "function",
3216
+ name: "withdrawAllAssetToUser",
3217
+ stateMutability: "nonpayable",
3218
+ inputs: [
3219
+ { name: "recipient", type: "address" },
3220
+ { name: "asset", type: "address" }
3221
+ ],
3222
+ outputs: []
3223
+ }
3224
+ ];
3225
+ function query(params) {
3226
+ const search = new URLSearchParams();
3227
+ for (const [key2, value] of Object.entries(params)) {
3228
+ if (value !== void 0) search.set(key2, String(value));
3229
+ }
3230
+ const encoded = search.toString();
3231
+ return encoded ? `?${encoded}` : "";
3232
+ }
3233
+ var YieldseekerAgent = class {
3234
+ id = "yieldseeker";
3235
+ balanceComposition = "tokens-plus-positions";
3236
+ supportedChainIds = [8453];
3237
+ supportedAssets = [
3238
+ {
3239
+ chainId: 8453,
3240
+ chain: "BASE",
3241
+ assets: [
3242
+ { symbol: "USDC", minDepositAmount: "10000000" },
3243
+ { symbol: "WETH", minDepositAmount: "1" }
3244
+ ]
3245
+ }
3246
+ ];
3247
+ api;
3248
+ auth;
3249
+ transactionExecutor;
3250
+ unwindReceiptWaiter;
3251
+ agentContexts = /* @__PURE__ */ new Map();
3252
+ users = /* @__PURE__ */ new Map();
3253
+ pendingAgents = /* @__PURE__ */ new Map();
3254
+ yieldOptions = /* @__PURE__ */ new Map();
3255
+ pendingYieldOptions = /* @__PURE__ */ new Map();
3256
+ constructor(owneyApiKey, options = {}) {
3257
+ this.api = new YieldseekerApiClient(
3258
+ owneyApiKey,
3259
+ options.baseUrl ?? getYieldseekerProxyBaseUrl(),
3260
+ options.fetchFn
3261
+ );
3262
+ this.auth = new YieldseekerAuth(options.auth);
3263
+ this.transactionExecutor = options.transactionExecutor;
3264
+ this.unwindReceiptWaiter = options.unwindReceiptWaiter;
3265
+ }
3266
+ async disconnect() {
3267
+ this.auth.clear();
3268
+ for (const key2 of this.users.keys()) {
3269
+ const [walletAddress, chainId] = key2.split(":");
3270
+ clearYieldseekerIdentity(walletAddress, Number(chainId));
3271
+ }
3272
+ this.users.clear();
3273
+ this.agentContexts.clear();
3274
+ this.pendingAgents.clear();
3275
+ }
3276
+ async activateAgent(state, chainId, asset) {
3277
+ this.assertChain(chainId);
3278
+ const targetAsset = asset ?? "USDC";
3279
+ this.assertAsset(targetAsset);
3280
+ await this.ensureAgent(state, chainId, targetAsset);
3281
+ }
3282
+ async deposit(state, chainId, amount, asset, depositCallback) {
3283
+ this.assertChain(chainId);
3284
+ this.assertAsset(asset);
3285
+ if (BigInt(amount) <= 0n) {
3286
+ throw new OwneyError(
3287
+ "DEPOSIT_AMOUNT_BELOW_MINIMUM",
3288
+ "Yieldseeker deposits must be greater than zero.",
3289
+ { amount, minDepositAmount: "1" },
3290
+ this.id
3291
+ );
3292
+ }
3293
+ const context = await this.ensureAgent(state, chainId, asset);
3294
+ let txHash;
3295
+ try {
3296
+ if (depositCallback) {
3297
+ provideDepositVerificationContext(depositCallback, {
3298
+ agentId: "yieldseeker",
3299
+ signature: await this.auth.getToken(state, chainId),
3300
+ userId: context.user.userId,
3301
+ yieldseekerAgentId: context.agent.agentId
3302
+ });
3303
+ txHash = await depositCallback(
3304
+ context.wallet.walletAddress,
3305
+ chainId,
3306
+ amount
3307
+ );
3308
+ await this.waitForReceipt(state, chainId, txHash);
3309
+ } else {
3310
+ txHash = await this.submitTransaction(state, chainId, {
3311
+ from: getAddress2(state.walletAddress),
3312
+ to: YIELDSEEKER_ASSET_METADATA[asset].address,
3313
+ data: encodeFunctionData({
3314
+ abi: erc20Abi,
3315
+ functionName: "transfer",
3316
+ args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
3317
+ }),
3318
+ value: "0",
3319
+ chainId
3320
+ });
3321
+ }
3322
+ } finally {
3323
+ await this.refreshSnapshotAfterMovement(
3324
+ state,
3325
+ chainId,
3326
+ context,
3327
+ "deposit"
3328
+ );
3329
+ }
3330
+ return {
3331
+ txHash,
3332
+ smartWallet: context.wallet.walletAddress,
3333
+ amount
3334
+ };
3335
+ }
3336
+ async withdraw(state, chainId, asset, amount) {
3337
+ this.assertChain(chainId);
3338
+ this.assertAsset(asset);
3339
+ if (amount !== void 0 && BigInt(amount) <= 0n) {
3340
+ throw new OwneyError(
3341
+ "WITHDRAW_FAILED",
3342
+ "Yieldseeker withdrawals must be greater than zero.",
3343
+ { amount },
3344
+ this.id
3345
+ );
3346
+ }
3347
+ const context = await this.findAgent(state, chainId, asset);
3348
+ if (!context) {
3349
+ throw new OwneyError(
3350
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3351
+ `No Yieldseeker ${asset} agent exists for this wallet.`,
3352
+ { asset, available: "0" },
3353
+ this.id
3354
+ );
3355
+ }
3356
+ try {
3357
+ const portfolio = await this.loadPortfolioContext(
3358
+ state,
3359
+ chainId,
3360
+ context
3361
+ );
3362
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3363
+ const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
3364
+ ([address]) => address.toLowerCase() === metadata.address.toLowerCase()
3365
+ );
3366
+ const idle = BigInt(idleEntry?.[1] ?? "0");
3367
+ const deployed = portfolio.positions.reduce(
3368
+ (total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
3369
+ 0n
3370
+ );
3371
+ const totalAvailable = idle + deployed;
3372
+ const requested = amount === void 0 ? totalAvailable : BigInt(amount);
3373
+ if (requested > totalAvailable) {
3374
+ throw new OwneyError(
3375
+ "WITHDRAW_INSUFFICIENT_BALANCE",
3376
+ `Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
3377
+ {
3378
+ asset,
3379
+ requested: requested.toString(),
3380
+ available: totalAvailable.toString()
3381
+ },
3382
+ this.id
3383
+ );
3384
+ }
3385
+ let remaining = requested > idle ? requested - idle : 0n;
3386
+ for (const position2 of portfolio.positions) {
3387
+ if (remaining === 0n) break;
3388
+ const available = BigInt(position2.withdrawableAssetsRaw);
3389
+ if (available <= 0n) continue;
3390
+ const assetsRaw = available < remaining ? available : remaining;
3391
+ const response = await this.walletRequest(
3392
+ state,
3393
+ chainId,
3394
+ this.agentPath(context, "withdraw-from-position"),
3395
+ {
3396
+ method: "POST",
3397
+ body: {
3398
+ chainId,
3399
+ vaultAddress: position2.yieldOption.address,
3400
+ assetsRaw: assetsRaw.toString()
3401
+ }
3402
+ }
3403
+ );
3404
+ if (!this.isTransactionHash(response?.transactionHash)) {
3405
+ throw this.invalidResponse("position withdrawal");
3406
+ }
3407
+ await this.waitForReceipt(state, chainId, response.transactionHash);
3408
+ remaining -= assetsRaw;
3409
+ }
3410
+ if (remaining > 0n) {
3411
+ throw this.invalidResponse("yield positions", {
3412
+ reason: "Withdrawable positions could not cover the request.",
3413
+ remaining: remaining.toString()
3414
+ });
3415
+ }
3416
+ const account = getAddress2(state.walletAddress);
3417
+ const txHash = await this.submitTransaction(state, chainId, {
3418
+ from: account,
3419
+ to: getAddress2(context.wallet.walletAddress),
3420
+ data: amount === void 0 ? encodeFunctionData({
3421
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
3422
+ functionName: "withdrawAllAssetToUser",
3423
+ args: [account, metadata.address]
3424
+ }) : encodeFunctionData({
3425
+ abi: YIELDSEEKER_AGENT_WALLET_ABI,
3426
+ functionName: "withdrawAssetToUser",
3427
+ args: [account, metadata.address, requested]
3428
+ }),
3429
+ value: "0",
3430
+ chainId
3431
+ });
3432
+ return {
3433
+ txHash,
3434
+ type: amount === void 0 ? "full" : "partial",
3435
+ amount: requested.toString()
3436
+ };
3437
+ } finally {
3438
+ await this.refreshSnapshotAfterMovement(
3439
+ state,
3440
+ chainId,
3441
+ context,
3442
+ "withdrawal"
3443
+ );
3444
+ }
3445
+ }
3446
+ async getBalances(state, chainId) {
3447
+ this.assertChain(chainId);
3448
+ return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
3449
+ }
3450
+ async getEarnings(state, chainId) {
3451
+ this.assertChain(chainId);
3452
+ return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
3453
+ }
3454
+ async getAccountApy(state, chainId, days, tokenSymbol) {
3455
+ this.assertChain(chainId);
3456
+ const asset = tokenSymbol?.toUpperCase();
3457
+ if (asset !== void 0) this.assertAsset(asset);
3458
+ const contexts = await this.loadPortfolio(state, chainId, {
3459
+ ...asset ? { asset } : {},
3460
+ historic: true
2754
3461
  });
2755
- } catch (error) {
2756
- if (error instanceof OwneyError && (error.code === "SWAP_ORDER_REFUNDED" || error.code === "SWAP_ORDER_CANCELLED")) {
2757
- clearOrder(built.orderHash);
3462
+ return mapYieldseekerApy(state.walletAddress, contexts, days);
3463
+ }
3464
+ async getHistory(state, chainId, options) {
3465
+ this.assertChain(chainId);
3466
+ const asset = options?.tokenSymbol?.toUpperCase();
3467
+ if (asset !== void 0) this.assertAsset(asset);
3468
+ const contexts = await this.loadPortfolio(state, chainId, {
3469
+ ...asset ? { asset } : {},
3470
+ historic: true,
3471
+ actions: true
3472
+ });
3473
+ const catalog = await Promise.all(
3474
+ [...new Set(contexts.map((context) => context.asset))].map(
3475
+ (contextAsset) => this.loadYieldOptions(contextAsset)
3476
+ )
3477
+ );
3478
+ const vaultAddresses = new Set(
3479
+ catalog.flat().filter(
3480
+ (yieldOption) => yieldOption.chainId === chainId && isAddress2(yieldOption.address)
3481
+ ).map((yieldOption) => yieldOption.address.toLowerCase())
3482
+ );
3483
+ return mapYieldseekerHistory(contexts, {
3484
+ limit: options?.limit ?? 10,
3485
+ ownerAddress: state.walletAddress,
3486
+ vaultAddresses,
3487
+ ...options?.fromDate ? { fromDate: options.fromDate } : {},
3488
+ ...options?.toDate ? { toDate: options.toDate } : {}
3489
+ });
3490
+ }
3491
+ async getUserProfile(state, chainId) {
3492
+ this.assertChain(chainId);
3493
+ return mapYieldseekerProfile(
3494
+ state.walletAddress,
3495
+ await this.loadPortfolio(state, chainId, {})
3496
+ );
3497
+ }
3498
+ async getAgentApy(days, options) {
3499
+ this.assertOptionalChain(options?.chainId);
3500
+ const requested = options?.tokenSymbol?.toUpperCase();
3501
+ if (requested !== void 0) this.assertAsset(requested);
3502
+ const assets = requested ? [requested] : ["USDC", "WETH"];
3503
+ const values = await Promise.all(
3504
+ assets.map(async (asset) => {
3505
+ return { asset, yieldOptions: await this.loadYieldOptions(asset) };
3506
+ })
3507
+ );
3508
+ return mapYieldseekerAgentApy(values, days);
3509
+ }
3510
+ async loadYieldOptions(asset) {
3511
+ const cached = this.yieldOptions.get(asset);
3512
+ if (cached && cached.expiresAt > Date.now()) return cached.value;
3513
+ const pending = this.pendingYieldOptions.get(asset);
3514
+ if (pending) return pending;
3515
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3516
+ const request = this.api.request(
3517
+ `/chains/8453/assets/${metadata.address}/yield-options`
3518
+ ).then((response) => {
3519
+ if (!Array.isArray(response?.yieldOptions)) {
3520
+ throw this.invalidResponse("yield options");
3521
+ }
3522
+ this.yieldOptions.set(asset, {
3523
+ expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
3524
+ value: response.yieldOptions
3525
+ });
3526
+ return response.yieldOptions;
3527
+ }).finally(() => this.pendingYieldOptions.delete(asset));
3528
+ this.pendingYieldOptions.set(asset, request);
3529
+ return request;
3530
+ }
3531
+ userKey(state, chainId) {
3532
+ return `${state.walletAddress.toLowerCase()}:${chainId}`;
3533
+ }
3534
+ contextKey(state, chainId, asset) {
3535
+ return `${this.userKey(state, chainId)}:${asset}`;
3536
+ }
3537
+ async resolveUser(state, chainId) {
3538
+ const key2 = this.userKey(state, chainId);
3539
+ const inMemory = this.users.get(key2);
3540
+ if (inMemory) return inMemory;
3541
+ const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
3542
+ if (persisted) {
3543
+ this.users.set(key2, persisted);
3544
+ return persisted;
3545
+ }
3546
+ const walletAddress = getAddress2(state.walletAddress);
3547
+ let user = null;
3548
+ try {
3549
+ const login = await this.providerRequest(
3550
+ state,
3551
+ chainId,
3552
+ "/users/login-with-wallet",
3553
+ { method: "POST", body: { walletAddress } }
3554
+ );
3555
+ user = login?.user ?? null;
3556
+ if (!user) {
3557
+ throw this.invalidResponse("wallet login", {
3558
+ reason: "A successful login returned no user."
3559
+ });
3560
+ }
3561
+ } catch (error) {
3562
+ if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
3563
+ if (error instanceof OwneyError) throw error;
3564
+ throw this.mapApiError(error);
3565
+ }
3566
+ let created;
3567
+ for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
3568
+ try {
3569
+ created = await this.providerRequest(
3570
+ state,
3571
+ chainId,
3572
+ "/users",
3573
+ {
3574
+ method: "POST",
3575
+ body: {
3576
+ walletAddress,
3577
+ username: generateYieldseekerUsername()
3578
+ }
3579
+ }
3580
+ );
3581
+ break;
3582
+ } catch (createError) {
3583
+ const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
3584
+ if (canRetry) continue;
3585
+ throw this.mapApiError(createError);
3586
+ }
3587
+ }
3588
+ user = created?.user ?? null;
3589
+ }
3590
+ if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
3591
+ throw this.invalidResponse("wallet identity");
3592
+ }
3593
+ const resolved = { userId: user.userId };
3594
+ this.users.set(key2, resolved);
3595
+ writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
3596
+ return resolved;
3597
+ }
3598
+ forgetUser(state, chainId) {
3599
+ this.users.delete(this.userKey(state, chainId));
3600
+ clearYieldseekerIdentity(state.walletAddress, chainId);
3601
+ }
3602
+ async ensureAgent(state, chainId, asset) {
3603
+ const key2 = this.contextKey(state, chainId, asset);
3604
+ const cached = this.agentContexts.get(key2);
3605
+ if (cached) return cached;
3606
+ const pending = this.pendingAgents.get(key2);
3607
+ if (pending) return pending;
3608
+ const request = this.resolveAgent(state, chainId, asset, true).then(
3609
+ async (context) => {
3610
+ if (!context) throw this.invalidResponse("agent creation");
3611
+ await this.deployAgent(state, chainId, context);
3612
+ this.agentContexts.set(key2, context);
3613
+ return context;
3614
+ }
3615
+ );
3616
+ this.pendingAgents.set(key2, request);
3617
+ try {
3618
+ return await request;
3619
+ } finally {
3620
+ this.pendingAgents.delete(key2);
3621
+ }
3622
+ }
3623
+ async findAgent(state, chainId, asset) {
3624
+ const key2 = this.contextKey(state, chainId, asset);
3625
+ const cached = this.agentContexts.get(key2);
3626
+ if (cached) return cached;
3627
+ const context = await this.resolveAgent(state, chainId, asset, false);
3628
+ if (context) this.agentContexts.set(key2, context);
3629
+ return context;
3630
+ }
3631
+ async resolveAgent(state, chainId, asset, createIfMissing) {
3632
+ const user = await this.resolveUser(state, chainId);
3633
+ const response = await this.walletRequest(
3634
+ state,
3635
+ chainId,
3636
+ `/users/${user.userId}/agents`
3637
+ );
3638
+ if (!Array.isArray(response?.agents)) {
3639
+ throw this.invalidResponse("agent list");
3640
+ }
3641
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3642
+ let agent = response.agents.find(
3643
+ (candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
3644
+ );
3645
+ if (!agent && createIfMissing) {
3646
+ const created = await this.walletRequest(
3647
+ state,
3648
+ chainId,
3649
+ `/users/${user.userId}/agents`,
3650
+ {
3651
+ method: "POST",
3652
+ body: {
3653
+ name: OWNEY_AGENT_NAME,
3654
+ emoji: "\u{1F989}",
3655
+ chainId,
3656
+ assetAddress: metadata.address,
3657
+ type: "vault",
3658
+ rulePreset: null
3659
+ }
3660
+ }
3661
+ );
3662
+ agent = created?.agent;
3663
+ }
3664
+ if (!agent) return null;
3665
+ this.assertAgent(agent);
3666
+ const walletResponse = await this.walletRequest(
3667
+ state,
3668
+ chainId,
3669
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3670
+ );
3671
+ if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
3672
+ throw this.invalidResponse("agent wallet");
3673
+ }
3674
+ return { user, agent, wallet: walletResponse.agentWallet, asset };
3675
+ }
3676
+ async loadPortfolio(state, chainId, options) {
3677
+ const user = await this.resolveUser(state, chainId);
3678
+ const response = await this.walletRequest(
3679
+ state,
3680
+ chainId,
3681
+ `/users/${user.userId}/agents`
3682
+ );
3683
+ if (!Array.isArray(response?.agents)) {
3684
+ throw this.invalidResponse("agent list");
3685
+ }
3686
+ const contexts = [];
3687
+ for (const agent of response.agents) {
3688
+ const asset = this.assetForAgent(agent);
3689
+ if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
3690
+ continue;
3691
+ }
3692
+ this.assertAgent(agent);
3693
+ const walletResponse = await this.walletRequest(
3694
+ state,
3695
+ chainId,
3696
+ `/users/${user.userId}/agents/${agent.agentId}/wallet`
3697
+ );
3698
+ if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
3699
+ throw this.invalidResponse("agent wallet");
3700
+ }
3701
+ const context = {
3702
+ user,
3703
+ agent,
3704
+ wallet: walletResponse.agentWallet,
3705
+ asset
3706
+ };
3707
+ this.agentContexts.set(this.contextKey(state, chainId, asset), context);
3708
+ contexts.push(context);
3709
+ }
3710
+ return Promise.all(
3711
+ contexts.map(
3712
+ (context) => this.loadPortfolioContext(state, chainId, context, options)
3713
+ )
3714
+ );
3715
+ }
3716
+ async loadPortfolioContext(state, chainId, context, options = {}) {
3717
+ const [snapshot, positions, historic, actions] = await Promise.all([
3718
+ this.walletRequest(
3719
+ state,
3720
+ chainId,
3721
+ `${this.agentPath(context, "snapshot")}${query({
3722
+ shouldOnlyUseRecentValue: true,
3723
+ shouldAllowStaleOnError: true
3724
+ })}`
3725
+ ),
3726
+ this.walletRequest(
3727
+ state,
3728
+ chainId,
3729
+ this.agentPath(context, "yield-positions")
3730
+ ),
3731
+ options.historic ? this.walletRequest(
3732
+ state,
3733
+ chainId,
3734
+ this.agentPath(context, "wallet/historic-position")
3735
+ ) : Promise.resolve(void 0),
3736
+ options.actions ? this.walletRequest(
3737
+ state,
3738
+ chainId,
3739
+ this.agentPath(context, "actions")
3740
+ ) : Promise.resolve(void 0)
3741
+ ]);
3742
+ if (!snapshot?.agentSnapshot) {
3743
+ throw this.invalidResponse("agent snapshot");
3744
+ }
3745
+ if (!Array.isArray(positions?.yieldPositions)) {
3746
+ throw this.invalidResponse("yield positions");
3747
+ }
3748
+ return {
3749
+ ...context,
3750
+ snapshot: snapshot.agentSnapshot,
3751
+ positions: positions.yieldPositions,
3752
+ ...historic?.position ? { historic: historic.position } : {},
3753
+ ...actions?.actions ? { actions: actions.actions } : {}
3754
+ };
3755
+ }
3756
+ async deployAgent(state, chainId, context) {
3757
+ if (context.wallet.initializedDate != null) return;
3758
+ const walletAddress = context.wallet.walletAddress.toLowerCase();
3759
+ const deployed = await this.walletRequest(
3760
+ state,
3761
+ chainId,
3762
+ this.agentPath(context, "deploy"),
3763
+ { method: "POST", body: {} }
3764
+ );
3765
+ if (!deployed?.agentWallet || !isAddress2(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
3766
+ throw this.invalidResponse("agent deployment", {
3767
+ reason: "Deploy did not return the expected Agent Wallet."
3768
+ });
3769
+ }
3770
+ context.wallet = deployed.agentWallet;
3771
+ }
3772
+ async refreshSnapshotAfterMovement(state, chainId, context, movement) {
3773
+ try {
3774
+ const response = await this.walletRequest(
3775
+ state,
3776
+ chainId,
3777
+ `${this.agentPath(context, "snapshot")}${query({
3778
+ shouldForceRefresh: true
3779
+ })}`
3780
+ );
3781
+ if (!response?.agentSnapshot) {
3782
+ throw this.invalidResponse("agent snapshot refresh");
3783
+ }
3784
+ } catch (error) {
3785
+ console.warn(
3786
+ `[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
3787
+ error
3788
+ );
3789
+ }
3790
+ }
3791
+ agentPath(context, suffix) {
3792
+ return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
3793
+ }
3794
+ async walletRequest(state, chainId, path, options = {}) {
3795
+ try {
3796
+ return await this.providerRequest(state, chainId, path, options);
3797
+ } catch (error) {
3798
+ throw this.mapApiError(error);
3799
+ }
3800
+ }
3801
+ async providerRequest(state, chainId, path, options = {}) {
3802
+ this.assertChain(chainId);
3803
+ const request = (signature2) => this.api.request(path, {
3804
+ ...options,
3805
+ signature: signature2
3806
+ });
3807
+ let signature = await this.auth.getToken(state, chainId);
3808
+ try {
3809
+ return await request(signature);
3810
+ } catch (error) {
3811
+ if (!(error instanceof YieldseekerApiError)) throw error;
3812
+ if (error.providerCode === "NO_USER") throw error;
3813
+ if (!error.isAuthenticationError) throw error;
3814
+ signature = await this.auth.refreshToken(state, chainId, signature);
3815
+ try {
3816
+ return await request(signature);
3817
+ } catch (retryError) {
3818
+ if (retryError instanceof YieldseekerApiError && retryError.isAuthenticationError) {
3819
+ this.forgetUser(state, chainId);
3820
+ }
3821
+ throw retryError;
3822
+ }
3823
+ }
3824
+ }
3825
+ mapApiError(error) {
3826
+ if (!(error instanceof YieldseekerApiError)) {
3827
+ return new OwneyError(
3828
+ "AGENT_API_ERROR",
3829
+ "Yieldseeker request failed.",
3830
+ { cause: error instanceof Error ? error.message : String(error) },
3831
+ this.id
3832
+ );
3833
+ }
3834
+ const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
3835
+ return new OwneyError(
3836
+ code,
3837
+ `Yieldseeker request failed: ${error.providerCode}.`,
3838
+ {
3839
+ statusCode: error.status,
3840
+ providerCode: error.providerCode,
3841
+ ...error.responseFields ? { fields: error.responseFields } : {}
3842
+ },
3843
+ this.id
3844
+ );
3845
+ }
3846
+ async submitTransaction(state, chainId, transaction) {
3847
+ if (this.transactionExecutor) {
3848
+ return this.transactionExecutor(state, chainId, transaction);
3849
+ }
3850
+ this.assertTransaction(transaction, state, chainId);
3851
+ const account = getAddress2(state.walletAddress);
3852
+ const walletClient = createWalletClient2({
3853
+ account,
3854
+ chain: base3,
3855
+ transport: custom2(state.provider)
3856
+ });
3857
+ const publicClient = createPublicClient3({
3858
+ chain: base3,
3859
+ transport: custom2(state.provider)
3860
+ });
3861
+ await ensureWalletOnChain(
3862
+ publicClient,
3863
+ walletClient,
3864
+ 8453
3865
+ );
3866
+ const hash = await walletClient.sendTransaction({
3867
+ account,
3868
+ chain: base3,
3869
+ to: getAddress2(transaction.to),
3870
+ data: transaction.data,
3871
+ value: BigInt(transaction.value)
3872
+ });
3873
+ const receipt = await publicClient.waitForTransactionReceipt({
3874
+ hash,
3875
+ confirmations: 1
3876
+ });
3877
+ if (receipt.status !== "success") {
3878
+ throw new OwneyError(
3879
+ "AGENT_TRANSACTION_REVERTED",
3880
+ `Yieldseeker transaction reverted (${hash}).`,
3881
+ { transactionHash: hash },
3882
+ this.id
3883
+ );
3884
+ }
3885
+ return hash;
3886
+ }
3887
+ async waitForReceipt(state, chainId, transactionHash) {
3888
+ if (this.unwindReceiptWaiter) {
3889
+ await this.unwindReceiptWaiter(state, chainId, transactionHash);
3890
+ return;
3891
+ }
3892
+ const publicClient = createPublicClient3({
3893
+ chain: base3,
3894
+ transport: custom2(state.provider)
3895
+ });
3896
+ const receipt = await publicClient.waitForTransactionReceipt({
3897
+ hash: transactionHash,
3898
+ confirmations: 1
3899
+ });
3900
+ if (receipt.status !== "success") {
3901
+ throw new OwneyError(
3902
+ "AGENT_TRANSACTION_REVERTED",
3903
+ `Yieldseeker transaction reverted (${transactionHash}).`,
3904
+ { transactionHash },
3905
+ this.id
3906
+ );
3907
+ }
3908
+ }
3909
+ assertTransaction(transaction, state, chainId) {
3910
+ 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)) {
3911
+ throw this.invalidResponse("transaction");
3912
+ }
3913
+ }
3914
+ assertAgent(agent) {
3915
+ if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress2(agent.assetAddress) || agent.chainId !== 8453) {
3916
+ throw this.invalidResponse("agent");
2758
3917
  }
2759
- throw error;
2760
3918
  }
2761
- clearOrder(built.orderHash);
2762
- return { orderHash: built.orderHash };
2763
- }
2764
-
2765
- // src/lib/swap/swap.arrival.ts
2766
- var DEFAULT_TIMEOUT_MS2 = 18e4;
2767
- var DEFAULT_POLL_MS2 = 4e3;
2768
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2769
- async function awaitWithdrawalArrival(options) {
2770
- const {
2771
- readBalance,
2772
- baseline,
2773
- timeoutMs = DEFAULT_TIMEOUT_MS2,
2774
- pollMs = DEFAULT_POLL_MS2
2775
- } = options;
2776
- const deadline = Date.now() + timeoutMs;
2777
- debugLog("owney-sdk", "withdraw: waiting for funds to land", {
2778
- baseline: baseline.toString(),
2779
- timeoutMs
2780
- });
2781
- let lastError;
2782
- for (; ; ) {
2783
- try {
2784
- const balance = await readBalance();
2785
- if (balance > baseline) {
2786
- const arrived = balance - baseline;
2787
- debugLog("owney-sdk", "withdraw: funds landed", {
2788
- arrived: arrived.toString()
2789
- });
2790
- return arrived;
3919
+ isOwneyAgent(agent) {
3920
+ return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
3921
+ }
3922
+ assetForAgent(agent) {
3923
+ for (const asset of ["USDC", "WETH"]) {
3924
+ if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
3925
+ return asset;
2791
3926
  }
2792
- } catch (error) {
2793
- lastError = error;
2794
- debugLog("owney-sdk", "withdraw: balance read failed, retrying", {
2795
- message: error instanceof Error ? error.message : String(error)
2796
- });
2797
3927
  }
2798
- if (Date.now() >= deadline) {
3928
+ return null;
3929
+ }
3930
+ isTransactionHash(value) {
3931
+ return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
3932
+ }
3933
+ assertChain(chainId) {
3934
+ if (chainId !== 8453) {
3935
+ throw new OwneyError(
3936
+ "CHAIN_UNSUPPORTED",
3937
+ `Yieldseeker does not support chain ${chainId}.`,
3938
+ { chainId, supportedChainIds: [8453] },
3939
+ this.id
3940
+ );
3941
+ }
3942
+ }
3943
+ assertOptionalChain(chainId) {
3944
+ if (chainId !== void 0) this.assertChain(chainId);
3945
+ }
3946
+ assertAsset(asset) {
3947
+ if (asset !== "USDC" && asset !== "WETH") {
2799
3948
  throw new OwneyError(
2800
- "WITHDRAW_ARRIVAL_TIMEOUT",
2801
- "The withdrawal was accepted but the funds had not arrived in time to swap them. They are on their way to your wallet in the original asset.",
3949
+ "ASSET_UNSUPPORTED",
3950
+ `Yieldseeker does not support asset ${asset} in the Owney rollout.`,
2802
3951
  {
2803
- baseline: baseline.toString(),
2804
- waitedMs: timeoutMs,
2805
- ...lastError ? {
2806
- lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
2807
- } : {}
2808
- }
3952
+ asset,
3953
+ supportedAssets: ["USDC", "WETH"],
3954
+ providerAlsoAdvertises: ["cbBTC"]
3955
+ },
3956
+ this.id
2809
3957
  );
2810
3958
  }
2811
- await sleep(pollMs);
2812
3959
  }
3960
+ invalidResponse(operation, details = {}) {
3961
+ return new OwneyError(
3962
+ "AGENT_INVALID_RESPONSE",
3963
+ `Yieldseeker returned an invalid ${operation} response.`,
3964
+ details,
3965
+ this.id
3966
+ );
3967
+ }
3968
+ };
3969
+
3970
+ // src/lib/routing-api.ts
3971
+ var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3972
+ async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
3973
+ const url = `${baseUrl}/api/v1/agent/org-config`;
3974
+ try {
3975
+ const res = await fetch(url, {
3976
+ method: "GET",
3977
+ headers: {
3978
+ "Content-Type": "application/json",
3979
+ "x-owney-api-key": `${apiKey}`
3980
+ }
3981
+ });
3982
+ if (!res.ok) {
3983
+ if (res.status !== 404) {
3984
+ console.warn(
3985
+ `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
3986
+ );
3987
+ }
3988
+ return null;
3989
+ }
3990
+ const json = await res.json();
3991
+ const policy = json.success ? json.data ?? null : null;
3992
+ debugLog(
3993
+ "owney-sdk",
3994
+ policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
3995
+ policy ?? void 0
3996
+ );
3997
+ return policy;
3998
+ } catch (error) {
3999
+ console.warn(
4000
+ "[owney-sdk] Could not read org agent config (non-fatal):",
4001
+ error instanceof Error ? error.message : String(error)
4002
+ );
4003
+ return null;
4004
+ }
4005
+ }
4006
+ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
4007
+ const url = `${baseUrl}/api/v1/agent/keys`;
4008
+ const res = await fetch(url, {
4009
+ method: "GET",
4010
+ headers: {
4011
+ "Content-Type": "application/json",
4012
+ "x-owney-api-key": `${apiKey}`
4013
+ }
4014
+ });
4015
+ if (!res.ok) {
4016
+ const text = await res.text().catch(() => "");
4017
+ throw new OwneyError(
4018
+ "API_ROUTING_ERROR",
4019
+ `Routing API error ${res.status}: ${text}`,
4020
+ { statusCode: res.status, responseBody: text }
4021
+ );
4022
+ }
4023
+ const json = await res.json();
4024
+ if (!json.success) {
4025
+ throw new OwneyError(
4026
+ "API_ROUTING_FAILED",
4027
+ `Routing API request failed: ${json.message}`,
4028
+ { message: json.message }
4029
+ );
4030
+ }
4031
+ return json.data;
2813
4032
  }
2814
4033
 
2815
4034
  // src/lib/health-report.ts
2816
- var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2817
- async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
4035
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
4036
+ async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
2818
4037
  try {
2819
4038
  await fetch(`${baseUrl}/api/v1/agent/health-report`, {
2820
4039
  method: "POST",
@@ -2854,8 +4073,29 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
2854
4073
  const tokenBalance = agentBalance?.tokens.find(
2855
4074
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
2856
4075
  );
2857
- if (!tokenBalance) return { agent, balance: 0n };
2858
- return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
4076
+ let balance = tokenBalance ? parseUnits(tokenBalance.amount, decimals) : 0n;
4077
+ if (agent.balanceComposition === "tokens-plus-positions") {
4078
+ const chainNameById = {
4079
+ 1: "ETHEREUM",
4080
+ 8453: "BASE",
4081
+ 42161: "ARBITRUM"
4082
+ };
4083
+ const targetChain = chainNameById[chainId];
4084
+ for (const position2 of agentBalance?.positions ?? []) {
4085
+ const positionChain = position2.chain.trim().toUpperCase();
4086
+ const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
4087
+ if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
4088
+ if (position2.amountRaw !== void 0) {
4089
+ try {
4090
+ balance += BigInt(position2.amountRaw);
4091
+ continue;
4092
+ } catch {
4093
+ }
4094
+ }
4095
+ balance += parseUnits(position2.amount, decimals);
4096
+ }
4097
+ }
4098
+ return { agent, balance };
2859
4099
  });
2860
4100
  }
2861
4101
  function planProportionalShares(balances, requested, totalAvailable) {
@@ -2881,7 +4121,9 @@ function planProportionalShares(balances, requested, totalAvailable) {
2881
4121
  return plans;
2882
4122
  }
2883
4123
  function planDisabledDrain(disabled, requested) {
2884
- const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
4124
+ const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
4125
+ (a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
4126
+ );
2885
4127
  const plans = [];
2886
4128
  let remaining = requested;
2887
4129
  for (const { agent, balance } of sorted) {
@@ -2932,6 +4174,13 @@ function balanceForApyScope(balance, chainId, tokenSymbol) {
2932
4174
  return Number.isFinite(total) && total > 0 ? total : 0;
2933
4175
  }
2934
4176
  const normalizedToken = tokenSymbol.toUpperCase();
4177
+ const snapshots = balance.assetBalances?.filter(
4178
+ (token) => Number(token.chainId) === chainId && String(token.asset).toUpperCase() === normalizedToken
4179
+ );
4180
+ if (snapshots?.length) {
4181
+ const amount = snapshots.reduce((sum, token) => sum + Number(token.amount), 0);
4182
+ if (Number.isFinite(amount)) return Math.max(0, amount);
4183
+ }
2935
4184
  return balance.tokens.reduce((total, token) => {
2936
4185
  if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
2937
4186
  return total;
@@ -3003,329 +4252,312 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
3003
4252
 
3004
4253
  // src/client.ts
3005
4254
  import {
3006
- createPublicClient as createPublicClient2,
3007
- createWalletClient,
3008
- custom,
3009
- erc20Abi as erc20Abi2
4255
+ createPublicClient as createPublicClient4,
4256
+ createWalletClient as createWalletClient3,
4257
+ custom as custom3
3010
4258
  } from "viem";
3011
- import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
4259
+ import { base as base4, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
3012
4260
 
3013
- // src/lib/transfer-auth.ts
3014
- import { bytesToHex as bytesToHex2 } from "viem";
3015
- var ERC20_META_ABI = [
3016
- { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
3017
- { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
3018
- ];
3019
- function buildTransferWithAuthorizationTypedData(input) {
4261
+ // src/lib/sponsored-token-batch.ts
4262
+ import {
4263
+ isAddressEqual,
4264
+ keccak256,
4265
+ toBytes
4266
+ } from "viem";
4267
+
4268
+ // src/lib/permit2-batch.ts
4269
+ import { parseAbi as parseAbi2, hashStruct } from "viem";
4270
+ var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
4271
+ var PERMIT_BATCH_TYPES = {
4272
+ PermitBatchWitnessTransferFrom: [
4273
+ { name: "permitted", type: "TokenPermissions[]" },
4274
+ { name: "spender", type: "address" },
4275
+ { name: "nonce", type: "uint256" },
4276
+ { name: "deadline", type: "uint256" },
4277
+ { name: "witness", type: "Deposit" }
4278
+ ],
4279
+ Deposit: [{ name: "recipients", type: "address[]" }],
4280
+ TokenPermissions: [
4281
+ { name: "token", type: "address" },
4282
+ { name: "amount", type: "uint256" }
4283
+ ]
4284
+ };
4285
+ var PERMIT2_BATCH_ABI = parseAbi2([
4286
+ "struct TokenPermissions { address token; uint256 amount; }",
4287
+ "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
4288
+ "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
4289
+ "function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
4290
+ "function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
4291
+ ]);
4292
+ function batchPermit(b) {
3020
4293
  return {
3021
- domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
3022
- types: {
3023
- TransferWithAuthorization: [
3024
- { name: "from", type: "address" },
3025
- { name: "to", type: "address" },
3026
- { name: "value", type: "uint256" },
3027
- { name: "validAfter", type: "uint256" },
3028
- { name: "validBefore", type: "uint256" },
3029
- { name: "nonce", type: "bytes32" }
3030
- ]
3031
- },
3032
- primaryType: "TransferWithAuthorization",
3033
- message: input.message
4294
+ permitted: b.transfers.map((t) => ({
4295
+ token: b.token,
4296
+ amount: BigInt(t.amount)
4297
+ })),
4298
+ nonce: BigInt(b.nonce),
4299
+ deadline: BigInt(b.deadline)
3034
4300
  };
3035
4301
  }
3036
- async function readTokenMeta(publicClient, token) {
3037
- const [tokenName, tokenVersion] = await Promise.all([
3038
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
3039
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
3040
- ]);
3041
- return { tokenName, tokenVersion };
3042
- }
3043
- function randomAuthNonce() {
3044
- const bytes = new Uint8Array(32);
3045
- globalThis.crypto.getRandomValues(bytes);
3046
- return bytesToHex2(bytes);
4302
+ function batchTypedData(b, spender) {
4303
+ return {
4304
+ domain: {
4305
+ name: "Permit2",
4306
+ chainId: b.chainId,
4307
+ verifyingContract: BATCH_PERMIT2_ADDRESS
4308
+ },
4309
+ types: PERMIT_BATCH_TYPES,
4310
+ primaryType: "PermitBatchWitnessTransferFrom",
4311
+ message: {
4312
+ ...batchPermit(b),
4313
+ spender,
4314
+ witness: { recipients: b.transfers.map((t) => t.to) }
4315
+ }
4316
+ };
3047
4317
  }
3048
4318
 
3049
- // src/lib/sponsor-client.ts
3050
- var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
3051
- async function postSponsorTransferAuth(input) {
3052
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
3053
- let res;
3054
- try {
3055
- res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
3056
- method: "POST",
3057
- headers: {
3058
- "content-type": "application/json",
3059
- "x-owney-api-key": input.apiKey
3060
- },
3061
- body: JSON.stringify(input.body)
3062
- });
3063
- } catch (networkError) {
3064
- throw new OwneyError(
3065
- "SPONSOR_REQUEST_FAILED",
3066
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
3067
- { cause: String(networkError) }
3068
- );
3069
- }
3070
- const text = await res.text();
3071
- let parsed = null;
3072
- try {
3073
- parsed = JSON.parse(text);
3074
- } catch {
3075
- }
3076
- if (!res.ok || !parsed?.success || !parsed.data) {
3077
- throw new OwneyError(
3078
- "SPONSOR_REQUEST_FAILED",
3079
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
3080
- {
3081
- statusCode: res.status,
3082
- responseBody: text.slice(0, 500),
3083
- // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
3084
- // before broadcast, so it is safe to fall back to a user-paid deposit.
3085
- safeToFallback: res.status === 503
3086
- }
3087
- );
3088
- }
3089
- return parsed.data;
4319
+ // src/lib/sponsored-token-batch.ts
4320
+ var memory = /* @__PURE__ */ new Map();
4321
+ var inflight = /* @__PURE__ */ new Map();
4322
+ var planOf = (transfers) => JSON.stringify(
4323
+ transfers.map((t) => ({
4324
+ to: t.to.toLowerCase(),
4325
+ amount: BigInt(t.amount).toString()
4326
+ }))
4327
+ );
4328
+ function read(key2) {
4329
+ return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
3090
4330
  }
3091
- async function postSponsorPermit2Transfer(input) {
3092
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
3093
- let res;
3094
- try {
3095
- res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
3096
- method: "POST",
3097
- headers: {
3098
- "content-type": "application/json",
3099
- "x-owney-api-key": input.apiKey
3100
- },
3101
- body: JSON.stringify(input.body)
3102
- });
3103
- } catch (networkError) {
3104
- throw new OwneyError(
3105
- "SPONSOR_REQUEST_FAILED",
3106
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
3107
- { cause: String(networkError), safeToFallback: false }
3108
- );
3109
- }
3110
- const text = await res.text();
3111
- let parsed = null;
3112
- try {
3113
- parsed = JSON.parse(text);
3114
- } catch {
3115
- }
3116
- if (!res.ok || !parsed?.success || !parsed.data) {
3117
- throw new OwneyError(
3118
- "SPONSOR_REQUEST_FAILED",
3119
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
3120
- {
3121
- statusCode: res.status,
3122
- responseBody: text.slice(0, 500),
3123
- safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
3124
- }
3125
- );
3126
- }
3127
- return parsed.data;
4331
+ function save(key2, body) {
4332
+ const value = JSON.stringify({
4333
+ ...body,
4334
+ transfers: body.transfers.map(({ to, amount }) => ({ to, amount }))
4335
+ });
4336
+ if (typeof window === "undefined") memory.set(key2, value);
4337
+ else window.localStorage.setItem(key2, value);
3128
4338
  }
3129
- async function getSponsorRelayerAddress(input) {
3130
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
3131
- let res;
3132
- try {
3133
- res = await fetch(
3134
- `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
3135
- {
3136
- headers: { "x-owney-api-key": input.apiKey }
3137
- }
3138
- );
3139
- } catch (networkError) {
3140
- throw new OwneyError(
3141
- "SPONSOR_REQUEST_FAILED",
3142
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
3143
- { cause: String(networkError), safeToFallback: true }
3144
- );
3145
- }
3146
- const text = await res.text();
3147
- let parsed = null;
3148
- try {
3149
- parsed = JSON.parse(text);
3150
- } catch {
3151
- }
3152
- if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
3153
- throw new OwneyError(
3154
- "SPONSOR_REQUEST_FAILED",
3155
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
3156
- {
3157
- statusCode: res.status,
3158
- responseBody: text.slice(0, 500),
3159
- safeToFallback: true
3160
- }
3161
- );
3162
- }
3163
- return parsed.data.relayer;
4339
+ function clear(key2) {
4340
+ if (typeof window === "undefined") memory.delete(key2);
4341
+ else window.localStorage.removeItem(key2);
3164
4342
  }
3165
-
3166
- // src/lib/sponsored-deposit.ts
3167
- var AUTH_WINDOW_SECONDS = 15 * 60;
3168
- function makeSponsoredDepositCallback(deps) {
3169
- const post = deps.httpPost ?? postSponsorTransferAuth;
3170
- return async (smartWallet, chainId, amount) => {
3171
- const cid = chainId;
3172
- const token = deps.tokenAddressByChain[cid];
3173
- if (!token) {
3174
- throw new OwneyError(
3175
- "CHAIN_UNSUPPORTED",
3176
- `No sponsored token configured for chain ${chainId}`
4343
+ function sponsorTokenBatch(i) {
4344
+ const key2 = `owney.token-batch.v1:${keccak256(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
4345
+ const plan = planOf(i.transfers);
4346
+ const active = inflight.get(key2);
4347
+ if (active) {
4348
+ if (active.plan !== plan)
4349
+ return Promise.reject(
4350
+ new Error(
4351
+ "A token deposit is already in progress. Wait for its result before depositing again."
4352
+ )
3177
4353
  );
3178
- }
3179
- const pub = deps.getPublicClient(cid);
3180
- const wallet = deps.getWalletClient(cid);
3181
- await ensureWalletOnChain(pub, wallet, cid);
4354
+ return active.promise;
4355
+ }
4356
+ const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
4357
+ inflight.set(key2, { plan, promise });
4358
+ return promise;
4359
+ }
4360
+ async function execute(i, key2, plan) {
4361
+ if (!i.transfers.length || i.transfers.length > 16 || i.transfers.some(
4362
+ (t) => BigInt(t.amount) <= 0n || BigInt(t.amount) >= 1n << 256n
4363
+ ) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
4364
+ throw new Error("Invalid token deposit shares.");
4365
+ const send = async (initial) => {
4366
+ let body = initial;
4367
+ save(key2, body);
3182
4368
  try {
3183
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
3184
- if (balance < BigInt(amount)) {
3185
- throw new OwneyError(
3186
- "DEPOSIT_INSUFFICIENT_BALANCE",
3187
- "Insufficient balance for this deposit.",
3188
- { token, chainId: cid, balance: balance.toString(), amount }
3189
- );
4369
+ if (!body.serializedTransaction) {
4370
+ const prepared = await postSponsorBatchTransfer({
4371
+ apiKey: i.apiKey,
4372
+ baseUrl: i.baseUrl,
4373
+ body
4374
+ });
4375
+ if (!prepared.serializedTransaction || keccak256(prepared.serializedTransaction) !== prepared.txHash)
4376
+ throw new Error(
4377
+ "Sponsorship API did not return a valid prepared transaction."
4378
+ );
4379
+ body = {
4380
+ ...body,
4381
+ serializedTransaction: prepared.serializedTransaction
4382
+ };
4383
+ save(key2, body);
3190
4384
  }
3191
- } catch (err) {
3192
- if (err instanceof OwneyError) throw err;
3193
- console.warn(
3194
- "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
3195
- err instanceof Error ? err.message : String(err)
3196
- );
4385
+ const result = await postSponsorBatchTransfer({
4386
+ apiKey: i.apiKey,
4387
+ baseUrl: i.baseUrl,
4388
+ body
4389
+ });
4390
+ if (result.txHash !== keccak256(body.serializedTransaction))
4391
+ throw new Error(
4392
+ "Sponsorship receipt does not match the pending transaction."
4393
+ );
4394
+ clear(key2);
4395
+ return result.txHash;
4396
+ } catch (error) {
4397
+ if (!body.serializedTransaction || error instanceof OwneyError && error.details?.notSubmitted === true)
4398
+ clear(key2);
4399
+ throw error;
3197
4400
  }
3198
- const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
3199
- const validAfter = 0n;
3200
- const validBefore = BigInt(
3201
- Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
3202
- );
3203
- const nonce = randomAuthNonce();
3204
- const typedData = buildTransferWithAuthorizationTypedData({
3205
- token,
3206
- chainId: cid,
3207
- tokenName,
3208
- tokenVersion,
3209
- message: {
3210
- from: deps.ownerAddress,
3211
- to: smartWallet,
3212
- value: BigInt(amount),
3213
- validAfter,
3214
- validBefore,
3215
- nonce
3216
- }
3217
- });
3218
- const authSignature = await wallet.signTypedData({
3219
- account: deps.ownerAddress,
3220
- ...typedData
3221
- });
3222
- deps.onApproved?.();
3223
- const result = await post({
3224
- baseUrl: deps.baseUrl,
3225
- apiKey: deps.apiKey,
3226
- body: {
3227
- chainId: cid,
3228
- token,
3229
- from: deps.ownerAddress,
3230
- to: smartWallet,
3231
- value: amount,
3232
- validAfter: validAfter.toString(),
3233
- validBefore: validBefore.toString(),
3234
- nonce,
3235
- authSignature,
3236
- tokenName,
3237
- tokenVersion
3238
- }
3239
- });
3240
- return result.txHash;
3241
4401
  };
4402
+ const saved = read(key2);
4403
+ if (saved) {
4404
+ const previous = JSON.parse(saved);
4405
+ if (previous.chainId !== i.chainId || !isAddressEqual(previous.from, i.owner) || !isAddressEqual(previous.token, i.token) || planOf(previous.transfers) !== plan)
4406
+ throw new Error(
4407
+ "Retry the previous token deposit and agent split first to reconcile its status."
4408
+ );
4409
+ i.onApproved?.();
4410
+ return send({ ...previous, transfers: i.transfers });
4411
+ }
4412
+ const total = i.transfers.reduce((sum, t) => sum + BigInt(t.amount), 0n);
4413
+ const [balance, allowance] = await Promise.all([
4414
+ readErc20Balance(i.pub, i.token, i.owner),
4415
+ readPermit2Allowance(i.pub, i.token, i.owner)
4416
+ ]);
4417
+ if (balance < total)
4418
+ throw new OwneyError(
4419
+ "DEPOSIT_INSUFFICIENT_BALANCE",
4420
+ "Insufficient token balance for this deposit."
4421
+ );
4422
+ if (allowance < total)
4423
+ throw new OwneyError(
4424
+ "PERMIT2_APPROVAL_REQUIRED",
4425
+ "token deposits need a one-time Permit2 approval."
4426
+ );
4427
+ const relayer = await getSponsorRelayerAddress({
4428
+ apiKey: i.apiKey,
4429
+ baseUrl: i.baseUrl,
4430
+ chainId: i.chainId
4431
+ });
4432
+ const now = (await i.pub.getBlock()).timestamp;
4433
+ const unsigned = {
4434
+ chainId: i.chainId,
4435
+ token: i.token,
4436
+ from: i.owner,
4437
+ transfers: i.transfers,
4438
+ nonce: randomPermit2Nonce().toString(),
4439
+ deadline: (now + 900n).toString()
4440
+ };
4441
+ const signature = await i.wallet.signTypedData({
4442
+ account: i.owner,
4443
+ ...batchTypedData(unsigned, relayer)
4444
+ });
4445
+ i.onApproved?.();
4446
+ return send({ ...unsigned, signature });
3242
4447
  }
3243
4448
 
3244
- // src/lib/sponsored-weth-deposit.ts
3245
- var PERMIT_WINDOW_SECONDS = 15 * 60;
3246
- function makeSponsoredWethCallback(deps) {
3247
- const get = deps.httpGet ?? getSponsorRelayerAddress;
3248
- const post = deps.httpPost ?? postSponsorPermit2Transfer;
3249
- return async (smartWallet, chainId, amount) => {
3250
- const cid = chainId;
3251
- const token = deps.tokenAddressByChain[cid];
3252
- if (!token) {
4449
+ // src/lib/sponsored-token-deposit.ts
4450
+ function makeSponsoredTokenCallback(deps) {
4451
+ const batch = async (chainId, transfers) => {
4452
+ if (chainId !== 8453 && chainId !== 42161 && chainId !== 1)
3253
4453
  throw new OwneyError(
3254
4454
  "CHAIN_UNSUPPORTED",
3255
- `No sponsored WETH configured for chain ${chainId}`
3256
- );
3257
- }
3258
- const amountWei = BigInt(amount);
3259
- const pub = deps.getPublicClient(cid);
3260
- const wallet = deps.getWalletClient(cid);
3261
- await ensureWalletOnChain(pub, wallet, cid);
3262
- try {
3263
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
3264
- if (balance < amountWei) {
3265
- throw new OwneyError(
3266
- "DEPOSIT_INSUFFICIENT_BALANCE",
3267
- "Insufficient WETH balance for this deposit.",
3268
- { token, chainId: cid, balance: balance.toString(), amount }
3269
- );
3270
- }
3271
- } catch (err) {
3272
- if (err instanceof OwneyError) throw err;
3273
- console.warn(
3274
- "[owney-sdk] WETH balance pre-check failed (non-fatal):",
3275
- err instanceof Error ? err.message : String(err)
4455
+ `No sponsored token configured for chain ${chainId}`
3276
4456
  );
3277
- }
3278
- const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
3279
- if (allowance < amountWei) {
4457
+ const token = deps.tokenAddressByChain[chainId];
4458
+ if (!token)
3280
4459
  throw new OwneyError(
3281
- "PERMIT2_APPROVAL_REQUIRED",
3282
- "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
3283
- { token, chainId: cid, allowance: allowance.toString(), amount }
4460
+ "CHAIN_UNSUPPORTED",
4461
+ `No sponsored token configured for chain ${chainId}`
3284
4462
  );
3285
- }
3286
- const relayer = await get({
3287
- baseUrl: deps.baseUrl,
4463
+ const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
4464
+ await ensureWalletOnChain(pub, wallet, chainId);
4465
+ return sponsorTokenBatch({
3288
4466
  apiKey: deps.apiKey,
3289
- chainId: cid
3290
- });
3291
- const nonce = randomPermit2Nonce();
3292
- const deadline = BigInt(
3293
- Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
3294
- );
3295
- const typedData = buildPermitTransferFromTypedData({
3296
- chainId: cid,
3297
- message: {
3298
- permitted: { token, amount: amountWei },
3299
- spender: relayer,
3300
- nonce,
3301
- deadline
3302
- }
3303
- });
3304
- const signature = await wallet.signTypedData({
3305
- account: deps.ownerAddress,
3306
- ...typedData
3307
- });
3308
- deps.onApproved?.();
3309
- const result = await post({
3310
4467
  baseUrl: deps.baseUrl,
3311
- apiKey: deps.apiKey,
3312
- body: {
3313
- chainId: cid,
3314
- token,
3315
- from: deps.ownerAddress,
3316
- to: smartWallet,
3317
- amount,
3318
- nonce: nonce.toString(),
3319
- deadline: deadline.toString(),
3320
- signature
3321
- }
4468
+ owner: deps.ownerAddress,
4469
+ token,
4470
+ chainId,
4471
+ transfers,
4472
+ pub,
4473
+ wallet,
4474
+ onApproved: deps.onApproved
3322
4475
  });
3323
- return result.txHash;
3324
4476
  };
4477
+ const callback = makeVerificationAwareDepositCallback(
4478
+ (to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
4479
+ );
4480
+ registerDepositBatch(callback, batch);
4481
+ return callback;
4482
+ }
4483
+
4484
+ // src/lib/agent-deposit-batch.ts
4485
+ function deferred() {
4486
+ let resolve, reject;
4487
+ const promise = new Promise((yes, no) => {
4488
+ resolve = yes;
4489
+ reject = no;
4490
+ });
4491
+ void promise.catch(() => {
4492
+ });
4493
+ return { promise, resolve, reject };
4494
+ }
4495
+ async function runAgentDepositBatch(chainId, legs, transfer) {
4496
+ const funding = deferred();
4497
+ const tasks = [];
4498
+ const transfers = [];
4499
+ try {
4500
+ for (const leg of legs) {
4501
+ const ready = deferred();
4502
+ let entered = false;
4503
+ const callback = makeVerificationAwareDepositCallback(
4504
+ (to, cid, amount, verification) => {
4505
+ if (entered || cid !== chainId || BigInt(amount) !== BigInt(leg.amount)) {
4506
+ const error = new Error(
4507
+ "Agent changed its prepared deposit share."
4508
+ );
4509
+ ready.reject(error);
4510
+ throw error;
4511
+ }
4512
+ entered = true;
4513
+ ready.resolve(toBatchTransfer(to, amount, verification));
4514
+ return funding.promise;
4515
+ }
4516
+ );
4517
+ const task = Promise.resolve().then(() => leg.run(callback));
4518
+ tasks.push(task);
4519
+ void task.then(
4520
+ () => {
4521
+ if (!entered)
4522
+ ready.reject(
4523
+ new Error("Agent did not prepare a deposit transfer.")
4524
+ );
4525
+ },
4526
+ (error) => ready.reject(error)
4527
+ );
4528
+ transfers.push(await ready.promise);
4529
+ }
4530
+ const txHash = await transfer(chainId, transfers);
4531
+ funding.resolve(txHash);
4532
+ const settled = await Promise.allSettled(tasks);
4533
+ const agentResults = {};
4534
+ const failures = [];
4535
+ for (const [index, result] of settled.entries()) {
4536
+ if (result.status === "fulfilled")
4537
+ agentResults[legs[index].id] = result.value;
4538
+ else failures.push(legs[index].id);
4539
+ }
4540
+ if (failures.length)
4541
+ throw new OwneyError(
4542
+ "DEPOSIT_PARTIAL_FAILURE",
4543
+ "The deposit was sent to all agents, but some agent updates could not be confirmed. Check activity before depositing again.",
4544
+ {
4545
+ txHash,
4546
+ fundsSubmitted: true,
4547
+ agentResults,
4548
+ failedAgentIds: failures
4549
+ }
4550
+ );
4551
+ return { agentResults };
4552
+ } catch (error) {
4553
+ funding.reject(error);
4554
+ await Promise.allSettled(tasks);
4555
+ throw error;
4556
+ }
3325
4557
  }
3326
4558
 
3327
4559
  // src/lib/sponsored-calls-deposit.ts
3328
- import { encodeFunctionData, erc20Abi, toHex as toHex2 } from "viem";
4560
+ import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi2, toHex } from "viem";
3329
4561
  var DEFAULT_POLL_INTERVAL_MS = 1500;
3330
4562
  var DEFAULT_MAX_POLLS = 30;
3331
4563
  async function paymasterSupported(provider, owner, chainId) {
@@ -3333,7 +4565,7 @@ async function paymasterSupported(provider, owner, chainId) {
3333
4565
  method: "wallet_getCapabilities",
3334
4566
  params: [owner]
3335
4567
  });
3336
- const forChain = caps?.[toHex2(chainId)] ?? caps?.[String(chainId)];
4568
+ const forChain = caps?.[toHex(chainId)] ?? caps?.[String(chainId)];
3337
4569
  return Boolean(forChain?.paymasterService?.supported);
3338
4570
  }
3339
4571
  function makeSponsoredCallsCallback(deps) {
@@ -3351,7 +4583,7 @@ function makeSponsoredCallsCallback(deps) {
3351
4583
  }
3352
4584
  return new URL(configured, origin).toString();
3353
4585
  };
3354
- return async (smartWallet, chainId, amount) => {
4586
+ const batch = async (chainId, transfers) => {
3355
4587
  const cid = chainId;
3356
4588
  const token = deps.tokenAddressByChain[cid];
3357
4589
  if (!token) {
@@ -3367,22 +4599,53 @@ function makeSponsoredCallsCallback(deps) {
3367
4599
  { chainId }
3368
4600
  );
3369
4601
  }
3370
- const data = encodeFunctionData({
3371
- abi: erc20Abi,
3372
- functionName: "transfer",
3373
- args: [smartWallet, BigInt(amount)]
3374
- });
4602
+ const calls = transfers.map((transfer) => ({
4603
+ to: token,
4604
+ value: "0x0",
4605
+ data: encodeFunctionData2({
4606
+ abi: erc20Abi2,
4607
+ functionName: "transfer",
4608
+ args: [transfer.to, BigInt(transfer.amount)]
4609
+ })
4610
+ }));
4611
+ let paymasterUrl = absolutePaymasterUrl();
4612
+ for (const transfer of transfers) {
4613
+ const verification = transfer.yieldseeker;
4614
+ if (!verification) continue;
4615
+ if (chainId !== 8453)
4616
+ throw new OwneyError(
4617
+ "CHAIN_UNSUPPORTED",
4618
+ `Yieldseeker sponsorship is not available on chain ${chainId}.`
4619
+ );
4620
+ const { intent } = await postPaymasterIntent({
4621
+ baseUrl: deps.routingApiBaseUrl,
4622
+ apiKey: deps.apiKey,
4623
+ yieldseekerSignature: verification.signature,
4624
+ body: {
4625
+ chainId,
4626
+ token,
4627
+ from: deps.ownerAddress,
4628
+ to: transfer.to,
4629
+ amount: transfer.amount,
4630
+ yieldseekerUserId: verification.userId,
4631
+ yieldseekerAgentId: verification.agentId
4632
+ }
4633
+ });
4634
+ const url = new URL(paymasterUrl);
4635
+ url.searchParams.append("owneyIntent", intent);
4636
+ paymasterUrl = url.toString();
4637
+ }
3375
4638
  const sendResult = await deps.provider.request({
3376
4639
  method: "wallet_sendCalls",
3377
4640
  params: [
3378
4641
  {
3379
4642
  version: "2.0.0",
3380
4643
  from: deps.ownerAddress,
3381
- chainId: toHex2(chainId),
3382
- atomicRequired: false,
3383
- calls: [{ to: token, value: "0x0", data }],
4644
+ chainId: toHex(chainId),
4645
+ atomicRequired: transfers.length > 1,
4646
+ calls,
3384
4647
  capabilities: {
3385
- paymasterService: { url: absolutePaymasterUrl() }
4648
+ paymasterService: { url: paymasterUrl }
3386
4649
  }
3387
4650
  }
3388
4651
  ]
@@ -3402,7 +4665,24 @@ function makeSponsoredCallsCallback(deps) {
3402
4665
  params: [callsId]
3403
4666
  });
3404
4667
  const txHash = status?.receipts?.[0]?.transactionHash;
3405
- if (txHash) return txHash;
4668
+ if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
4669
+ throw new OwneyError(
4670
+ "SPONSOR_REQUEST_FAILED",
4671
+ "The sponsored deposit did not complete successfully.",
4672
+ { chainId, callsId, safeToFallback: false }
4673
+ );
4674
+ }
4675
+ if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
4676
+ if (status?.receipts?.some(
4677
+ (receipt) => receipt.transactionHash !== txHash
4678
+ ))
4679
+ throw new OwneyError(
4680
+ "SPONSORED_CALLS_NO_RECEIPT",
4681
+ "The wallet returned multiple transactions for an atomic deposit. Check activity before trying again.",
4682
+ { chainId, callsId }
4683
+ );
4684
+ return txHash;
4685
+ }
3406
4686
  if (pollIntervalMs > 0) {
3407
4687
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
3408
4688
  }
@@ -3413,6 +4693,11 @@ function makeSponsoredCallsCallback(deps) {
3413
4693
  { chainId, callsId }
3414
4694
  );
3415
4695
  };
4696
+ const callback = makeVerificationAwareDepositCallback(
4697
+ (to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
4698
+ );
4699
+ registerDepositBatch(callback, batch);
4700
+ return callback;
3416
4701
  }
3417
4702
 
3418
4703
  // src/client.ts
@@ -3442,7 +4727,7 @@ var SPONSORED_USDC_BY_CHAIN = {
3442
4727
  1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
3443
4728
  };
3444
4729
  var VIEM_CHAIN2 = {
3445
- 8453: base2,
4730
+ 8453: base4,
3446
4731
  42161: arbitrum2,
3447
4732
  1: mainnet2
3448
4733
  };
@@ -3451,6 +4736,11 @@ var SPONSORED_WETH_BY_CHAIN = {
3451
4736
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
3452
4737
  1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
3453
4738
  };
4739
+ var SPONSORED_TOKENS_BY_ASSET = { USDC: SPONSORED_USDC_BY_CHAIN, WETH: SPONSORED_WETH_BY_CHAIN };
4740
+ function sponsoredTokensFor(asset) {
4741
+ if (!Object.prototype.hasOwnProperty.call(SPONSORED_TOKENS_BY_ASSET, asset)) throw new OwneyError("ASSET_UNSUPPORTED", `No sponsored token configured for ${asset}`);
4742
+ return SPONSORED_TOKENS_BY_ASSET[asset];
4743
+ }
3454
4744
  function shouldFallbackToUserPaid(error, asset, appCallback) {
3455
4745
  return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
3456
4746
  }
@@ -3472,6 +4762,8 @@ var OwneySDK = class {
3472
4762
  orgAgentConfig;
3473
4763
  orgAgentConfigPromise = null;
3474
4764
  zyfaiRpcUrls;
4765
+ yieldseekerApiBaseUrl;
4766
+ yieldseekerSiweOrigin;
3475
4767
  routingApiBaseUrl;
3476
4768
  referralSource;
3477
4769
  cachedSponsoredCallback = null;
@@ -3494,6 +4786,8 @@ var OwneySDK = class {
3494
4786
  this.apiKey = config.apiKey;
3495
4787
  if (config.debug) setOwneyDebug(true);
3496
4788
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
4789
+ this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
4790
+ this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
3497
4791
  this.routingApiBaseUrl = config.routingApiBaseUrl;
3498
4792
  this.paymasterServiceUrl = config.paymasterServiceUrl;
3499
4793
  this.referralSource = config.referralSource;
@@ -3527,6 +4821,7 @@ var OwneySDK = class {
3527
4821
  * After calling this, `connect()` must be called again before using agent methods.
3528
4822
  */
3529
4823
  async disconnect() {
4824
+ this.state = null;
3530
4825
  for (const agent of this.agents.values()) {
3531
4826
  await agent.disconnect();
3532
4827
  }
@@ -3580,18 +4875,13 @@ var OwneySDK = class {
3580
4875
  }
3581
4876
  return this.state.provider;
3582
4877
  }
3583
- /**
3584
- * Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
3585
- * used when the caller omits `depositCallback`. Wraps the connected EIP-1193
3586
- * provider with viem `custom(provider)` to read token meta and sign the
3587
- * `TransferWithAuthorization`, then POSTs to the sponsor API.
3588
- */
4878
+ /** Builds the default USDC batch callback for the connected wallet. */
3589
4879
  getDefaultSponsoredCallback(onApproved) {
3590
4880
  if (!onApproved && this.cachedSponsoredCallback)
3591
4881
  return this.cachedSponsoredCallback;
3592
4882
  const provider = this.requireConnectedProvider();
3593
4883
  const owner = this.state.walletAddress;
3594
- const callback = makeSponsoredDepositCallback({
4884
+ const callback = makeSponsoredTokenCallback({
3595
4885
  apiKey: this.apiKey,
3596
4886
  baseUrl: this.routingApiBaseUrl,
3597
4887
  ownerAddress: owner,
@@ -3600,35 +4890,32 @@ var OwneySDK = class {
3600
4890
  // Casts work around viem's chain-narrowed Client vs the generic
3601
4891
  // PublicClient/WalletClient param types — structurally identical at
3602
4892
  // runtime, but the two share a name TS treats as unrelated.
3603
- getPublicClient: (cid) => createPublicClient2({
4893
+ getPublicClient: (cid) => createPublicClient4({
3604
4894
  chain: VIEM_CHAIN2[cid],
3605
- transport: custom(provider)
4895
+ transport: custom3(provider)
3606
4896
  }),
3607
- getWalletClient: (cid) => createWalletClient({
4897
+ getWalletClient: (cid) => createWalletClient3({
3608
4898
  account: owner,
3609
4899
  chain: VIEM_CHAIN2[cid],
3610
- transport: custom(provider)
4900
+ transport: custom3(provider)
3611
4901
  })
3612
4902
  });
3613
4903
  if (!onApproved) this.cachedSponsoredCallback = callback;
3614
4904
  return callback;
3615
4905
  }
3616
- /**
3617
- * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
3618
- * callback used when the caller omits `depositCallback` for a WETH
3619
- * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
3620
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
3621
- */
4906
+ /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
3622
4907
  getDefaultSponsoredCallsCallback(asset, onApproved) {
3623
4908
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
3624
4909
  if (!onApproved && cached) return cached;
3625
4910
  const provider = this.requireConnectedProvider();
3626
4911
  const callback = makeSponsoredCallsCallback({
4912
+ apiKey: this.apiKey,
4913
+ routingApiBaseUrl: this.routingApiBaseUrl,
3627
4914
  provider,
3628
4915
  ownerAddress: this.state.walletAddress,
3629
4916
  paymasterServiceUrl: this.paymasterServiceUrl,
3630
4917
  onApproved,
3631
- tokenAddressByChain: asset === "WETH" ? SPONSORED_WETH_BY_CHAIN : SPONSORED_USDC_BY_CHAIN
4918
+ tokenAddressByChain: sponsoredTokensFor(asset)
3632
4919
  });
3633
4920
  if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
3634
4921
  return callback;
@@ -3637,14 +4924,14 @@ var OwneySDK = class {
3637
4924
  * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
3638
4925
  * callback used when the caller omits `depositCallback` for a WETH deposit.
3639
4926
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
3640
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
4927
+ * single-use batch authorization instead of an EIP-3009 authorization.
3641
4928
  */
3642
4929
  getDefaultWethSponsoredCallback(onApproved) {
3643
4930
  if (!onApproved && this.cachedWethSponsoredCallback)
3644
4931
  return this.cachedWethSponsoredCallback;
3645
4932
  const provider = this.requireConnectedProvider();
3646
4933
  const owner = this.state.walletAddress;
3647
- const callback = makeSponsoredWethCallback({
4934
+ const callback = makeSponsoredTokenCallback({
3648
4935
  apiKey: this.apiKey,
3649
4936
  baseUrl: this.routingApiBaseUrl,
3650
4937
  ownerAddress: owner,
@@ -3653,14 +4940,14 @@ var OwneySDK = class {
3653
4940
  // Casts work around viem's chain-narrowed Client vs the generic
3654
4941
  // PublicClient/WalletClient param types — structurally identical at
3655
4942
  // runtime, but the two share a name TS treats as unrelated.
3656
- getPublicClient: (cid) => createPublicClient2({
4943
+ getPublicClient: (cid) => createPublicClient4({
3657
4944
  chain: VIEM_CHAIN2[cid],
3658
- transport: custom(provider)
4945
+ transport: custom3(provider)
3659
4946
  }),
3660
- getWalletClient: (cid) => createWalletClient({
4947
+ getWalletClient: (cid) => createWalletClient3({
3661
4948
  account: owner,
3662
4949
  chain: VIEM_CHAIN2[cid],
3663
- transport: custom(provider)
4950
+ transport: custom3(provider)
3664
4951
  })
3665
4952
  });
3666
4953
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -3734,7 +5021,14 @@ var OwneySDK = class {
3734
5021
  this.routingApiBaseUrl
3735
5022
  );
3736
5023
  this.disabledAgents.clear();
3737
- for (const { key: key2, agent_type, is_enabled } of agentKeys) {
5024
+ for (const {
5025
+ key: key2,
5026
+ agent_type,
5027
+ is_enabled,
5028
+ is_configured
5029
+ } of agentKeys) {
5030
+ const configured = is_configured ?? Boolean(key2);
5031
+ if (!configured) continue;
3738
5032
  const agent = this.createAgent(agent_type, key2);
3739
5033
  if (!agent) continue;
3740
5034
  this.agents.set(agent_type, agent);
@@ -3758,8 +5052,15 @@ var OwneySDK = class {
3758
5052
  }
3759
5053
  createAgent(agentId, key2) {
3760
5054
  if (agentId === "zyfai") {
5055
+ if (!key2) return null;
3761
5056
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
3762
5057
  }
5058
+ if (agentId === "yieldseeker") {
5059
+ return new YieldseekerAgent(this.apiKey, {
5060
+ auth: { origin: this.yieldseekerSiweOrigin },
5061
+ baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
5062
+ });
5063
+ }
3763
5064
  return null;
3764
5065
  }
3765
5066
  /**
@@ -3804,9 +5105,10 @@ var OwneySDK = class {
3804
5105
  * If provided, ALL specified agents must support the chainId or the call
3805
5106
  * throws before activating any agent.
3806
5107
  */
3807
- async activateAgent(chainId, agentId) {
5108
+ async activateAgent(chainId, agentId, asset) {
3808
5109
  const state = this.requireState();
3809
5110
  await this.ensureAgentsInitialized();
5111
+ this.assertActivationSession(state);
3810
5112
  if (agentId !== void 0) {
3811
5113
  if (agentId.length === 0) {
3812
5114
  throw new OwneyError(
@@ -3840,7 +5142,7 @@ var OwneySDK = class {
3840
5142
  this.activeAgents.add(id);
3841
5143
  }
3842
5144
  state.chainId = chainId;
3843
- await this.activateAgentsInTurn(agents, state, chainId);
5145
+ await this.activateAgentsInTurn(agents, state, chainId, asset);
3844
5146
  return;
3845
5147
  }
3846
5148
  const compatible = [...this.agents.values()].filter(
@@ -3861,7 +5163,12 @@ var OwneySDK = class {
3861
5163
  const enabledCompatible = compatible.filter(
3862
5164
  (agent) => !this.isAgentDisabled(agent.id)
3863
5165
  );
3864
- await this.activateAgentsInTurn(enabledCompatible, state, chainId);
5166
+ await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
5167
+ }
5168
+ assertActivationSession(state) {
5169
+ if (this.state !== state) {
5170
+ throw new OwneyError("NOT_CONNECTED", "Wallet connection changed. Please try again.");
5171
+ }
3865
5172
  }
3866
5173
  /**
3867
5174
  * Activate agents ONE AT A TIME, each followed by its org policy.
@@ -3876,26 +5183,51 @@ var OwneySDK = class {
3876
5183
  * Serializing costs no real wall-clock: the user can only approve one prompt
3877
5184
  * at a time anyway.
3878
5185
  *
3879
- * Every agent is attempted even if an earlier one fails, so one declined
3880
- * signature can't deny the remaining agents their turn. The first failure is
3881
- * rethrown (matching the previous `Promise.all` rejection) once all agents
3882
- * have had a chance to activate.
5186
+ * Stop at the first failure so a canceled sign-in does not open another
5187
+ * agent's wallet prompt. Report any earlier successes for diagnostics; the
5188
+ * app discards the session when the complete sign-in does not succeed.
3883
5189
  */
3884
- async activateAgentsInTurn(agents, state, chainId) {
5190
+ async activateAgentsInTurn(agents, state, chainId, asset) {
3885
5191
  let firstError = null;
5192
+ const activatedAgentIds = [];
5193
+ const failedAgents = [];
3886
5194
  for (const agent of agents) {
5195
+ this.assertActivationSession(state);
3887
5196
  try {
3888
- await agent.activateAgent(state, chainId);
5197
+ await agent.activateAgent(state, chainId, asset);
5198
+ this.assertActivationSession(state);
3889
5199
  await this.applyOrgPolicyTo(agent, state, chainId);
5200
+ this.assertActivationSession(state);
5201
+ activatedAgentIds.push(agent.id);
3890
5202
  } catch (error) {
5203
+ this.assertActivationSession(state);
5204
+ 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.";
5205
+ failedAgents.push({
5206
+ agentId: agent.id,
5207
+ code: error instanceof OwneyError ? error.code : void 0,
5208
+ message,
5209
+ ...error instanceof OwneyError && error.details ? { details: error.details } : {}
5210
+ });
3891
5211
  if (firstError === null) {
3892
5212
  firstError = error;
3893
5213
  } else {
3894
5214
  console.error(`activateAgent(${agent.id}) failed:`, error);
3895
5215
  }
5216
+ break;
3896
5217
  }
3897
5218
  }
3898
- if (firstError !== null) throw firstError;
5219
+ if (firstError === null) return;
5220
+ if (activatedAgentIds.length === 0) throw firstError;
5221
+ const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
5222
+ const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
5223
+ const failureMessages = failedAgents.map(
5224
+ ({ agentId, message }) => `${this.formatAgentName(agentId)} activation failed: ${message}`
5225
+ ).join(" ");
5226
+ throw new OwneyError(
5227
+ "AGENT_ACTIVATION_PARTIAL_FAILURE",
5228
+ `${activeNames} activated. ${failureMessages}`,
5229
+ { activatedAgentIds, failedAgentIds, failures: failedAgents }
5230
+ );
3899
5231
  }
3900
5232
  /**
3901
5233
  * Deposit into a specific agent, or distribute across all eligible agents.
@@ -3905,7 +5237,8 @@ var OwneySDK = class {
3905
5237
  * @param options.asset - Asset symbol to deposit (e.g. "USDC")
3906
5238
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
3907
5239
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
3908
- * split amount and smart wallet address — expect multiple wallet prompts.
5240
+ * split amount and smart wallet address. Default sponsored deposits batch
5241
+ * all shares into one signature; custom callbacks still run once per agent.
3909
5242
  * @param options.agentId - Optional explicit target. Otherwise split equally,
3910
5243
  * or fund remaining agents when a recovery deposit cannot meet every minimum.
3911
5244
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
@@ -3990,6 +5323,39 @@ var OwneySDK = class {
3990
5323
  }
3991
5324
  );
3992
5325
  }
5326
+ const batchTransfer = getDepositBatchTransfer(effectiveCallback);
5327
+ if (!depositCallback && batchTransfer) {
5328
+ return runAgentDepositBatch(
5329
+ chainId,
5330
+ agentAmounts.map(({ agent, amount: amount2 }) => ({
5331
+ id: agent.id,
5332
+ amount: amount2,
5333
+ run: (callback) => withFailureReporting(
5334
+ this.apiKey,
5335
+ agent.id,
5336
+ () => agent.deposit(state, chainId, amount2, asset, callback),
5337
+ this.routingApiBaseUrl
5338
+ )
5339
+ })),
5340
+ async (cid, transfers) => {
5341
+ try {
5342
+ return await batchTransfer(cid, transfers);
5343
+ } catch (error) {
5344
+ if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
5345
+ throw error;
5346
+ const requiredAmount = transfers.reduce(
5347
+ (sum, transfer) => sum + BigInt(transfer.amount),
5348
+ 0n
5349
+ );
5350
+ await this.approvePermit2(
5351
+ asset,
5352
+ requiredAmount
5353
+ );
5354
+ return batchTransfer(cid, transfers);
5355
+ }
5356
+ }
5357
+ );
5358
+ }
3993
5359
  const agentResults = {};
3994
5360
  for (const [
3995
5361
  index,
@@ -4029,7 +5395,7 @@ var OwneySDK = class {
4029
5395
  *
4030
5396
  * 1. Missing Permit2 allowance: when the app did not supply its own
4031
5397
  * callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
4032
- * WETH deposit, this is the wallet's first gasless WETH deposit. We send
5398
+ * token deposit, this is the wallet's first Permit2 deposit for that token. We send
4033
5399
  * the one-time (user-paid) Permit2 approval via `approvePermit2()` and
4034
5400
  * retry the SAME sponsored attempt once. Bounded to one approval attempt
4035
5401
  * per call so a wallet/agent that keeps reporting the allowance as
@@ -4066,12 +5432,15 @@ var OwneySDK = class {
4066
5432
  try {
4067
5433
  return await attempt(effectiveCallback);
4068
5434
  } catch (error) {
4069
- if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
5435
+ if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
4070
5436
  approvalAttempted = true;
4071
5437
  console.warn(
4072
- "[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
5438
+ "[owney-sdk] First token deposit: sending one-time Permit2 approval..."
5439
+ );
5440
+ await this.approvePermit2(
5441
+ asset,
5442
+ BigInt(amount)
4073
5443
  );
4074
- await this.approvePermit2();
4075
5444
  continue;
4076
5445
  }
4077
5446
  if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
@@ -4109,10 +5478,10 @@ var OwneySDK = class {
4109
5478
  agent,
4110
5479
  amount: i === agents.length - 1 ? perAgent + remainder : perAgent
4111
5480
  }));
4112
- const valid = splits.filter(
5481
+ const valid2 = splits.filter(
4113
5482
  (s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
4114
5483
  );
4115
- if (valid.length === agents.length) {
5484
+ if (valid2.length === agents.length) {
4116
5485
  return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
4117
5486
  }
4118
5487
  const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
@@ -4131,6 +5500,11 @@ var OwneySDK = class {
4131
5500
  )
4132
5501
  }));
4133
5502
  }
5503
+ formatAgentName(agentId) {
5504
+ if (agentId === "zyfai") return "Zyfai";
5505
+ if (agentId === "yieldseeker") return "Yieldseeker";
5506
+ return agentId;
5507
+ }
4134
5508
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
4135
5509
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
4136
5510
  const parsedAmount = BigInt(amount);
@@ -4162,12 +5536,12 @@ var OwneySDK = class {
4162
5536
  (t) => t.chainId === chainId && t.asset.toLowerCase() === target
4163
5537
  );
4164
5538
  const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
4165
- const position = (balance.positions ?? []).find((p) => {
5539
+ const position2 = (balance.positions ?? []).find((p) => {
4166
5540
  const positionChain = p.chain.trim().toUpperCase();
4167
5541
  const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
4168
5542
  return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
4169
5543
  });
4170
- return !!token && Number(token.amount) > 0 || !!position;
5544
+ return !!token && Number(token.amount) > 0 || !!position2;
4171
5545
  } catch (error) {
4172
5546
  if (requireReliableRead) {
4173
5547
  throw new OwneyError(
@@ -4213,330 +5587,6 @@ var OwneySDK = class {
4213
5587
  return eligible;
4214
5588
  }
4215
5589
  // --- Fund operations ---
4216
- // --- Swap to yield (ROUT-242) ---
4217
- /** Lazily built so an app that never swaps pays nothing for it. */
4218
- swapApiClient;
4219
- swapApi() {
4220
- this.swapApiClient ??= createSwapApi(
4221
- this.routingApiBaseUrl ?? ROUTING_API_BASE_URL,
4222
- this.apiKey
4223
- );
4224
- return this.swapApiClient;
4225
- }
4226
- /**
4227
- * Put the wallet on `chainId`, or fail with something actionable.
4228
- *
4229
- * Reuses the same guard the deposit rail uses, which re-reads the chain after
4230
- * switching — some wallets resolve wallet_switchEthereumChain before the
4231
- * network has actually changed.
4232
- */
4233
- async ensureSwapChain(chainId) {
4234
- const provider = this.requireConnectedProvider();
4235
- const state = this.requireState();
4236
- const chain = VIEM_CHAIN2[chainId];
4237
- if (!chain) {
4238
- throw new OwneyError(
4239
- "CHAIN_UNSUPPORTED",
4240
- `Chain ${chainId} is not supported`,
4241
- { chainId }
4242
- );
4243
- }
4244
- await ensureWalletOnChain(
4245
- createPublicClient2({ chain, transport: custom(provider) }),
4246
- createWalletClient({
4247
- account: state.walletAddress,
4248
- chain,
4249
- transport: custom(provider)
4250
- }),
4251
- chainId
4252
- );
4253
- }
4254
- /**
4255
- * Binds the executor's abstract deps to this client's wallet.
4256
- *
4257
- * Kept as a builder rather than baked into the executor so the whole swap
4258
- * flow stays testable without a provider — the executor never imports viem.
4259
- */
4260
- buildSwapDeps(quote) {
4261
- const state = this.requireState();
4262
- const provider = this.requireConnectedProvider();
4263
- const srcChain = VIEM_CHAIN2[quote.src.chainId];
4264
- const dstChain = VIEM_CHAIN2[quote.dst.chainId];
4265
- const wallet = createWalletClient({
4266
- account: state.walletAddress,
4267
- chain: srcChain,
4268
- transport: custom(provider)
4269
- });
4270
- const srcPublic = createPublicClient2({
4271
- chain: srcChain,
4272
- transport: swapReadTransport(quote.src.chainId, this.zyfaiRpcUrls)
4273
- });
4274
- const dstPublic = createPublicClient2({
4275
- chain: dstChain,
4276
- transport: swapReadTransport(quote.dst.chainId, this.zyfaiRpcUrls)
4277
- });
4278
- return {
4279
- api: this.swapApi(),
4280
- // Native-aware, like readSourceBalance below. Native ETH is never a
4281
- // DEPOSIT target, so this only ever mattered once withdrawal shipped —
4282
- // and there it is the headline case. balanceOf() on the 0xEeee sentinel
4283
- // reverts, which would have read as "the swap landed nothing".
4284
- readTargetBalance: async () => {
4285
- const dst = quote.dst.address;
4286
- if (dst.toLowerCase().startsWith("0xeeee")) {
4287
- return dstPublic.getBalance({ address: state.walletAddress });
4288
- }
4289
- return dstPublic.readContract({
4290
- address: dst,
4291
- abi: erc20Abi2,
4292
- functionName: "balanceOf",
4293
- args: [state.walletAddress]
4294
- });
4295
- },
4296
- sendTransaction: async (tx) => {
4297
- const hash = await wallet.sendTransaction({
4298
- to: tx.to,
4299
- data: tx.data,
4300
- value: BigInt(tx.value || "0"),
4301
- account: state.walletAddress,
4302
- chain: srcChain
4303
- });
4304
- const receipt = await srcPublic.waitForTransactionReceipt({
4305
- timeout: receiptTimeoutMs(quote.src.chainId),
4306
- hash,
4307
- confirmations: 1
4308
- });
4309
- if (receipt.status !== "success") {
4310
- throw new OwneyError(
4311
- "SWAP_REQUEST_FAILED",
4312
- `Swap transaction reverted (tx ${hash})`,
4313
- { hash }
4314
- );
4315
- }
4316
- return hash;
4317
- },
4318
- signTypedData: (typedData) => wallet.signTypedData({
4319
- account: state.walletAddress,
4320
- ...typedData
4321
- }),
4322
- // Chain-bound like every other read here: the wallet provider's chain is
4323
- // not ours to rely on mid-swap.
4324
- readSourceBalance: async () => {
4325
- const src = quote.src.address;
4326
- if (src.toLowerCase().startsWith("0xeeee")) {
4327
- return srcPublic.getBalance({ address: state.walletAddress });
4328
- }
4329
- return srcPublic.readContract({
4330
- address: src,
4331
- abi: ERC20_ALLOWANCE_ABI,
4332
- functionName: "balanceOf",
4333
- args: [state.walletAddress]
4334
- });
4335
- },
4336
- readAllowance: (spender) => srcPublic.readContract({
4337
- address: quote.src.address,
4338
- abi: ERC20_ALLOWANCE_ABI,
4339
- functionName: "allowance",
4340
- args: [state.walletAddress, spender]
4341
- }),
4342
- approve: async (spender, amount) => {
4343
- const hash = await wallet.writeContract({
4344
- address: quote.src.address,
4345
- abi: ERC20_ALLOWANCE_ABI,
4346
- functionName: "approve",
4347
- args: [spender, amount],
4348
- account: state.walletAddress,
4349
- chain: srcChain
4350
- });
4351
- await srcPublic.waitForTransactionReceipt({
4352
- hash,
4353
- confirmations: 1,
4354
- timeout: receiptTimeoutMs(quote.src.chainId)
4355
- });
4356
- return hash;
4357
- },
4358
- ensureChain: (chainId) => this.ensureSwapChain(chainId),
4359
- runner: {
4360
- readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
4361
- submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
4362
- orderStatus: (h) => this.swapApi().orderStatus(h),
4363
- sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
4364
- now: () => Date.now()
4365
- }
4366
- };
4367
- }
4368
- /**
4369
- * Assets the user may pay with, and what each chain deposits into.
4370
- *
4371
- * The source list is deliberately wider than the deposit list: it includes
4372
- * native ETH and USDT, which Owney never holds but users often do.
4373
- */
4374
- async getSwapTokens() {
4375
- return this.swapApi().listTokens();
4376
- }
4377
- /**
4378
- * Price a swap without committing to it.
4379
- *
4380
- * `dstAmountMin` is the number to validate against a deposit minimum —
4381
- * `dst.amount` is an estimate that a decaying auction or slippage can undercut,
4382
- * and a swap landing below the floor leaves the user swapped but not
4383
- * deposited.
4384
- */
4385
- async getSwapQuote(params) {
4386
- const state = this.requireState();
4387
- return this.swapApi().quote({
4388
- ...params,
4389
- walletAddress: state.walletAddress
4390
- });
4391
- }
4392
- /**
4393
- * Swap an asset the user holds into a deposit asset, then deposit it.
4394
- *
4395
- * Kept separate from `deposit()` rather than bolted on as an option: the
4396
- * return shape differs, the staging callback is meaningless on the plain
4397
- * path, and integrators who never swap should not have to reason about any
4398
- * of it.
4399
- *
4400
- * The deposit runs on the MEASURED arrival, not the quote. A quote is an
4401
- * estimate, so depositing the quoted figure would either strand dust or try
4402
- * to move funds that never came.
4403
- *
4404
- * Failure modes differ in a way callers must respect. A same-chain swap is
4405
- * atomic — if it fails, nothing moved. A cross-chain swap escrows the user's
4406
- * funds first, so SWAP_ORDER_EXPIRED / REFUNDED / CANCELLED all mean the
4407
- * money left the wallet. Only the former can honestly say "nothing has left
4408
- * your wallet".
4409
- */
4410
- async swapAndDeposit(options) {
4411
- const state = this.requireState();
4412
- const api = this.swapApi();
4413
- const quote = await api.quote({
4414
- from: options.from,
4415
- to: options.to,
4416
- walletAddress: state.walletAddress
4417
- });
4418
- await this.ensureSwapChain(quote.src.chainId);
4419
- const swap = await executeSwap(this.buildSwapDeps(quote), {
4420
- quote,
4421
- walletAddress: state.walletAddress,
4422
- ...options.slippage === void 0 ? {} : { slippage: options.slippage },
4423
- ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
4424
- });
4425
- options.onSwapProgress?.("depositing");
4426
- await this.ensureSwapChain(quote.dst.chainId);
4427
- const deposit = await this.deposit({
4428
- amount: swap.received,
4429
- asset: options.to.symbol,
4430
- ...options.agentId ? { agentId: options.agentId } : {}
4431
- });
4432
- return { swap, deposit };
4433
- }
4434
- /**
4435
- * Withdraw from an agent and swap the proceeds into whatever the user wants
4436
- * to hold, delivered to their own wallet.
4437
- *
4438
- * The mirror of `swapAndDeposit()`, with one structural difference that
4439
- * drives the whole implementation: a deposit swap starts from funds already
4440
- * sitting in the wallet, but a withdrawal has to wait for them. The agent's
4441
- * provider acknowledges a withdrawal and *then* queues the on-chain transfer
4442
- * to the EOA, so `withdraw()` resolving means "accepted", not "arrived".
4443
- * Quoting before the tokens land would size the swap against a balance that
4444
- * is not there yet.
4445
- *
4446
- * The swap is therefore sized from the MEASURED arrival, exactly as the
4447
- * deposit path sizes its deposit from the measured swap output. On a full
4448
- * withdrawal there is no other number available — "MAX" has no figure until
4449
- * the agent picks one.
4450
- *
4451
- * **Failure here is not symmetrical with the deposit path.** A failed
4452
- * deposit-swap leaves the user holding what they started with. A failed
4453
- * withdrawal-swap leaves them holding the AGENT'S asset in their own wallet:
4454
- * the money is out, safe, and in the wrong denomination. Both
4455
- * `WITHDRAW_ARRIVAL_TIMEOUT` and `WITHDRAW_SWAP_FAILED` carry `withdrawn` for
4456
- * that reason — the UI has to tell the user where their money actually is,
4457
- * and must never present either as a lost withdrawal.
4458
- */
4459
- async withdrawAndSwap(options) {
4460
- const state = this.requireState();
4461
- const activeChainId = this.requireChainId();
4462
- if (options.from.chainId !== activeChainId) {
4463
- throw new OwneyError(
4464
- "CHAIN_MISMATCH",
4465
- `Cannot withdraw from chain ${options.from.chainId} while the active chain is ${activeChainId}. Activate on that chain first.`,
4466
- { requested: options.from.chainId, active: activeChainId }
4467
- );
4468
- }
4469
- const asset = SupportedAssets.find(
4470
- (a) => a.chainId === options.from.chainId && a.symbol === options.from.symbol.toUpperCase()
4471
- );
4472
- if (!asset) {
4473
- throw new OwneyError(
4474
- "WITHDRAW_NO_PERMITTED_TOKENS",
4475
- `${options.from.symbol} on chain ${options.from.chainId} is not an asset Owney holds`,
4476
- { ...options.from }
4477
- );
4478
- }
4479
- const srcChain = VIEM_CHAIN2[options.from.chainId];
4480
- const srcPublic = createPublicClient2({
4481
- chain: srcChain,
4482
- transport: swapReadTransport(options.from.chainId, this.zyfaiRpcUrls)
4483
- });
4484
- const readWalletBalance = () => srcPublic.readContract({
4485
- address: asset.address,
4486
- abi: erc20Abi2,
4487
- functionName: "balanceOf",
4488
- args: [state.walletAddress]
4489
- });
4490
- const baseline = await readWalletBalance();
4491
- debugLog("owney-sdk", "withdrawAndSwap: baseline", {
4492
- asset: `${asset.symbol}@${asset.chainId}`,
4493
- baseline: baseline.toString()
4494
- });
4495
- options.onSwapProgress?.("withdrawing");
4496
- const withdraw = await this.withdraw({
4497
- asset: options.from.symbol,
4498
- ...options.amount === void 0 ? {} : { amount: options.amount },
4499
- ...options.agentId ? { agentId: options.agentId } : {}
4500
- });
4501
- const arrived = await awaitWithdrawalArrival({
4502
- readBalance: readWalletBalance,
4503
- baseline,
4504
- ...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs }
4505
- });
4506
- const withdrawn = arrived.toString();
4507
- options.onSwapProgress?.("withdrawn");
4508
- try {
4509
- const quote = await this.swapApi().quote({
4510
- from: { ...options.from, amount: withdrawn },
4511
- to: options.to,
4512
- direction: "withdraw",
4513
- walletAddress: state.walletAddress
4514
- });
4515
- await this.ensureSwapChain(quote.src.chainId);
4516
- const swap = await executeSwap(this.buildSwapDeps(quote), {
4517
- quote,
4518
- walletAddress: state.walletAddress,
4519
- direction: "withdraw",
4520
- ...options.slippage === void 0 ? {} : { slippage: options.slippage },
4521
- ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
4522
- });
4523
- return { withdraw, withdrawn, swap };
4524
- } catch (error) {
4525
- throw new OwneyError(
4526
- "WITHDRAW_SWAP_FAILED",
4527
- `Withdrew ${withdrawn} ${asset.symbol} to your wallet, but the swap to ${options.to.symbol} did not complete. The funds are in your wallet as ${asset.symbol}.`,
4528
- {
4529
- withdrawn,
4530
- asset: asset.symbol,
4531
- chainId: asset.chainId,
4532
- intendedSymbol: options.to.symbol,
4533
- intendedChainId: options.to.chainId,
4534
- cause: error instanceof Error ? error.message : String(error),
4535
- ...error instanceof OwneyError ? { causeCode: error.code } : {}
4536
- }
4537
- );
4538
- }
4539
- }
4540
5590
  /**
4541
5591
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
4542
5592
  * Validates that the asset is supported by the target agent(s) on the active chain.
@@ -4606,6 +5656,10 @@ var OwneySDK = class {
4606
5656
  }
4607
5657
  const requested = BigInt(amount);
4608
5658
  const aggregated = await this.getBalances();
5659
+ const unavailableAgents = eligibleAgents.filter(
5660
+ (agent) => !(agent.id in aggregated.agentBalances)
5661
+ );
5662
+ const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
4609
5663
  const balances = projectAgentBalancesForAsset(
4610
5664
  eligibleAgents,
4611
5665
  aggregated.agentBalances,
@@ -4614,7 +5668,18 @@ var OwneySDK = class {
4614
5668
  assetInfo.decimals
4615
5669
  );
4616
5670
  const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
4617
- if (totalAvailable < requested) {
5671
+ if (totalAvailable === 0n && unavailableAgents.length > 0) {
5672
+ throw new OwneyError(
5673
+ "WITHDRAW_BALANCE_UNAVAILABLE",
5674
+ `Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
5675
+ {
5676
+ asset,
5677
+ unavailableAgents: unavailableAgentIds,
5678
+ agentErrors: aggregated.agentErrors
5679
+ }
5680
+ );
5681
+ }
5682
+ if (totalAvailable < requested && unavailableAgents.length === 0) {
4618
5683
  throw new OwneyError(
4619
5684
  "WITHDRAW_INSUFFICIENT_BALANCE",
4620
5685
  `Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
@@ -4625,6 +5690,7 @@ var OwneySDK = class {
4625
5690
  }
4626
5691
  );
4627
5692
  }
5693
+ const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
4628
5694
  const disabledBalances = balances.filter(
4629
5695
  (b) => this.isAgentDisabled(b.agent.id)
4630
5696
  );
@@ -4633,7 +5699,7 @@ var OwneySDK = class {
4633
5699
  );
4634
5700
  const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
4635
5701
  disabledBalances,
4636
- requested
5702
+ plannedTarget
4637
5703
  );
4638
5704
  const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
4639
5705
  const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
@@ -4643,7 +5709,9 @@ var OwneySDK = class {
4643
5709
  }));
4644
5710
  const plans = [...disabledPlans, ...enabledPlans];
4645
5711
  const results = {};
4646
- const agentErrors = {};
5712
+ const agentErrors = {
5713
+ ...aggregated.agentErrors ?? {}
5714
+ };
4647
5715
  for (let i = 0; i < plans.length; i++) {
4648
5716
  const p = plans[i];
4649
5717
  if (p.planned === 0n) continue;
@@ -4690,7 +5758,8 @@ var OwneySDK = class {
4690
5758
  requested: amount,
4691
5759
  withdrawn: withdrawn.toString(),
4692
5760
  partialResults: results,
4693
- agentErrors
5761
+ agentErrors,
5762
+ ...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
4694
5763
  }
4695
5764
  );
4696
5765
  }
@@ -4707,24 +5776,25 @@ var OwneySDK = class {
4707
5776
  const chainId = this.requireChainId();
4708
5777
  if (agentId) {
4709
5778
  const agent = this.getAgent(agentId);
4710
- const result = await this.readAgent(
4711
- agent,
4712
- "balances",
4713
- () => agent.getBalances(state, chainId)
4714
- );
4715
- return result;
5779
+ const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
5780
+ return {
5781
+ ...result,
5782
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5783
+ };
4716
5784
  }
4717
5785
  let totalBalance = 0;
4718
5786
  const results = {};
4719
5787
  const entries = [...this.getActiveAgents().entries()];
4720
5788
  const balanceResults = await Promise.allSettled(
4721
5789
  entries.map(async ([id, agent]) => {
4722
- const b = await this.readAgent(
4723
- agent,
4724
- "balances",
4725
- () => agent.getBalances(state, chainId)
4726
- );
4727
- return [id, b];
5790
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
5791
+ return [
5792
+ id,
5793
+ {
5794
+ ...b,
5795
+ balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
5796
+ }
5797
+ ];
4728
5798
  })
4729
5799
  );
4730
5800
  let successCount = 0;
@@ -4744,9 +5814,9 @@ var OwneySDK = class {
4744
5814
  const reason = settledResult.reason;
4745
5815
  agentFailures.push(reason);
4746
5816
  const retryDelay = rateLimitDelay(reason);
4747
- if (retryDelay !== void 0)
4748
- agentRetryAt[agentId2] = Date.now() + retryDelay;
5817
+ if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
4749
5818
  agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
5819
+ console.error(`[owney-sdk] Balance fetch failed for agent "${agentId2}":`, reason);
4750
5820
  }
4751
5821
  if (successCount === 0) {
4752
5822
  throw new OwneyError(
@@ -4772,22 +5842,14 @@ var OwneySDK = class {
4772
5842
  const chainId = this.requireChainId();
4773
5843
  if (agentId) {
4774
5844
  const agent = this.getAgent(agentId);
4775
- return this.readAgent(
4776
- agent,
4777
- "earnings",
4778
- () => agent.getEarnings(state, chainId)
4779
- );
5845
+ return this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
4780
5846
  }
4781
5847
  let totalEarnings = 0;
4782
5848
  const results = {};
4783
5849
  const entries = [...this.getActiveAgents().entries()];
4784
5850
  const earningsResults = await Promise.all(
4785
5851
  entries.map(async ([id, agent]) => {
4786
- const e = await this.readAgent(
4787
- agent,
4788
- "earnings",
4789
- () => agent.getEarnings(state, chainId)
4790
- );
5852
+ const e = await this.readAgent(agent, "earnings", () => agent.getEarnings(state, chainId));
4791
5853
  return [id, e];
4792
5854
  })
4793
5855
  );
@@ -4932,12 +5994,11 @@ var OwneySDK = class {
4932
5994
  ),
4933
5995
  Promise.all(
4934
5996
  entries.map(async ([id, agent]) => {
4935
- const b = await this.readAgent(
4936
- agent,
4937
- "balances",
4938
- () => agent.getBalances(state, chainId)
4939
- );
4940
- return [id, balanceForApyScope(b, chainId, tokenSymbol)];
5997
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
5998
+ return [
5999
+ id,
6000
+ balanceForApyScope(b, chainId, tokenSymbol)
6001
+ ];
4941
6002
  })
4942
6003
  )
4943
6004
  ]);
@@ -4996,12 +6057,7 @@ var OwneySDK = class {
4996
6057
  const { agentId, filters } = options ?? {};
4997
6058
  if (agentId) {
4998
6059
  const agent = this.getAgent(agentId);
4999
- return this.readAgent(
5000
- agent,
5001
- "history",
5002
- () => agent.getHistory(state, chainId, filters),
5003
- filters
5004
- );
6060
+ return this.readAgent(agent, "history", () => agent.getHistory(state, chainId, filters), filters);
5005
6061
  }
5006
6062
  const activeAgents = [...this.getActiveAgents().values()];
5007
6063
  const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
@@ -5058,21 +6114,13 @@ var OwneySDK = class {
5058
6114
  const chainId = this.requireChainId();
5059
6115
  if (agentId) {
5060
6116
  const agent = this.getAgent(agentId);
5061
- return this.readAgent(
5062
- agent,
5063
- "profile",
5064
- () => agent.getUserProfile(state, chainId)
5065
- );
6117
+ return this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
5066
6118
  }
5067
6119
  const results = {};
5068
6120
  const entries = [...this.getActiveAgents().entries()];
5069
6121
  const profileResults = await Promise.all(
5070
6122
  entries.map(async ([id, agent]) => {
5071
- const p = await this.readAgent(
5072
- agent,
5073
- "profile",
5074
- () => agent.getUserProfile(state, chainId)
5075
- );
6123
+ const p = await this.readAgent(agent, "profile", () => agent.getUserProfile(state, chainId));
5076
6124
  return [id, p];
5077
6125
  })
5078
6126
  );
@@ -5111,43 +6159,44 @@ var OwneySDK = class {
5111
6159
  return pending;
5112
6160
  }
5113
6161
  /**
5114
- * One-time, user-paid approval of Permit2 on the sponsored WETH token for
5115
- * the active chain. Required once per wallet per chain before gasless WETH
5116
- * deposits; afterwards deposit() is signature-only. Resolves only after the
5117
- * approval transaction is mined (1 confirmation), so a subsequent deposit()
5118
- * will see the new allowance; throws if the transaction reverted.
6162
+ * User-paid approval of Permit2 on the selected token for the active chain.
6163
+ * Grants the maximum ERC20 allowance so later deposits do not require another
6164
+ * approval. Resolves after one confirmation so the subsequent deposit attempt
6165
+ * sees the new allowance.
6166
+ *
6167
+ * @param requiredAmount Raw base-unit amount the pending deposit must cover.
5119
6168
  * @returns the approval transaction hash.
5120
6169
  */
5121
- async approvePermit2(asset = "WETH") {
5122
- void asset;
6170
+ async approvePermit2(asset = "WETH", requiredAmount = 0n) {
5123
6171
  const state = this.requireState();
5124
6172
  const chainId = this.requireChainId();
5125
- this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
5126
- const token = SPONSORED_WETH_BY_CHAIN[chainId];
6173
+ this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
6174
+ const token = sponsoredTokensFor(asset)[chainId];
5127
6175
  if (!token) {
5128
6176
  throw new OwneyError(
5129
6177
  "CHAIN_UNSUPPORTED",
5130
- `No sponsored WETH on chain ${chainId}`
6178
+ `No sponsored token on chain ${chainId}`
5131
6179
  );
5132
6180
  }
5133
6181
  const provider = this.requireConnectedProvider();
5134
- const wallet = createWalletClient({
6182
+ const publicClient = createPublicClient4({
6183
+ chain: VIEM_CHAIN2[chainId],
6184
+ transport: custom3(provider)
6185
+ });
6186
+ const approvalAmount = permit2ApprovalAmount(requiredAmount);
6187
+ const wallet = createWalletClient3({
5135
6188
  account: state.walletAddress,
5136
6189
  chain: VIEM_CHAIN2[chainId],
5137
- transport: custom(provider)
6190
+ transport: custom3(provider)
5138
6191
  });
5139
6192
  const hash = await wallet.writeContract({
5140
6193
  address: token,
5141
6194
  abi: ERC20_ALLOWANCE_ABI,
5142
6195
  functionName: "approve",
5143
- args: [PERMIT2_ADDRESS, MAX_UINT256],
6196
+ args: [PERMIT2_ADDRESS, approvalAmount],
5144
6197
  account: state.walletAddress,
5145
6198
  chain: VIEM_CHAIN2[chainId]
5146
6199
  });
5147
- const publicClient = createPublicClient2({
5148
- chain: VIEM_CHAIN2[chainId],
5149
- transport: custom(provider)
5150
- });
5151
6200
  const receipt = await publicClient.waitForTransactionReceipt({
5152
6201
  hash,
5153
6202
  confirmations: 1
@@ -5180,23 +6229,15 @@ var OwneySDK = class {
5180
6229
  const agentOptions = { tokenSymbol, chainId };
5181
6230
  if (agentId) {
5182
6231
  const agent = this.getAgent(agentId);
5183
- return this.readAgent(
5184
- agent,
5185
- "agentApy",
5186
- () => agent.getAgentApy(days, agentOptions),
5187
- { days, ...agentOptions }
5188
- );
6232
+ return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
5189
6233
  }
5190
6234
  const results = {};
5191
- const agentEntries = [...this.agents.entries()];
6235
+ const agentEntries = [...this.agents.entries()].filter(
6236
+ ([id]) => !this.isAgentDisabled(id)
6237
+ );
5192
6238
  const apyResults = await Promise.all(
5193
6239
  agentEntries.map(async ([id, agent]) => {
5194
- const apy = await this.readAgent(
5195
- agent,
5196
- "agentApy",
5197
- () => agent.getAgentApy(days, agentOptions),
5198
- { days, ...agentOptions }
5199
- );
6240
+ const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
5200
6241
  return [id, apy];
5201
6242
  })
5202
6243
  );
@@ -5223,11 +6264,7 @@ var OwneySDK = class {
5223
6264
  const entries = [...activeAgents.entries()];
5224
6265
  const balanceResults = await Promise.allSettled(
5225
6266
  entries.map(async ([id, agent]) => {
5226
- const b = await this.readAgent(
5227
- agent,
5228
- "balances",
5229
- () => agent.getBalances(state, chainId)
5230
- );
6267
+ const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
5231
6268
  return [id, b.positions ?? []];
5232
6269
  })
5233
6270
  );
@@ -5272,13 +6309,13 @@ var OwneySDK = class {
5272
6309
  };
5273
6310
 
5274
6311
  // src/agents/zyfai/zyfai.siwx.ts
5275
- import { getAddress } from "viem";
5276
- import { SiweMessage } from "siwe";
6312
+ import { getAddress as getAddress3 } from "viem";
6313
+ import { SiweMessage as SiweMessage2 } from "siwe";
5277
6314
  import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
5278
6315
 
5279
6316
  // src/agents/zyfai/zyfai.siwx-cache.ts
5280
- var KEY_PREFIX3 = "owney.siwx.session";
5281
- var storage3 = () => {
6317
+ var KEY_PREFIX4 = "owney.siwx.session";
6318
+ var storage4 = () => {
5282
6319
  if (typeof window === "undefined") return null;
5283
6320
  try {
5284
6321
  return window.localStorage;
@@ -5286,8 +6323,8 @@ var storage3 = () => {
5286
6323
  return null;
5287
6324
  }
5288
6325
  };
5289
- var buildKey2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}`;
5290
- var legacyKeyPrefix2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}:`;
6326
+ var buildKey3 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
6327
+ var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
5291
6328
  var memorySiwxSessions = /* @__PURE__ */ new Map();
5292
6329
  var readLegacySiwxSession = (store, address) => {
5293
6330
  if (!store) return null;
@@ -5318,17 +6355,17 @@ var readLegacySiwxSession = (store, address) => {
5318
6355
  };
5319
6356
  var readSiwxSession = (address, chainId) => {
5320
6357
  if (typeof window === "undefined") return null;
5321
- const key2 = buildKey2(address);
5322
- const store = storage3();
5323
- let raw = null;
6358
+ const key2 = buildKey3(address);
6359
+ const store = storage4();
6360
+ let raw2 = null;
5324
6361
  try {
5325
- raw = store?.getItem(key2) ?? null;
6362
+ raw2 = store?.getItem(key2) ?? null;
5326
6363
  } catch {
5327
- raw = null;
6364
+ raw2 = null;
5328
6365
  }
5329
- if (raw) {
6366
+ if (raw2) {
5330
6367
  try {
5331
- return JSON.parse(raw);
6368
+ return JSON.parse(raw2);
5332
6369
  } catch {
5333
6370
  memorySiwxSessions.delete(key2);
5334
6371
  try {
@@ -5347,18 +6384,18 @@ var readSiwxSession = (address, chainId) => {
5347
6384
  };
5348
6385
  var writeSiwxSession = (address, _chainId, session) => {
5349
6386
  if (typeof window === "undefined") return;
5350
- const key2 = buildKey2(address);
6387
+ const key2 = buildKey3(address);
5351
6388
  memorySiwxSessions.set(key2, session);
5352
- const store = storage3();
6389
+ const store = storage4();
5353
6390
  try {
5354
6391
  store?.setItem(key2, JSON.stringify(session));
5355
6392
  } catch {
5356
6393
  }
5357
6394
  };
5358
6395
  var clearSiwxSession = (address, _chainId) => {
5359
- const key2 = buildKey2(address);
6396
+ const key2 = buildKey3(address);
5360
6397
  memorySiwxSessions.delete(key2);
5361
- const store = storage3();
6398
+ const store = storage4();
5362
6399
  try {
5363
6400
  store?.removeItem(key2);
5364
6401
  } catch {
@@ -5398,8 +6435,8 @@ function buildSIWXConfig(deps) {
5398
6435
  statement: STATEMENT,
5399
6436
  issuedAt,
5400
6437
  toString() {
5401
- return new SiweMessage({
5402
- address: getAddress(accountAddress),
6438
+ return new SiweMessage2({
6439
+ address: getAddress3(accountAddress),
5403
6440
  chainId: numericChainId(chainId),
5404
6441
  domain,
5405
6442
  uri,
@@ -5441,7 +6478,7 @@ function buildSIWXConfig(deps) {
5441
6478
  const persistSession = async (session) => {
5442
6479
  const address = session.data.accountAddress;
5443
6480
  const id = numericChainId(session.data.chainId);
5444
- const message = new SiweMessage(session.message);
6481
+ const message = new SiweMessage2(session.message);
5445
6482
  const login = await post("/auth/login", {
5446
6483
  message,
5447
6484
  signature: session.signature,
@@ -5477,9 +6514,9 @@ function buildSIWXConfig(deps) {
5477
6514
  }
5478
6515
  function createOwneySIWX(config) {
5479
6516
  const zyfai = new ZyfaiSDK2({ apiKey: config.apiKey });
5480
- const http4 = zyfai.httpClient;
6517
+ const http2 = zyfai.httpClient;
5481
6518
  return buildSIWXConfig({
5482
- post: (url, data) => http4.post(url, data),
6519
+ post: (url, data) => http2.post(url, data),
5483
6520
  referralSource: config.referralSource
5484
6521
  });
5485
6522
  }
@@ -5490,7 +6527,7 @@ export {
5490
6527
  NotConnectedError,
5491
6528
  OwneyError,
5492
6529
  OwneySDK,
6530
+ YieldseekerAgent,
5493
6531
  createOwneySIWX,
5494
- listOrders as listPendingSwaps,
5495
6532
  setOwneyDebug
5496
6533
  };