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

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